mermaid-ast 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +53 -0
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3 -3
  5. package/dist/parser/class-parser.d.ts +15 -0
  6. package/dist/parser/class-parser.d.ts.map +1 -0
  7. package/dist/parser/class-parser.js +295 -0
  8. package/dist/parser/flowchart-parser.d.ts.map +1 -1
  9. package/dist/parser/flowchart-parser.js +32 -5
  10. package/dist/parser/index.d.ts +1 -0
  11. package/dist/parser/index.d.ts.map +1 -1
  12. package/dist/parser/index.js +7 -1
  13. package/dist/parser/sequence-parser.d.ts.map +1 -1
  14. package/dist/parser/sequence-parser.js +55 -0
  15. package/dist/parser/state-parser.d.ts +17 -0
  16. package/dist/parser/state-parser.d.ts.map +1 -0
  17. package/dist/parser/state-parser.js +221 -0
  18. package/dist/renderer/class-renderer.d.ts +11 -0
  19. package/dist/renderer/class-renderer.d.ts.map +1 -0
  20. package/dist/renderer/class-renderer.js +238 -0
  21. package/dist/renderer/flowchart-renderer.js +2 -2
  22. package/dist/renderer/index.d.ts +1 -0
  23. package/dist/renderer/index.d.ts.map +1 -1
  24. package/dist/renderer/index.js +6 -1
  25. package/dist/renderer/sequence-renderer.d.ts +4 -0
  26. package/dist/renderer/sequence-renderer.d.ts.map +1 -1
  27. package/dist/renderer/sequence-renderer.js +25 -2
  28. package/dist/renderer/state-renderer.d.ts +11 -0
  29. package/dist/renderer/state-renderer.d.ts.map +1 -0
  30. package/dist/renderer/state-renderer.js +150 -0
  31. package/dist/types/class.d.ts +64 -0
  32. package/dist/types/class.d.ts.map +1 -0
  33. package/dist/types/class.js +17 -0
  34. package/dist/types/index.d.ts +8 -2
  35. package/dist/types/index.d.ts.map +1 -1
  36. package/dist/types/index.js +7 -0
  37. package/dist/types/state.d.ts +150 -0
  38. package/dist/types/state.d.ts.map +1 -0
  39. package/dist/types/state.js +20 -0
  40. package/dist/vendored/VERSION +1 -0
  41. package/dist/vendored/grammars/class.jison +429 -0
  42. package/dist/vendored/grammars/flowchart.jison +631 -0
  43. package/dist/vendored/grammars/sequence.jison +353 -0
  44. package/dist/vendored/grammars/state.jison +336 -0
  45. package/dist/vendored/parsers/class.js +942 -0
  46. package/dist/vendored/parsers/flowchart.js +1089 -0
  47. package/dist/vendored/parsers/sequence.js +941 -0
  48. package/dist/vendored/parsers/state.js +876 -0
  49. package/dist/vendored/sync-info.json +28 -0
  50. package/package.json +3 -2
package/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # mermaid-ast
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/mermaid-ast)](https://www.npmjs.com/package/mermaid-ast)
3
4
  [![Built with Slate](https://img.shields.io/badge/Built%20with-Slate-blue)](https://randomlabs.ai)
4
5
 
5
6
  Parse and render Mermaid diagrams to/from AST (Abstract Syntax Tree).
@@ -18,6 +19,7 @@ This library provides a way to programmatically work with Mermaid diagrams by pa
18
19
 
19
20
  - **Flowchart** (`flowchart`, `graph`)
20
21
  - **Sequence Diagram** (`sequenceDiagram`)
22
+ - **Class Diagram** (`classDiagram`)
21
23
 
22
24
  ## Installation
23
25
 
@@ -91,6 +93,33 @@ console.log(ast.statements.length); // 2
91
93
  const output = renderSequence(ast);
92
94
  ```
93
95
 
96
+ ### Class Diagrams
97
+
98
+ ```typescript
99
+ import { parseClassDiagram, renderClassDiagram } from "mermaid-ast";
100
+
101
+ const ast = parseClassDiagram(`classDiagram
102
+ class Animal {
103
+ +String name
104
+ +int age
105
+ +eat()
106
+ +sleep()
107
+ }
108
+ class Duck {
109
+ +String beakColor
110
+ +swim()
111
+ +quack()
112
+ }
113
+ Animal <|-- Duck`);
114
+
115
+ // Access AST properties
116
+ console.log(ast.classes.size); // 2
117
+ console.log(ast.relations.length); // 1
118
+
119
+ // Render back
120
+ const output = renderClassDiagram(ast);
121
+ ```
122
+
94
123
  ### Diagram Type Detection
95
124
 
96
125
  ```typescript
@@ -98,6 +127,7 @@ import { detectDiagramType } from "mermaid-ast";
98
127
 
99
128
  detectDiagramType("flowchart LR\n A --> B"); // "flowchart"
100
129
  detectDiagramType("sequenceDiagram\n A->>B: Hi"); // "sequence"
130
+ detectDiagramType("classDiagram\n class Animal"); // "class"
101
131
  detectDiagramType("unknown diagram"); // null
102
132
  ```
103
133
 
@@ -127,6 +157,14 @@ detectDiagramType("unknown diagram"); // null
127
157
  | `renderSequence(ast: SequenceAST): string` | Render sequence AST |
128
158
  | `isSequenceDiagram(input: string): boolean` | Check if input is sequence |
129
159
 
160
+ ### Class Diagram Functions
161
+
162
+ | Function | Description |
163
+ |----------|-------------|
164
+ | `parseClassDiagram(input: string): ClassDiagramAST` | Parse class diagram |
165
+ | `renderClassDiagram(ast: ClassDiagramAST): string` | Render class diagram AST |
166
+ | `isClassDiagram(input: string): boolean` | Check if input is class diagram |
167
+
130
168
  ## Supported Flowchart Features
131
169
 
132
170
  - **Directions**: LR, RL, TB, TD, BT
@@ -148,6 +186,21 @@ detectDiagramType("unknown diagram"); // null
148
186
  - **Notes**: `note left of`, `note right of`, `note over`
149
187
  - **Actor lifecycle**: `create`, `destroy`
150
188
 
189
+ ## Supported Class Diagram Features
190
+
191
+ - **Classes**: With labels, members (attributes and methods)
192
+ - **Visibility modifiers**: `+` (public), `-` (private), `#` (protected), `~` (package)
193
+ - **Relationships**: Inheritance (`<|--`), composition (`*--`), aggregation (`o--`), dependency (`<--`), lollipop (`()--`)
194
+ - **Line types**: Solid (`--`), dotted (`..`)
195
+ - **Cardinality**: `"1" --> "*"` relationship labels
196
+ - **Annotations**: `<<interface>>`, `<<abstract>>`, `<<service>>`, etc.
197
+ - **Namespaces**: Group related classes
198
+ - **Notes**: `note for Class "text"`
199
+ - **Direction**: `direction LR`, `direction TB`, etc.
200
+ - **Styling**: `cssClass`, `classDef`
201
+ - **Interactions**: `callback`, `link`
202
+ - **Generic types**: `class List~T~`
203
+
151
204
  ## Development
152
205
 
153
206
  ### Prerequisites
package/dist/index.d.ts CHANGED
@@ -17,6 +17,6 @@
17
17
  * ```
18
18
  */
19
19
  export * from "./types/index.js";
20
- export { parse, parseFlowchart, parseSequence, detectDiagramType, isFlowchartDiagram, isSequenceDiagram, } from "./parser/index.js";
21
- export { render, renderFlowchart, renderSequence, } from "./renderer/index.js";
20
+ export { parse, parseFlowchart, parseSequence, parseClassDiagram, detectDiagramType, isFlowchartDiagram, isSequenceDiagram, isClassDiagram, } from "./parser/index.js";
21
+ export { render, renderFlowchart, renderSequence, renderClassDiagram, } from "./renderer/index.js";
22
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,OAAO,EACL,KAAK,EACL,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,MAAM,EACN,eAAe,EACf,cAAc,GACf,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,cAAc,kBAAkB,CAAC;AAGjC,OAAO,EACL,KAAK,EACL,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,MAAM,EACN,eAAe,EACf,cAAc,EACd,kBAAkB,GACnB,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -19,6 +19,6 @@
19
19
  // Export types
20
20
  export * from "./types/index.js";
21
21
  // Export parser functions
22
- export { parse, parseFlowchart, parseSequence, detectDiagramType, isFlowchartDiagram, isSequenceDiagram, } from "./parser/index.js";
23
- // Export renderer functions (to be implemented)
24
- export { render, renderFlowchart, renderSequence, } from "./renderer/index.js";
22
+ export { parse, parseFlowchart, parseSequence, parseClassDiagram, detectDiagramType, isFlowchartDiagram, isSequenceDiagram, isClassDiagram, } from "./parser/index.js";
23
+ // Export renderer functions
24
+ export { render, renderFlowchart, renderSequence, renderClassDiagram, } from "./renderer/index.js";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Class Diagram Parser
3
+ *
4
+ * Parses Mermaid class diagram syntax into an AST using the vendored JISON parser.
5
+ */
6
+ import { type ClassDiagramAST } from "../types/class.js";
7
+ /**
8
+ * Parse a class diagram string into an AST
9
+ */
10
+ export declare function parseClassDiagram(input: string): ClassDiagramAST;
11
+ /**
12
+ * Detect if input is a class diagram
13
+ */
14
+ export declare function isClassDiagram(input: string): boolean;
15
+ //# sourceMappingURL=class-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"class-parser.d.ts","sourceRoot":"","sources":["../../src/parser/class-parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,eAAe,EASrB,MAAM,mBAAmB,CAAC;AAwS3B;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAsBhE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOrD"}
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Class Diagram Parser
3
+ *
4
+ * Parses Mermaid class diagram syntax into an AST using the vendored JISON parser.
5
+ */
6
+ import { createClassDiagramAST, } from "../types/class.js";
7
+ // Import the vendored parser
8
+ // @ts-ignore - Generated JS file without types
9
+ import classParser from "../vendored/parsers/class.js";
10
+ /**
11
+ * Parse a member string to extract visibility and type
12
+ */
13
+ function parseMember(memberStr) {
14
+ const trimmed = memberStr.trim();
15
+ let visibility;
16
+ let text = trimmed;
17
+ // Check for visibility prefix
18
+ if (trimmed.startsWith("+")) {
19
+ visibility = "+";
20
+ text = trimmed.slice(1).trim();
21
+ }
22
+ else if (trimmed.startsWith("-")) {
23
+ visibility = "-";
24
+ text = trimmed.slice(1).trim();
25
+ }
26
+ else if (trimmed.startsWith("#")) {
27
+ visibility = "#";
28
+ text = trimmed.slice(1).trim();
29
+ }
30
+ else if (trimmed.startsWith("~")) {
31
+ visibility = "~";
32
+ text = trimmed.slice(1).trim();
33
+ }
34
+ // Determine if method or attribute
35
+ const isMethod = text.includes("(") && text.includes(")");
36
+ return {
37
+ text,
38
+ visibility,
39
+ type: isMethod ? "method" : "attribute",
40
+ };
41
+ }
42
+ /**
43
+ * Map relation type number to string
44
+ */
45
+ function mapRelationType(type) {
46
+ if (typeof type === "string")
47
+ return type;
48
+ switch (type) {
49
+ case 0:
50
+ return "aggregation";
51
+ case 1:
52
+ return "extension";
53
+ case 2:
54
+ return "composition";
55
+ case 3:
56
+ return "dependency";
57
+ case 4:
58
+ return "lollipop";
59
+ default:
60
+ return "none";
61
+ }
62
+ }
63
+ /**
64
+ * Map line type number to string
65
+ */
66
+ function mapLineType(type) {
67
+ if (typeof type === "string")
68
+ return type;
69
+ return type === 0 ? "solid" : "dotted";
70
+ }
71
+ /**
72
+ * Create the yy object that the JISON parser uses
73
+ */
74
+ function createClassYY(ast) {
75
+ // Helper to get or create a class
76
+ function getOrCreateClass(id) {
77
+ let cls = ast.classes.get(id);
78
+ if (!cls) {
79
+ cls = {
80
+ id,
81
+ members: [],
82
+ annotations: [],
83
+ cssClasses: [],
84
+ styles: [],
85
+ };
86
+ ast.classes.set(id, cls);
87
+ }
88
+ return cls;
89
+ }
90
+ return {
91
+ // Relation type constants (used by the parser)
92
+ relationType: {
93
+ AGGREGATION: 0,
94
+ EXTENSION: 1,
95
+ COMPOSITION: 2,
96
+ DEPENDENCY: 3,
97
+ LOLLIPOP: 4,
98
+ },
99
+ // Line type constants
100
+ lineType: {
101
+ LINE: 0,
102
+ DOTTED_LINE: 1,
103
+ },
104
+ // Set diagram direction
105
+ setDirection(dir) {
106
+ ast.direction = dir;
107
+ },
108
+ // Add a class
109
+ addClass(id) {
110
+ getOrCreateClass(id);
111
+ },
112
+ // Set class label
113
+ setClassLabel(id, label) {
114
+ const cls = getOrCreateClass(id);
115
+ cls.label = label;
116
+ },
117
+ // Add members to a class
118
+ addMembers(id, members) {
119
+ const cls = getOrCreateClass(id);
120
+ for (const member of members) {
121
+ const trimmed = member.trim();
122
+ if (trimmed) { // Skip empty members
123
+ cls.members.push(parseMember(member));
124
+ }
125
+ }
126
+ },
127
+ // Add a single member (called from memberStatement)
128
+ addMember(id, member) {
129
+ const cls = getOrCreateClass(id);
130
+ const trimmed = member.trim();
131
+ if (trimmed) { // Skip empty members
132
+ cls.members.push(parseMember(member));
133
+ }
134
+ },
135
+ // Add a relation
136
+ addRelation(relation) {
137
+ // Ensure both classes exist
138
+ getOrCreateClass(relation.id1);
139
+ getOrCreateClass(relation.id2);
140
+ const rel = {
141
+ id1: relation.id1,
142
+ id2: relation.id2,
143
+ relation: {
144
+ type1: mapRelationType(relation.relation.type1),
145
+ type2: mapRelationType(relation.relation.type2),
146
+ lineType: mapLineType(relation.relation.lineType),
147
+ },
148
+ };
149
+ if (relation.relationTitle1 && relation.relationTitle1 !== "none") {
150
+ rel.relationTitle1 = relation.relationTitle1;
151
+ }
152
+ if (relation.relationTitle2 && relation.relationTitle2 !== "none") {
153
+ rel.relationTitle2 = relation.relationTitle2;
154
+ }
155
+ if (relation.title) {
156
+ rel.title = relation.title;
157
+ }
158
+ ast.relations.push(rel);
159
+ },
160
+ // Add annotation (like <<interface>>)
161
+ addAnnotation(className, annotation) {
162
+ const cls = getOrCreateClass(className);
163
+ cls.annotations.push(annotation);
164
+ },
165
+ // Add namespace
166
+ addNamespace(name) {
167
+ if (!ast.namespaces.has(name)) {
168
+ ast.namespaces.set(name, { name, classes: [] });
169
+ }
170
+ },
171
+ // Add classes to namespace
172
+ addClassesToNamespace(namespaceName, classIds) {
173
+ let ns = ast.namespaces.get(namespaceName);
174
+ if (!ns) {
175
+ ns = { name: namespaceName, classes: [] };
176
+ ast.namespaces.set(namespaceName, ns);
177
+ }
178
+ for (const classId of classIds) {
179
+ if (!ns.classes.includes(classId)) {
180
+ ns.classes.push(classId);
181
+ }
182
+ }
183
+ },
184
+ // Add note
185
+ addNote(text, forClass) {
186
+ const note = { text };
187
+ if (forClass) {
188
+ note.forClass = forClass;
189
+ }
190
+ ast.notes.push(note);
191
+ },
192
+ // Set CSS class on a class (id can be comma-separated list like "C1,C2")
193
+ setCssClass(id, className) {
194
+ // Split by comma to handle "cssClass C1,C2 styleClass" syntax
195
+ const classIds = id.split(",").map(s => s.trim()).filter(s => s.length > 0);
196
+ for (const classId of classIds) {
197
+ const cls = getOrCreateClass(classId);
198
+ if (!cls.cssClasses.includes(className)) {
199
+ cls.cssClasses.push(className);
200
+ }
201
+ }
202
+ },
203
+ // Set inline CSS style
204
+ setCssStyle(id, styles) {
205
+ const cls = getOrCreateClass(id);
206
+ cls.styles.push(...styles);
207
+ },
208
+ // Define a CSS class (classDef)
209
+ defineClass(classList, styles) {
210
+ for (const className of classList) {
211
+ ast.classDefs.set(className, { name: className, styles });
212
+ }
213
+ },
214
+ // Set click event
215
+ setClickEvent(id, callback, callbackArgs) {
216
+ const cls = getOrCreateClass(id);
217
+ cls.callback = callback;
218
+ if (callbackArgs) {
219
+ cls.callbackArgs = callbackArgs;
220
+ }
221
+ },
222
+ // Set link
223
+ setLink(id, href, target) {
224
+ const cls = getOrCreateClass(id);
225
+ cls.link = href;
226
+ if (target) {
227
+ cls.linkTarget = target;
228
+ }
229
+ },
230
+ // Set tooltip
231
+ setTooltip(id, tooltip) {
232
+ const cls = getOrCreateClass(id);
233
+ cls.tooltip = tooltip;
234
+ },
235
+ // Clean up label (remove surrounding quotes, colons)
236
+ cleanupLabel(label) {
237
+ let result = label;
238
+ if (result.startsWith(":")) {
239
+ result = result.slice(1);
240
+ }
241
+ result = result.trim();
242
+ if ((result.startsWith('"') && result.endsWith('"')) ||
243
+ (result.startsWith("'") && result.endsWith("'"))) {
244
+ result = result.slice(1, -1);
245
+ }
246
+ return result;
247
+ },
248
+ // Accessibility
249
+ setAccTitle(title) {
250
+ ast.accTitle = title;
251
+ },
252
+ setAccDescription(description) {
253
+ ast.accDescription = description;
254
+ },
255
+ // These are called but we don't need to do anything special
256
+ getTooltip: () => undefined,
257
+ lookUpDomId: (id) => id,
258
+ setDiagramTitle: () => { },
259
+ getDiagramTitle: () => "",
260
+ getConfig: () => ({}),
261
+ clear: () => { },
262
+ };
263
+ }
264
+ /**
265
+ * Parse a class diagram string into an AST
266
+ */
267
+ export function parseClassDiagram(input) {
268
+ const ast = createClassDiagramAST();
269
+ const yy = createClassYY(ast);
270
+ // Set up the parser with our yy object
271
+ classParser.yy = yy;
272
+ try {
273
+ classParser.parse(input);
274
+ }
275
+ catch (error) {
276
+ if (error instanceof Error) {
277
+ throw new Error(`Failed to parse class diagram: ${error.message}`);
278
+ }
279
+ throw error;
280
+ }
281
+ // Reverse members to maintain source order (JISON grammar processes bottom-up)
282
+ for (const [, cls] of ast.classes) {
283
+ cls.members.reverse();
284
+ }
285
+ return ast;
286
+ }
287
+ /**
288
+ * Detect if input is a class diagram
289
+ */
290
+ export function isClassDiagram(input) {
291
+ const trimmed = input.trim();
292
+ const firstLine = trimmed.split("\n")[0].trim().toLowerCase();
293
+ return (firstLine.startsWith("classdiagram") ||
294
+ firstLine.startsWith("classdiagram-v2"));
295
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"flowchart-parser.d.ts","sourceRoot":"","sources":["../../src/parser/flowchart-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,KAAK,YAAY,EAalB,MAAM,uBAAuB,CAAC;AA+Z/B;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAkB1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQzD"}
1
+ {"version":3,"file":"flowchart-parser.d.ts","sourceRoot":"","sources":["../../src/parser/flowchart-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,KAAK,YAAY,EAalB,MAAM,uBAAuB,CAAC;AAyb/B;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAkB1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQzD"}
@@ -205,10 +205,10 @@ function createFlowchartYY(ast) {
205
205
  subgraphId = `subGraph${subgraphIdCounter++}`;
206
206
  }
207
207
  else if (typeof id === "object" && "text" in id) {
208
- subgraphId = id.text;
208
+ subgraphId = id.text.trim();
209
209
  }
210
210
  else {
211
- subgraphId = id;
211
+ subgraphId = id.trim();
212
212
  }
213
213
  // Collect node IDs from the nodeList and look for direction statements
214
214
  const nodes = [];
@@ -216,10 +216,21 @@ function createFlowchartYY(ast) {
216
216
  if (Array.isArray(nodeList)) {
217
217
  for (const item of nodeList) {
218
218
  if (typeof item === "string") {
219
- nodes.push(item);
219
+ // Trim and filter empty strings
220
+ const trimmed = item.trim();
221
+ if (trimmed) {
222
+ nodes.push(trimmed);
223
+ }
220
224
  }
221
225
  else if (Array.isArray(item)) {
222
- nodes.push(...item.filter((x) => typeof x === "string"));
226
+ for (const x of item) {
227
+ if (typeof x === "string") {
228
+ const trimmed = x.trim();
229
+ if (trimmed) {
230
+ nodes.push(trimmed);
231
+ }
232
+ }
233
+ }
223
234
  }
224
235
  else if (item && typeof item === "object") {
225
236
  // Check for direction statement: {stmt: 'dir', value: 'TB'}
@@ -229,12 +240,28 @@ function createFlowchartYY(ast) {
229
240
  else if ("nodes" in item) {
230
241
  const itemNodes = item.nodes;
231
242
  if (Array.isArray(itemNodes)) {
232
- nodes.push(...itemNodes);
243
+ for (const x of itemNodes) {
244
+ if (typeof x === "string") {
245
+ const trimmed = x.trim();
246
+ if (trimmed) {
247
+ nodes.push(trimmed);
248
+ }
249
+ }
250
+ }
233
251
  }
234
252
  }
235
253
  }
236
254
  }
237
255
  }
256
+ else if (typeof nodeList === "string") {
257
+ // Handle case where nodeList is a string (might be space-separated node IDs)
258
+ const trimmed = nodeList.trim();
259
+ if (trimmed) {
260
+ // Split by whitespace and filter empty strings
261
+ const parts = trimmed.split(/\s+/).filter(p => p.length > 0);
262
+ nodes.push(...parts);
263
+ }
264
+ }
238
265
  const subgraph = {
239
266
  id: subgraphId,
240
267
  nodes,
@@ -5,6 +5,7 @@
5
5
  */
6
6
  export { parseFlowchart, isFlowchartDiagram } from "./flowchart-parser.js";
7
7
  export { parseSequence, isSequenceDiagram } from "./sequence-parser.js";
8
+ export { parseClassDiagram, isClassDiagram } from "./class-parser.js";
8
9
  import type { MermaidAST, DiagramType } from "../types/index.js";
9
10
  /**
10
11
  * Detect the diagram type from input text
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIjE;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAInE;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAiB/C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parser/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKjE;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAKnE;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAmB/C"}
@@ -5,8 +5,10 @@
5
5
  */
6
6
  export { parseFlowchart, isFlowchartDiagram } from "./flowchart-parser.js";
7
7
  export { parseSequence, isSequenceDiagram } from "./sequence-parser.js";
8
+ export { parseClassDiagram, isClassDiagram } from "./class-parser.js";
8
9
  import { parseFlowchart, isFlowchartDiagram } from "./flowchart-parser.js";
9
10
  import { parseSequence, isSequenceDiagram } from "./sequence-parser.js";
11
+ import { parseClassDiagram, isClassDiagram } from "./class-parser.js";
10
12
  /**
11
13
  * Detect the diagram type from input text
12
14
  */
@@ -15,6 +17,8 @@ export function detectDiagramType(input) {
15
17
  return "flowchart";
16
18
  if (isSequenceDiagram(input))
17
19
  return "sequence";
20
+ if (isClassDiagram(input))
21
+ return "class";
18
22
  return null;
19
23
  }
20
24
  /**
@@ -23,13 +27,15 @@ export function detectDiagramType(input) {
23
27
  export function parse(input) {
24
28
  const type = detectDiagramType(input);
25
29
  if (!type) {
26
- throw new Error("Unable to detect diagram type. Supported types: flowchart, sequence");
30
+ throw new Error("Unable to detect diagram type. Supported types: flowchart, sequence, class");
27
31
  }
28
32
  switch (type) {
29
33
  case "flowchart":
30
34
  return parseFlowchart(input);
31
35
  case "sequence":
32
36
  return parseSequence(input);
37
+ case "class":
38
+ return parseClassDiagram(input);
33
39
  default:
34
40
  throw new Error(`Unsupported diagram type: ${type}`);
35
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sequence-parser.d.ts","sourceRoot":"","sources":["../../src/parser/sequence-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,WAAW,EAiBjB,MAAM,sBAAsB,CAAC;AAygB9B;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAmBxD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIxD"}
1
+ {"version":3,"file":"sequence-parser.d.ts","sourceRoot":"","sources":["../../src/parser/sequence-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,WAAW,EAiBjB,MAAM,sBAAsB,CAAC;AAkkB9B;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,CAsBxD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIxD"}
@@ -471,6 +471,59 @@ function ensureActor(ast, id) {
471
471
  });
472
472
  }
473
473
  }
474
+ /**
475
+ * Post-process statements to normalize activate/deactivate shortcut syntax.
476
+ *
477
+ * Mermaid's parser handles + and - shortcuts inconsistently:
478
+ * - For +: Sets activate:true on message AND creates activeStart statement
479
+ * - For -: Only creates activeEnd statement (doesn't set deactivate on message)
480
+ *
481
+ * This function normalizes the AST by setting deactivate:true on messages
482
+ * that are immediately followed by a deactivate for the message's from actor.
483
+ */
484
+ function normalizeActivationShortcuts(statements) {
485
+ for (let i = 0; i < statements.length - 1; i++) {
486
+ const current = statements[i];
487
+ const next = statements[i + 1];
488
+ // Check if current is a message and next is a deactivate for the same actor
489
+ if (current.type === "message" && next.type === "deactivate") {
490
+ const msg = current;
491
+ const deactivation = next;
492
+ // The - shortcut deactivates the sender (from), not the receiver
493
+ if (msg.from === deactivation.actor && !msg.deactivate) {
494
+ msg.deactivate = true;
495
+ }
496
+ }
497
+ // Recursively process nested statements (loops, alt, etc.)
498
+ // Using type narrowing based on statement type
499
+ switch (current.type) {
500
+ case "loop":
501
+ case "opt":
502
+ case "break":
503
+ case "rect": {
504
+ const stmt = current;
505
+ normalizeActivationShortcuts(stmt.statements);
506
+ break;
507
+ }
508
+ case "alt":
509
+ case "par": {
510
+ const stmt = current;
511
+ for (const section of stmt.sections) {
512
+ normalizeActivationShortcuts(section.statements);
513
+ }
514
+ break;
515
+ }
516
+ case "critical": {
517
+ const stmt = current;
518
+ normalizeActivationShortcuts(stmt.statements);
519
+ for (const option of stmt.options) {
520
+ normalizeActivationShortcuts(option.statements);
521
+ }
522
+ break;
523
+ }
524
+ }
525
+ }
526
+ }
474
527
  /**
475
528
  * Parse a sequence diagram string into an AST
476
529
  */
@@ -490,6 +543,8 @@ export function parseSequence(input) {
490
543
  }
491
544
  throw error;
492
545
  }
546
+ // Normalize activation shortcuts to handle inconsistent parser behavior
547
+ normalizeActivationShortcuts(ast.statements);
493
548
  return ast;
494
549
  }
495
550
  /**
@@ -0,0 +1,17 @@
1
+ /**
2
+ * State Diagram Parser
3
+ *
4
+ * Parses Mermaid state diagram syntax into an AST using the vendored JISON parser.
5
+ * The JISON parser calls methods on a `yy` object - we provide our own implementation
6
+ * that builds an AST instead of mermaid's internal db structure.
7
+ */
8
+ import { type StateDiagramAST } from "../types/state.js";
9
+ /**
10
+ * Parse a state diagram string into an AST
11
+ */
12
+ export declare function parseStateDiagram(input: string): StateDiagramAST;
13
+ /**
14
+ * Detect if input is a state diagram
15
+ */
16
+ export declare function isStateDiagram(input: string): boolean;
17
+ //# sourceMappingURL=state-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-parser.d.ts","sourceRoot":"","sources":["../../src/parser/state-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,KAAK,eAAe,EAQrB,MAAM,mBAAmB,CAAC;AA6M3B;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAuBhE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOrD"}