mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-10-25 00:44:10 +02:00 
			
		
		
		
	Compare commits
	
		
			17 Commits
		
	
	
		
			848f69a75c
			...
			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