Refactor mindmap validation to handle multiple root nodes and add SquareNode support

This commit is contained in:
Knut Sveidqvist
2025-04-24 16:51:25 +02:00
parent 0bbfa8e602
commit 7dd31dc7c9
4 changed files with 34 additions and 18 deletions

View File

@@ -41,9 +41,10 @@ export class MindmapValidator {
checkSingleRoot(doc: MindmapDoc, accept: ValidationAcceptor): void {
// eslint-disable-next-line no-console
console.debug('CHECKING SINGLE ROOT');
let rootNodeFound = false;
let rootNodeIndentation;
for (const row of doc.MindmapRows) {
console.debug('ROW BY ROW', row.indent);
// Skip non-node items (e.g., class decorations, icon decorations)
if (
!row.item ||
@@ -52,18 +53,24 @@ export class MindmapValidator {
) {
continue;
}
// Check if this is a root node (no indentation)
if (row.indent === undefined) {
if (rootNodeFound) {
// If we've already found a root node, report an error
accept('error', 'Multiple root nodes are not allowed in a mindmap.', {
node: row,
property: 'item',
});
} else {
rootNodeFound = true;
}
if (
rootNodeIndentation === undefined && // Check if this is a root node (no indentation)
row.indent === undefined
) {
rootNodeIndentation = 0;
} else if (row.indent === undefined) {
console.debug('FAIL 1', rootNodeIndentation, row.indent);
// If we've already found a root node, report an error
accept('error', 'Multiple root nodes are not allowed in a mindmap.', {
node: row,
property: 'item',
});
} else if (rootNodeIndentation >= row.indent) {
console.debug('FAIL 2', rootNodeIndentation, row.indent, row.item);
accept('error', 'Multiple root nodes are not allowed in a mindmap.', {
node: row,
property: 'item',
});
}
}
}

View File

@@ -18,7 +18,7 @@ Item:
// Use a special rule order to handle the parsing precedence
Node:
CircleNode | OtherComplex | SimpleNode | RoundedNode;
CircleNode | OtherComplex | SimpleNode | RoundedNode | SquareNode;
// Specifically handle double parentheses case - highest priority
CircleNode:
@@ -29,6 +29,9 @@ CircleNode:
RoundedNode:
(id=ID)? desc=(ROUNDED_STR_QUOTES|ROUNDED_STR);
SquareNode:
(id=ID)? desc=(SQUARE_STR_QUOTES|SQUARE_STR);
// Handle other complex node variants
OtherComplex:
id=ID
@@ -62,6 +65,8 @@ terminal CLASS_KEYWORD: ':::';
terminal CIRCLE_STR: /\(\(([\s\S]*?)\)\)/;
terminal ROUNDED_STR_QUOTES: /\(\"([\s\S]*?)\"\)/;
terminal ROUNDED_STR: /\(([\s\S]*?)\)/;
terminal SQUARE_STR_QUOTES: /\[\"([\s\S]*?)\"\]/;
terminal SQUARE_STR: /\[([\s\S]*?)\]/;
// terminal CIRCLE_STR: /(?!\(\()[\s\S]+?(?!\(\()/;
terminal ID: /[a-zA-Z0-9_\-\.\/]+/;
terminal STRING: /"[^"]*"|'[^']*'/;

View File

@@ -15,6 +15,10 @@ export class MindmapValueConverter extends AbstractMermaidValueConverter {
return input.replace('(', '').replace(')', '').trim();
} else if (rule.name === 'ROUNDED_STR_QUOTES') {
return input.replace('("', '').replace('")', '').trim();
} else if (rule.name === 'SQUARE_STR') {
return input.replace('[', '').replace(']', '').trim();
} else if (rule.name === 'SQUARE_STR_QUOTES') {
return input.replace('["', '').replace('"]', '').trim();
} else if (rule.name === 'ARCH_TEXT_ICON') {
return input.replace(/["()]/g, '');
} else if (rule.name === 'ARCH_TITLE') {

View File

@@ -133,7 +133,7 @@ describe('Hierarchy (ported from mindmap.spec.ts)', () => {
expect(child2Node.id).toBe('child2');
});
it.only('MMP-5 Multiple roots are illegal', async () => {
it('MMP-5 Multiple roots are illegal', async () => {
const str = 'mindmap\nroot\nfakeRoot';
const result = await validatedParse(str, { validation: true });
// Langium parser may not throw, but should have parserErrors
@@ -145,10 +145,10 @@ describe('Hierarchy (ported from mindmap.spec.ts)', () => {
expect(result2.diagnostics?.length).toBe(0);
});
it('MMP-6 real root in wrong place', () => {
it('MMP-6 real root in wrong place', async () => {
const str = 'mindmap\n root\n fakeRoot\nrealRootWrongPlace';
const result = parse(str);
expect(result.parserErrors.length).toBeGreaterThan(0);
const r2 = await validatedParse(str, { validation: true });
expect(r2.diagnostics?.length).toBe(0);
});
});