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.
- package/README.md +53 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/parser/class-parser.d.ts +15 -0
- package/dist/parser/class-parser.d.ts.map +1 -0
- package/dist/parser/class-parser.js +295 -0
- package/dist/parser/flowchart-parser.d.ts.map +1 -1
- package/dist/parser/flowchart-parser.js +32 -5
- package/dist/parser/index.d.ts +1 -0
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +7 -1
- package/dist/parser/sequence-parser.d.ts.map +1 -1
- package/dist/parser/sequence-parser.js +55 -0
- package/dist/parser/state-parser.d.ts +17 -0
- package/dist/parser/state-parser.d.ts.map +1 -0
- package/dist/parser/state-parser.js +221 -0
- package/dist/renderer/class-renderer.d.ts +11 -0
- package/dist/renderer/class-renderer.d.ts.map +1 -0
- package/dist/renderer/class-renderer.js +238 -0
- package/dist/renderer/flowchart-renderer.js +2 -2
- package/dist/renderer/index.d.ts +1 -0
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +6 -1
- package/dist/renderer/sequence-renderer.d.ts +4 -0
- package/dist/renderer/sequence-renderer.d.ts.map +1 -1
- package/dist/renderer/sequence-renderer.js +25 -2
- package/dist/renderer/state-renderer.d.ts +11 -0
- package/dist/renderer/state-renderer.d.ts.map +1 -0
- package/dist/renderer/state-renderer.js +150 -0
- package/dist/types/class.d.ts +64 -0
- package/dist/types/class.d.ts.map +1 -0
- package/dist/types/class.js +17 -0
- package/dist/types/index.d.ts +8 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +7 -0
- package/dist/types/state.d.ts +150 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +20 -0
- package/dist/vendored/VERSION +1 -0
- package/dist/vendored/grammars/class.jison +429 -0
- package/dist/vendored/grammars/flowchart.jison +631 -0
- package/dist/vendored/grammars/sequence.jison +353 -0
- package/dist/vendored/grammars/state.jison +336 -0
- package/dist/vendored/parsers/class.js +942 -0
- package/dist/vendored/parsers/flowchart.js +1089 -0
- package/dist/vendored/parsers/sequence.js +941 -0
- package/dist/vendored/parsers/state.js +876 -0
- package/dist/vendored/sync-info.json +28 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# mermaid-ast
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/mermaid-ast)
|
|
3
4
|
[](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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
package/dist/parser/index.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/parser/index.js
CHANGED
|
@@ -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;
|
|
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"}
|