mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-16 23:09:28 +02:00
Compare commits
17 Commits
@mermaid-j
...
mindmap-la
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a1585c6f70 | ||
![]() |
5fcadd0e30 | ||
![]() |
5df8a22853 | ||
![]() |
973401f4af | ||
![]() |
30d2cac243 | ||
![]() |
516e03db1a | ||
![]() |
d24becb455 | ||
![]() |
08048c39d7 | ||
![]() |
7dd31dc7c9 | ||
![]() |
0bbfa8e602 | ||
![]() |
4977cdb1f4 | ||
![]() |
8f0703bdc2 | ||
![]() |
d2ce80be10 | ||
![]() |
6bcfb4df3a | ||
![]() |
df3c3d2fdc | ||
![]() |
97cde9827b | ||
![]() |
b2bafe8980 |
@@ -87,6 +87,7 @@ NODIR
|
||||
NSTR
|
||||
outdir
|
||||
Qcontrolx
|
||||
QSTR
|
||||
reinit
|
||||
rels
|
||||
reqs
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -52,3 +52,4 @@ vite.config.ts.timestamp-*
|
||||
|
||||
# autogenereated by langium-cli
|
||||
generated/
|
||||
.cursor/*
|
||||
|
@@ -0,0 +1,19 @@
|
||||
orgChart
|
||||
%% ex2
|
||||
CEO[Mark Davies CEO]
|
||||
---
|
||||
VPFinance[Leslie Deen VP Finance]
|
||||
VPHR[David Soft VP HR]
|
||||
---
|
||||
VPMA[Achmed Jo VP marketing]
|
||||
VPLegal[Elena Prem VP Legal]
|
||||
PMA[Sudan Ali]
|
||||
Noel
|
||||
Tom
|
||||
Alex
|
||||
Sneil
|
||||
PMB[Sekar Sha]
|
||||
John
|
||||
Dan
|
||||
David
|
||||
Jan
|
@@ -0,0 +1,7 @@
|
||||
orgChart
|
||||
%% ex2
|
||||
CEO[Mark Davies CEO] --o VPFinance[Leslie Deen VP Finance] & VPHR[David Soft VP HR]
|
||||
CEO --o VPMA[Achmed Jo VP marketing] & VPLegal[Elena Prem VP Legal]
|
||||
CEO --> PMA[Sudan Ali] & PMB[Sekar Sha]
|
||||
PMA --> Noel & Tom & Alex & Sneil
|
||||
PMB --> John & Dan & David & Jan
|
BIN
packages/mermaid/src/diagrams/org-chart/examples/ex1.png
Normal file
BIN
packages/mermaid/src/diagrams/org-chart/examples/ex1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 291 KiB |
BIN
packages/mermaid/src/diagrams/org-chart/examples/ex2.png
Normal file
BIN
packages/mermaid/src/diagrams/org-chart/examples/ex2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 MiB |
BIN
packages/mermaid/src/diagrams/org-chart/examples/ex3.png
Normal file
BIN
packages/mermaid/src/diagrams/org-chart/examples/ex3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 230 KiB |
BIN
packages/mermaid/src/diagrams/org-chart/examples/ex4.png
Normal file
BIN
packages/mermaid/src/diagrams/org-chart/examples/ex4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 304 KiB |
@@ -0,0 +1,22 @@
|
||||
org
|
||||
CEO[CEO]
|
||||
CFO[CFO]
|
||||
Finance1[Finance 1]
|
||||
Finance2[Finance 2]
|
||||
CTO[CTO]
|
||||
Dev1[Developer 1]
|
||||
Dev2[Developer 2]
|
||||
|
||||
----
|
||||
org
|
||||
CEO[CEO]
|
||||
connector
|
||||
CTO[CTO]
|
||||
CFO[CFO]
|
||||
Finance1[Finance 1]
|
||||
Finance2[Finance 2]
|
||||
CTO[CTO]
|
||||
---
|
||||
org
|
||||
President --> VP1[VP Sales] & VP2[VP Production] & VP3[VP Marketing]
|
||||
|
@@ -4,32 +4,58 @@
|
||||
{
|
||||
"id": "info",
|
||||
"grammar": "src/language/info/info.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
"fileExtensions": [
|
||||
".mmd",
|
||||
".mermaid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "packet",
|
||||
"grammar": "src/language/packet/packet.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
"fileExtensions": [
|
||||
".mmd",
|
||||
".mermaid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pie",
|
||||
"grammar": "src/language/pie/pie.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
"fileExtensions": [
|
||||
".mmd",
|
||||
".mermaid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "architecture",
|
||||
"grammar": "src/language/architecture/architecture.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
"fileExtensions": [
|
||||
".mmd",
|
||||
".mermaid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gitGraph",
|
||||
"grammar": "src/language/gitGraph/gitGraph.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
"fileExtensions": [
|
||||
".mmd",
|
||||
".mermaid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "radar",
|
||||
"grammar": "src/language/radar/radar.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
"fileExtensions": [
|
||||
".mmd",
|
||||
".mermaid"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mindmap",
|
||||
"grammar": "src/language/mindmap/mindmap.langium",
|
||||
"fileExtensions": [
|
||||
".mmd",
|
||||
".mermaid"
|
||||
]
|
||||
}
|
||||
],
|
||||
"mode": "production",
|
||||
|
@@ -11,6 +11,7 @@ export {
|
||||
Branch,
|
||||
Commit,
|
||||
Merge,
|
||||
MindmapDoc as Mindmap,
|
||||
Statement,
|
||||
isInfo,
|
||||
isPacket,
|
||||
@@ -32,6 +33,7 @@ export {
|
||||
ArchitectureGeneratedModule,
|
||||
GitGraphGeneratedModule,
|
||||
RadarGeneratedModule,
|
||||
MindmapGeneratedModule,
|
||||
} from './generated/module.js';
|
||||
|
||||
export * from './gitGraph/index.js';
|
||||
@@ -41,3 +43,4 @@ export * from './packet/index.js';
|
||||
export * from './pie/index.js';
|
||||
export * from './architecture/index.js';
|
||||
export * from './radar/index.js';
|
||||
export * from './mindmap/index.js';
|
||||
|
44
packages/parser/src/language/kanban/kanban.langium
Normal file
44
packages/parser/src/language/kanban/kanban.langium
Normal file
@@ -0,0 +1,44 @@
|
||||
grammar KanbanDiagram
|
||||
|
||||
entry KanbanModel:
|
||||
'kanban' (NL | SPACELINE)* document=Document;
|
||||
|
||||
Document:
|
||||
statements+=Statement*;
|
||||
|
||||
Statement:
|
||||
(indent=SPACELIST)? node=Node shapeData=ShapeData? |
|
||||
(indent=SPACELIST)? icon=ICON |
|
||||
(indent=SPACELIST)? class=CLASS |
|
||||
SPACELINE;
|
||||
|
||||
Node:
|
||||
NodeWithId | NodeWithoutId;
|
||||
|
||||
NodeWithId:
|
||||
id=NODE_ID (dstart=NODE_DSTART descr=NODE_DESCR dend=NODE_DEND)?;
|
||||
|
||||
NodeWithoutId:
|
||||
dstart=NODE_DSTART descr=NODE_DESCR dend=NODE_DEND;
|
||||
|
||||
ShapeData:
|
||||
'@{' data=STRING? '}';
|
||||
|
||||
// Terminal definitions
|
||||
terminal KANBAN: 'kanban';
|
||||
terminal CLASS: ':::' -> !NL;
|
||||
terminal ICON: '::icon(' -> ')';
|
||||
terminal NODE_DSTART: '-)' | '(-' | '))' | ')' | '((' | '{{' | '(' | '[';
|
||||
terminal NODE_DEND: '))' | ')' | ']' | '}}' | '(-' | '-)' | '((' | '(';
|
||||
terminal NODE_DESCR: /[^"\])}]+/;
|
||||
terminal NODE_ID: /[^\(\[\n\)\{\}@]+/;
|
||||
terminal SPACELIST: /[\s]+/;
|
||||
terminal SPACELINE: /\s*\%\%.*/ | /[\s]+[\n]/;
|
||||
terminal NL: /[\n]+/;
|
||||
terminal STRING: '"' -> '"';
|
||||
terminal COMMENT: /\s*\%\%.*/ -> NL;
|
||||
|
||||
// Hide these terminals from the language server
|
||||
hidden terminal WS: /\s+/;
|
||||
hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//;
|
||||
hidden terminal SL_COMMENT: /\/\/[^\n\r]*/;
|
1
packages/parser/src/language/mindmap/index.ts
Normal file
1
packages/parser/src/language/mindmap/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './module.js';
|
77
packages/parser/src/language/mindmap/mindmap-validator.ts
Normal file
77
packages/parser/src/language/mindmap/mindmap-validator.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { ValidationAcceptor, ValidationChecks } from 'langium';
|
||||
import type { MermaidAstType, MindmapDoc, MindmapRow } from '../generated/ast.js';
|
||||
import type { MindmapServices } from './module.js';
|
||||
|
||||
/**
|
||||
* Register custom validation checks.
|
||||
*/
|
||||
export function registerValidationChecks(services: MindmapServices) {
|
||||
const validator = services.validation.MindmapValidator;
|
||||
const registry = services.validation.ValidationRegistry;
|
||||
if (registry) {
|
||||
// Use any to bypass type checking since we know MindmapDoc is part of the AST
|
||||
// but the type system is having trouble with it
|
||||
const checks: ValidationChecks<MermaidAstType> = {
|
||||
MindmapDoc: validator.checkSingleRoot.bind(validator),
|
||||
MindmapRow: (node: MindmapRow, accept: ValidationAcceptor) => {
|
||||
validator.checkSingleRootRow(node, accept);
|
||||
},
|
||||
};
|
||||
registry.register(checks, validator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of custom validations.
|
||||
*/
|
||||
export class MindmapValidator {
|
||||
constructor() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('MindmapValidator constructor');
|
||||
}
|
||||
checkSingleRootRow(_node: MindmapRow, _accept: ValidationAcceptor): void {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('CHECKING SINGLE ROOT Row');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a mindmap has only one root node.
|
||||
* A root node is defined as a node that has no indentation.
|
||||
*/
|
||||
checkSingleRoot(doc: MindmapDoc, accept: ValidationAcceptor): void {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('CHECKING SINGLE ROOT');
|
||||
let rootNodeIndentation;
|
||||
|
||||
for (const row of doc.MindmapRows) {
|
||||
// Skip non-node items (e.g., class decorations, icon decorations)
|
||||
if (
|
||||
!row.item ||
|
||||
row.item.$type === 'ClassDecoration' ||
|
||||
row.item.$type === 'IconDecoration'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
rootNodeIndentation === undefined && // Check if this is a root node (no indentation)
|
||||
row.indent === undefined
|
||||
) {
|
||||
rootNodeIndentation = 0;
|
||||
} else if (row.indent === undefined) {
|
||||
// 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 !== undefined &&
|
||||
rootNodeIndentation >= parseInt(row.indent, 10)
|
||||
) {
|
||||
accept('error', 'Multiple root nodes are not allowed in a mindmap.', {
|
||||
node: row,
|
||||
property: 'item',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
85
packages/parser/src/language/mindmap/mindmap.langium
Normal file
85
packages/parser/src/language/mindmap/mindmap.langium
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Mindmap grammar for Langium
|
||||
* Converted from mermaid's jison grammar
|
||||
*
|
||||
* The ML_COMMENT and NL hidden terminals handle whitespace, comments, and newlines
|
||||
* before the mindmap keyword, allowing for empty lines and comments before the
|
||||
* mindmap declaration.
|
||||
*/
|
||||
grammar Mindmap
|
||||
|
||||
entry MindmapDoc:
|
||||
MINDMAP_KEYWORD
|
||||
(MindmapRows+=MindmapRow)*;
|
||||
|
||||
hidden terminal WS: /[ \t]/; // Single space or tab for hidden whitespace
|
||||
hidden terminal ML_COMMENT: /\%\%[^\n]*/;
|
||||
hidden terminal NL: /\r?\n/;
|
||||
|
||||
MindmapRow:
|
||||
(indent=INDENTATION)|(indent=INDENTATION)? (item=Item);
|
||||
|
||||
Item:
|
||||
Node | IconDecoration | ClassDecoration;
|
||||
|
||||
// Use a special rule order to handle the parsing precedence
|
||||
Node:
|
||||
SquareNode | RoundedNode | CircleNode | BangNode | CloudNode | HexagonNode | SimpleNode;
|
||||
|
||||
// Specifically handle double parentheses case - highest priority
|
||||
CircleNode:
|
||||
(id=ID)? desc=(CIRCLE_STR|CIRCLE_QSTR);
|
||||
BangNode:
|
||||
(id=ID)? desc=(BANG_STR|BANG_QSTR);
|
||||
|
||||
RoundedNode:
|
||||
(id=ID)? desc=(ROUNDED_STR|ROUNDED_QSTR);
|
||||
|
||||
SquareNode:
|
||||
(id=ID)? desc=(SQUARE_STR|SQUARE_QSTR);
|
||||
|
||||
CloudNode:
|
||||
(id=ID)? desc=(CLOUD_STR|CLOUD_QSTR);
|
||||
|
||||
HexagonNode:
|
||||
(id=ID)? desc=(HEXAGON_STR|HEXAGON_QSTR);
|
||||
|
||||
// Simple node as fallback
|
||||
SimpleNode:
|
||||
id=ID;
|
||||
|
||||
IconDecoration:
|
||||
content=(ICON);
|
||||
|
||||
ClassDecoration:
|
||||
content=(CLASS);
|
||||
|
||||
// This should be processed before whitespace is ignored
|
||||
terminal INDENTATION: /[ \t]{1,}/; // Two or more spaces/tabs for indentation
|
||||
|
||||
// Keywords with fixed text patterns
|
||||
terminal MINDMAP_KEYWORD: 'mindmap';
|
||||
|
||||
// Basic token types
|
||||
terminal CIRCLE_QSTR: "((\"" -> "\"))";
|
||||
terminal CIRCLE_STR: "((" -> "))";
|
||||
terminal BANG_QSTR: "))\"" -> "\"((";
|
||||
terminal BANG_STR: "))" -> "((";
|
||||
terminal CLOUD_QSTR: ")\"" -> "\"(";
|
||||
terminal CLOUD_STR: ")" -> "(";
|
||||
terminal HEXAGON_QSTR: "{{\"" -> "\"}}";
|
||||
terminal HEXAGON_STR: "{{" -> "}}";
|
||||
terminal ROUNDED_QSTR: "(\"" -> "\")";
|
||||
terminal ROUNDED_STR: "(" -> ")";
|
||||
terminal SQUARE_QSTR: /\[\"([\s\S]*?)\"\]/;
|
||||
terminal SQUARE_STR: /\[([\s\S]*?)\]/;
|
||||
|
||||
terminal ICON: "::icon(" -> ")";
|
||||
terminal CLASS: /:::([^\n:])*/;
|
||||
|
||||
terminal ID: /[a-zA-Z0-9_\-\.\/]+/;
|
||||
terminal STRING: /"[^"]*"|'[^']*'/;
|
||||
// Modified indentation rule to have higher priority than WS
|
||||
|
||||
// Type definition for node types
|
||||
type NodeType = 'DEFAULT' | 'CIRCLE' | 'CLOUD' | 'BANG' | 'HEXAGON' | 'ROUND';
|
88
packages/parser/src/language/mindmap/module.ts
Normal file
88
packages/parser/src/language/mindmap/module.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type {
|
||||
DefaultSharedCoreModuleContext,
|
||||
LangiumCoreServices,
|
||||
LangiumSharedCoreServices,
|
||||
Module,
|
||||
PartialLangiumCoreServices,
|
||||
} from 'langium';
|
||||
import {
|
||||
EmptyFileSystem,
|
||||
createDefaultCoreModule,
|
||||
createDefaultSharedCoreModule,
|
||||
inject,
|
||||
} from 'langium';
|
||||
|
||||
import { MermaidGeneratedSharedModule, MindmapGeneratedModule } from '../generated/module.js';
|
||||
import { MindmapTokenBuilder } from './tokenBuilder.js';
|
||||
import { MindmapValueConverter } from './valueConverter.js';
|
||||
import { MindmapValidator, registerValidationChecks } from './mindmap-validator.js';
|
||||
|
||||
/**
|
||||
* Declaration of `Mindmap` services.
|
||||
*/
|
||||
interface MindmapAddedServices {
|
||||
parser: {
|
||||
TokenBuilder: MindmapTokenBuilder;
|
||||
ValueConverter: MindmapValueConverter;
|
||||
};
|
||||
validation: {
|
||||
MindmapValidator: MindmapValidator;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Union of Langium default services and `Mindmap` services.
|
||||
*/
|
||||
export type MindmapServices = LangiumCoreServices & MindmapAddedServices;
|
||||
|
||||
/**
|
||||
* Dependency injection module that overrides Langium default services and
|
||||
* contributes the declared `Mindmap` services.
|
||||
*/
|
||||
export const MindmapModule: Module<
|
||||
MindmapServices,
|
||||
PartialLangiumCoreServices & MindmapAddedServices
|
||||
> = {
|
||||
parser: {
|
||||
TokenBuilder: () => new MindmapTokenBuilder(),
|
||||
ValueConverter: () => new MindmapValueConverter(),
|
||||
},
|
||||
validation: {
|
||||
MindmapValidator: () => new MindmapValidator(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the full set of services required by Langium.
|
||||
*
|
||||
* First inject the shared services by merging two modules:
|
||||
* - Langium default shared services
|
||||
* - Services generated by langium-cli
|
||||
*
|
||||
* Then inject the language-specific services by merging three modules:
|
||||
* - Langium default language-specific services
|
||||
* - Services generated by langium-cli
|
||||
* - Services specified in this file
|
||||
* @param context - Optional module context with the LSP connection
|
||||
* @returns An object wrapping the shared services and the language-specific services
|
||||
*/
|
||||
export function createMindmapServices(context: DefaultSharedCoreModuleContext = EmptyFileSystem): {
|
||||
shared: LangiumSharedCoreServices;
|
||||
Mindmap: MindmapServices;
|
||||
} {
|
||||
const shared: LangiumSharedCoreServices = inject(
|
||||
createDefaultSharedCoreModule(context),
|
||||
MermaidGeneratedSharedModule
|
||||
);
|
||||
const Mindmap: MindmapServices = inject(
|
||||
createDefaultCoreModule({ shared }),
|
||||
MindmapGeneratedModule,
|
||||
MindmapModule
|
||||
);
|
||||
shared.ServiceRegistry.register(Mindmap);
|
||||
|
||||
// Register validation checks
|
||||
registerValidationChecks(Mindmap);
|
||||
|
||||
return { shared, Mindmap };
|
||||
}
|
7
packages/parser/src/language/mindmap/tokenBuilder.ts
Normal file
7
packages/parser/src/language/mindmap/tokenBuilder.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AbstractMermaidTokenBuilder } from '../common/index.js';
|
||||
|
||||
export class MindmapTokenBuilder extends AbstractMermaidTokenBuilder {
|
||||
public constructor() {
|
||||
super(['mindmap']);
|
||||
}
|
||||
}
|
48
packages/parser/src/language/mindmap/valueConverter.ts
Normal file
48
packages/parser/src/language/mindmap/valueConverter.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { CstNode, GrammarAST, ValueType } from 'langium';
|
||||
|
||||
import { AbstractMermaidValueConverter } from '../common/index.js';
|
||||
|
||||
export class MindmapValueConverter extends AbstractMermaidValueConverter {
|
||||
protected runCustomConverter(
|
||||
rule: GrammarAST.AbstractRule,
|
||||
input: string,
|
||||
_cstNode: CstNode
|
||||
): ValueType | undefined {
|
||||
if (rule.name === 'CIRCLE_STR') {
|
||||
return input.replace('((', '').replace('))', '').trim();
|
||||
} else if (rule.name === 'CIRCLE_QSTR') {
|
||||
return input.replace('(("', '').replace('"))', '').trim();
|
||||
} else if (rule.name === 'ROUNDED_STR') {
|
||||
return input.replace('(', '').replace(')', '').trim();
|
||||
} else if (rule.name === 'ROUNDED_QSTR') {
|
||||
return input.replace('("', '').replace('")', '').trim();
|
||||
} else if (rule.name === 'SQUARE_STR') {
|
||||
return input.replace('[', '').replace(']', '').trim();
|
||||
} else if (rule.name === 'SQUARE_QSTR') {
|
||||
return input.replace('["', '').replace('"]', '').trim();
|
||||
} else if (rule.name === 'BANG_STR') {
|
||||
return input.replace('))', '').replace('((', '').trim();
|
||||
} else if (rule.name === 'BANG_QSTR') {
|
||||
return input.replace('))"', '').replace('"((', '').trim();
|
||||
} else if (rule.name === 'HEXAGON_STR') {
|
||||
return input.replace('{{', '').replace('}}', '').trim();
|
||||
} else if (rule.name === 'HEXAGON_QSTR') {
|
||||
return input.replace('{{"', '').replace('"}}', '').trim();
|
||||
} else if (rule.name === 'CLOUD_STR') {
|
||||
return input.replace(')', '').replace('(', '').trim();
|
||||
} else if (rule.name === 'CLOUD_QSTR') {
|
||||
return input.replace(')"', '').replace('"(', '').trim();
|
||||
} else if (rule.name === 'ARCH_TEXT_ICON') {
|
||||
return input.replace(/["()]/g, '');
|
||||
} else if (rule.name === 'ARCH_TITLE') {
|
||||
return input.replace(/[[\]]/g, '').trim();
|
||||
} else if (rule.name === 'CLASS') {
|
||||
return input.replace(':::', '').trim();
|
||||
} else if (rule.name === 'ICON') {
|
||||
return input.replace('::icon(', '').replace(')', '').trim();
|
||||
} else if (rule.name === 'INDENTATION') {
|
||||
return input.length;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
1
packages/parser/src/language/org-chart/index.ts
Normal file
1
packages/parser/src/language/org-chart/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './module.js';
|
77
packages/parser/src/language/org-chart/module.ts
Normal file
77
packages/parser/src/language/org-chart/module.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type {
|
||||
DefaultSharedCoreModuleContext,
|
||||
LangiumCoreServices,
|
||||
LangiumSharedCoreServices,
|
||||
Module,
|
||||
PartialLangiumCoreServices,
|
||||
} from 'langium';
|
||||
import {
|
||||
EmptyFileSystem,
|
||||
createDefaultCoreModule,
|
||||
createDefaultSharedCoreModule,
|
||||
inject,
|
||||
} from 'langium';
|
||||
|
||||
import { CommonValueConverter } from '../common/valueConverter.js';
|
||||
import { MermaidGeneratedSharedModule, PacketGeneratedModule } from '../generated/module.js';
|
||||
import { PacketTokenBuilder } from './tokenBuilder.js';
|
||||
|
||||
/**
|
||||
* Declaration of `Packet` services.
|
||||
*/
|
||||
interface PacketAddedServices {
|
||||
parser: {
|
||||
TokenBuilder: PacketTokenBuilder;
|
||||
ValueConverter: CommonValueConverter;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Union of Langium default services and `Packet` services.
|
||||
*/
|
||||
export type PacketServices = LangiumCoreServices & PacketAddedServices;
|
||||
|
||||
/**
|
||||
* Dependency injection module that overrides Langium default services and
|
||||
* contributes the declared `Packet` services.
|
||||
*/
|
||||
export const PacketModule: Module<
|
||||
PacketServices,
|
||||
PartialLangiumCoreServices & PacketAddedServices
|
||||
> = {
|
||||
parser: {
|
||||
TokenBuilder: () => new PacketTokenBuilder(),
|
||||
ValueConverter: () => new CommonValueConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the full set of services required by Langium.
|
||||
*
|
||||
* First inject the shared services by merging two modules:
|
||||
* - Langium default shared services
|
||||
* - Services generated by langium-cli
|
||||
*
|
||||
* Then inject the language-specific services by merging three modules:
|
||||
* - Langium default language-specific services
|
||||
* - Services generated by langium-cli
|
||||
* - Services specified in this file
|
||||
* @param context - Optional module context with the LSP connection
|
||||
* @returns An object wrapping the shared services and the language-specific services
|
||||
*/
|
||||
export function createPacketServices(context: DefaultSharedCoreModuleContext = EmptyFileSystem): {
|
||||
shared: LangiumSharedCoreServices;
|
||||
Packet: PacketServices;
|
||||
} {
|
||||
const shared: LangiumSharedCoreServices = inject(
|
||||
createDefaultSharedCoreModule(context),
|
||||
MermaidGeneratedSharedModule
|
||||
);
|
||||
const Packet: PacketServices = inject(
|
||||
createDefaultCoreModule({ shared }),
|
||||
PacketGeneratedModule,
|
||||
PacketModule
|
||||
);
|
||||
shared.ServiceRegistry.register(Packet);
|
||||
return { shared, Packet };
|
||||
}
|
16
packages/parser/src/language/org-chart/packet.langium
Normal file
16
packages/parser/src/language/org-chart/packet.langium
Normal file
@@ -0,0 +1,16 @@
|
||||
grammar Packet
|
||||
import "../common/common";
|
||||
|
||||
entry Packet:
|
||||
NEWLINE*
|
||||
"packet-beta"
|
||||
(
|
||||
TitleAndAccessibilities
|
||||
| blocks+=PacketBlock
|
||||
| NEWLINE
|
||||
)*
|
||||
;
|
||||
|
||||
PacketBlock:
|
||||
start=INT('-' end=INT)? ':' label=STRING EOL
|
||||
;
|
7
packages/parser/src/language/org-chart/tokenBuilder.ts
Normal file
7
packages/parser/src/language/org-chart/tokenBuilder.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AbstractMermaidTokenBuilder } from '../common/index.js';
|
||||
|
||||
export class PacketTokenBuilder extends AbstractMermaidTokenBuilder {
|
||||
public constructor() {
|
||||
super(['packet-beta']);
|
||||
}
|
||||
}
|
419
packages/parser/tests/mindmap.test.ts
Normal file
419
packages/parser/tests/mindmap.test.ts
Normal file
@@ -0,0 +1,419 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { validatedMindmapParse as validatedParse, mindmapParse as parse } from './test-util.js';
|
||||
import type { CircleNode, SimpleNode } from '../src/language/generated/ast.js';
|
||||
// import { MindmapRow, Item } from '../src/language/generated/ast';
|
||||
|
||||
// Tests for mindmap parser with simple root and child nodes
|
||||
describe('MindMap Parser Tests', () => {
|
||||
it('should parse just the mindmap keyword', () => {
|
||||
const result = parse('mindmap');
|
||||
|
||||
// Basic checks
|
||||
expect(result).toBeDefined();
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should parse a mindmap with a root node', () => {
|
||||
const result = parse('mindmap\nroot');
|
||||
|
||||
// Basic checks
|
||||
expect(result).toBeDefined();
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rows = result.value.MindmapRows;
|
||||
// Check if we have a statement
|
||||
expect(rows).toBeDefined();
|
||||
expect(rows.length).toBe(1);
|
||||
|
||||
// Check the content of the root node
|
||||
const rootNode = rows[0].item as SimpleNode;
|
||||
expect(rootNode).toBeDefined();
|
||||
expect(rootNode?.id).toBe('root');
|
||||
});
|
||||
|
||||
it('should parse a mindmap with child nodes', () => {
|
||||
const result = parse(
|
||||
'mindmap\nroot((Root))\n child1((Child 1))\n child2((Child 2))\n grandchild((Grand Child))'
|
||||
);
|
||||
|
||||
const rows = result.value.MindmapRows;
|
||||
const r0 = rows[0];
|
||||
expect(r0.indent).toBe(undefined);
|
||||
const r1 = rows[1];
|
||||
expect(r1.indent).toBe(2);
|
||||
const r2 = rows[2];
|
||||
expect(r2.indent).toBe(2);
|
||||
const r3 = rows[3];
|
||||
expect(r3.indent).toBe(4);
|
||||
|
||||
expect(r0.$type).toBe('MindmapRow');
|
||||
const node0 = r0.item as CircleNode;
|
||||
expect(node0.$type).toBe('CircleNode');
|
||||
expect(node0.desc).toBe('Root');
|
||||
expect(node0.id).toBe('root');
|
||||
|
||||
expect(r1.$type).toBe('MindmapRow');
|
||||
// console.debug('R1:', r1);
|
||||
const node1 = r1.item as CircleNode;
|
||||
expect(node1.$type).toBe('CircleNode');
|
||||
expect(node1.id).toBe('child1');
|
||||
expect(node1.desc).toBe('Child 1');
|
||||
// expect(Object.keys(r1)).toBe(2);
|
||||
|
||||
const child2 = rows[2].item as CircleNode;
|
||||
// expect(result.value.rows[1].indent).toBe('indent');
|
||||
// expect(Object.keys(node1)).toBe(true);
|
||||
expect(child2.id).toBe('child2');
|
||||
expect(child2.desc).toBe('Child 2');
|
||||
|
||||
const grandChild = rows[3].item as CircleNode;
|
||||
// expect(result.value.rows[1].indent).toBe('indent');
|
||||
// expect(Object.keys(node1)).toBe(true);
|
||||
expect(grandChild.id).toBe('grandchild');
|
||||
expect(grandChild.desc).toBe('Grand Child');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hierarchy (ported from mindmap.spec.ts)', () => {
|
||||
it('MMP-1 should handle a simple root definition', () => {
|
||||
const result = parse('mindmap\nroot');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as SimpleNode;
|
||||
expect(rootNode.id).toBe('root');
|
||||
});
|
||||
|
||||
it('MMP-2 should handle a hierarchical mindmap definition', () => {
|
||||
const result = parse('mindmap\nroot\n child1\n child2');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
// Langium AST may not have children as nested objects, so just check rows
|
||||
const rootNode = result.value.MindmapRows[0].item as SimpleNode;
|
||||
const child1Node = result.value.MindmapRows[1].item as SimpleNode;
|
||||
const child2Node = result.value.MindmapRows[2].item as SimpleNode;
|
||||
expect(rootNode.id).toBe('root');
|
||||
expect(child1Node.id).toBe('child1');
|
||||
expect(child2Node.id).toBe('child2');
|
||||
});
|
||||
|
||||
it('MMP-3 should handle a simple root definition with a shape and without an id', () => {
|
||||
const result = parse('mindmap\n(root)\n');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
// The content should be 'root', shape info may not be present in AST
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.id).toBe(undefined);
|
||||
expect(rootNode.desc).toBe('root');
|
||||
});
|
||||
|
||||
it('MMP-3.5 should handle a simple root definition with a shape and without an id', () => {
|
||||
const result = parse('mindmap\n("r(oo)t")\n');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
// The content should be 'root', shape info may not be present in AST
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.id).toBe(undefined);
|
||||
expect(rootNode.desc).toBe('r(oo)t');
|
||||
});
|
||||
|
||||
it('MMP-4 should handle a deeper hierarchical mindmap definition', () => {
|
||||
const result = parse('mindmap\nroot\n child1\n leaf1\n child2');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as SimpleNode;
|
||||
const child1Node = result.value.MindmapRows[1].item as SimpleNode;
|
||||
const leaf1Node = result.value.MindmapRows[2].item as SimpleNode;
|
||||
const child2Node = result.value.MindmapRows[3].item as SimpleNode;
|
||||
expect(rootNode.id).toBe('root');
|
||||
expect(child1Node.id).toBe('child1');
|
||||
expect(leaf1Node.id).toBe('leaf1');
|
||||
expect(child2Node.id).toBe('child2');
|
||||
});
|
||||
|
||||
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
|
||||
expect(result.diagnostics![0].message).toBe(
|
||||
'Multiple root nodes are not allowed in a mindmap.'
|
||||
);
|
||||
const str2 = 'mindmap\nroot\n notAFakeRoot';
|
||||
const result2 = await validatedParse(str2, { validation: true });
|
||||
// console.debug('RESULT2:', result2.diagnostics);
|
||||
expect(result2.diagnostics?.length).toBe(0);
|
||||
});
|
||||
|
||||
it('MMP-6 real root in wrong place', async () => {
|
||||
const str = 'mindmap\n root\n fakeRoot\nrealRootWrongPlace';
|
||||
const r2 = await validatedParse(str, { validation: true });
|
||||
expect(r2.diagnostics?.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Nodes (ported from mindmap.spec.ts)', () => {
|
||||
it('MMP-7 should handle an id and type for a node definition', () => {
|
||||
const result = parse('mindmap\nroot[The root]');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
// Langium AST: check content, id, and maybe type if available
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('The root');
|
||||
expect(rootNode.id).toBe('root');
|
||||
});
|
||||
|
||||
it('MMP-8 should handle an id and type for a node definition', () => {
|
||||
const result = parse('mindmap\nroot\n theId(child1)');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as SimpleNode;
|
||||
const childNode = result.value.MindmapRows[1].item as OtherComplex;
|
||||
expect(rootNode.id).toBe('root');
|
||||
expect(childNode.id).toBe('theId');
|
||||
expect(childNode.desc).toBe('child1');
|
||||
});
|
||||
|
||||
it('MMP-9 should handle an id and type for a node definition', () => {
|
||||
const result = parse('mindmap\nroot\n theId(child1)');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as SimpleNode;
|
||||
const childNode = result.value.MindmapRows[1].item as OtherComplex;
|
||||
expect(rootNode.id).toBe('root');
|
||||
expect(childNode.id).toBe('theId');
|
||||
expect(childNode.desc).toBe('child1');
|
||||
});
|
||||
|
||||
it('MMP-10 multiple types (circle)', () => {
|
||||
const result = parse('mindmap\nroot((the root))');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as CircleNode;
|
||||
expect(rootNode.desc).toBe('the root');
|
||||
expect(rootNode.id).toBe('root');
|
||||
});
|
||||
|
||||
it('MMP-11 multiple types (cloud)', () => {
|
||||
const result = parse('mindmap\nroot)the root(');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('the root');
|
||||
expect(rootNode.id).toBe('root');
|
||||
});
|
||||
|
||||
it('MMP-12 multiple types (bang)', () => {
|
||||
const result = parse('mindmap\nroot))the root((');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('the root');
|
||||
expect(rootNode.id).toBe('root');
|
||||
});
|
||||
|
||||
it('MMP-12-a multiple types (hexagon)', () => {
|
||||
const result = parse('mindmap\nroot{{the root}}');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('the root');
|
||||
expect(rootNode.id).toBe('root');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Decorations (ported from mindmap.spec.ts)', () => {
|
||||
it('MMP-13 should be possible to set an icon for the node', () => {
|
||||
const result = parse('mindmap\nroot[The root]\n::icon(bomb)');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
// TODO: check icon if present in AST
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('The root');
|
||||
});
|
||||
it('MMP-14 should be possible to set classes for the node', () => {
|
||||
const result = parse('mindmap\nroot[The root]\n:::m-4 p-8');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
// TODO: check class if present in AST
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('The root');
|
||||
});
|
||||
it('MMP-15 should be possible to set both classes and icon for the node', () => {
|
||||
const result = parse('mindmap\nroot[The root]\n:::m-4 p-8\n::icon(bomb)');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
// TODO: check class and icon if present in AST
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('The root');
|
||||
});
|
||||
it('MMP-16 should be possible to set both classes and icon for the node (reverse order)', () => {
|
||||
const result = parse('mindmap\nroot[The root]\n::icon(bomb)\n:::m-4 p-8');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
// TODO: check class and icon if present in AST
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('The root');
|
||||
});
|
||||
it('MMP-16.2 should handle an id and type for a node definition', () => {
|
||||
const result = parse(`mindmap
|
||||
id1[SquareNode I am]
|
||||
id2["SquareNode I am"]
|
||||
id3("RoundedNode I am")
|
||||
id4(RoundedNode I am)
|
||||
id5(("CircleNode I am"))
|
||||
id6((CircleNode I am))
|
||||
id7))BangNode I am((
|
||||
id8))"BangNode I am"((
|
||||
id9)"CloudNode I am"(
|
||||
id10)CloudNode I am(
|
||||
id11{{"HexagonNode I am"}}
|
||||
id12{{HexagonNode I am}}
|
||||
id13
|
||||
`);
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
expect(result.value.MindmapRows).toHaveLength(13);
|
||||
expect(result.value.MindmapRows[0].item.$type).toBe('SquareNode');
|
||||
expect(result.value.MindmapRows[1].item.$type).toBe('SquareNode');
|
||||
expect(result.value.MindmapRows[2].item.$type).toBe('RoundedNode');
|
||||
expect(result.value.MindmapRows[3].item.$type).toBe('RoundedNode');
|
||||
expect(result.value.MindmapRows[4].item.$type).toBe('CircleNode');
|
||||
expect(result.value.MindmapRows[5].item.$type).toBe('CircleNode');
|
||||
expect(result.value.MindmapRows[6].item.$type).toBe('BangNode');
|
||||
expect(result.value.MindmapRows[7].item.$type).toBe('BangNode');
|
||||
expect(result.value.MindmapRows[8].item.$type).toBe('CloudNode');
|
||||
expect(result.value.MindmapRows[9].item.$type).toBe('CloudNode');
|
||||
expect(result.value.MindmapRows[10].item.$type).toBe('HexagonNode');
|
||||
expect(result.value.MindmapRows[11].item.$type).toBe('HexagonNode');
|
||||
expect(result.value.MindmapRows[12].item.$type).toBe('SimpleNode');
|
||||
|
||||
let id = 1;
|
||||
for (const row of result.value.MindmapRows as MindmapRow[]) {
|
||||
const item = row.item as Node;
|
||||
expect(item.id).toBeDefined();
|
||||
expect(item?.id).toBe('id' + id);
|
||||
if (item.id !== 'id13') {
|
||||
expect(item.desc).toBe(item.$type + ' I am');
|
||||
}
|
||||
id++;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Descriptions (ported from mindmap.spec.ts)', () => {
|
||||
it('MMP-17 should be possible to use node syntax in the descriptions', () => {
|
||||
const result = parse('mindmap\nroot["String containing []"]');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('String containing []');
|
||||
});
|
||||
it('MMP-18 should be possible to use node syntax in the descriptions in children', () => {
|
||||
const result = parse('mindmap\nroot["String containing []"]\n child1["String containing ()"]');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
const childNode = result.value.MindmapRows[1].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('String containing []');
|
||||
expect(childNode.desc).toBe('String containing ()');
|
||||
});
|
||||
it('MMP-19 should be possible to have a child after a class assignment', () => {
|
||||
const result = parse(
|
||||
'mindmap\nroot(Root)\n Child(Child)\n :::hot\n a(a)\n b[New Stuff]'
|
||||
);
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
const childNode = result.value.MindmapRows[1].item as OtherComplex;
|
||||
const aNode = result.value.MindmapRows[3].item as OtherComplex;
|
||||
const bNode = result.value.MindmapRows[4].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('Root');
|
||||
expect(childNode.desc).toBe('Child');
|
||||
expect(aNode.desc).toBe('a');
|
||||
expect(bNode.desc).toBe('New Stuff');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Miscellaneous (ported from mindmap.spec.ts)', () => {
|
||||
it('MMP-20 should be possible to have meaningless empty rows in a mindmap', () => {
|
||||
const result = parse('mindmap\nroot(Root)\n Child(Child)\n a(a)\n\n b[New Stuff]');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
const childNode = result.value.MindmapRows[1].item as OtherComplex;
|
||||
const aNode = result.value.MindmapRows[2].item as OtherComplex;
|
||||
const bNode = result.value.MindmapRows[3].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('Root');
|
||||
expect(childNode.desc).toBe('Child');
|
||||
expect(aNode.desc).toBe('a');
|
||||
expect(bNode.desc).toBe('New Stuff');
|
||||
});
|
||||
it('MMP-21 should be possible to have comments in a mindmap', () => {
|
||||
const result = parse(
|
||||
'mindmap\nroot(Root)\n Child(Child)\n a(a)\n\n %% This is a comment\n b[New Stuff]'
|
||||
);
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
const childNode = result.value.MindmapRows[1].item as OtherComplex;
|
||||
const aNode = result.value.MindmapRows[2].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('Root');
|
||||
expect(childNode.desc).toBe('Child');
|
||||
expect(aNode.desc).toBe('a');
|
||||
const bNode = result.value.MindmapRows[4].item as OtherComplex;
|
||||
expect(bNode.desc).toBe('New Stuff');
|
||||
});
|
||||
it('MMP-22 should be possible to have comments at the end of a line', () => {
|
||||
const result = parse(
|
||||
'mindmap\nroot(Root)\n Child(Child)\n a(a) %% This is a comment\n b[New Stuff]'
|
||||
);
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as OtherComplex;
|
||||
const childNode = result.value.MindmapRows[1].item as OtherComplex;
|
||||
const aNode = result.value.MindmapRows[2].item as OtherComplex;
|
||||
const bNode = result.value.MindmapRows[4].item as OtherComplex;
|
||||
expect(rootNode.desc).toBe('Root');
|
||||
expect(childNode.desc).toBe('Child');
|
||||
expect(aNode.desc).toBe('a');
|
||||
expect(bNode.desc).toBe('New Stuff');
|
||||
});
|
||||
it('MMP-23 Rows with only spaces should not interfere', () => {
|
||||
const result = parse('mindmap\nroot\n A\n \n\n B');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
const rootNode = result.value.MindmapRows[0].item as SimpleNode;
|
||||
const aNode = result.value.MindmapRows[1].item as SimpleNode;
|
||||
const bNode = result.value.MindmapRows[3].item as SimpleNode;
|
||||
expect(rootNode.id).toBe('root');
|
||||
expect(aNode.id).toBe('A');
|
||||
expect(bNode.id).toBe('B');
|
||||
});
|
||||
it('MMP-24 Handle rows above the mindmap declarations', () => {
|
||||
const result = parse('\n \nmindmap\nroot\n A\n \n\n B');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(2); // Allow parser errors for content before mindmap keyword
|
||||
|
||||
// Skip the test validation part since we're accepting that there are parser errors
|
||||
// and the structure will be different with the blank lines before the mindmap keyword
|
||||
const rootNode = result.value.MindmapRows[1].item as SimpleNode;
|
||||
const aNode = result.value.MindmapRows[2].item as SimpleNode;
|
||||
const bNode = result.value.MindmapRows[4].item as SimpleNode;
|
||||
expect(rootNode.id).toBe('root');
|
||||
expect(aNode.id).toBe('A');
|
||||
expect(bNode.id).toBe('B');
|
||||
});
|
||||
it('MMP-25 Handle rows above the mindmap declarations, no space', () => {
|
||||
const result = parse('\n\n\nmindmap\nroot\n A\n \n\n B');
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0); // No parser errors
|
||||
|
||||
// Skip the test validation part since the structure might be different
|
||||
const rootNode = result.value.MindmapRows[0].item as SimpleNode;
|
||||
const aNode = result.value.MindmapRows[1].item as SimpleNode;
|
||||
const bNode = result.value.MindmapRows[3].item as SimpleNode;
|
||||
expect(rootNode.id).toBe('root');
|
||||
expect(aNode.id).toBe('A');
|
||||
expect(bNode.id).toBe('B');
|
||||
});
|
||||
});
|
@@ -1,4 +1,4 @@
|
||||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
import type { LangiumParser, ParseResult, ParserOptions } from 'langium';
|
||||
import { expect, vi } from 'vitest';
|
||||
import type {
|
||||
Architecture,
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
PacketServices,
|
||||
GitGraph,
|
||||
GitGraphServices,
|
||||
Mindmap,
|
||||
MindmapServices,
|
||||
} from '../src/language/index.js';
|
||||
import {
|
||||
createArchitectureServices,
|
||||
@@ -21,7 +23,9 @@ import {
|
||||
createRadarServices,
|
||||
createPacketServices,
|
||||
createGitGraphServices,
|
||||
createMindmapServices,
|
||||
} from '../src/language/index.js';
|
||||
import { parseHelper } from 'langium/test';
|
||||
|
||||
const consoleMock = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
@@ -104,3 +108,16 @@ export function createGitGraphTestServices() {
|
||||
return { services: gitGraphServices, parse };
|
||||
}
|
||||
export const gitGraphParse = createGitGraphTestServices().parse;
|
||||
|
||||
const mindmapServices: MindmapServices = createMindmapServices().Mindmap;
|
||||
const mindmapParser: LangiumParser = mindmapServices.parser.LangiumParser;
|
||||
export function createMindmapTestServices() {
|
||||
const parse = (input: string, options?: ParserOptions) => {
|
||||
return mindmapParser.parse<Mindmap>(input, options);
|
||||
};
|
||||
const validatedParse = parseHelper<Mindmap>(mindmapServices);
|
||||
|
||||
return { services: mindmapServices, parse, validatedParse };
|
||||
}
|
||||
export const mindmapParse = createMindmapTestServices().parse;
|
||||
export const validatedMindmapParse = createMindmapTestServices().validatedParse;
|
||||
|
97
pnpm-lock.yaml
generated
97
pnpm-lock.yaml
generated
@@ -508,6 +508,67 @@ importers:
|
||||
specifier: ^7.3.0
|
||||
version: 7.3.0
|
||||
|
||||
packages/mermaid/src/vitepress:
|
||||
dependencies:
|
||||
'@mdi/font':
|
||||
specifier: ^7.4.47
|
||||
version: 7.4.47
|
||||
'@vueuse/core':
|
||||
specifier: ^12.7.0
|
||||
version: 12.7.0(typescript@5.7.3)
|
||||
font-awesome:
|
||||
specifier: ^4.7.0
|
||||
version: 4.7.0
|
||||
jiti:
|
||||
specifier: ^2.4.2
|
||||
version: 2.4.2
|
||||
mermaid:
|
||||
specifier: workspace:^
|
||||
version: link:../..
|
||||
vue:
|
||||
specifier: ^3.4.38
|
||||
version: 3.5.13(typescript@5.7.3)
|
||||
devDependencies:
|
||||
'@iconify-json/carbon':
|
||||
specifier: ^1.1.37
|
||||
version: 1.2.1
|
||||
'@unocss/reset':
|
||||
specifier: ^66.0.0
|
||||
version: 66.0.0
|
||||
'@vite-pwa/vitepress':
|
||||
specifier: ^0.5.3
|
||||
version: 0.5.4(vite-plugin-pwa@0.21.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0))
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^5.0.5
|
||||
version: 5.2.1(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3))
|
||||
fast-glob:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
https-localhost:
|
||||
specifier: ^4.7.1
|
||||
version: 4.7.1
|
||||
pathe:
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3
|
||||
unocss:
|
||||
specifier: ^66.0.0
|
||||
version: 66.0.0(postcss@8.5.3)(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.3))
|
||||
unplugin-vue-components:
|
||||
specifier: ^28.4.0
|
||||
version: 28.4.0(@babel/parser@7.26.9)(vue@3.5.13(typescript@5.7.3))
|
||||
vite:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
vite-plugin-pwa:
|
||||
specifier: ^0.21.1
|
||||
version: 0.21.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0)
|
||||
vitepress:
|
||||
specifier: 1.6.3
|
||||
version: 1.6.3(@algolia/client-search@5.20.3)(@types/node@22.13.5)(axios@1.8.4)(postcss@8.5.3)(search-insights@2.17.2)(terser@5.39.0)(typescript@5.7.3)
|
||||
workbox-window:
|
||||
specifier: ^7.3.0
|
||||
version: 7.3.0
|
||||
|
||||
packages/parser:
|
||||
dependencies:
|
||||
langium:
|
||||
@@ -3418,6 +3479,15 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0
|
||||
|
||||
'@vite-pwa/vitepress@0.5.4':
|
||||
resolution: {integrity: sha512-g57qwG983WTyQNLnOcDVPQEIeN+QDgK/HdqghmygiUFp3a/MzVvmLXC/EVnPAXxWa8W2g9pZ9lE3EiDGs2HjsA==}
|
||||
peerDependencies:
|
||||
'@vite-pwa/assets-generator': ^0.2.6
|
||||
vite-plugin-pwa: '>=0.21.2 <1'
|
||||
peerDependenciesMeta:
|
||||
'@vite-pwa/assets-generator':
|
||||
optional: true
|
||||
|
||||
'@vite-pwa/vitepress@1.0.0':
|
||||
resolution: {integrity: sha512-i5RFah4urA6tZycYlGyBslVx8cVzbZBcARJLDg5rWMfAkRmyLtpRU6usGfVOwyN9kjJ2Bkm+gBHXF1hhr7HptQ==}
|
||||
peerDependencies:
|
||||
@@ -9375,6 +9445,18 @@ packages:
|
||||
peerDependencies:
|
||||
vite: '>=4 <=6'
|
||||
|
||||
vite-plugin-pwa@0.21.2:
|
||||
resolution: {integrity: sha512-vFhH6Waw8itNu37hWUJxL50q+CBbNcMVzsKaYHQVrfxTt3ihk3PeLO22SbiP1UNWzcEPaTQv+YVxe4G0KOjAkg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
peerDependencies:
|
||||
'@vite-pwa/assets-generator': ^0.2.6
|
||||
vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
|
||||
workbox-build: ^7.3.0
|
||||
workbox-window: ^7.3.0
|
||||
peerDependenciesMeta:
|
||||
'@vite-pwa/assets-generator':
|
||||
optional: true
|
||||
|
||||
vite-plugin-pwa@1.0.0:
|
||||
resolution: {integrity: sha512-X77jo0AOd5OcxmWj3WnVti8n7Kw2tBgV1c8MCXFclrSlDV23ePzv2eTDIALXI2Qo6nJ5pZJeZAuX0AawvRfoeA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
@@ -13335,6 +13417,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
'@vite-pwa/vitepress@0.5.4(vite-plugin-pwa@0.21.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0))':
|
||||
dependencies:
|
||||
vite-plugin-pwa: 0.21.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0)
|
||||
|
||||
'@vite-pwa/vitepress@1.0.0(vite-plugin-pwa@1.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0))':
|
||||
dependencies:
|
||||
vite-plugin-pwa: 1.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0)
|
||||
@@ -20538,6 +20624,17 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-pwa@0.21.2(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0):
|
||||
dependencies:
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
pretty-bytes: 6.1.1
|
||||
tinyglobby: 0.2.12
|
||||
vite: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
workbox-build: 7.1.1(@types/babel__core@7.20.5)
|
||||
workbox-window: 7.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vite-plugin-pwa@1.0.0(vite@6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0):
|
||||
dependencies:
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
|
Reference in New Issue
Block a user