diff --git a/instructions.md b/instructions.md
new file mode 100644
index 000000000..1ac0b70cc
--- /dev/null
+++ b/instructions.md
@@ -0,0 +1,221 @@
+# Jison to Chevrotain Parser Conversion Instructions
+
+## Overview
+This guide provides step-by-step instructions for converting a Jison-based parser to Chevrotain, specifically for the flowchart parser located at `src/diagrams/flowchart/parser/flow.jison`.
+
+## Critical Requirements
+- **Multi-mode lexing is MANDATORY** - This is crucial for mirroring Jison's lexical states
+- Preserve the existing parser structure to maintain compatibility
+- All original test cases must be included in the converted test suite
+- Minimize changes to test implementation
+
+## Understanding Jison States
+The Jison parser uses multiple lexical states defined with `%x`:
+- string, md_string, acc_title, acc_descr, acc_descr_multiline
+- dir, vertex, text, ellipseText, trapText, edgeText
+- thickEdgeText, dottedEdgeText, click, href, callbackname
+- callbackargs, shapeData, shapeDataStr, shapeDataEndBracket
+
+### State Management in Jison:
+- `this.pushState(stateName)` or `this.begin(stateName)` - Enter a new state
+- `this.popState()` - Return to the previous state
+- States operate as a stack (LIFO - Last In, First Out)
+
+## Conversion Process
+
+### Phase 1: Analysis
+1. **Study the Jison file thoroughly**
+ - Map all lexical states and their purposes
+ - Document which tokens are available in each state
+ - Note all state transitions (when states are entered/exited)
+ - Identify semantic actions and their data transformations
+
+2. **Create a state transition diagram**
+ - Document which tokens trigger state changes
+ - Map the relationships between states
+ - Identify any nested state scenarios
+
+### Phase 2: Lexer Implementation
+1. **Set up Chevrotain multi-mode lexer structure**
+ - Create a mode for each Jison state
+ - Define a default mode corresponding to Jison's INITIAL state
+ - Ensure mode names match Jison state names for clarity
+
+2. **Convert token definitions**
+ - For each Jison token rule, create equivalent Chevrotain token
+ - Pay special attention to tokens that trigger state changes
+ - Preserve token precedence and ordering from Jison
+
+3. **Implement state transitions**
+ - Tokens that call `pushState` should use Chevrotain's push_mode
+ - Tokens that call `popState` should use Chevrotain's pop_mode
+ - Maintain the stack-based behavior of Jison states
+
+### Phase 3: Parser Implementation
+1. **Convert grammar rules**
+ - Translate each Jison grammar rule to Chevrotain's format
+ - Preserve the rule hierarchy and structure
+ - Maintain the same rule names where possible
+
+2. **Handle semantic actions**
+ - Convert Jison's semantic actions to Chevrotain's visitor pattern
+ - Ensure data structures remain compatible
+ - Preserve any side effects or state mutations
+
+### Phase 4: Testing Strategy
+1. **Test file naming convention**
+ - Original: `*.spec.js`
+ - Converted: `*-chev.spec.ts`
+ - Keep test files in the same directory: `src/diagrams/flowchart/parser/`
+
+2. **Test conversion approach**
+ - Copy each original test file
+ - Rename with `-chev.spec.ts` suffix
+ - Modify only the import statements and parser initialization
+ - Keep test cases and assertions unchanged
+ - Run tests individually: `vitest packages/mermaid/src/diagrams/flowchart/parser/flow-chev.spec.ts --run`
+
+3. **Validation checklist**
+ - All original test cases must pass
+ - Test coverage should match the original
+ - Performance should be comparable or better
+
+### Phase 5: Integration
+1. **API compatibility**
+ - Ensure the new parser exposes the same public interface
+ - Return values should match the original parser
+ - Error messages should be equivalent
+
+2. **Gradual migration**
+ - Create a feature flag to switch between parsers
+ - Allow parallel testing of both implementations
+ - Monitor for any behavioral differences
+
+## Common Pitfalls to Avoid
+1. **State management differences**
+ - Chevrotain's modes are more rigid than Jison's states
+ - Ensure proper mode stack behavior is maintained
+ - Test deeply nested state scenarios
+
+2. **Token precedence**
+ - Chevrotain's token ordering matters more than in Jison
+ - Longer patterns should generally come before shorter ones
+ - Test edge cases with ambiguous inputs
+
+3. **Semantic action timing**
+ - Chevrotain processes semantic actions differently
+ - Ensure actions execute at the correct parse phase
+ - Validate that data flows correctly through the parse tree
+
+## Success Criteria
+- All original tests pass with the new parser
+- No changes required to downstream code
+- Performance is equal or better
+- Parser behavior is identical for all valid inputs
+- Error handling remains consistent
+
+
+# This is a reference to how Chevrotain handles multi-mode lexing
+
+## Summary: Using Multi-Mode Lexing in Chevrotain
+
+Chevrotain supports *multi-mode lexing*, allowing you to define different sets of tokenization rules (modes) that the lexer can switch between based on context. This is essential for parsing languages with embedded or context-sensitive syntax, such as HTML or templating languages[3][2].
+
+**Key Concepts:**
+
+- **Modes:** Each mode is an array of token types (constructors) defining the valid tokens in that context.
+- **Mode Stack:** The lexer maintains a stack of modes. Only the top (current) mode's tokens are active at any time[2].
+- **Switching Modes:**
+ - Use `PUSH_MODE` on a token to switch to a new mode after matching that token.
+ - Use `POP_MODE` on a token to return to the previous mode.
+
+**Implementation Steps:**
+
+1. **Define Tokens with Mode Switching:**
+ - Tokens can specify `PUSH_MODE` or `POP_MODE` to control mode transitions.
+ ```javascript
+ const EnterLetters = createToken({ name: "EnterLetters", pattern: /LETTERS/, push_mode: "letter_mode" });
+ const ExitLetters = createToken({ name: "ExitLetters", pattern: /EXIT_LETTERS/, pop_mode: true });
+ ```
+
+2. **Create the Multi-Mode Lexer Definition:**
+ - Structure your modes as an object mapping mode names to arrays of token constructors.
+ ```javascript
+ const multiModeLexerDefinition = {
+ modes: {
+ numbers_mode: [One, Two, EnterLetters, ExitNumbers, Whitespace],
+ letter_mode: [Alpha, Beta, ExitLetters, Whitespace],
+ },
+ defaultMode: "numbers_mode"
+ };
+ ```
+
+3. **Instantiate the Lexer:**
+ - Pass the multi-mode definition to the Chevrotain `Lexer` constructor.
+ ```javascript
+ const MultiModeLexer = new Lexer(multiModeLexerDefinition);
+ ```
+
+4. **Tokenize Input:**
+ - The lexer will automatically switch modes as it encounters tokens with `PUSH_MODE` or `POP_MODE`.
+ ```javascript
+ const lexResult = MultiModeLexer.tokenize(input);
+ ```
+
+5. **Parser Integration:**
+ - When constructing the parser, provide a flat array of all token constructors used in all modes, as the parser does not natively accept the multi-mode structure[1].
+ ```javascript
+ // Flatten all tokens from all modes for the parser
+ let tokenCtors = [];
+ for (let mode in multiModeLexerDefinition.modes) {
+ tokenCtors = tokenCtors.concat(multiModeLexerDefinition.modes[mode]);
+ }
+ class MultiModeParser extends Parser {
+ constructor(tokens) {
+ super(tokens, tokenCtors);
+ }
+ }
+ ```
+
+**Best Practices:**
+
+- Place more specific tokens before more general ones to avoid prefix-matching issues[2].
+- Use the mode stack judiciously to manage nested or recursive language constructs.
+
+**References:**
+- Chevrotain documentation on [lexer modes][3]
+- Example code and integration notes from Chevrotain issues and docs[1][2]
+
+This approach enables robust, context-sensitive lexing for complex language grammars in Chevrotain.
+
+[1] https://github.com/chevrotain/chevrotain/issues/395
+[2] https://chevrotain.io/documentation/0_7_2/classes/lexer.html
+[3] https://chevrotain.io/docs/features/lexer_modes.html
+[4] https://github.com/SAP/chevrotain/issues/370
+[5] https://galaxy.ai/youtube-summarizer/understanding-lexers-parsers-and-interpreters-with-chevrotain-l-jMsoAY64k
+[6] https://chevrotain.io/documentation/8_0_1/classes/lexer.html
+[7] https://fastly.jsdelivr.net/npm/chevrotain@11.0.3/src/scan/lexer.ts
+[8] https://chevrotain.io/docs/guide/resolving_lexer_errors.html
+[9] https://www.youtube.com/watch?v=l-jMsoAY64k
+[10] https://github.com/SAP/chevrotain/blob/master/packages/chevrotain/test/scan/lexer_spec.ts
+
+**Important**
+Always assume I want the exact code edit!
+Always assume I want you to apply this fixes directly!
+
+# Running tests
+
+Run tests in one file from the project root using this command:
+`vitest #filename-relative-to-project-root# --run`
+
+Example:
+`vitest packages/mermaid/src/diagrams/flowchart/parser/flow-chev.spec.ts --run`
+
+To run all flowchart test for the migration
+`vitest packages/mermaid/src/diagrams/flowchart/parser/*flow*-chev.spec.ts --run`
+
+To run a specific test in a test file:
+`vitest #filename-relative-to-project-root# -t "string-matching-test" --run`
+
+Example:
+`vitest packages/mermaid/src/diagrams/flowchart/parser/flow-chev-singlenode.spec.js -t "diamond node with html in it (SN3)" --run`
diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json
index 7f8230229..3ee766d02 100644
--- a/packages/mermaid/package.json
+++ b/packages/mermaid/package.json
@@ -71,6 +71,7 @@
"@iconify/utils": "^2.1.33",
"@mermaid-js/parser": "workspace:^",
"@types/d3": "^7.4.3",
+ "chevrotain": "^11.0.3",
"cytoscape": "^3.29.3",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts b/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
index 588e9f3ba..0866dac01 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
+++ b/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts
@@ -2,9 +2,9 @@ import type { MermaidConfig } from '../../config.type.js';
import { setConfig } from '../../diagram-api/diagramAPI.js';
import { FlowDB } from './flowDb.js';
import renderer from './flowRenderer-v3-unified.js';
-// @ts-ignore: JISON doesn't support types
-//import flowParser from './parser/flow.jison';
-import flowParser from './parser/flowParser.ts';
+// Replace the Jison import with Chevrotain parser
+// import flowParser from './parser/flow.jison';
+import flowParser from './parser/flowParserAdapter.js';
import flowStyles from './styles.js';
export const diagram = {
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts.backup b/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts.backup
new file mode 100644
index 000000000..588e9f3ba
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/flowDiagram.ts.backup
@@ -0,0 +1,27 @@
+import type { MermaidConfig } from '../../config.type.js';
+import { setConfig } from '../../diagram-api/diagramAPI.js';
+import { FlowDB } from './flowDb.js';
+import renderer from './flowRenderer-v3-unified.js';
+// @ts-ignore: JISON doesn't support types
+//import flowParser from './parser/flow.jison';
+import flowParser from './parser/flowParser.ts';
+import flowStyles from './styles.js';
+
+export const diagram = {
+ parser: flowParser,
+ get db() {
+ return new FlowDB();
+ },
+ renderer,
+ styles: flowStyles,
+ init: (cnf: MermaidConfig) => {
+ if (!cnf.flowchart) {
+ cnf.flowchart = {};
+ }
+ if (cnf.layout) {
+ setConfig({ layout: cnf.layout });
+ }
+ cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
+ setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
+ },
+};
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-chev-arrows.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-chev-arrows.spec.js
new file mode 100644
index 000000000..723d19eea
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-chev-arrows.spec.js
@@ -0,0 +1,244 @@
+import { FlowDB } from '../flowDb.js';
+import flow from './flowParserAdapter.js';
+import { setConfig } from '../../../config.js';
+
+setConfig({
+ securityLevel: 'strict',
+});
+
+describe('[Chevrotain Arrows] when parsing', () => {
+ beforeEach(function () {
+ flow.yy = new FlowDB();
+ flow.yy.clear();
+ });
+
+ it('should handle basic arrow', function () {
+ const res = flow.parse('graph TD;A-->B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_point');
+ });
+
+ it('should handle arrow with text', function () {
+ const res = flow.parse('graph TD;A-->|text|B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].text).toBe('text');
+ });
+
+ it('should handle dotted arrow', function () {
+ const res = flow.parse('graph TD;A-.->B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_dotted');
+ });
+
+ it('should handle dotted arrow with text', function () {
+ const res = flow.parse('graph TD;A-.-|text|B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].text).toBe('text');
+ expect(edges[0].type).toBe('arrow_dotted');
+ });
+
+ it('should handle thick arrow', function () {
+ const res = flow.parse('graph TD;A==>B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_thick');
+ });
+
+ it('should handle thick arrow with text', function () {
+ const res = flow.parse('graph TD;A==|text|==>B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].text).toBe('text');
+ expect(edges[0].type).toBe('arrow_thick');
+ });
+
+ it('should handle open arrow', function () {
+ const res = flow.parse('graph TD;A---B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_open');
+ });
+
+ it('should handle open arrow with text', function () {
+ const res = flow.parse('graph TD;A---|text|B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].text).toBe('text');
+ expect(edges[0].type).toBe('arrow_open');
+ });
+
+ it('should handle cross arrow', function () {
+ const res = flow.parse('graph TD;A--xB;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_cross');
+ });
+
+ it('should handle circle arrow', function () {
+ const res = flow.parse('graph TD;A--oB;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_circle');
+ });
+
+ it('should handle bidirectional arrow', function () {
+ const res = flow.parse('graph TD;A<-->B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('double_arrow_point');
+ });
+
+ it('should handle bidirectional arrow with text', function () {
+ const res = flow.parse('graph TD;A<--|text|-->B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].text).toBe('text');
+ expect(edges[0].type).toBe('double_arrow_point');
+ });
+
+ it('should handle multiple arrows in sequence', function () {
+ const res = flow.parse('graph TD;A-->B-->C;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(vert.get('C').id).toBe('C');
+ expect(edges.length).toBe(2);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[1].start).toBe('B');
+ expect(edges[1].end).toBe('C');
+ });
+
+ it('should handle multiple arrows with different types', function () {
+ const res = flow.parse('graph TD;A-->B-.->C==>D;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(3);
+ expect(edges[0].type).toBe('arrow_point');
+ expect(edges[1].type).toBe('arrow_dotted');
+ expect(edges[2].type).toBe('arrow_thick');
+ });
+
+ it('should handle long arrows', function () {
+ const res = flow.parse('graph TD;A---->B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_point');
+ expect(edges[0].length).toBe('long');
+ });
+
+ it('should handle extra long arrows', function () {
+ const res = flow.parse('graph TD;A------>B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_point');
+ expect(edges[0].length).toBe('extralong');
+ });
+});
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-chev-edges.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-chev-edges.spec.js
new file mode 100644
index 000000000..1b5e299a5
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-chev-edges.spec.js
@@ -0,0 +1,240 @@
+import { FlowDB } from '../flowDb.js';
+import flow from './flowParserAdapter.js';
+import { setConfig } from '../../../config.js';
+
+setConfig({
+ securityLevel: 'strict',
+});
+
+describe('[Chevrotain Edges] when parsing', () => {
+ beforeEach(function () {
+ flow.yy = new FlowDB();
+ flow.yy.clear();
+ });
+
+ it('should handle a single edge', function () {
+ const res = flow.parse('graph TD;A-->B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ });
+
+ it('should handle multiple edges', function () {
+ const res = flow.parse('graph TD;A-->B;B-->C;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(vert.get('C').id).toBe('C');
+ expect(edges.length).toBe(2);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[1].start).toBe('B');
+ expect(edges[1].end).toBe('C');
+ });
+
+ it('should handle chained edges', function () {
+ const res = flow.parse('graph TD;A-->B-->C;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(vert.get('C').id).toBe('C');
+ expect(edges.length).toBe(2);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[1].start).toBe('B');
+ expect(edges[1].end).toBe('C');
+ });
+
+ it('should handle edges with text', function () {
+ const res = flow.parse('graph TD;A-->|text|B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].text).toBe('text');
+ });
+
+ it('should handle edges with quoted text', function () {
+ const res = flow.parse('graph TD;A-->|"quoted text"|B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].text).toBe('quoted text');
+ });
+
+ it('should handle edges with complex text', function () {
+ const res = flow.parse('graph TD;A-->|"text with spaces and symbols!"|B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].text).toBe('text with spaces and symbols!');
+ });
+
+ it('should handle multiple edges from one node', function () {
+ const res = flow.parse('graph TD;A-->B;A-->C;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(vert.get('C').id).toBe('C');
+ expect(edges.length).toBe(2);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[1].start).toBe('A');
+ expect(edges[1].end).toBe('C');
+ });
+
+ it('should handle multiple edges to one node', function () {
+ const res = flow.parse('graph TD;A-->C;B-->C;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(vert.get('C').id).toBe('C');
+ expect(edges.length).toBe(2);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('C');
+ expect(edges[1].start).toBe('B');
+ expect(edges[1].end).toBe('C');
+ });
+
+ it('should handle edges with node shapes', function () {
+ const res = flow.parse('graph TD;A[Start]-->B{Decision};');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('A').type).toBe('square');
+ expect(vert.get('A').text).toBe('Start');
+ expect(vert.get('B').id).toBe('B');
+ expect(vert.get('B').type).toBe('diamond');
+ expect(vert.get('B').text).toBe('Decision');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ });
+
+ it('should handle complex edge patterns', function () {
+ const res = flow.parse('graph TD;A[Start]-->B{Decision};B-->|Yes|C[Process];B-->|No|D[End];');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(3);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[1].start).toBe('B');
+ expect(edges[1].end).toBe('C');
+ expect(edges[1].text).toBe('Yes');
+ expect(edges[2].start).toBe('B');
+ expect(edges[2].end).toBe('D');
+ expect(edges[2].text).toBe('No');
+ });
+
+ it('should handle edges with ampersand syntax', function () {
+ const res = flow.parse('graph TD;A & B --> C;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(vert.get('C').id).toBe('C');
+ expect(edges.length).toBe(2);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('C');
+ expect(edges[1].start).toBe('B');
+ expect(edges[1].end).toBe('C');
+ });
+
+ it('should handle edges with multiple ampersands', function () {
+ const res = flow.parse('graph TD;A & B & C --> D;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(vert.get('C').id).toBe('C');
+ expect(vert.get('D').id).toBe('D');
+ expect(edges.length).toBe(3);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('D');
+ expect(edges[1].start).toBe('B');
+ expect(edges[1].end).toBe('D');
+ expect(edges[2].start).toBe('C');
+ expect(edges[2].end).toBe('D');
+ });
+
+ it('should handle self-referencing edges', function () {
+ const res = flow.parse('graph TD;A-->A;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('A');
+ });
+
+ it('should handle edges with numeric node IDs', function () {
+ const res = flow.parse('graph TD;1-->2;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('1').id).toBe('1');
+ expect(vert.get('2').id).toBe('2');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('1');
+ expect(edges[0].end).toBe('2');
+ });
+
+ it('should handle edges with mixed alphanumeric node IDs', function () {
+ const res = flow.parse('graph TD;A1-->B2;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A1').id).toBe('A1');
+ expect(vert.get('B2').id).toBe('B2');
+ expect(edges.length).toBe(1);
+ expect(edges[0].start).toBe('A1');
+ expect(edges[0].end).toBe('B2');
+ });
+});
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-chev-singlenode.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-chev-singlenode.spec.js
new file mode 100644
index 000000000..96e7751c8
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-chev-singlenode.spec.js
@@ -0,0 +1,362 @@
+import { FlowDB } from '../flowDb.js';
+import flow from './flowParserAdapter.js';
+import { setConfig } from '../../../config.js';
+
+setConfig({
+ securityLevel: 'strict',
+});
+
+const keywords = [
+ 'graph',
+ 'flowchart',
+ 'flowchart-elk',
+ 'style',
+ 'default',
+ 'linkStyle',
+ 'interpolate',
+ 'classDef',
+ 'class',
+ 'href',
+ 'call',
+ 'click',
+ '_self',
+ '_blank',
+ '_parent',
+ '_top',
+ 'end',
+ 'subgraph',
+];
+
+const specialChars = ['#', ':', '0', '&', ',', '*', '.', '\\', 'v', '-', '/', '_'];
+
+describe('[Chevrotain Singlenodes] when parsing', () => {
+ beforeEach(function () {
+ flow.yy = new FlowDB();
+ flow.yy.clear();
+ });
+
+ it('should handle a single node', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;A;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('A').styles.length).toBe(0);
+ });
+
+ it('should handle a single node with white space after it (SN1)', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;A ;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('A').styles.length).toBe(0);
+ });
+
+ it('should handle a single square node', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a[A];');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').styles.length).toBe(0);
+ expect(vert.get('a').type).toBe('square');
+ });
+
+ it('should handle a single round square node', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a[A];');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').styles.length).toBe(0);
+ expect(vert.get('a').type).toBe('square');
+ });
+
+ it('should handle a single circle node', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a((A));');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('circle');
+ });
+
+ it('should handle a single round node', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a(A);');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('round');
+ });
+
+ it('should handle a single diamond node', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a{A};');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('diamond');
+ });
+
+ it('should handle a single diamond node with whitespace after it', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a{A} ;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('diamond');
+ });
+
+ it('should handle a single diamond node with html in it (SN3)', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a{A
end};');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('diamond');
+ expect(vert.get('a').text).toBe('A
end');
+ });
+
+ it('should handle a single hexagon node', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a{{A}};');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('hexagon');
+ });
+
+ it('should handle a single hexagon node with html in it', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a{{A
end}};');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('hexagon');
+ expect(vert.get('a').text).toBe('A
end');
+ });
+
+ it('should handle a single round node with html in it', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a(A
end);');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('round');
+ expect(vert.get('a').text).toBe('A
end');
+ });
+
+ it('should handle a single double circle node', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a(((A)));');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('doublecircle');
+ });
+
+ it('should handle a single double circle node with whitespace after it', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a(((A))) ;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('doublecircle');
+ });
+
+ it('should handle a single double circle node with html in it (SN3)', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;a(((A
end)));');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('a').type).toBe('doublecircle');
+ expect(vert.get('a').text).toBe('A
end');
+ });
+
+ it('should handle a single node with alphanumerics starting on a char', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;id1;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('id1').styles.length).toBe(0);
+ });
+
+ it('should handle a single node with a single digit', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;1;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('1').text).toBe('1');
+ });
+
+ it('should handle a single node with a single digit in a subgraph', function () {
+ // Silly but syntactically correct
+
+ const res = flow.parse('graph TD;subgraph "hello";1;end;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('1').text).toBe('1');
+ });
+
+ it('should handle a single node with alphanumerics starting on a num', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;1id;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('1id').styles.length).toBe(0);
+ });
+
+ it('should handle a single node with alphanumerics containing a minus sign', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;i-d;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('i-d').styles.length).toBe(0);
+ });
+
+ it('should handle a single node with alphanumerics containing a underscore sign', function () {
+ // Silly but syntactically correct
+ const res = flow.parse('graph TD;i_d;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(edges.length).toBe(0);
+ expect(vert.get('i_d').styles.length).toBe(0);
+ });
+
+ it.each(keywords)('should handle keywords between dashes "-"', function (keyword) {
+ const res = flow.parse(`graph TD;a-${keyword}-node;`);
+ const vert = flow.yy.getVertices();
+ expect(vert.get(`a-${keyword}-node`).text).toBe(`a-${keyword}-node`);
+ });
+
+ it.each(keywords)('should handle keywords between periods "."', function (keyword) {
+ const res = flow.parse(`graph TD;a.${keyword}.node;`);
+ const vert = flow.yy.getVertices();
+ expect(vert.get(`a.${keyword}.node`).text).toBe(`a.${keyword}.node`);
+ });
+
+ it.each(keywords)('should handle keywords between underscores "_"', function (keyword) {
+ const res = flow.parse(`graph TD;a_${keyword}_node;`);
+ const vert = flow.yy.getVertices();
+ expect(vert.get(`a_${keyword}_node`).text).toBe(`a_${keyword}_node`);
+ });
+
+ it.each(keywords)('should handle nodes ending in %s', function (keyword) {
+ const res = flow.parse(`graph TD;node_${keyword};node.${keyword};node-${keyword};`);
+ const vert = flow.yy.getVertices();
+ expect(vert.get(`node_${keyword}`).text).toBe(`node_${keyword}`);
+ expect(vert.get(`node.${keyword}`).text).toBe(`node.${keyword}`);
+ expect(vert.get(`node-${keyword}`).text).toBe(`node-${keyword}`);
+ });
+
+ const errorKeywords = [
+ 'graph',
+ 'flowchart',
+ 'flowchart-elk',
+ 'style',
+ 'linkStyle',
+ 'interpolate',
+ 'classDef',
+ 'class',
+ '_self',
+ '_blank',
+ '_parent',
+ '_top',
+ 'end',
+ 'subgraph',
+ ];
+
+ it.each(errorKeywords)('should throw error at nodes beginning with %s', function (keyword) {
+ const str = `graph TD;${keyword}.node;${keyword}-node;${keyword}/node`;
+ const vert = flow.yy.getVertices();
+
+ expect(() => flow.parse(str)).toThrowError();
+ });
+
+ const workingKeywords = ['default', 'href', 'click', 'call'];
+
+ it.each(workingKeywords)('should parse node beginning with %s', function (keyword) {
+ flow.parse(`graph TD; ${keyword}.node;${keyword}-node;${keyword}/node;`);
+ const vert = flow.yy.getVertices();
+ expect(vert.get(`${keyword}.node`).text).toBe(`${keyword}.node`);
+ expect(vert.get(`${keyword}-node`).text).toBe(`${keyword}-node`);
+ expect(vert.get(`${keyword}/node`).text).toBe(`${keyword}/node`);
+ });
+
+ it.each(specialChars)(
+ 'should allow node ids of single special characters',
+ function (specialChar) {
+ flow.parse(`graph TD; ${specialChar} --> A`);
+ const vert = flow.yy.getVertices();
+ expect(vert.get(`${specialChar}`).text).toBe(`${specialChar}`);
+ }
+ );
+
+ it.each(specialChars)(
+ 'should allow node ids with special characters at start of id',
+ function (specialChar) {
+ flow.parse(`graph TD; ${specialChar}node --> A`);
+ const vert = flow.yy.getVertices();
+ expect(vert.get(`${specialChar}node`).text).toBe(`${specialChar}node`);
+ }
+ );
+
+ it.each(specialChars)(
+ 'should allow node ids with special characters at end of id',
+ function (specialChar) {
+ flow.parse(`graph TD; node${specialChar} --> A`);
+ const vert = flow.yy.getVertices();
+ expect(vert.get(`node${specialChar}`).text).toBe(`node${specialChar}`);
+ }
+ );
+});
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-chev.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-chev.spec.js
new file mode 100644
index 000000000..232307ff0
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-chev.spec.js
@@ -0,0 +1,230 @@
+import { FlowDB } from '../flowDb.js';
+import flow from './flowParserAdapter.js';
+import { cleanupComments } from '../../../diagram-api/comments.js';
+import { setConfig } from '../../../config.js';
+
+setConfig({
+ securityLevel: 'strict',
+});
+
+describe('parsing a flow chart with Chevrotain', function () {
+ beforeEach(function () {
+ flow.yy = new FlowDB();
+ flow.yy.clear();
+ });
+
+ it('should handle a trailing whitespaces after statements', function () {
+ const res = flow.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B; \n B-->C;'));
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ expect(edges.length).toBe(2);
+ expect(edges[0].start).toBe('A');
+ expect(edges[0].end).toBe('B');
+ expect(edges[0].type).toBe('arrow_point');
+ expect(edges[0].text).toBe('');
+ });
+
+ it('should handle node names with "end" substring', function () {
+ const res = flow.parse('graph TD\nendpoint --> sender');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('endpoint').id).toBe('endpoint');
+ expect(vert.get('sender').id).toBe('sender');
+ expect(edges[0].start).toBe('endpoint');
+ expect(edges[0].end).toBe('sender');
+ });
+
+ it('should handle node names ending with keywords', function () {
+ const res = flow.parse('graph TD\nblend --> monograph');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('blend').id).toBe('blend');
+ expect(vert.get('monograph').id).toBe('monograph');
+ expect(edges[0].start).toBe('blend');
+ expect(edges[0].end).toBe('monograph');
+ });
+
+ it('should allow default in the node name/id', function () {
+ const res = flow.parse('graph TD\ndefault --> monograph');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('default').id).toBe('default');
+ expect(vert.get('monograph').id).toBe('monograph');
+ expect(edges[0].start).toBe('default');
+ expect(edges[0].end).toBe('monograph');
+ });
+
+ describe('special characters should be handled.', function () {
+ const charTest = function (char, result) {
+ const res = flow.parse('graph TD;A(' + char + ')-->B;');
+
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
+
+ expect(vert.get('A').id).toBe('A');
+ expect(vert.get('B').id).toBe('B');
+ if (result) {
+ expect(vert.get('A').text).toBe(result);
+ } else {
+ expect(vert.get('A').text).toBe(char);
+ }
+ flow.yy.clear();
+ };
+
+ it("should be able to parse a '.'", function () {
+ charTest('.');
+ charTest('Start 103a.a1');
+ });
+
+ it("should be able to parse a ':'", function () {
+ charTest(':');
+ });
+
+ it("should be able to parse a ','", function () {
+ charTest(',');
+ });
+
+ it("should be able to parse text containing '-'", function () {
+ charTest('a-b');
+ });
+
+ it("should be able to parse a '+'", function () {
+ charTest('+');
+ });
+
+ it("should be able to parse a '*'", function () {
+ charTest('*');
+ });
+
+ it("should be able to parse a '<'", function () {
+ charTest('<', '<');
+ });
+
+ it("should be able to parse a '&'", function () {
+ charTest('&');
+ });
+ });
+
+ it('should be possible to use direction in node ids', function () {
+ let statement = '';
+
+ statement = statement + 'graph TD;' + '\n';
+ statement = statement + ' node1TB\n';
+
+ const res = flow.parse(statement);
+ const vertices = flow.yy.getVertices();
+ const classes = flow.yy.getClasses();
+ expect(vertices.get('node1TB').id).toBe('node1TB');
+ });
+
+ it('should be possible to use direction in node ids', function () {
+ let statement = '';
+
+ statement = statement + 'graph TD;A--x|text including URL space|B;';
+ const res = flow.parse(statement);
+ const vertices = flow.yy.getVertices();
+ const classes = flow.yy.getClasses();
+ expect(vertices.get('A').id).toBe('A');
+ });
+
+ it('should be possible to use numbers as labels', function () {
+ let statement = '';
+
+ statement = statement + 'graph TB;subgraph "number as labels";1;end;';
+ const res = flow.parse(statement);
+ const vertices = flow.yy.getVertices();
+
+ expect(vertices.get('1').id).toBe('1');
+ });
+
+ it('should add accTitle and accDescr to flow chart', function () {
+ const flowChart = `graph LR
+ accTitle: Big decisions
+ accDescr: Flow chart of the decision making process
+ A[Hard] -->|Text| B(Round)
+ B --> C{Decision}
+ C -->|One| D[Result 1]
+ C -->|Two| E[Result 2]
+ `;
+
+ flow.parse(flowChart);
+ expect(flow.yy.getAccTitle()).toBe('Big decisions');
+ expect(flow.yy.getAccDescription()).toBe('Flow chart of the decision making process');
+ });
+
+ it('should add accTitle and a multi line accDescr to flow chart', function () {
+ const flowChart = `graph LR
+ accTitle: Big decisions
+
+ accDescr {
+ Flow chart of the decision making process
+ with a second line
+ }
+
+ A[Hard] -->|Text| B(Round)
+ B --> C{Decision}
+ C -->|One| D[Result 1]
+ C -->|Two| E[Result 2]
+`;
+
+ flow.parse(flowChart);
+ expect(flow.yy.getAccTitle()).toBe('Big decisions');
+ expect(flow.yy.getAccDescription()).toBe(
+ `Flow chart of the decision making process
+with a second line`
+ );
+ });
+
+ for (const unsafeProp of ['__proto__', 'constructor']) {
+ it(`should work with node id ${unsafeProp}`, function () {
+ const flowChart = `graph LR
+ ${unsafeProp} --> A;`;
+
+ expect(() => {
+ flow.parse(flowChart);
+ }).not.toThrow();
+ });
+
+ it(`should work with tooltip id ${unsafeProp}`, function () {
+ const flowChart = `graph LR
+ click ${unsafeProp} callback "${unsafeProp}";`;
+
+ expect(() => {
+ flow.parse(flowChart);
+ }).not.toThrow();
+ });
+
+ it(`should work with class id ${unsafeProp}`, function () {
+ const flowChart = `graph LR
+ ${unsafeProp} --> A;
+ classDef ${unsafeProp} color:#ffffff,fill:#000000;
+ class ${unsafeProp} ${unsafeProp};`;
+
+ expect(() => {
+ flow.parse(flowChart);
+ }).not.toThrow();
+ });
+
+ it(`should work with subgraph id ${unsafeProp}`, function () {
+ const flowChart = `graph LR
+ ${unsafeProp} --> A;
+ subgraph ${unsafeProp}
+ C --> D;
+ end;`;
+
+ expect(() => {
+ flow.parse(flowChart);
+ }).not.toThrow();
+ });
+ }
+});
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js
index 99462f8db..b7964c372 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-comments.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
import { cleanupComments } from '../../../diagram-api/comments.js';
@@ -9,15 +9,15 @@ setConfig({
describe('[Comments] when parsing', () => {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
+ flow.yy = new FlowDB();
+ flow.yy.clear();
});
it('should handle comments', function () {
- const res = flow.parser.parse(cleanupComments('graph TD;\n%% Comment\n A-->B;'));
+ const res = flow.parse(cleanupComments('graph TD;\n%% Comment\n A-->B;'));
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -29,10 +29,10 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the start', function () {
- const res = flow.parser.parse(cleanupComments('%% Comment\ngraph TD;\n A-->B;'));
+ const res = flow.parse(cleanupComments('%% Comment\ngraph TD;\n A-->B;'));
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -44,10 +44,10 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the end', function () {
- const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n %% Comment at the end\n'));
+ const res = flow.parse(cleanupComments('graph TD;\n A-->B\n %% Comment at the end\n'));
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -59,10 +59,10 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the end no trailing newline', function () {
- const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment'));
+ const res = flow.parse(cleanupComments('graph TD;\n A-->B\n%% Comment'));
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -74,10 +74,10 @@ describe('[Comments] when parsing', () => {
});
it('should handle comments at the end many trailing newlines', function () {
- const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment\n\n\n'));
+ const res = flow.parse(cleanupComments('graph TD;\n A-->B\n%% Comment\n\n\n'));
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -89,10 +89,10 @@ describe('[Comments] when parsing', () => {
});
it('should handle no trailing newlines', function () {
- const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B'));
+ const res = flow.parse(cleanupComments('graph TD;\n A-->B'));
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -104,10 +104,10 @@ describe('[Comments] when parsing', () => {
});
it('should handle many trailing newlines', function () {
- const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n\n'));
+ const res = flow.parse(cleanupComments('graph TD;\n A-->B\n\n'));
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -119,10 +119,10 @@ describe('[Comments] when parsing', () => {
});
it('should handle a comment with blank rows in-between', function () {
- const res = flow.parser.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B;'));
+ const res = flow.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B;'));
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -134,14 +134,14 @@ describe('[Comments] when parsing', () => {
});
it('should handle a comment with mermaid flowchart code in them', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
cleanupComments(
'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line
edge comment|ro;\n A-->B;'
)
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js
index 560bc2a1b..9cc37816a 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -8,18 +8,18 @@ setConfig({
describe('when parsing directions', function () {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
- flow.parser.yy.setGen('gen-2');
+ flow.yy = new FlowDB();
+ flow.yy.clear();
+ flow.yy.setGen('gen-2');
});
it('should use default direction from top level', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
subgraph A
a --> b
end`);
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2);
@@ -29,13 +29,13 @@ describe('when parsing directions', function () {
expect(subgraph.dir).toBe(undefined);
});
it('should handle a subgraph with a direction', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
subgraph A
direction BT
a --> b
end`);
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2);
@@ -45,14 +45,14 @@ describe('when parsing directions', function () {
expect(subgraph.dir).toBe('BT');
});
it('should use the last defined direction', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
subgraph A
direction BT
a --> b
direction RL
end`);
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2);
@@ -63,7 +63,7 @@ describe('when parsing directions', function () {
});
it('should handle nested subgraphs 1', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
subgraph A
direction RL
b-->B
@@ -75,7 +75,7 @@ describe('when parsing directions', function () {
c
end`);
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(2);
const subgraphA = subgraphs.find((o) => o.id === 'A');
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
index f8c7a5162..4ff411194 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-edges.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -63,27 +63,27 @@ const regularEdges = [
describe('[Edges] when parsing', () => {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
+ flow.yy = new FlowDB();
+ flow.yy.clear();
});
it('should handle open ended edges', function () {
- const res = flow.parser.parse('graph TD;A---B;');
- const edges = flow.parser.yy.getEdges();
+ const res = flow.parse('graph TD;A---B;');
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_open');
});
it('should handle cross ended edges', function () {
- const res = flow.parser.parse('graph TD;A--xB;');
- const edges = flow.parser.yy.getEdges();
+ const res = flow.parse('graph TD;A--xB;');
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle open ended edges', function () {
- const res = flow.parser.parse('graph TD;A--oB;');
- const edges = flow.parser.yy.getEdges();
+ const res = flow.parse('graph TD;A--oB;');
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_circle');
});
@@ -92,11 +92,9 @@ describe('[Edges] when parsing', () => {
describe('open ended edges with ids and labels', function () {
regularEdges.forEach((edgeType) => {
it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
- const res = flow.parser.parse(
- `flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
- );
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const res = flow.parse(`flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`);
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
expect(edges.length).toBe(1);
@@ -108,11 +106,9 @@ describe('[Edges] when parsing', () => {
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
});
it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
- const res = flow.parser.parse(
- `flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
- );
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const res = flow.parse(`flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`);
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
expect(edges.length).toBe(1);
@@ -125,11 +121,11 @@ describe('[Edges] when parsing', () => {
});
});
it('should handle normal edges where you also have a node with metadata', function () {
- const res = flow.parser.parse(`flowchart LR
+ const res = flow.parse(`flowchart LR
A id1@-->B
A@{ shape: 'rect' }
`);
- const edges = flow.parser.yy.getEdges();
+ const edges = flow.yy.getEdges();
expect(edges[0].id).toBe('id1');
});
@@ -137,11 +133,11 @@ A@{ shape: 'rect' }
describe('double ended edges with ids and labels', function () {
doubleEndedEdges.forEach((edgeType) => {
it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
`flowchart TD;\nA e1@${edgeType.edgeStart} label ${edgeType.edgeEnd} B;`
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
expect(edges.length).toBe(1);
@@ -159,10 +155,10 @@ A@{ shape: 'rect' }
describe('edges', function () {
doubleEndedEdges.forEach((edgeType) => {
it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
- const res = flow.parser.parse(`graph TD;\nA ${edgeType.edgeStart}${edgeType.edgeEnd} B;`);
+ const res = flow.parse(`graph TD;\nA ${edgeType.edgeStart}${edgeType.edgeEnd} B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -175,12 +171,12 @@ A@{ shape: 'rect' }
});
it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
`graph TD;\nA ${edgeType.edgeStart} text ${edgeType.edgeEnd} B;`
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -195,12 +191,12 @@ A@{ shape: 'rect' }
it.each(keywords)(
`should handle ${edgeType.stroke} ${edgeType.type} with %s text`,
function (keyword) {
- const res = flow.parser.parse(
+ const res = flow.parse(
`graph TD;\nA ${edgeType.edgeStart} ${keyword} ${edgeType.edgeEnd} B;`
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -216,11 +212,11 @@ A@{ shape: 'rect' }
});
it('should handle multiple edges', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD;A---|This is the 123 s text|B;\nA---|This is the second edge|B;'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -242,10 +238,10 @@ A@{ shape: 'rect' }
describe('edge length', function () {
for (let length = 1; length <= 3; ++length) {
it(`should handle normal edges with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA -${'-'.repeat(length)}- B;`);
+ const res = flow.parse(`graph TD;\nA -${'-'.repeat(length)}- B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -261,10 +257,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle normal labelled edges with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA -- Label -${'-'.repeat(length)}- B;`);
+ const res = flow.parse(`graph TD;\nA -- Label -${'-'.repeat(length)}- B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -280,10 +276,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle normal edges with arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA -${'-'.repeat(length)}> B;`);
+ const res = flow.parse(`graph TD;\nA -${'-'.repeat(length)}> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -299,10 +295,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle normal labelled edges with arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA -- Label -${'-'.repeat(length)}> B;`);
+ const res = flow.parse(`graph TD;\nA -- Label -${'-'.repeat(length)}> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -318,10 +314,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle normal edges with double arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA <-${'-'.repeat(length)}> B;`);
+ const res = flow.parse(`graph TD;\nA <-${'-'.repeat(length)}> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -337,10 +333,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle normal labelled edges with double arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA <-- Label -${'-'.repeat(length)}> B;`);
+ const res = flow.parse(`graph TD;\nA <-- Label -${'-'.repeat(length)}> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -356,10 +352,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle thick edges with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA =${'='.repeat(length)}= B;`);
+ const res = flow.parse(`graph TD;\nA =${'='.repeat(length)}= B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -375,10 +371,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle thick labelled edges with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA == Label =${'='.repeat(length)}= B;`);
+ const res = flow.parse(`graph TD;\nA == Label =${'='.repeat(length)}= B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -394,10 +390,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle thick edges with arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA =${'='.repeat(length)}> B;`);
+ const res = flow.parse(`graph TD;\nA =${'='.repeat(length)}> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -413,10 +409,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle thick labelled edges with arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA == Label =${'='.repeat(length)}> B;`);
+ const res = flow.parse(`graph TD;\nA == Label =${'='.repeat(length)}> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -432,10 +428,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle thick edges with double arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA <=${'='.repeat(length)}> B;`);
+ const res = flow.parse(`graph TD;\nA <=${'='.repeat(length)}> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -451,10 +447,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle thick labelled edges with double arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA <== Label =${'='.repeat(length)}> B;`);
+ const res = flow.parse(`graph TD;\nA <== Label =${'='.repeat(length)}> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -470,10 +466,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle dotted edges with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA -${'.'.repeat(length)}- B;`);
+ const res = flow.parse(`graph TD;\nA -${'.'.repeat(length)}- B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -489,10 +485,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle dotted labelled edges with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA -. Label ${'.'.repeat(length)}- B;`);
+ const res = flow.parse(`graph TD;\nA -. Label ${'.'.repeat(length)}- B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -508,10 +504,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle dotted edges with arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA -${'.'.repeat(length)}-> B;`);
+ const res = flow.parse(`graph TD;\nA -${'.'.repeat(length)}-> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -527,10 +523,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle dotted labelled edges with arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA -. Label ${'.'.repeat(length)}-> B;`);
+ const res = flow.parse(`graph TD;\nA -. Label ${'.'.repeat(length)}-> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -546,10 +542,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle dotted edges with double arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA <-${'.'.repeat(length)}-> B;`);
+ const res = flow.parse(`graph TD;\nA <-${'.'.repeat(length)}-> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -565,10 +561,10 @@ A@{ shape: 'rect' }
for (let length = 1; length <= 3; ++length) {
it(`should handle dotted edges with double arrows with length ${length}`, function () {
- const res = flow.parser.parse(`graph TD;\nA <-. Label ${'.'.repeat(length)}-> B;`);
+ const res = flow.parse(`graph TD;\nA <-. Label ${'.'.repeat(length)}-> B;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-interactions.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-interactions.spec.js
index d45c7d4dc..a05a53704 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-interactions.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-interactions.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
import { vi } from 'vitest';
const spyOn = vi.spyOn;
@@ -12,26 +12,26 @@ describe('[Interactions] when parsing', () => {
let flowDb;
beforeEach(function () {
flowDb = new FlowDB();
- flow.parser.yy = flowDb;
- flow.parser.yy.clear();
+ flow.yy = flowDb;
+ flow.yy.clear();
});
it('should be possible to use click to a callback', function () {
spyOn(flowDb, 'setClickEvent');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A callback');
+ const res = flow.parse('graph TD\nA-->B\nclick A callback');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback');
});
it('should be possible to use click to a click and call callback', function () {
spyOn(flowDb, 'setClickEvent');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A call callback()');
+ const res = flow.parse('graph TD\nA-->B\nclick A call callback()');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback');
});
@@ -39,10 +39,10 @@ describe('[Interactions] when parsing', () => {
it('should be possible to use click to a callback with tooltip', function () {
spyOn(flowDb, 'setClickEvent');
spyOn(flowDb, 'setTooltip');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A callback "tooltip"');
+ const res = flow.parse('graph TD\nA-->B\nclick A callback "tooltip"');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback');
expect(flowDb.setTooltip).toHaveBeenCalledWith('A', 'tooltip');
@@ -51,10 +51,10 @@ describe('[Interactions] when parsing', () => {
it('should be possible to use click to a click and call callback with tooltip', function () {
spyOn(flowDb, 'setClickEvent');
spyOn(flowDb, 'setTooltip');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A call callback() "tooltip"');
+ const res = flow.parse('graph TD\nA-->B\nclick A call callback() "tooltip"');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback');
expect(flowDb.setTooltip).toHaveBeenCalledWith('A', 'tooltip');
@@ -62,30 +62,30 @@ describe('[Interactions] when parsing', () => {
it('should be possible to use click to a callback with an arbitrary number of args', function () {
spyOn(flowDb, 'setClickEvent');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A call callback("test0", test1, test2)');
+ const res = flow.parse('graph TD\nA-->B\nclick A call callback("test0", test1, test2)');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setClickEvent).toHaveBeenCalledWith('A', 'callback', '"test0", test1, test2');
});
it('should handle interaction - click to a link', function () {
spyOn(flowDb, 'setLink');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html"');
+ const res = flow.parse('graph TD\nA-->B\nclick A "click.html"');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html');
});
it('should handle interaction - click to a click and href link', function () {
spyOn(flowDb, 'setLink');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A href "click.html"');
+ const res = flow.parse('graph TD\nA-->B\nclick A href "click.html"');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html');
});
@@ -93,10 +93,10 @@ describe('[Interactions] when parsing', () => {
it('should handle interaction - click to a link with tooltip', function () {
spyOn(flowDb, 'setLink');
spyOn(flowDb, 'setTooltip');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html" "tooltip"');
+ const res = flow.parse('graph TD\nA-->B\nclick A "click.html" "tooltip"');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html');
expect(flowDb.setTooltip).toHaveBeenCalledWith('A', 'tooltip');
@@ -105,10 +105,10 @@ describe('[Interactions] when parsing', () => {
it('should handle interaction - click to a click and href link with tooltip', function () {
spyOn(flowDb, 'setLink');
spyOn(flowDb, 'setTooltip');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A href "click.html" "tooltip"');
+ const res = flow.parse('graph TD\nA-->B\nclick A href "click.html" "tooltip"');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html');
expect(flowDb.setTooltip).toHaveBeenCalledWith('A', 'tooltip');
@@ -116,20 +116,20 @@ describe('[Interactions] when parsing', () => {
it('should handle interaction - click to a link with target', function () {
spyOn(flowDb, 'setLink');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html" _blank');
+ const res = flow.parse('graph TD\nA-->B\nclick A "click.html" _blank');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html', '_blank');
});
it('should handle interaction - click to a click and href link with target', function () {
spyOn(flowDb, 'setLink');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A href "click.html" _blank');
+ const res = flow.parse('graph TD\nA-->B\nclick A href "click.html" _blank');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html', '_blank');
});
@@ -137,10 +137,10 @@ describe('[Interactions] when parsing', () => {
it('should handle interaction - click to a link with tooltip and target', function () {
spyOn(flowDb, 'setLink');
spyOn(flowDb, 'setTooltip');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html" "tooltip" _blank');
+ const res = flow.parse('graph TD\nA-->B\nclick A "click.html" "tooltip" _blank');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html', '_blank');
expect(flowDb.setTooltip).toHaveBeenCalledWith('A', 'tooltip');
@@ -149,10 +149,10 @@ describe('[Interactions] when parsing', () => {
it('should handle interaction - click to a click and href link with tooltip and target', function () {
spyOn(flowDb, 'setLink');
spyOn(flowDb, 'setTooltip');
- const res = flow.parser.parse('graph TD\nA-->B\nclick A href "click.html" "tooltip" _blank');
+ const res = flow.parse('graph TD\nA-->B\nclick A href "click.html" "tooltip" _blank');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(flowDb.setLink).toHaveBeenCalledWith('A', 'click.html', '_blank');
expect(flowDb.setTooltip).toHaveBeenCalledWith('A', 'tooltip');
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-lines.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-lines.spec.js
index 6b1bc7fbb..144ef3807 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-lines.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-lines.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -8,21 +8,21 @@ setConfig({
describe('[Lines] when parsing', () => {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
+ flow.yy = new FlowDB();
+ flow.yy.clear();
});
it('should handle line interpolation default definitions', function () {
- const res = flow.parser.parse('graph TD\n' + 'A-->B\n' + 'linkStyle default interpolate basis');
+ const res = flow.parse('graph TD\n' + 'A-->B\n' + 'linkStyle default interpolate basis');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.defaultInterpolate).toBe('basis');
});
it('should handle line interpolation numbered definitions', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD\n' +
'A-->B\n' +
'A-->C\n' +
@@ -30,38 +30,38 @@ describe('[Lines] when parsing', () => {
'linkStyle 1 interpolate cardinal'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].interpolate).toBe('basis');
expect(edges[1].interpolate).toBe('cardinal');
});
it('should handle line interpolation multi-numbered definitions', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD\n' + 'A-->B\n' + 'A-->C\n' + 'linkStyle 0,1 interpolate basis'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].interpolate).toBe('basis');
expect(edges[1].interpolate).toBe('basis');
});
it('should handle line interpolation default with style', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD\n' + 'A-->B\n' + 'linkStyle default interpolate basis stroke-width:1px;'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.defaultInterpolate).toBe('basis');
});
it('should handle line interpolation numbered with style', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD\n' +
'A-->B\n' +
'A-->C\n' +
@@ -69,20 +69,20 @@ describe('[Lines] when parsing', () => {
'linkStyle 1 interpolate cardinal stroke-width:1px;'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].interpolate).toBe('basis');
expect(edges[1].interpolate).toBe('cardinal');
});
it('should handle line interpolation multi-numbered with style', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD\n' + 'A-->B\n' + 'A-->C\n' + 'linkStyle 0,1 interpolate basis stroke-width:1px;'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].interpolate).toBe('basis');
expect(edges[1].interpolate).toBe('basis');
@@ -90,28 +90,28 @@ describe('[Lines] when parsing', () => {
describe('it should handle new line type notation', function () {
it('should handle regular lines', function () {
- const res = flow.parser.parse('graph TD;A-->B;');
+ const res = flow.parse('graph TD;A-->B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].stroke).toBe('normal');
});
it('should handle dotted lines', function () {
- const res = flow.parser.parse('graph TD;A-.->B;');
+ const res = flow.parse('graph TD;A-.->B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].stroke).toBe('dotted');
});
it('should handle dotted lines', function () {
- const res = flow.parser.parse('graph TD;A==>B;');
+ const res = flow.parse('graph TD;A==>B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].stroke).toBe('thick');
});
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js
index b7d3a8c73..bad7df596 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -8,16 +8,16 @@ setConfig({
describe('parsing a flow chart with markdown strings', function () {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
+ flow.yy = new FlowDB();
+ flow.yy.clear();
});
it('markdown formatting in nodes and labels', function () {
- const res = flow.parser.parse(`flowchart
+ const res = flow.parse(`flowchart
A["\`The cat in **the** hat\`"]-- "\`The *bat* in the chat\`" -->B["The dog in the hog"] -- "The rat in the mat" -->C;`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('A').text).toBe('The cat in **the** hat');
@@ -38,7 +38,7 @@ A["\`The cat in **the** hat\`"]-- "\`The *bat* in the chat\`" -->B["The dog in t
expect(edges[1].labelType).toBe('string');
});
it('markdown formatting in subgraphs', function () {
- const res = flow.parser.parse(`flowchart LR
+ const res = flow.parse(`flowchart LR
subgraph "One"
a("\`The **cat**
in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
@@ -48,7 +48,7 @@ subgraph "\`**Two**\`"
in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
end`);
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(2);
const subgraph = subgraphs[0];
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js
index 0aeccd776..2a3237e26 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -8,105 +8,105 @@ setConfig({
describe('when parsing directions', function () {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
- flow.parser.yy.setGen('gen-2');
+ flow.yy = new FlowDB();
+ flow.yy.clear();
+ flow.yy.setGen('gen-2');
});
it('should handle basic shape data statements', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded}`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
});
it('should handle basic shape data statements', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded }`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
});
it('should handle basic shape data statements with &', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded } & E`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(2);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle shape data statements with edges', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded } --> E`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(2);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 1', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded } & E --> F`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 2', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded } & E@{ shape: rounded } --> F`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 3', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded } & E@{ shape: rounded } --> F & G@{ shape: rounded }`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(4);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 4', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded } & E@{ shape: rounded } --> F@{ shape: rounded } & G@{ shape: rounded }`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(4);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should handle basic shape data statements with amp and edges 5, trailing space', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded } & E@{ shape: rounded } --> F{ shape: rounded } & G{ shape: rounded } `);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(4);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
expect(data4Layout.nodes[1].label).toEqual('E');
});
it('should no matter of there are no leading spaces', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{shape: rounded}`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
@@ -114,10 +114,10 @@ describe('when parsing directions', function () {
});
it('should no matter of there are many leading spaces', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded}`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
@@ -125,27 +125,27 @@ describe('when parsing directions', function () {
});
it('should be forgiving with many spaces before the end', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded }`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('D');
});
it('should be possible to add multiple properties on the same line', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
D@{ shape: rounded , label: "DD"}`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('rounded');
expect(data4Layout.nodes[0].label).toEqual('DD');
});
it('should be possible to link to a node with more data', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
A --> D@{
shape: circle
other: "clock"
@@ -153,7 +153,7 @@ describe('when parsing directions', function () {
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(2);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('A');
@@ -163,7 +163,7 @@ describe('when parsing directions', function () {
expect(data4Layout.edges.length).toBe(1);
});
it('should not disturb adding multiple nodes after each other', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
A[hello]
B@{
shape: circle
@@ -175,7 +175,7 @@ describe('when parsing directions', function () {
}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('hello');
@@ -185,21 +185,21 @@ describe('when parsing directions', function () {
expect(data4Layout.nodes[2].label).toEqual('Hello');
});
it('should use handle bracket end (}) character inside the shape data', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
A@{
label: "This is }"
other: "clock"
}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is }');
});
it('should error on nonexistent shape', function () {
expect(() => {
- flow.parser.parse(`flowchart TB
+ flow.parse(`flowchart TB
A@{ shape: this-shape-does-not-exist }
`);
}).toThrow('No such shape: this-shape-does-not-exist.');
@@ -207,23 +207,23 @@ describe('when parsing directions', function () {
it('should error on internal-only shape', function () {
expect(() => {
// this shape does exist, but it's only supposed to be for internal/backwards compatibility use
- flow.parser.parse(`flowchart TB
+ flow.parse(`flowchart TB
A@{ shape: rect_left_inv_arrow }
`);
}).toThrow('No such shape: rect_left_inv_arrow. Shape names should be lowercase.');
});
it('Diamond shapes should work as usual', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
A{This is a label}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('diamond');
expect(data4Layout.nodes[0].label).toEqual('This is a label');
});
it('Multi line strings should be supported', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
A@{
label: |
This is a
@@ -232,13 +232,13 @@ describe('when parsing directions', function () {
}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a\nmultiline string\n');
});
it('Multi line strings should be supported', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
A@{
label: "This is a
multiline string"
@@ -246,57 +246,57 @@ describe('when parsing directions', function () {
}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a
multiline string');
});
it('should be possible to use } in strings', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
A@{
label: "This is a string with }"
other: "clock"
}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a string with }');
});
it('should be possible to use @ in strings', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
A@{
label: "This is a string with @"
other: "clock"
}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a string with @');
});
it('should be possible to use @ in strings', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
A@{
label: "This is a string with}"
other: "clock"
}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(1);
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a string with}');
});
it('should be possible to use @ syntax to add labels on multi nodes', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
n2["label for n2"] & n4@{ label: "label for n4"} & n5@{ label: "label for n5"}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].label).toEqual('label for n2');
expect(data4Layout.nodes[1].label).toEqual('label for n4');
@@ -304,12 +304,12 @@ describe('when parsing directions', function () {
});
it('should be possible to use @ syntax to add labels on multi nodes with edge/link', function () {
- const res = flow.parser.parse(`flowchart TD
+ const res = flow.parse(`flowchart TD
A["A"] --> B["for B"] & C@{ label: "for c"} & E@{label : "for E"}
D@{label: "for D"}
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(5);
expect(data4Layout.nodes[0].label).toEqual('A');
expect(data4Layout.nodes[1].label).toEqual('for B');
@@ -319,7 +319,7 @@ describe('when parsing directions', function () {
});
it('should be possible to use @ syntax in labels', function () {
- const res = flow.parser.parse(`flowchart TD
+ const res = flow.parse(`flowchart TD
A["@A@"] --> B["@for@ B@"] & C@{ label: "@for@ c@"} & E{"\`@for@ E@\`"} & D(("@for@ D@"))
H1{{"@for@ H@"}}
H2{{"\`@for@ H@\`"}}
@@ -329,7 +329,7 @@ describe('when parsing directions', function () {
AS2>"\`@for@ AS@\`"]
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(11);
expect(data4Layout.nodes[0].label).toEqual('@A@');
expect(data4Layout.nodes[1].label).toEqual('@for@ B@');
@@ -345,12 +345,12 @@ describe('when parsing directions', function () {
});
it('should handle unique edge creation with using @ and &', function () {
- const res = flow.parser.parse(`flowchart TD
+ const res = flow.parse(`flowchart TD
A & B e1@--> C & D
A1 e2@--> C1 & D1
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(7);
expect(data4Layout.edges.length).toBe(6);
expect(data4Layout.edges[0].id).toEqual('L_A_C_0');
@@ -362,12 +362,12 @@ describe('when parsing directions', function () {
});
it('should handle redefine same edge ids again', function () {
- const res = flow.parser.parse(`flowchart TD
+ const res = flow.parse(`flowchart TD
A & B e1@--> C & D
A1 e1@--> C1 & D1
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(7);
expect(data4Layout.edges.length).toBe(6);
expect(data4Layout.edges[0].id).toEqual('L_A_C_0');
@@ -379,7 +379,7 @@ describe('when parsing directions', function () {
});
it('should handle overriding edge animate again', function () {
- const res = flow.parser.parse(`flowchart TD
+ const res = flow.parse(`flowchart TD
A e1@--> B
C e2@--> D
E e3@--> F
@@ -389,7 +389,7 @@ describe('when parsing directions', function () {
e3@{ animate: false }
`);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(6);
expect(data4Layout.edges.length).toBe(3);
expect(data4Layout.edges[0].id).toEqual('e1');
@@ -401,12 +401,12 @@ describe('when parsing directions', function () {
});
it.skip('should be possible to use @ syntax to add labels with trail spaces', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
`flowchart TB
n2["label for n2"] & n4@{ label: "label for n4"} & n5@{ label: "label for n5"} `
);
- const data4Layout = flow.parser.yy.getData();
+ const data4Layout = flow.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].label).toEqual('label for n2');
expect(data4Layout.nodes[1].label).toEqual('label for n4');
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-singlenode.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-singlenode.spec.js
index 89e0cc621..30ca25821 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-singlenode.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-singlenode.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -31,26 +31,26 @@ const specialChars = ['#', ':', '0', '&', ',', '*', '.', '\\', 'v', '-', '/', '_
describe('[Singlenodes] when parsing', () => {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
+ flow.yy = new FlowDB();
+ flow.yy.clear();
});
it('should handle a single node', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;A;');
+ const res = flow.parse('graph TD;A;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('A').styles.length).toBe(0);
});
it('should handle a single node with white space after it (SN1)', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;A ;');
+ const res = flow.parse('graph TD;A ;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('A').styles.length).toBe(0);
@@ -58,10 +58,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single square node', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a[A];');
+ const res = flow.parse('graph TD;a[A];');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').styles.length).toBe(0);
@@ -70,10 +70,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single round square node', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a[A];');
+ const res = flow.parse('graph TD;a[A];');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').styles.length).toBe(0);
@@ -82,10 +82,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single circle node', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a((A));');
+ const res = flow.parse('graph TD;a((A));');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('circle');
@@ -93,10 +93,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single round node', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a(A);');
+ const res = flow.parse('graph TD;a(A);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('round');
@@ -104,10 +104,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single odd node', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a>A];');
+ const res = flow.parse('graph TD;a>A];');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('odd');
@@ -115,10 +115,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single diamond node', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a{A};');
+ const res = flow.parse('graph TD;a{A};');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('diamond');
@@ -126,10 +126,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single diamond node with whitespace after it', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a{A} ;');
+ const res = flow.parse('graph TD;a{A} ;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('diamond');
@@ -137,10 +137,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single diamond node with html in it (SN3)', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a{A
end};');
+ const res = flow.parse('graph TD;a{A
end};');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('diamond');
@@ -149,10 +149,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single hexagon node', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a{{A}};');
+ const res = flow.parse('graph TD;a{{A}};');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('hexagon');
@@ -160,10 +160,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single hexagon node with html in it', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a{{A
end}};');
+ const res = flow.parse('graph TD;a{{A
end}};');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('hexagon');
@@ -172,10 +172,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single round node with html in it', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a(A
end);');
+ const res = flow.parse('graph TD;a(A
end);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('round');
@@ -184,10 +184,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single double circle node', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a(((A)));');
+ const res = flow.parse('graph TD;a(((A)));');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('doublecircle');
@@ -195,10 +195,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single double circle node with whitespace after it', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a(((A))) ;');
+ const res = flow.parse('graph TD;a(((A))) ;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('doublecircle');
@@ -206,10 +206,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single double circle node with html in it (SN3)', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;a(((A
end)));');
+ const res = flow.parse('graph TD;a(((A
end)));');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('a').type).toBe('doublecircle');
@@ -218,10 +218,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single node with alphanumerics starting on a char', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;id1;');
+ const res = flow.parse('graph TD;id1;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('id1').styles.length).toBe(0);
@@ -229,10 +229,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single node with a single digit', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;1;');
+ const res = flow.parse('graph TD;1;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('1').text).toBe('1');
@@ -241,10 +241,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single node with a single digit in a subgraph', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;subgraph "hello";1;end;');
+ const res = flow.parse('graph TD;subgraph "hello";1;end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('1').text).toBe('1');
@@ -252,10 +252,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single node with alphanumerics starting on a num', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;1id;');
+ const res = flow.parse('graph TD;1id;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('1id').styles.length).toBe(0);
@@ -263,10 +263,10 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single node with alphanumerics containing a minus sign', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;i-d;');
+ const res = flow.parse('graph TD;i-d;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('i-d').styles.length).toBe(0);
@@ -274,36 +274,36 @@ describe('[Singlenodes] when parsing', () => {
it('should handle a single node with alphanumerics containing a underscore sign', function () {
// Silly but syntactically correct
- const res = flow.parser.parse('graph TD;i_d;');
+ const res = flow.parse('graph TD;i_d;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges.length).toBe(0);
expect(vert.get('i_d').styles.length).toBe(0);
});
it.each(keywords)('should handle keywords between dashes "-"', function (keyword) {
- const res = flow.parser.parse(`graph TD;a-${keyword}-node;`);
- const vert = flow.parser.yy.getVertices();
+ const res = flow.parse(`graph TD;a-${keyword}-node;`);
+ const vert = flow.yy.getVertices();
expect(vert.get(`a-${keyword}-node`).text).toBe(`a-${keyword}-node`);
});
it.each(keywords)('should handle keywords between periods "."', function (keyword) {
- const res = flow.parser.parse(`graph TD;a.${keyword}.node;`);
- const vert = flow.parser.yy.getVertices();
+ const res = flow.parse(`graph TD;a.${keyword}.node;`);
+ const vert = flow.yy.getVertices();
expect(vert.get(`a.${keyword}.node`).text).toBe(`a.${keyword}.node`);
});
it.each(keywords)('should handle keywords between underscores "_"', function (keyword) {
- const res = flow.parser.parse(`graph TD;a_${keyword}_node;`);
- const vert = flow.parser.yy.getVertices();
+ const res = flow.parse(`graph TD;a_${keyword}_node;`);
+ const vert = flow.yy.getVertices();
expect(vert.get(`a_${keyword}_node`).text).toBe(`a_${keyword}_node`);
});
it.each(keywords)('should handle nodes ending in %s', function (keyword) {
- const res = flow.parser.parse(`graph TD;node_${keyword};node.${keyword};node-${keyword};`);
- const vert = flow.parser.yy.getVertices();
+ const res = flow.parse(`graph TD;node_${keyword};node.${keyword};node-${keyword};`);
+ const vert = flow.yy.getVertices();
expect(vert.get(`node_${keyword}`).text).toBe(`node_${keyword}`);
expect(vert.get(`node.${keyword}`).text).toBe(`node.${keyword}`);
expect(vert.get(`node-${keyword}`).text).toBe(`node-${keyword}`);
@@ -327,16 +327,16 @@ describe('[Singlenodes] when parsing', () => {
];
it.each(errorKeywords)('should throw error at nodes beginning with %s', function (keyword) {
const str = `graph TD;${keyword}.node;${keyword}-node;${keyword}/node`;
- const vert = flow.parser.yy.getVertices();
+ const vert = flow.yy.getVertices();
- expect(() => flow.parser.parse(str)).toThrowError();
+ expect(() => flow.parse(str)).toThrowError();
});
const workingKeywords = ['default', 'href', 'click', 'call'];
it.each(workingKeywords)('should parse node beginning with %s', function (keyword) {
- flow.parser.parse(`graph TD; ${keyword}.node;${keyword}-node;${keyword}/node;`);
- const vert = flow.parser.yy.getVertices();
+ flow.parse(`graph TD; ${keyword}.node;${keyword}-node;${keyword}/node;`);
+ const vert = flow.yy.getVertices();
expect(vert.get(`${keyword}.node`).text).toBe(`${keyword}.node`);
expect(vert.get(`${keyword}-node`).text).toBe(`${keyword}-node`);
expect(vert.get(`${keyword}/node`).text).toBe(`${keyword}/node`);
@@ -345,8 +345,8 @@ describe('[Singlenodes] when parsing', () => {
it.each(specialChars)(
'should allow node ids of single special characters',
function (specialChar) {
- flow.parser.parse(`graph TD; ${specialChar} --> A`);
- const vert = flow.parser.yy.getVertices();
+ flow.parse(`graph TD; ${specialChar} --> A`);
+ const vert = flow.yy.getVertices();
expect(vert.get(`${specialChar}`).text).toBe(`${specialChar}`);
}
);
@@ -354,8 +354,8 @@ describe('[Singlenodes] when parsing', () => {
it.each(specialChars)(
'should allow node ids with special characters at start of id',
function (specialChar) {
- flow.parser.parse(`graph TD; ${specialChar}node --> A`);
- const vert = flow.parser.yy.getVertices();
+ flow.parse(`graph TD; ${specialChar}node --> A`);
+ const vert = flow.yy.getVertices();
expect(vert.get(`${specialChar}node`).text).toBe(`${specialChar}node`);
}
);
@@ -363,8 +363,8 @@ describe('[Singlenodes] when parsing', () => {
it.each(specialChars)(
'should allow node ids with special characters at end of id',
function (specialChar) {
- flow.parser.parse(`graph TD; node${specialChar} --> A`);
- const vert = flow.parser.yy.getVertices();
+ flow.parse(`graph TD; node${specialChar} --> A`);
+ const vert = flow.yy.getVertices();
expect(vert.get(`node${specialChar}`).text).toBe(`node${specialChar}`);
}
);
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-style.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-style.spec.js
index 316b035b0..84baf069b 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-style.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-style.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -8,27 +8,27 @@ setConfig({
describe('[Style] when parsing', () => {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
- flow.parser.yy.setGen('gen-2');
+ flow.yy = new FlowDB();
+ flow.yy.clear();
+ flow.yy.setGen('gen-2');
});
- // log.debug(flow.parser.parse('graph TD;style Q background:#fff;'));
+ // log.debug(flow.parse('graph TD;style Q background:#fff;'));
it('should handle styles for vertices', function () {
- const res = flow.parser.parse('graph TD;style Q background:#fff;');
+ const res = flow.parse('graph TD;style Q background:#fff;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('Q').styles.length).toBe(1);
expect(vert.get('Q').styles[0]).toBe('background:#fff');
});
it('should handle multiple styles for a vortex', function () {
- const res = flow.parser.parse('graph TD;style R background:#fff,border:1px solid red;');
+ const res = flow.parse('graph TD;style R background:#fff,border:1px solid red;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('R').styles.length).toBe(2);
expect(vert.get('R').styles[0]).toBe('background:#fff');
@@ -36,12 +36,12 @@ describe('[Style] when parsing', () => {
});
it('should handle multiple styles in a graph', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD;style S background:#aaa;\nstyle T background:#bbb,border:1px solid red;'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('S').styles.length).toBe(1);
expect(vert.get('T').styles.length).toBe(2);
@@ -51,12 +51,12 @@ describe('[Style] when parsing', () => {
});
it('should handle styles and graph definitions in a graph', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD;S-->T;\nstyle S background:#aaa;\nstyle T background:#bbb,border:1px solid red;'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('S').styles.length).toBe(1);
expect(vert.get('T').styles.length).toBe(2);
@@ -66,10 +66,10 @@ describe('[Style] when parsing', () => {
});
it('should handle styles and graph definitions in a graph', function () {
- const res = flow.parser.parse('graph TD;style T background:#bbb,border:1px solid red;');
- // const res = flow.parser.parse('graph TD;style T background: #bbb;');
+ const res = flow.parse('graph TD;style T background:#bbb,border:1px solid red;');
+ // const res = flow.parse('graph TD;style T background: #bbb;');
- const vert = flow.parser.yy.getVertices();
+ const vert = flow.yy.getVertices();
expect(vert.get('T').styles.length).toBe(2);
expect(vert.get('T').styles[0]).toBe('background:#bbb');
@@ -77,11 +77,11 @@ describe('[Style] when parsing', () => {
});
it('should keep node label text (if already defined) when a style is applied', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD;A(( ));B((Test));C;style A background:#fff;style D border:1px solid red;'
);
- const vert = flow.parser.yy.getVertices();
+ const vert = flow.yy.getVertices();
expect(vert.get('A').text).toBe('');
expect(vert.get('B').text).toBe('Test');
@@ -90,12 +90,12 @@ describe('[Style] when parsing', () => {
});
it('should be possible to declare a class', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD;classDef exClass background:#bbb,border:1px solid red;'
);
- // const res = flow.parser.parse('graph TD;style T background: #bbb;');
+ // const res = flow.parse('graph TD;style T background: #bbb;');
- const classes = flow.parser.yy.getClasses();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(classes.get('exClass').styles[0]).toBe('background:#bbb');
@@ -103,11 +103,11 @@ describe('[Style] when parsing', () => {
});
it('should be possible to declare multiple classes', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD;classDef firstClass,secondClass background:#bbb,border:1px solid red;'
);
- const classes = flow.parser.yy.getClasses();
+ const classes = flow.yy.getClasses();
expect(classes.get('firstClass').styles.length).toBe(2);
expect(classes.get('firstClass').styles[0]).toBe('background:#bbb');
@@ -119,24 +119,24 @@ describe('[Style] when parsing', () => {
});
it('should be possible to declare a class with a dot in the style', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD;classDef exClass background:#bbb,border:1.5px solid red;'
);
- // const res = flow.parser.parse('graph TD;style T background: #bbb;');
+ // const res = flow.parse('graph TD;style T background: #bbb;');
- const classes = flow.parser.yy.getClasses();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(classes.get('exClass').styles[0]).toBe('background:#bbb');
expect(classes.get('exClass').styles[1]).toBe('border:1.5px solid red');
});
it('should be possible to declare a class with a space in the style', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD;classDef exClass background: #bbb,border:1.5px solid red;'
);
- // const res = flow.parser.parse('graph TD;style T background : #bbb;');
+ // const res = flow.parse('graph TD;style T background : #bbb;');
- const classes = flow.parser.yy.getClasses();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(classes.get('exClass').styles[0]).toBe('background: #bbb');
@@ -150,9 +150,9 @@ describe('[Style] when parsing', () => {
statement = statement + 'a-->b;' + '\n';
statement = statement + 'class a exClass;';
- const res = flow.parser.parse(statement);
+ const res = flow.parse(statement);
- const classes = flow.parser.yy.getClasses();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(classes.get('exClass').styles[0]).toBe('background:#bbb');
@@ -166,9 +166,9 @@ describe('[Style] when parsing', () => {
statement = statement + 'a_a-->b_b;' + '\n';
statement = statement + 'class a_a exClass;';
- const res = flow.parser.parse(statement);
+ const res = flow.parse(statement);
- const classes = flow.parser.yy.getClasses();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(classes.get('exClass').styles[0]).toBe('background:#bbb');
@@ -181,9 +181,9 @@ describe('[Style] when parsing', () => {
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'a-->b[test]:::exClass;' + '\n';
- const res = flow.parser.parse(statement);
- const vertices = flow.parser.yy.getVertices();
- const classes = flow.parser.yy.getClasses();
+ const res = flow.parse(statement);
+ const vertices = flow.yy.getVertices();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(vertices.get('b').classes[0]).toBe('exClass');
@@ -198,9 +198,9 @@ describe('[Style] when parsing', () => {
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'b[test]:::exClass;' + '\n';
- const res = flow.parser.parse(statement);
- const vertices = flow.parser.yy.getVertices();
- const classes = flow.parser.yy.getClasses();
+ const res = flow.parse(statement);
+ const vertices = flow.yy.getVertices();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(vertices.get('b').classes[0]).toBe('exClass');
@@ -215,9 +215,9 @@ describe('[Style] when parsing', () => {
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'A[test]:::exClass-->B[test2];' + '\n';
- const res = flow.parser.parse(statement);
- const vertices = flow.parser.yy.getVertices();
- const classes = flow.parser.yy.getClasses();
+ const res = flow.parse(statement);
+ const vertices = flow.yy.getVertices();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(vertices.get('A').classes[0]).toBe('exClass');
@@ -232,9 +232,9 @@ describe('[Style] when parsing', () => {
statement = statement + 'classDef exClass background:#bbb,border:1px solid red;' + '\n';
statement = statement + 'a-->b[1 a a text!.]:::exClass;' + '\n';
- const res = flow.parser.parse(statement);
- const vertices = flow.parser.yy.getVertices();
- const classes = flow.parser.yy.getClasses();
+ const res = flow.parse(statement);
+ const vertices = flow.yy.getVertices();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(vertices.get('b').classes[0]).toBe('exClass');
@@ -249,10 +249,10 @@ describe('[Style] when parsing', () => {
statement = statement + 'a-->b;' + '\n';
statement = statement + 'class a,b exClass;';
- const res = flow.parser.parse(statement);
+ const res = flow.parse(statement);
- const classes = flow.parser.yy.getClasses();
- const vertices = flow.parser.yy.getVertices();
+ const classes = flow.yy.getClasses();
+ const vertices = flow.yy.getVertices();
expect(classes.get('exClass').styles.length).toBe(2);
expect(classes.get('exClass').styles[0]).toBe('background:#bbb');
@@ -262,7 +262,7 @@ describe('[Style] when parsing', () => {
});
it('should handle style definitions with more then 1 digit in a row', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD\n' +
'A-->B1\n' +
'A-->B2\n' +
@@ -278,8 +278,8 @@ describe('[Style] when parsing', () => {
'linkStyle 10 stroke-width:1px;'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
@@ -299,17 +299,17 @@ describe('[Style] when parsing', () => {
});
it('should handle style definitions within number of edges', function () {
- const res = flow.parser.parse(`graph TD
+ const res = flow.parse(`graph TD
A-->B
linkStyle 0 stroke-width:1px;`);
- const edges = flow.parser.yy.getEdges();
+ const edges = flow.yy.getEdges();
expect(edges[0].style[0]).toBe('stroke-width:1px');
});
it('should handle multi-numbered style definitions with more then 1 digit in a row', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD\n' +
'A-->B1\n' +
'A-->B2\n' +
@@ -326,41 +326,41 @@ describe('[Style] when parsing', () => {
'linkStyle 10,11 stroke-width:1px;'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
it('should handle classDefs with style in classes', function () {
- const res = flow.parser.parse('graph TD\nA-->B\nclassDef exClass font-style:bold;');
+ const res = flow.parse('graph TD\nA-->B\nclassDef exClass font-style:bold;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
it('should handle classDefs with % in classes', function () {
- const res = flow.parser.parse(
+ const res = flow.parse(
'graph TD\nA-->B\nclassDef exClass fill:#f96,stroke:#333,stroke-width:4px,font-size:50%,font-style:bold;'
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
it('should handle multiple vertices with style', function () {
- const res = flow.parser.parse(`
+ const res = flow.parse(`
graph TD
classDef C1 stroke-dasharray:4
classDef C2 stroke-dasharray:6
A & B:::C1 & D:::C1 --> E:::C2
`);
- const vert = flow.parser.yy.getVertices();
+ const vert = flow.yy.getVertices();
expect(vert.get('A').classes.length).toBe(0);
expect(vert.get('B').classes[0]).toBe('C1');
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-text.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-text.spec.js
index a4ffab87e..3a18c4cda 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-text.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-text.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -8,187 +8,187 @@ setConfig({
describe('[Text] when parsing', () => {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
+ flow.yy = new FlowDB();
+ flow.yy.clear();
});
describe('it should handle text on edges', function () {
it('should handle text without space', function () {
- const res = flow.parser.parse('graph TD;A--x|textNoSpace|B;');
+ const res = flow.parse('graph TD;A--x|textNoSpace|B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle with space', function () {
- const res = flow.parser.parse('graph TD;A--x|text including space|B;');
+ const res = flow.parse('graph TD;A--x|text including space|B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle text with /', function () {
- const res = flow.parser.parse('graph TD;A--x|text with / should work|B;');
+ const res = flow.parse('graph TD;A--x|text with / should work|B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].text).toBe('text with / should work');
});
it('should handle space and space between vertices and link', function () {
- const res = flow.parser.parse('graph TD;A --x|textNoSpace| B;');
+ const res = flow.parse('graph TD;A --x|textNoSpace| B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and CAPS', function () {
- const res = flow.parser.parse('graph TD;A--x|text including CAPS space|B;');
+ const res = flow.parse('graph TD;A--x|text including CAPS space|B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and dir', function () {
- const res = flow.parser.parse('graph TD;A--x|text including URL space|B;');
+ const res = flow.parse('graph TD;A--x|text including URL space|B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including URL space');
});
it('should handle space and send', function () {
- const res = flow.parser.parse('graph TD;A--text including URL space and send-->B;');
+ const res = flow.parse('graph TD;A--text including URL space and send-->B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
expect(edges[0].text).toBe('text including URL space and send');
});
it('should handle space and send', function () {
- const res = flow.parser.parse('graph TD;A-- text including URL space and send -->B;');
+ const res = flow.parse('graph TD;A-- text including URL space and send -->B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
expect(edges[0].text).toBe('text including URL space and send');
});
it('should handle space and dir (TD)', function () {
- const res = flow.parser.parse('graph TD;A--x|text including R TD space|B;');
+ const res = flow.parse('graph TD;A--x|text including R TD space|B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including R TD space');
});
it('should handle `', function () {
- const res = flow.parser.parse('graph TD;A--x|text including `|B;');
+ const res = flow.parse('graph TD;A--x|text including `|B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including `');
});
it('should handle v in node ids only v', function () {
// only v
- const res = flow.parser.parse('graph TD;A--xv(my text);');
+ const res = flow.parse('graph TD;A--xv(my text);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(vert.get('v').text).toBe('my text');
});
it('should handle v in node ids v at end', function () {
// v at end
- const res = flow.parser.parse('graph TD;A--xcsv(my text);');
+ const res = flow.parse('graph TD;A--xcsv(my text);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(vert.get('csv').text).toBe('my text');
});
it('should handle v in node ids v in middle', function () {
// v in middle
- const res = flow.parser.parse('graph TD;A--xava(my text);');
+ const res = flow.parse('graph TD;A--xava(my text);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(vert.get('ava').text).toBe('my text');
});
it('should handle v in node ids, v at start', function () {
// v at start
- const res = flow.parser.parse('graph TD;A--xva(my text);');
+ const res = flow.parse('graph TD;A--xva(my text);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(vert.get('va').text).toBe('my text');
});
it('should handle keywords', function () {
- const res = flow.parser.parse('graph TD;A--x|text including graph space|B;');
+ const res = flow.parse('graph TD;A--x|text including graph space|B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].text).toBe('text including graph space');
});
it('should handle keywords', function () {
- const res = flow.parser.parse('graph TD;V-->a[v]');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const res = flow.parse('graph TD;V-->a[v]');
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('a').text).toBe('v');
});
it('should handle quoted text', function () {
- const res = flow.parser.parse('graph TD;V-- "test string()" -->a[v]');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const res = flow.parse('graph TD;V-- "test string()" -->a[v]');
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].text).toBe('test string()');
});
});
describe('it should handle text on lines', () => {
it('should handle normal text on lines', function () {
- const res = flow.parser.parse('graph TD;A-- test text with == -->B;');
+ const res = flow.parse('graph TD;A-- test text with == -->B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].stroke).toBe('normal');
});
it('should handle dotted text on lines (TD3)', function () {
- const res = flow.parser.parse('graph TD;A-. test text with == .->B;');
+ const res = flow.parse('graph TD;A-. test text with == .->B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].stroke).toBe('dotted');
});
it('should handle thick text on lines', function () {
- const res = flow.parser.parse('graph TD;A== test text with - ==>B;');
+ const res = flow.parse('graph TD;A== test text with - ==>B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].stroke).toBe('thick');
});
@@ -196,99 +196,99 @@ describe('[Text] when parsing', () => {
describe('it should handle text on edges using the new notation', function () {
it('should handle text without space', function () {
- const res = flow.parser.parse('graph TD;A-- textNoSpace --xB;');
+ const res = flow.parse('graph TD;A-- textNoSpace --xB;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle text with multiple leading space', function () {
- const res = flow.parser.parse('graph TD;A-- textNoSpace --xB;');
+ const res = flow.parse('graph TD;A-- textNoSpace --xB;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle with space', function () {
- const res = flow.parser.parse('graph TD;A-- text including space --xB;');
+ const res = flow.parse('graph TD;A-- text including space --xB;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle text with /', function () {
- const res = flow.parser.parse('graph TD;A -- text with / should work --x B;');
+ const res = flow.parse('graph TD;A -- text with / should work --x B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].text).toBe('text with / should work');
});
it('should handle space and space between vertices and link', function () {
- const res = flow.parser.parse('graph TD;A -- textNoSpace --x B;');
+ const res = flow.parse('graph TD;A -- textNoSpace --x B;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and CAPS', function () {
- const res = flow.parser.parse('graph TD;A-- text including CAPS space --xB;');
+ const res = flow.parse('graph TD;A-- text including CAPS space --xB;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and dir', function () {
- const res = flow.parser.parse('graph TD;A-- text including URL space --xB;');
+ const res = flow.parse('graph TD;A-- text including URL space --xB;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including URL space');
});
it('should handle space and dir (TD2)', function () {
- const res = flow.parser.parse('graph TD;A-- text including R TD space --xB;');
+ const res = flow.parse('graph TD;A-- text including R TD space --xB;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including R TD space');
});
it('should handle keywords', function () {
- const res = flow.parser.parse('graph TD;A-- text including graph space and v --xB;');
+ const res = flow.parse('graph TD;A-- text including graph space and v --xB;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].text).toBe('text including graph space and v');
});
it('should handle keywords', function () {
- const res = flow.parser.parse('graph TD;A-- text including graph space and v --xB[blav]');
+ const res = flow.parse('graph TD;A-- text including graph space and v --xB[blav]');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].text).toBe('text including graph space and v');
});
// it.skip('should handle text on open links',function(){
- // const res = flow.parser.parse('graph TD;A-- text including graph space --B');
+ // const res = flow.parse('graph TD;A-- text including graph space --B');
//
- // const vert = flow.parser.yy.getVertices();
- // const edges = flow.parser.yy.getEdges();
+ // const vert = flow.yy.getVertices();
+ // const edges = flow.yy.getEdges();
//
// expect(edges[0].text).toBe('text including graph space');
//
@@ -297,10 +297,10 @@ describe('[Text] when parsing', () => {
describe('it should handle text in vertices, ', function () {
it('should handle space', function () {
- const res = flow.parser.parse('graph TD;A-->C(Chimpansen hoppar);');
+ const res = flow.parse('graph TD;A-->C(Chimpansen hoppar);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('C').type).toBe('round');
expect(vert.get('C').text).toBe('Chimpansen hoppar');
@@ -347,109 +347,109 @@ describe('[Text] when parsing', () => {
shapes.forEach((shape) => {
it.each(keywords)(`should handle %s keyword in ${shape.name} vertex`, function (keyword) {
- const rest = flow.parser.parse(
+ const rest = flow.parse(
`graph TD;A_${keyword}_node-->B${shape.start}This node has a ${keyword} as text${shape.end};`
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('B').type).toBe(`${shape.name}`);
expect(vert.get('B').text).toBe(`This node has a ${keyword} as text`);
});
});
it.each(keywords)('should handle %s keyword in rect vertex', function (keyword) {
- const rest = flow.parser.parse(
+ const rest = flow.parse(
`graph TD;A_${keyword}_node-->B[|borders:lt|This node has a ${keyword} as text];`
);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('B').type).toBe('rect');
expect(vert.get('B').text).toBe(`This node has a ${keyword} as text`);
});
it('should handle edge case for odd vertex with node id ending with minus', function () {
- const res = flow.parser.parse('graph TD;A_node-->odd->Vertex Text];');
- const vert = flow.parser.yy.getVertices();
+ const res = flow.parse('graph TD;A_node-->odd->Vertex Text];');
+ const vert = flow.yy.getVertices();
expect(vert.get('odd-').type).toBe('odd');
expect(vert.get('odd-').text).toBe('Vertex Text');
});
it('should allow forward slashes in lean_right vertices', function () {
- const rest = flow.parser.parse(`graph TD;A_node-->B[/This node has a / as text/];`);
+ const rest = flow.parse(`graph TD;A_node-->B[/This node has a / as text/];`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('B').type).toBe('lean_right');
expect(vert.get('B').text).toBe(`This node has a / as text`);
});
it('should allow back slashes in lean_left vertices', function () {
- const rest = flow.parser.parse(`graph TD;A_node-->B[\\This node has a \\ as text\\];`);
+ const rest = flow.parse(`graph TD;A_node-->B[\\This node has a \\ as text\\];`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('B').type).toBe('lean_left');
expect(vert.get('B').text).toBe(`This node has a \\ as text`);
});
it('should handle åäö and minus', function () {
- const res = flow.parser.parse('graph TD;A-->C{Chimpansen hoppar åäö-ÅÄÖ};');
+ const res = flow.parse('graph TD;A-->C{Chimpansen hoppar åäö-ÅÄÖ};');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('C').type).toBe('diamond');
expect(vert.get('C').text).toBe('Chimpansen hoppar åäö-ÅÄÖ');
});
it('should handle with åäö, minus and space and br', function () {
- const res = flow.parser.parse('graph TD;A-->C(Chimpansen hoppar åäö
- ÅÄÖ);');
+ const res = flow.parse('graph TD;A-->C(Chimpansen hoppar åäö
- ÅÄÖ);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('C').type).toBe('round');
expect(vert.get('C').text).toBe('Chimpansen hoppar åäö
- ÅÄÖ');
});
// it.skip('should handle åäö, minus and space and br',function(){
- // const res = flow.parser.parse('graph TD; A[Object(foo,bar)]-->B(Thing);');
+ // const res = flow.parse('graph TD; A[Object(foo,bar)]-->B(Thing);');
//
- // const vert = flow.parser.yy.getVertices();
- // const edges = flow.parser.yy.getEdges();
+ // const vert = flow.yy.getVertices();
+ // const edges = flow.yy.getEdges();
//
// expect(vert.get('C').type).toBe('round');
// expect(vert.get('C').text).toBe(' A[Object(foo,bar)]-->B(Thing);');
// });
it('should handle unicode chars', function () {
- const res = flow.parser.parse('graph TD;A-->C(Начало);');
+ const res = flow.parse('graph TD;A-->C(Начало);');
- const vert = flow.parser.yy.getVertices();
+ const vert = flow.yy.getVertices();
expect(vert.get('C').text).toBe('Начало');
});
it('should handle backslash', function () {
- const res = flow.parser.parse('graph TD;A-->C(c:\\windows);');
+ const res = flow.parse('graph TD;A-->C(c:\\windows);');
- const vert = flow.parser.yy.getVertices();
+ const vert = flow.yy.getVertices();
expect(vert.get('C').text).toBe('c:\\windows');
});
it('should handle CAPS', function () {
- const res = flow.parser.parse('graph TD;A-->C(some CAPS);');
+ const res = flow.parse('graph TD;A-->C(some CAPS);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('C').type).toBe('round');
expect(vert.get('C').text).toBe('some CAPS');
});
it('should handle directions', function () {
- const res = flow.parser.parse('graph TD;A-->C(some URL);');
+ const res = flow.parse('graph TD;A-->C(some URL);');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('C').type).toBe('round');
expect(vert.get('C').text).toBe('some URL');
@@ -457,10 +457,10 @@ describe('[Text] when parsing', () => {
});
it('should handle multi-line text', function () {
- const res = flow.parser.parse('graph TD;A--o|text space|B;\n B-->|more text with space|C;');
+ const res = flow.parse('graph TD;A--o|text space|B;\n B-->|more text with space|C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_circle');
expect(edges[1].type).toBe('arrow_point');
@@ -477,102 +477,102 @@ describe('[Text] when parsing', () => {
});
it('should handle text in vertices with space', function () {
- const res = flow.parser.parse('graph TD;A[chimpansen hoppar]-->C;');
+ const res = flow.parse('graph TD;A[chimpansen hoppar]-->C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').type).toBe('square');
expect(vert.get('A').text).toBe('chimpansen hoppar');
});
it('should handle text in vertices with space with spaces between vertices and link', function () {
- const res = flow.parser.parse('graph TD;A[chimpansen hoppar] --> C;');
+ const res = flow.parse('graph TD;A[chimpansen hoppar] --> C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').type).toBe('square');
expect(vert.get('A').text).toBe('chimpansen hoppar');
});
it('should handle text including _ in vertices', function () {
- const res = flow.parser.parse('graph TD;A[chimpansen_hoppar] --> C;');
+ const res = flow.parse('graph TD;A[chimpansen_hoppar] --> C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').type).toBe('square');
expect(vert.get('A').text).toBe('chimpansen_hoppar');
});
it('should handle quoted text in vertices ', function () {
- const res = flow.parser.parse('graph TD;A["chimpansen hoppar ()[]"] --> C;');
+ const res = flow.parse('graph TD;A["chimpansen hoppar ()[]"] --> C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').type).toBe('square');
expect(vert.get('A').text).toBe('chimpansen hoppar ()[]');
});
it('should handle text in circle vertices with space', function () {
- const res = flow.parser.parse('graph TD;A((chimpansen hoppar))-->C;');
+ const res = flow.parse('graph TD;A((chimpansen hoppar))-->C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').type).toBe('circle');
expect(vert.get('A').text).toBe('chimpansen hoppar');
});
it('should handle text in ellipse vertices', function () {
- const res = flow.parser.parse('graph TD\nA(-this is an ellipse-)-->B');
+ const res = flow.parse('graph TD\nA(-this is an ellipse-)-->B');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').type).toBe('ellipse');
expect(vert.get('A').text).toBe('this is an ellipse');
});
it('should not freeze when ellipse text has a `(`', function () {
- expect(() => flow.parser.parse('graph\nX(- My Text (')).toThrowError();
+ expect(() => flow.parse('graph\nX(- My Text (')).toThrowError();
});
it('should handle text in diamond vertices with space', function () {
- const res = flow.parser.parse('graph TD;A(chimpansen hoppar)-->C;');
+ const res = flow.parse('graph TD;A(chimpansen hoppar)-->C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').type).toBe('round');
expect(vert.get('A').text).toBe('chimpansen hoppar');
});
it('should handle text in with ?', function () {
- const res = flow.parser.parse('graph TD;A(?)-->|?|C;');
+ const res = flow.parse('graph TD;A(?)-->|?|C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').text).toBe('?');
expect(edges[0].text).toBe('?');
});
it('should handle text in with éèêàçô', function () {
- const res = flow.parser.parse('graph TD;A(éèêàçô)-->|éèêàçô|C;');
+ const res = flow.parse('graph TD;A(éèêàçô)-->|éèêàçô|C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').text).toBe('éèêàçô');
expect(edges[0].text).toBe('éèêàçô');
});
it('should handle text in with ,.?!+-*', function () {
- const res = flow.parser.parse('graph TD;A(,.?!+-*)-->|,.?!+-*|C;');
+ const res = flow.parse('graph TD;A(,.?!+-*)-->|,.?!+-*|C;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').text).toBe(',.?!+-*');
expect(edges[0].text).toBe(',.?!+-*');
@@ -580,30 +580,30 @@ describe('[Text] when parsing', () => {
it('should throw error at nested set of brackets', function () {
const str = 'graph TD; A[This is a () in text];';
- expect(() => flow.parser.parse(str)).toThrowError("got 'PS'");
+ expect(() => flow.parse(str)).toThrowError("got 'PS'");
});
it('should throw error for strings and text at the same time', function () {
const str = 'graph TD;A(this node has "string" and text)-->|this link has "string" and text|C;';
- expect(() => flow.parser.parse(str)).toThrowError("got 'STR'");
+ expect(() => flow.parse(str)).toThrowError("got 'STR'");
});
it('should throw error for escaping quotes in text state', function () {
//prettier-ignore
const str = 'graph TD; A[This is a \"()\" in text];'; //eslint-disable-line no-useless-escape
- expect(() => flow.parser.parse(str)).toThrowError("got 'STR'");
+ expect(() => flow.parse(str)).toThrowError("got 'STR'");
});
it('should throw error for nested quotation marks', function () {
const str = 'graph TD; A["This is a "()" in text"];';
- expect(() => flow.parser.parse(str)).toThrowError("Expecting 'SQE'");
+ expect(() => flow.parse(str)).toThrowError("Expecting 'SQE'");
});
it('should throw error', function () {
const str = `graph TD; node[hello ) world] --> works`;
- expect(() => flow.parser.parse(str)).toThrowError("got 'PE'");
+ expect(() => flow.parse(str)).toThrowError("got 'PE'");
});
});
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js
index 32cbcee2b..9adc0e5ae 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-vertice-chaining.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -8,19 +8,19 @@ setConfig({
describe('when parsing flowcharts', function () {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
- flow.parser.yy.setGen('gen-2');
+ flow.yy = new FlowDB();
+ flow.yy.clear();
+ flow.yy.setGen('gen-2');
});
it('should handle chaining of vertices', function () {
- const res = flow.parser.parse(`
+ const res = flow.parse(`
graph TD
A-->B-->C;
`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -36,13 +36,13 @@ describe('when parsing flowcharts', function () {
expect(edges[1].text).toBe('');
});
it('should handle chaining of vertices', function () {
- const res = flow.parser.parse(`
+ const res = flow.parse(`
graph TD
A & B --> C;
`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -58,13 +58,13 @@ describe('when parsing flowcharts', function () {
expect(edges[1].text).toBe('');
});
it('should multiple vertices in link statement in the beginning', function () {
- const res = flow.parser.parse(`
+ const res = flow.parse(`
graph TD
A-->B & C;
`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -80,13 +80,13 @@ describe('when parsing flowcharts', function () {
expect(edges[1].text).toBe('');
});
it('should multiple vertices in link statement at the end', function () {
- const res = flow.parser.parse(`
+ const res = flow.parse(`
graph TD
A & B--> C & D;
`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -111,13 +111,13 @@ describe('when parsing flowcharts', function () {
expect(edges[3].text).toBe('');
});
it('should handle chaining of vertices at both ends at once', function () {
- const res = flow.parser.parse(`
+ const res = flow.parse(`
graph TD
A & B--> C & D;
`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -142,13 +142,13 @@ describe('when parsing flowcharts', function () {
expect(edges[3].text).toBe('');
});
it('should handle chaining and multiple nodes in link statement FVC ', function () {
- const res = flow.parser.parse(`
+ const res = flow.parse(`
graph TD
A --> B & B2 & C --> D2;
`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
@@ -182,16 +182,16 @@ describe('when parsing flowcharts', function () {
expect(edges[5].text).toBe('');
});
it('should handle chaining and multiple nodes in link statement with extra info in statements', function () {
- const res = flow.parser.parse(`
+ const res = flow.parse(`
graph TD
A[ h ] -- hello --> B[" test "]:::exClass & C --> D;
classDef exClass background:#bbb,border:1px solid red;
`);
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
- const classes = flow.parser.yy.getClasses();
+ const classes = flow.yy.getClasses();
expect(classes.get('exClass').styles.length).toBe(2);
expect(classes.get('exClass').styles[0]).toBe('background:#bbb');
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flowAst.ts b/packages/mermaid/src/diagrams/flowchart/parser/flowAst.ts
new file mode 100644
index 000000000..2750feb40
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flowAst.ts
@@ -0,0 +1,951 @@
+import type { IToken } from 'chevrotain';
+import { FlowchartParser } from './flowParser.js';
+
+// Define interfaces matching existing Mermaid structures
+interface Vertex {
+ id: string;
+ text?: string;
+ type?: string;
+ style?: string;
+ classes?: string[];
+ dir?: string;
+ props?: Record;
+}
+
+interface Edge {
+ start: string | string[];
+ end: string | string[];
+ type?: string;
+ stroke?: string;
+ length?: number;
+ text?: string;
+}
+
+interface ParseResult {
+ vertices: Record;
+ edges: Edge[];
+ classes: Record;
+ subGraphs: any[];
+ direction: string;
+ clickEvents: any[];
+ tooltips: Record;
+ accTitle: string;
+ accDescription: string;
+}
+
+const BaseVisitor = new FlowchartParser().getBaseCstVisitorConstructor();
+
+export class FlowchartAstVisitor extends BaseVisitor {
+ private vertices: Record = {};
+ private edges: Edge[] = [];
+ private classes: Record = {};
+ private subGraphs: any[] = [];
+ private direction = 'TB';
+ private clickEvents: any[] = [];
+ private tooltips: Record = {};
+ private subCount = 0;
+ private accTitle = '';
+ private accDescription = '';
+
+ constructor() {
+ super();
+ this.validateVisitor();
+ }
+
+ // Clear visitor state for new parse
+ clear(): void {
+ this.vertices = {};
+ this.edges = [];
+ this.classes = {};
+ this.subGraphs = [];
+ this.direction = 'TB';
+ this.clickEvents = [];
+ this.tooltips = {};
+ this.subCount = 0;
+ this.lastNodeId = '';
+ this.accTitle = '';
+ this.accDescription = '';
+ }
+
+ flowchart(ctx: any): ParseResult {
+ this.visit(ctx.graphDeclaration);
+
+ if (ctx.statement) {
+ ctx.statement.forEach((stmt: any) => this.visit(stmt));
+ }
+
+ return {
+ vertices: this.vertices,
+ edges: this.edges,
+ classes: this.classes,
+ subGraphs: this.subGraphs,
+ direction: this.direction,
+ clickEvents: this.clickEvents,
+ tooltips: this.tooltips,
+ accTitle: this.accTitle,
+ accDescription: this.accDescription,
+ };
+ }
+
+ graphDeclaration(ctx: any): void {
+ if (ctx.DirectionValue) {
+ this.direction = ctx.DirectionValue[0].image;
+ }
+ }
+
+ statement(ctx: any): void {
+ if (ctx.vertexStatement) {
+ this.visit(ctx.vertexStatement);
+ } else if (ctx.styleStatement) {
+ this.visit(ctx.styleStatement);
+ } else if (ctx.linkStyleStatement) {
+ this.visit(ctx.linkStyleStatement);
+ } else if (ctx.classDefStatement) {
+ this.visit(ctx.classDefStatement);
+ } else if (ctx.classStatement) {
+ this.visit(ctx.classStatement);
+ } else if (ctx.clickStatement) {
+ this.visit(ctx.clickStatement);
+ } else if (ctx.subgraphStatement) {
+ this.visit(ctx.subgraphStatement);
+ } else if (ctx.directionStatement) {
+ this.visit(ctx.directionStatement);
+ } else if (ctx.accStatement) {
+ this.visit(ctx.accStatement);
+ }
+ }
+
+ // Statement separator (does nothing)
+ statementSeparator(_ctx: any): void {
+ // No action needed for separators
+ }
+
+ // Vertex statement - handles node chains with links
+ vertexStatement(ctx: any): void {
+ const nodes = ctx.node || [];
+ const links = ctx.link || [];
+
+ // Process first node and collect all node IDs (for ampersand syntax)
+ let startNodeIds: string[] = [];
+ if (nodes.length > 0) {
+ startNodeIds = this.visit(nodes[0]);
+ }
+
+ // Process alternating links and nodes
+ for (const [i, link] of links.entries()) {
+ const linkData = this.visit(link);
+ const nextNode = nodes[i + 1];
+
+ if (nextNode) {
+ // Collect target node IDs (for ampersand syntax)
+ const endNodeIds = this.visit(nextNode);
+
+ // Create edges from each start node to each end node
+ for (const startNodeId of startNodeIds) {
+ for (const endNodeId of endNodeIds) {
+ const edge: any = {
+ start: startNodeId,
+ end: endNodeId,
+ type: linkData.type,
+ text: linkData.text || '',
+ };
+
+ // Include length property if present
+ if (linkData.length) {
+ edge.length = linkData.length;
+ }
+
+ this.edges.push(edge);
+ }
+ }
+
+ // Update start nodes for next iteration
+ startNodeIds = endNodeIds;
+ }
+ }
+ }
+
+ // Node - handles multiple nodes with ampersand
+ node(ctx: any): string[] {
+ const styledVertices = ctx.styledVertex || [];
+ const nodeIds: string[] = [];
+ for (const vertex of styledVertices) {
+ this.visit(vertex);
+ // Collect the node ID that was just processed
+ nodeIds.push(this.lastNodeId);
+ }
+ return nodeIds;
+ }
+
+ // Styled vertex
+ styledVertex(ctx: any): void {
+ if (ctx.vertex) {
+ this.visit(ctx.vertex);
+ }
+ // TODO: Handle styling
+ }
+
+ // Vertex - handles different node shapes
+ vertex(ctx: any): void {
+ const nodeIds = ctx.nodeId || [];
+ const nodeTexts = ctx.nodeText || [];
+
+ if (nodeIds.length > 0) {
+ const nodeId = this.visit(nodeIds[0]);
+ let nodeText = nodeId;
+ let nodeType = 'default';
+
+ // Determine node type based on tokens present
+ if (ctx.SquareStart) {
+ nodeType = 'square';
+ if (nodeTexts.length > 0) {
+ nodeText = this.visit(nodeTexts[0]);
+ }
+ } else if (ctx.DoubleCircleStart) {
+ nodeType = 'doublecircle';
+ if (nodeTexts.length > 0) {
+ nodeText = this.visit(nodeTexts[0]);
+ }
+ } else if (ctx.CircleStart) {
+ nodeType = 'circle';
+ if (nodeTexts.length > 0) {
+ nodeText = this.visit(nodeTexts[0]);
+ }
+ } else if (ctx.PS) {
+ nodeType = 'round';
+ if (nodeTexts.length > 0) {
+ nodeText = this.visit(nodeTexts[0]);
+ }
+ } else if (ctx.HexagonStart) {
+ nodeType = 'hexagon';
+ if (nodeTexts.length > 0) {
+ nodeText = this.visit(nodeTexts[0]);
+ }
+ } else if (ctx.DiamondStart) {
+ nodeType = 'diamond';
+ if (nodeTexts.length > 0) {
+ nodeText = this.visit(nodeTexts[0]);
+ }
+ }
+
+ // Add vertex to the graph
+ this.vertices[nodeId] = {
+ id: nodeId,
+ text: nodeText,
+ type: nodeType,
+ classes: [],
+ };
+ this.lastNodeId = nodeId;
+ }
+ }
+
+ // Helper to get last processed node ID
+ private lastNodeId = '';
+ private getLastNodeId(): string {
+ return this.lastNodeId;
+ }
+
+ nodeStatement(ctx: any): void {
+ const nodes: string[] = [];
+
+ // Process first node
+ const firstNode = this.visit(ctx.nodeDefinition[0]);
+ nodes.push(firstNode.id);
+
+ // Process additional nodes (connected with &)
+ if (ctx.Ampersand) {
+ ctx.nodeDefinition.slice(1).forEach((nodeDef: any) => {
+ const node = this.visit(nodeDef);
+ nodes.push(node.id);
+ });
+ }
+
+ // Process link chain if present
+ if (ctx.linkChain) {
+ const linkedNodes = this.visit(ctx.linkChain);
+ // Create edges between nodes
+ const startNodes = nodes;
+ linkedNodes.forEach((linkData: any) => {
+ const edge: any = {
+ start: startNodes,
+ end: linkData.node,
+ type: linkData.linkType,
+ text: linkData.linkText,
+ };
+
+ // Include length property if present
+ if (linkData.linkLength) {
+ edge.length = linkData.linkLength;
+ }
+
+ this.edges.push(edge);
+ });
+ }
+ }
+
+ nodeDefinition(ctx: any): any {
+ const nodeId = this.visit(ctx.nodeId);
+ let text = nodeId;
+ let type = 'default';
+
+ if (ctx.nodeShape) {
+ const shapeData = this.visit(ctx.nodeShape);
+ text = shapeData.text || nodeId;
+ type = shapeData.type;
+ }
+
+ // Add to vertices if not exists
+ if (!this.vertices[nodeId]) {
+ this.vertices[nodeId] = {
+ id: nodeId,
+ text: text,
+ type: type,
+ };
+ }
+
+ // Handle style class
+ if (ctx.StyleSeparator && ctx.className) {
+ const className = this.visit(ctx.className);
+ this.vertices[nodeId].classes = [className];
+ }
+
+ return { id: nodeId, text, type };
+ }
+
+ nodeId(ctx: any): string {
+ let nodeId = '';
+
+ if (ctx.NODE_STRING) {
+ nodeId = ctx.NODE_STRING[0].image;
+ } else if (ctx.NumberToken) {
+ nodeId = ctx.NumberToken[0].image;
+ } else if (ctx.Default) {
+ nodeId = ctx.Default[0].image;
+ } else if (ctx.Ampersand) {
+ // Standalone & (uses CONSUME2)
+ nodeId = ctx.Ampersand[0].image;
+ } else if (ctx.Minus) {
+ // Standalone - (uses CONSUME2)
+ nodeId = ctx.Minus[0].image;
+ } else if (ctx.DirectionValue) {
+ // Standalone direction value (uses CONSUME2)
+ nodeId = ctx.DirectionValue[0].image;
+ } else if (ctx.Colon) {
+ // Standalone : character
+ nodeId = ctx.Colon[0].image;
+ } else if (ctx.Comma) {
+ // Standalone , character
+ nodeId = ctx.Comma[0].image;
+ }
+
+ // If no nodeId was found, it might be an empty context
+ if (!nodeId) {
+ throw new Error('Unable to parse node ID from context');
+ }
+
+ // Validate node ID against reserved keywords
+ this.validateNodeId(nodeId);
+
+ return nodeId;
+ }
+
+ private validateNodeId(nodeId: string): void {
+ // Keywords that should throw errors when used as node ID prefixes
+ const errorKeywords = [
+ 'graph',
+ 'flowchart',
+ 'flowchart-elk',
+ 'style',
+ 'linkStyle',
+ 'interpolate',
+ 'classDef',
+ 'class',
+ '_self',
+ '_blank',
+ '_parent',
+ '_top',
+ 'end',
+ 'subgraph',
+ ];
+
+ // Check if node ID starts with any error keyword followed by a delimiter
+ // This matches the original JISON parser behavior where keywords are only
+ // rejected when followed by delimiters like '.', '-', '/', etc.
+ for (const keyword of errorKeywords) {
+ if (nodeId.startsWith(keyword)) {
+ // Allow if the keyword is not followed by a delimiter (e.g., "endpoint" is OK, "end.node" is not)
+ const afterKeyword = nodeId.substring(keyword.length);
+ if (afterKeyword.length === 0 || /^[./\-]/.test(afterKeyword)) {
+ throw new Error(`Node ID cannot start with reserved keyword: ${keyword}`);
+ }
+ }
+ }
+ }
+
+ nodeShape(ctx: any): any {
+ if (ctx.squareShape) {
+ return this.visit(ctx.squareShape);
+ } else if (ctx.circleShape) {
+ return this.visit(ctx.circleShape);
+ } else if (ctx.diamondShape) {
+ return this.visit(ctx.diamondShape);
+ }
+ return { type: 'default', text: '' };
+ }
+
+ squareShape(ctx: any): any {
+ const text = this.visit(ctx.nodeText);
+ return { type: 'square', text };
+ }
+
+ circleShape(ctx: any): any {
+ const text = this.visit(ctx.nodeText);
+ return { type: 'doublecircle', text };
+ }
+
+ diamondShape(ctx: any): any {
+ const text = this.visit(ctx.nodeText);
+ return { type: 'diamond', text };
+ }
+
+ nodeText(ctx: any): string {
+ if (ctx.TextContent) {
+ return ctx.TextContent[0].image;
+ } else if (ctx.NODE_STRING) {
+ return ctx.NODE_STRING[0].image;
+ } else if (ctx.StringContent) {
+ return ctx.StringContent[0].image;
+ } else if (ctx.QuotedString) {
+ // Remove quotes from quoted string
+ const quoted = ctx.QuotedString[0].image;
+ return quoted.slice(1, -1);
+ } else if (ctx.NumberToken) {
+ return ctx.NumberToken[0].image;
+ }
+ return '';
+ }
+
+ linkChain(ctx: any): any[] {
+ const links: any[] = [];
+
+ for (const [i, link] of ctx.link.entries()) {
+ const linkData = this.visit(link);
+ const nodeData = this.visit(ctx.nodeDefinition[i]);
+
+ const linkInfo: any = {
+ linkType: linkData.type,
+ linkText: linkData.text,
+ node: nodeData.id,
+ };
+
+ // Include length property if present
+ if (linkData.length) {
+ linkInfo.linkLength = linkData.length;
+ }
+
+ links.push(linkInfo);
+ }
+
+ return links;
+ }
+
+ link(ctx: any): any {
+ let linkData = { type: 'arrow', text: '' };
+
+ if (ctx.linkStatement) {
+ linkData = this.visit(ctx.linkStatement);
+ } else if (ctx.linkWithEdgeText) {
+ linkData = this.visit(ctx.linkWithEdgeText);
+ } else if (ctx.linkWithArrowText) {
+ linkData = this.visit(ctx.linkWithArrowText);
+ }
+
+ return linkData;
+ }
+
+ linkStatement(ctx: any): any {
+ if (ctx.LINK) {
+ return this.parseLinkToken(ctx.LINK[0]);
+ } else if (ctx.THICK_LINK) {
+ return this.parseLinkToken(ctx.THICK_LINK[0]);
+ } else if (ctx.DOTTED_LINK) {
+ return this.parseLinkToken(ctx.DOTTED_LINK[0]);
+ }
+ return { type: 'arrow', text: '' };
+ }
+
+ parseLinkToken(token: IToken): any {
+ const image = token.image;
+ let type = 'arrow_point'; // Default for --> arrows
+ let length: string | undefined = undefined;
+
+ // Check for bidirectional arrows first
+ if (image.startsWith('<') && image.endsWith('>')) {
+ if (image.includes('.')) {
+ type = 'double_arrow_dotted';
+ } else if (image.includes('=')) {
+ type = 'double_arrow_thick';
+ } else {
+ type = 'double_arrow_point';
+ }
+ return { type, text: '', length };
+ }
+
+ // Determine link type based on pattern
+ if (image.includes('.')) {
+ type = 'arrow_dotted';
+ } else if (image.includes('=')) {
+ type = 'arrow_thick';
+ }
+
+ // Check for special endings
+ if (image.endsWith('o')) {
+ type = type.replace('_point', '_circle');
+ type = type.replace('_dotted', '_dotted_circle');
+ type = type.replace('_thick', '_thick_circle');
+ } else if (image.endsWith('x')) {
+ type = type.replace('_point', '_cross');
+ type = type.replace('_dotted', '_dotted_cross');
+ type = type.replace('_thick', '_thick_cross');
+ } else if (image.endsWith('-') && !image.includes('.') && !image.includes('=')) {
+ type = 'arrow_open'; // Open arrow (no arrowhead)
+ }
+
+ // Determine arrow length based on number of dashes
+ if (image.includes('-')) {
+ const dashCount = (image.match(/-/g) || []).length;
+ if (dashCount >= 6) {
+ length = 'extralong'; // cspell:disable-line
+ } else if (dashCount >= 4) {
+ length = 'long';
+ }
+ }
+
+ const result: any = { type, text: '' };
+ if (length) {
+ result.length = length;
+ }
+ return result;
+ }
+
+ linkWithEdgeText(ctx: any): any {
+ let text = '';
+ if (ctx.edgeText) {
+ text = this.visit(ctx.edgeText);
+ }
+
+ // Get the link type from the START_* token or EdgeTextEnd token
+ let linkData: any = { type: 'arrow', text: '' };
+
+ // Check for bidirectional arrows first
+ if (ctx.START_LINK && ctx.EdgeTextEnd) {
+ const startToken = ctx.START_LINK[0].image;
+ const endToken = ctx.EdgeTextEnd[0].image;
+
+ if (startToken.includes('<') && endToken.includes('>')) {
+ if (startToken.includes('.') || endToken.includes('.')) {
+ linkData = { type: 'double_arrow_dotted', text: '' };
+ } else if (startToken.includes('=') || endToken.includes('=')) {
+ linkData = { type: 'double_arrow_thick', text: '' };
+ } else {
+ linkData = { type: 'double_arrow_point', text: '' };
+ }
+ } else {
+ linkData = { type: 'arrow_point', text: '' };
+
+ // Check for arrow length in START_LINK token
+ const dashCount = (startToken.match(/-/g) || []).length;
+ if (dashCount >= 6) {
+ linkData.length = 'extralong'; // cspell:disable-line
+ } else if (dashCount >= 4) {
+ linkData.length = 'long';
+ }
+ }
+ } else if (ctx.START_DOTTED_LINK) {
+ linkData = { type: 'arrow_dotted', text: '' };
+ } else if (ctx.START_THICK_LINK) {
+ linkData = { type: 'arrow_thick', text: '' };
+ } else if (ctx.START_LINK) {
+ linkData = { type: 'arrow_point', text: '' };
+ } else if (ctx.EdgeTextEnd) {
+ linkData = this.parseLinkToken(ctx.EdgeTextEnd[0]);
+ }
+
+ linkData.text = text;
+ return linkData;
+ }
+
+ linkWithArrowText(ctx: any): any {
+ // Get the link type from the link token
+ let linkData: any = { type: 'arrow', text: '' };
+ if (ctx.LINK) {
+ linkData = this.parseLinkToken(ctx.LINK[0]);
+ } else if (ctx.THICK_LINK) {
+ linkData = this.parseLinkToken(ctx.THICK_LINK[0]);
+ } else if (ctx.DOTTED_LINK) {
+ linkData = this.parseLinkToken(ctx.DOTTED_LINK[0]);
+ }
+
+ // Get the arrow text
+ if (ctx.arrowText) {
+ linkData.text = this.visit(ctx.arrowText);
+ }
+
+ return linkData;
+ }
+
+ edgeText(ctx: any): string {
+ let text = '';
+ if (ctx.EdgeTextContent) {
+ ctx.EdgeTextContent.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ // Note: EdgeTextPipe tokens (|) are delimiters and should not be included in the text
+ if (ctx.NODE_STRING) {
+ ctx.NODE_STRING.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ if (ctx.EDGE_TEXT) {
+ return ctx.EDGE_TEXT[0].image;
+ } else if (ctx.String) {
+ return ctx.String[0].image.slice(1, -1); // Remove quotes
+ } else if (ctx.MarkdownString) {
+ return ctx.MarkdownString[0].image.slice(2, -2); // Remove markdown quotes
+ }
+ return text;
+ }
+
+ linkText(ctx: any): string {
+ let text = '';
+ if (ctx.NodeText) {
+ ctx.NodeText.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ if (ctx.String) {
+ ctx.String.forEach((token: IToken) => {
+ text += token.image.slice(1, -1); // Remove quotes
+ });
+ }
+ if (ctx.MarkdownString) {
+ ctx.MarkdownString.forEach((token: IToken) => {
+ text += token.image.slice(2, -2); // Remove markdown quotes
+ });
+ }
+ return text;
+ }
+
+ arrowText(ctx: any): string {
+ if (ctx.text) {
+ return this.visit(ctx.text);
+ }
+ return '';
+ }
+
+ text(ctx: any): string {
+ let text = '';
+ if (ctx.TextContent) {
+ ctx.TextContent.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ if (ctx.NODE_STRING) {
+ ctx.NODE_STRING.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ if (ctx.NumberToken) {
+ ctx.NumberToken.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ if (ctx.WhiteSpace) {
+ ctx.WhiteSpace.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ if (ctx.Colon) {
+ ctx.Colon.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ if (ctx.Minus) {
+ ctx.Minus.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ if (ctx.Ampersand) {
+ ctx.Ampersand.forEach((token: IToken) => {
+ text += token.image;
+ });
+ }
+ if (ctx.QuotedString) {
+ ctx.QuotedString.forEach((token: IToken) => {
+ // Remove quotes from quoted string
+ text += token.image.slice(1, -1);
+ });
+ }
+ return text;
+ }
+
+ styleStatement(ctx: any): void {
+ const nodeId = this.visit(ctx.nodeId);
+ const styles = this.visit(ctx.styleList);
+
+ if (this.vertices[nodeId]) {
+ // Ensure styles is an array before calling join
+ const styleArray = Array.isArray(styles) ? styles : [styles];
+ this.vertices[nodeId].style = styleArray.join(',');
+ }
+ }
+
+ classDefStatement(ctx: any): void {
+ const className = this.visit(ctx.className);
+ const styles = this.visit(ctx.styleList);
+
+ // Ensure styles is an array before calling join
+ const styleArray = Array.isArray(styles) ? styles : [styles];
+ this.classes[className] = styleArray.join(',');
+ }
+
+ classStatement(ctx: any): void {
+ const nodeIds = this.visit(ctx.nodeIdList);
+ const className = this.visit(ctx.className);
+
+ nodeIds.forEach((nodeId: string) => {
+ if (this.vertices[nodeId]) {
+ this.vertices[nodeId].classes = [className];
+ }
+ });
+ }
+
+ clickStatement(ctx: any): void {
+ const nodeId = this.visit(ctx.nodeId);
+
+ if (ctx.clickHref) {
+ const hrefData = this.visit(ctx.clickHref);
+ this.clickEvents.push({
+ id: nodeId,
+ type: 'href',
+ href: hrefData.href,
+ target: hrefData.target,
+ });
+ } else if (ctx.clickCall) {
+ const callData = this.visit(ctx.clickCall);
+ this.clickEvents.push({
+ id: nodeId,
+ type: 'call',
+ functionName: callData.functionName,
+ args: callData.args,
+ });
+ }
+
+ // Handle tooltip
+ if (ctx.String) {
+ const tooltip = ctx.String[0].image.slice(1, -1);
+ this.tooltips[nodeId] = tooltip;
+ }
+ }
+
+ subgraphStatement(ctx: any): void {
+ const subgraph: any = {
+ id: `subGraph${this.subCount++}`,
+ title: '',
+ nodes: [],
+ };
+
+ if (ctx.subgraphId) {
+ subgraph.id = this.visit(ctx.subgraphId);
+ }
+
+ if (ctx.nodeText) {
+ subgraph.title = this.visit(ctx.nodeText);
+ } else if (ctx.QuotedString) {
+ subgraph.title = ctx.QuotedString[0].image.slice(1, -1); // Remove quotes
+ }
+
+ // Store current state
+ const prevVertices = this.vertices;
+
+ // Process subgraph statements
+ if (ctx.statement) {
+ ctx.statement.forEach((stmt: any) => this.visit(stmt));
+ }
+
+ // Collect nodes added in subgraph
+ Object.keys(this.vertices).forEach((key) => {
+ if (!prevVertices[key]) {
+ subgraph.nodes.push(key);
+ }
+ });
+
+ this.subGraphs.push(subgraph);
+ }
+
+ directionStatement(ctx: any): void {
+ this.direction = ctx.DirectionValue[0].image;
+ }
+
+ // Helper methods for remaining rules...
+ className(ctx: any): string {
+ return ctx.NODE_STRING[0].image;
+ }
+
+ nodeIdList(ctx: any): string[] {
+ const ids = [this.visit(ctx.nodeId[0])];
+ if (ctx.nodeId.length > 1) {
+ ctx.nodeId.slice(1).forEach((node: any) => {
+ ids.push(this.visit(node));
+ });
+ }
+ return ids;
+ }
+
+ styleList(ctx: any): string[] {
+ const styles: string[] = [];
+ if (ctx.style) {
+ ctx.style.forEach((style: any) => {
+ styles.push(this.visit(style));
+ });
+ }
+ return styles;
+ }
+
+ style(ctx: any): string {
+ // Collect all tokens with their positions, excluding semicolons
+ const allTokens: IToken[] = [];
+
+ Object.keys(ctx).forEach((key) => {
+ if (ctx[key] && Array.isArray(ctx[key]) && key !== 'Semicolon') {
+ allTokens.push(...ctx[key]);
+ }
+ });
+
+ // Sort tokens by their position in the input
+ allTokens.sort((a, b) => a.startOffset - b.startOffset);
+
+ // Concatenate tokens in order
+ return allTokens.map((token) => token.image).join('');
+ }
+
+ standaloneLinkStatement(ctx: any): void {
+ const startNodeId = this.visit(ctx.nodeId[0]);
+ const endNodeId = this.visit(ctx.nodeId[1]);
+ const linkData = this.visit(ctx.link);
+
+ const edge: any = {
+ start: startNodeId,
+ end: endNodeId,
+ type: linkData.type,
+ text: linkData.text,
+ };
+
+ // Include length property if present
+ if (linkData.length) {
+ edge.length = linkData.length;
+ }
+
+ this.edges.push(edge);
+ }
+
+ // Missing visitor methods
+ linkStyleStatement(_ctx: any): void {
+ // Handle link style statements
+ // TODO: Implement link styling
+ }
+
+ linkIndexList(_ctx: any): number[] {
+ // Handle link index lists
+ // TODO: Implement link index parsing
+ return [];
+ }
+
+ numberList(_ctx: any): number[] {
+ // Handle number lists
+ // TODO: Implement number list parsing
+ return [];
+ }
+
+ accStatement(ctx: any): void {
+ if (ctx.AccTitle && ctx.AccTitleValue) {
+ this.accTitle = ctx.AccTitleValue[0].image.trim();
+ } else if (ctx.AccDescr && ctx.AccDescrValue) {
+ this.accDescription = ctx.AccDescrValue[0].image.trim();
+ } else if (ctx.AccDescrMultiline && ctx.AccDescrMultilineValue) {
+ this.accDescription = ctx.AccDescrMultilineValue[0].image.trim();
+ }
+ }
+
+ clickHref(ctx: any): any {
+ let href = '';
+ if (ctx.NODE_STRING) {
+ href = ctx.NODE_STRING[0].image;
+ } else if (ctx.QuotedString) {
+ href = ctx.QuotedString[0].image.slice(1, -1); // Remove quotes
+ }
+ return {
+ href: href,
+ target: undefined,
+ };
+ }
+
+ clickCall(ctx: any): any {
+ let functionName = '';
+
+ if (ctx.Call) {
+ if (ctx.NODE_STRING) {
+ functionName = ctx.NODE_STRING[0].image;
+ } else if (ctx.QuotedString) {
+ functionName = ctx.QuotedString[0].image.slice(1, -1); // Remove quotes
+ }
+ return {
+ functionName: functionName,
+ args: [], // TODO: Parse arguments if present
+ };
+ } else if (ctx.Callback) {
+ if (ctx.NODE_STRING) {
+ functionName = ctx.NODE_STRING[0].image;
+ } else if (ctx.QuotedString) {
+ functionName = ctx.QuotedString[0].image.slice(1, -1); // Remove quotes
+ } else if (ctx.StringStart && ctx.StringContent && ctx.StringEnd) {
+ functionName = ctx.StringContent[0].image; // String content without quotes
+ }
+ return {
+ functionName: functionName,
+ args: [],
+ };
+ }
+ return {
+ functionName: '',
+ args: [],
+ };
+ }
+
+ subgraphId(ctx: any): string {
+ if (ctx.NODE_STRING) {
+ return ctx.NODE_STRING[0].image;
+ } else if (ctx.QuotedString) {
+ return ctx.QuotedString[0].image.slice(1, -1); // Remove quotes
+ } else if (ctx.StringStart && ctx.StringContent && ctx.StringEnd) {
+ return ctx.StringContent[0].image; // String content without quotes
+ }
+ return '';
+ }
+
+ // Return the complete AST
+ getAST() {
+ return {
+ vertices: this.vertices,
+ edges: this.edges,
+ classes: this.classes,
+ subGraphs: this.subGraphs,
+ direction: this.direction,
+ clickEvents: this.clickEvents,
+ tooltips: this.tooltips,
+ accTitle: this.accTitle,
+ accDescription: this.accDescription,
+ };
+ }
+}
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flowLexer.ts b/packages/mermaid/src/diagrams/flowchart/parser/flowLexer.ts
new file mode 100644
index 000000000..9d5ecd93b
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flowLexer.ts
@@ -0,0 +1,855 @@
+import { createToken, Lexer } from 'chevrotain';
+
+// Debug flag for lexer logging
+const DEBUG_LEXER = false; // Set to true to enable debug logging
+
+// ============================================================================
+// JISON TO CHEVROTAIN MULTI-MODE LEXER IMPLEMENTATION
+// Following the instructions to implement all Jison states as Chevrotain modes
+// Based on flow.jison lines 9-28 and state transitions throughout the file
+// ============================================================================
+
+// ============================================================================
+// SHARED TOKENS (used across multiple modes)
+// ============================================================================
+
+// Whitespace and comments (skipped in all modes)
+const WhiteSpace = createToken({
+ name: 'WhiteSpace',
+ pattern: /[\t ]+/, // Only spaces and tabs, not newlines
+ group: Lexer.SKIPPED,
+});
+
+const Comment = createToken({
+ name: 'Comment',
+ pattern: /%%[^\n]*/,
+ group: Lexer.SKIPPED,
+});
+
+// Basic structural tokens
+const Newline = createToken({
+ name: 'Newline',
+ pattern: /(\r?\n)+/,
+});
+
+const Semicolon = createToken({
+ name: 'Semicolon',
+ pattern: /;/,
+});
+
+const Space = createToken({
+ name: 'Space',
+ pattern: /\s/,
+});
+
+const EOF = createToken({
+ name: 'EOF',
+ pattern: Lexer.NA,
+});
+
+// ============================================================================
+// NODE STRING AND IDENTIFIERS
+// ============================================================================
+
+// Node string pattern from JISON line 205-207
+// Modified to include special characters and handle minus character edge cases
+// Allows - in node IDs including standalone -, -at-start, and -at-end patterns
+// Avoids conflicts with link tokens by using negative lookahead for link patterns
+// Handles compound cases like &node, -node, vnode where special chars are followed by word chars
+// Only matches compound patterns (special char + word chars), not standalone special chars
+const NODE_STRING = createToken({
+ name: 'NODE_STRING',
+ pattern:
+ /\\\w+|\w+\\|&[\w!"#$%&'*+,./:?\\`]+[\w!"#$%&'*+,./:?\\`-]*|-[\w!"#$%&'*+,./:?\\`]+[\w!"#$%&'*+,./:?\\`-]*|[<>^v][\w!"#$%&'*+,./:?\\`]+[\w!"#$%&'*+,./:?\\`-]*|[\w!"#$%&'*+,./:?\\`](?:[\w!"#$%&'*+,./:?\\`]|-(?![.=-])|\.(?!-))*[\w!"#$%&'*+,./:?\\`]|[\w!"#$%&'*+,./:?\\`]|&|-|\\|\//,
+});
+
+// ============================================================================
+// KEYWORDS (with longer_alt to handle conflicts)
+// ============================================================================
+
+const Graph = createToken({
+ name: 'Graph',
+ pattern: /graph|flowchart|flowchart-elk/i,
+ longer_alt: NODE_STRING,
+});
+
+const Subgraph = createToken({
+ name: 'Subgraph',
+ pattern: /subgraph/i,
+ longer_alt: NODE_STRING,
+});
+
+const End = createToken({
+ name: 'End',
+ pattern: /end/i,
+ longer_alt: NODE_STRING,
+});
+
+const Style = createToken({
+ name: 'Style',
+ pattern: /style/i,
+ longer_alt: NODE_STRING,
+});
+
+const LinkStyle = createToken({
+ name: 'LinkStyle',
+ pattern: /linkstyle/i,
+ longer_alt: NODE_STRING,
+});
+
+const ClassDef = createToken({
+ name: 'ClassDef',
+ pattern: /classdef/i,
+ longer_alt: NODE_STRING,
+});
+
+const Class = createToken({
+ name: 'Class',
+ pattern: /class/i,
+ longer_alt: NODE_STRING,
+});
+
+const Click = createToken({
+ name: 'Click',
+ pattern: /click/i,
+ longer_alt: NODE_STRING,
+});
+
+const Href = createToken({
+ name: 'Href',
+ pattern: /href/i,
+ longer_alt: NODE_STRING,
+});
+
+const Callback = createToken({
+ name: 'Callback',
+ pattern: /callback/i,
+ longer_alt: NODE_STRING,
+});
+
+const Call = createToken({
+ name: 'Call',
+ pattern: /call/i,
+ longer_alt: NODE_STRING,
+});
+
+const Default = createToken({
+ name: 'Default',
+ pattern: /default/i,
+ longer_alt: NODE_STRING,
+});
+
+// ============================================================================
+// DIRECTION TOKENS (JISON lines 127-137)
+// ============================================================================
+
+const DirectionValue = createToken({
+ name: 'DirectionValue',
+ pattern: /LR|RL|TB|BT|TD|BR|<|>|\^|v/,
+});
+
+// ============================================================================
+// ACCESSIBILITY TOKENS (JISON lines 31-37)
+// ============================================================================
+
+// Mode-switching tokens for accessibility
+const AccTitle = createToken({
+ name: 'AccTitle',
+ pattern: /accTitle\s*:\s*/,
+ push_mode: 'acc_title_mode',
+});
+
+const AccDescr = createToken({
+ name: 'AccDescr',
+ pattern: /accDescr\s*:\s*/,
+ push_mode: 'acc_descr_mode',
+});
+
+const AccDescrMultiline = createToken({
+ name: 'AccDescrMultiline',
+ pattern: /accDescr\s*{\s*/,
+ push_mode: 'acc_descr_multiline_mode',
+});
+
+// ============================================================================
+// STRING TOKENS (JISON lines 82-87)
+// ============================================================================
+
+// Mode-switching tokens for strings
+const StringStart = createToken({
+ name: 'StringStart',
+ pattern: /"/,
+ push_mode: 'string_mode',
+});
+
+const MarkdownStringStart = createToken({
+ name: 'MarkdownStringStart',
+ pattern: /"`/,
+ push_mode: 'md_string_mode',
+});
+
+// ============================================================================
+// SHAPE DATA TOKENS (JISON lines 41-64)
+// ============================================================================
+
+const ShapeDataStart = createToken({
+ name: 'ShapeDataStart',
+ pattern: /@{/,
+ push_mode: 'shapeData_mode',
+});
+
+// ============================================================================
+// LINK TOKENS (JISON lines 154-164)
+// ============================================================================
+
+const LINK = createToken({
+ name: 'LINK',
+ pattern: /\s*[ox-]\s*/,
+});
+
+const START_LINK = createToken({
+ name: 'START_LINK',
+ pattern: /\s*[ox-]?\s*/,
+});
+
+const START_THICK_LINK = createToken({
+ name: 'START_THICK_LINK',
+ pattern: /\s*[ox-]?\s*/,
+});
+
+const START_DOTTED_LINK = createToken({
+ name: 'START_DOTTED_LINK',
+ pattern: /\s*[ox-])|(=+[=>ox])/,
+ pop_mode: true,
+});
+
+// Tokens for shapeData mode (JISON lines 57-64)
+const ShapeDataContent = createToken({
+ name: 'ShapeDataContent',
+ pattern: /[^"}]+/,
+});
+
+const ShapeDataStringStart = createToken({
+ name: 'ShapeDataStringStart',
+ pattern: /"/,
+ push_mode: 'shapeDataStr_mode',
+});
+
+const ShapeDataEnd = createToken({
+ name: 'ShapeDataEnd',
+ pattern: /}/,
+ pop_mode: true,
+});
+
+// Tokens for shapeDataStr mode (JISON lines 49-56)
+const ShapeDataStringContent = createToken({
+ name: 'ShapeDataStringContent',
+ pattern: /[^"]+/,
+});
+
+const ShapeDataStringEnd = createToken({
+ name: 'ShapeDataStringEnd',
+ pattern: /"/,
+ pop_mode: true,
+});
+
+// ============================================================================
+// MULTI-MODE LEXER DEFINITION
+// Following JISON states exactly
+// ============================================================================
+
+const multiModeLexerDefinition = {
+ modes: {
+ // INITIAL mode - equivalent to JISON default state
+ initial_mode: [
+ WhiteSpace,
+ Comment,
+
+ // Accessibility tokens (must come before other patterns)
+ AccTitle,
+ AccDescr,
+ AccDescrMultiline,
+
+ // Keywords (must come before NODE_STRING)
+ Graph,
+ Subgraph,
+ End,
+ Style,
+ LinkStyle,
+ ClassDef,
+ Class,
+ Click,
+ Href,
+ Callback,
+ Call,
+ Default,
+
+ // Links (order matters for precedence - must come before DirectionValue)
+ START_THICK_LINK,
+ THICK_LINK,
+ START_DOTTED_LINK,
+ DOTTED_LINK,
+ LINK,
+ START_LINK,
+
+ // Direction values (must come after LINK tokens)
+ DirectionValue,
+
+ // String starts (QuotedString must come before StringStart to avoid conflicts)
+ MarkdownStringStart,
+ QuotedString,
+ StringStart,
+
+ // Shape data
+ ShapeDataStart,
+
+ // Shape starts (order matters - longer patterns first)
+ SquareStart,
+ DoubleCircleStart,
+ CircleStart,
+ PS,
+ HexagonStart,
+ DiamondStart,
+
+ // Basic punctuation (must come before NODE_STRING)
+ Pipe,
+ Colon,
+ Comma,
+ Ampersand,
+ Minus,
+
+ // Node strings and numbers (must come after punctuation)
+ NODE_STRING,
+ NumberToken,
+
+ // Structural tokens
+ Newline,
+ Semicolon,
+ Space,
+ EOF,
+ ],
+
+ // acc_title mode (JISON line 32)
+ acc_title_mode: [WhiteSpace, Comment, AccTitleValue],
+
+ // acc_descr mode (JISON line 34)
+ acc_descr_mode: [WhiteSpace, Comment, AccDescrValue],
+
+ // acc_descr_multiline mode (JISON lines 36-37)
+ acc_descr_multiline_mode: [WhiteSpace, Comment, AccDescrMultilineEnd, AccDescrMultilineValue],
+
+ // string mode (JISON lines 85-86)
+ string_mode: [StringEnd, StringContent],
+
+ // md_string mode (JISON lines 82-83)
+ md_string_mode: [MarkdownStringEnd, MarkdownStringContent],
+
+ // text mode (JISON lines 272-283)
+ text_mode: [
+ WhiteSpace,
+ Comment,
+ SquareEnd,
+ DoubleCircleEnd,
+ CircleEnd,
+ PE,
+ HexagonEnd,
+ DiamondEnd,
+ QuotedString,
+ Pipe, // Special handling for pipe in text mode
+ TextContent,
+ ],
+
+ // edgeText mode (JISON line 156)
+ edgeText_mode: [WhiteSpace, Comment, EdgeTextEnd, EdgeTextPipe, QuotedString, EdgeTextContent],
+
+ // thickEdgeText mode (JISON line 160)
+ thickEdgeText_mode: [
+ WhiteSpace,
+ Comment,
+ EdgeTextEnd,
+ EdgeTextPipe,
+ QuotedString,
+ EdgeTextContent,
+ ],
+
+ // dottedEdgeText mode (JISON line 164)
+ dottedEdgeText_mode: [
+ WhiteSpace,
+ Comment,
+ EdgeTextEnd,
+ EdgeTextPipe,
+ QuotedString,
+ EdgeTextContent,
+ ],
+
+ // shapeData mode (JISON lines 57-64)
+ shapeData_mode: [WhiteSpace, Comment, ShapeDataEnd, ShapeDataStringStart, ShapeDataContent],
+
+ // shapeDataStr mode (JISON lines 49-56)
+ shapeDataStr_mode: [ShapeDataStringEnd, ShapeDataStringContent],
+ },
+
+ defaultMode: 'initial_mode',
+};
+
+const FlowchartLexer = new Lexer(multiModeLexerDefinition);
+
+// Debug wrapper for lexer tokenization
+const tokenizeWithDebug = (input: string) => {
+ const lexResult = FlowchartLexer.tokenize(input);
+
+ if (DEBUG_LEXER) {
+ // eslint-disable-next-line no-console
+ console.debug('Errors:\n', lexResult.errors);
+ // eslint-disable-next-line no-console
+ console.debug(
+ 'Tokens:\n',
+ lexResult.tokens.map((t) => [t.image, t.tokenType.name])
+ );
+ }
+
+ return lexResult;
+};
+
+// Extend FlowchartLexer with debug capability
+const FlowchartLexerWithDebug = {
+ ...FlowchartLexer,
+ tokenize: tokenizeWithDebug,
+};
+
+// Export all tokens for parser use
+export const allTokens = [
+ // Basic tokens
+ WhiteSpace,
+ Comment,
+ Newline,
+ Semicolon,
+ Space,
+ EOF,
+
+ // Node strings and identifiers
+ NODE_STRING,
+ NumberToken,
+
+ // Keywords
+ Graph,
+ Subgraph,
+ End,
+ Style,
+ LinkStyle,
+ ClassDef,
+ Class,
+ Click,
+ Href,
+ Call,
+ Default,
+
+ // Direction
+ DirectionValue,
+
+ // Accessibility
+ AccTitle,
+ AccTitleValue,
+ AccDescr,
+ AccDescrValue,
+ AccDescrMultiline,
+ AccDescrMultilineValue,
+ AccDescrMultilineEnd,
+
+ // Strings
+ StringStart,
+ StringContent,
+ StringEnd,
+ MarkdownStringStart,
+ MarkdownStringContent,
+ MarkdownStringEnd,
+
+ // Shape data
+ ShapeDataStart,
+ ShapeDataContent,
+ ShapeDataStringStart,
+ ShapeDataStringContent,
+ ShapeDataStringEnd,
+ ShapeDataEnd,
+
+ // Links
+ LINK,
+ START_LINK,
+ THICK_LINK,
+ START_THICK_LINK,
+ DOTTED_LINK,
+ START_DOTTED_LINK,
+
+ // Edge text
+ EdgeTextContent,
+ EdgeTextPipe,
+ EdgeTextEnd,
+
+ // Shapes
+ SquareStart,
+ SquareEnd,
+ DoubleCircleStart,
+ DoubleCircleEnd,
+ CircleStart,
+ CircleEnd,
+ PS,
+ PE,
+ HexagonStart,
+ HexagonEnd,
+ DiamondStart,
+ DiamondEnd,
+
+ // Text content
+ TextContent,
+ QuotedString,
+
+ // Basic punctuation
+ Colon,
+ Comma,
+ Pipe,
+ Ampersand,
+ Minus,
+];
+
+export { FlowchartLexerWithDebug as FlowchartLexer };
+
+// Export individual tokens for parser use
+export {
+ // Basic tokens
+ WhiteSpace,
+ Comment,
+ Newline,
+ Semicolon,
+ Space,
+ EOF,
+
+ // Node strings and identifiers
+ NODE_STRING,
+ NumberToken,
+
+ // Keywords
+ Graph,
+ Subgraph,
+ End,
+ Style,
+ LinkStyle,
+ ClassDef,
+ Class,
+ Click,
+ Href,
+ Callback,
+ Call,
+ Default,
+
+ // Direction
+ DirectionValue,
+
+ // Accessibility
+ AccTitle,
+ AccTitleValue,
+ AccDescr,
+ AccDescrValue,
+ AccDescrMultiline,
+ AccDescrMultilineValue,
+ AccDescrMultilineEnd,
+
+ // Strings
+ StringStart,
+ StringContent,
+ StringEnd,
+ MarkdownStringStart,
+ MarkdownStringContent,
+ MarkdownStringEnd,
+
+ // Shape data
+ ShapeDataStart,
+ ShapeDataContent,
+ ShapeDataStringStart,
+ ShapeDataStringContent,
+ ShapeDataStringEnd,
+ ShapeDataEnd,
+
+ // Links
+ LINK,
+ START_LINK,
+ THICK_LINK,
+ START_THICK_LINK,
+ DOTTED_LINK,
+ START_DOTTED_LINK,
+
+ // Edge text
+ EdgeTextContent,
+ EdgeTextPipe,
+ EdgeTextEnd,
+
+ // Shapes
+ SquareStart,
+ SquareEnd,
+ DoubleCircleStart,
+ DoubleCircleEnd,
+ CircleStart,
+ CircleEnd,
+ PS,
+ PE,
+ HexagonStart,
+ HexagonEnd,
+ DiamondStart,
+ DiamondEnd,
+
+ // Text content
+ TextContent,
+ QuotedString,
+
+ // Basic punctuation
+ Colon,
+ Comma,
+ Pipe,
+ Ampersand,
+ Minus,
+};
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flowLexerMultiMode.ts b/packages/mermaid/src/diagrams/flowchart/parser/flowLexerMultiMode.ts
new file mode 100644
index 000000000..26d96ffb8
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flowLexerMultiMode.ts
@@ -0,0 +1,277 @@
+import { createToken, Lexer } from 'chevrotain';
+
+// Define lexer mode names following JISON states
+const MODES = {
+ DEFAULT: 'default_mode',
+ STRING: 'string_mode',
+ MD_STRING: 'md_string_mode',
+ ACC_TITLE: 'acc_title_mode',
+ ACC_DESCR: 'acc_descr_mode',
+ ACC_DESCR_MULTILINE: 'acc_descr_multiline_mode',
+ DIR: 'dir_mode',
+ VERTEX: 'vertex_mode',
+ TEXT: 'text_mode',
+ ELLIPSE_TEXT: 'ellipseText_mode',
+ TRAP_TEXT: 'trapText_mode',
+ EDGE_TEXT: 'edgeText_mode',
+ THICK_EDGE_TEXT: 'thickEdgeText_mode',
+ DOTTED_EDGE_TEXT: 'dottedEdgeText_mode',
+ CLICK: 'click_mode',
+ HREF: 'href_mode',
+ CALLBACK_NAME: 'callbackname_mode',
+ CALLBACK_ARGS: 'callbackargs_mode',
+ SHAPE_DATA: 'shapeData_mode',
+ SHAPE_DATA_STR: 'shapeDataStr_mode',
+ SHAPE_DATA_END_BRACKET: 'shapeDataEndBracket_mode',
+};
+
+// Whitespace and comments (skipped in all modes)
+const WhiteSpace = createToken({
+ name: 'WhiteSpace',
+ pattern: /\s+/,
+ group: Lexer.SKIPPED,
+});
+
+const Comment = createToken({
+ name: 'Comment',
+ pattern: /%%[^\n]*/,
+ group: Lexer.SKIPPED,
+});
+
+// Keywords - following JISON patterns exactly
+const Graph = createToken({
+ name: 'Graph',
+ pattern: /graph|flowchart|flowchart-elk/i,
+});
+
+const Direction = createToken({
+ name: 'Direction',
+ pattern: /direction/i,
+});
+
+const Subgraph = createToken({
+ name: 'Subgraph',
+ pattern: /subgraph/i,
+});
+
+const End = createToken({
+ name: 'End',
+ pattern: /end/i,
+});
+
+// Mode switching tokens - following JISON patterns exactly
+
+// Links with edge text - following JISON lines 154-164
+const LINK = createToken({
+ name: 'LINK',
+ pattern: /\s*[ox-]\s*/,
+});
+
+const START_LINK = createToken({
+ name: 'START_LINK',
+ pattern: /\s*[ox]\s*/,
+});
+
+const START_THICK_LINK = createToken({
+ name: 'START_THICK_LINK',
+ pattern: /\s*[ox]?\s*/,
+});
+
+const START_DOTTED_LINK = createToken({
+ name: 'START_DOTTED_LINK',
+ pattern: /\s*[ {
- // remove the trailing whitespace after closing curly braces when ending a line break
- const newSrc = src.replace(/}\s*\n/g, '}\n');
- return flowJisonParser.parse(newSrc);
-};
+ this.performSelfAnalysis();
+ }
-export default newParser;
+ // Root rule
+ public flowchart = this.RULE('flowchart', () => {
+ this.SUBRULE(this.graphDeclaration);
+ // Handle statements and separators more flexibly
+ this.MANY(() => {
+ this.SUBRULE(this.statement);
+ // Optional separator after statement
+ this.OPTION(() => {
+ this.SUBRULE(this.statementSeparator);
+ });
+ });
+ });
+
+ // Graph declaration
+ private graphDeclaration = this.RULE('graphDeclaration', () => {
+ this.CONSUME(tokens.Graph);
+ this.OPTION(() => {
+ this.CONSUME(tokens.DirectionValue);
+ });
+ this.OPTION2(() => {
+ this.SUBRULE(this.statementSeparator);
+ });
+ });
+
+ // Statement separator
+ private statementSeparator = this.RULE('statementSeparator', () => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.Newline) },
+ { ALT: () => this.CONSUME(tokens.Semicolon) },
+ { ALT: () => this.CONSUME(tokens.WhiteSpace) }, // Allow whitespace as separator
+ ]);
+ // Allow trailing whitespace and newlines after separators
+ this.MANY(() => {
+ this.OR2([
+ { ALT: () => this.CONSUME2(tokens.WhiteSpace) },
+ { ALT: () => this.CONSUME2(tokens.Newline) },
+ ]);
+ });
+ });
+
+ // Statement - following JISON structure
+ private statement = this.RULE('statement', () => {
+ this.OR([
+ { ALT: () => this.SUBRULE(this.vertexStatement) },
+ { ALT: () => this.SUBRULE(this.styleStatement) },
+ { ALT: () => this.SUBRULE(this.linkStyleStatement) },
+ { ALT: () => this.SUBRULE(this.classDefStatement) },
+ { ALT: () => this.SUBRULE(this.classStatement) },
+ { ALT: () => this.SUBRULE(this.clickStatement) },
+ { ALT: () => this.SUBRULE(this.subgraphStatement) },
+ // Direction statement only when DirectionValue is followed by separator
+ {
+ ALT: () => this.SUBRULE(this.directionStatement),
+ GATE: () =>
+ this.LA(1).tokenType === tokens.DirectionValue &&
+ (this.LA(2).tokenType === tokens.Semicolon ||
+ this.LA(2).tokenType === tokens.Newline ||
+ this.LA(2).tokenType === tokens.WhiteSpace ||
+ this.LA(2) === undefined), // EOF
+ },
+ { ALT: () => this.SUBRULE(this.accStatement) }, // Re-enabled
+ ]);
+ });
+
+ // Vertex statement - avoiding left recursion
+ private vertexStatement = this.RULE('vertexStatement', () => {
+ this.SUBRULE(this.node);
+ this.MANY(() => {
+ this.SUBRULE(this.link);
+ this.SUBRULE2(this.node);
+ });
+ });
+
+ // Node - avoiding left recursion
+ private node = this.RULE('node', () => {
+ this.SUBRULE(this.styledVertex);
+ this.MANY(() => {
+ this.CONSUME(tokens.Ampersand);
+ this.SUBRULE2(this.styledVertex);
+ });
+ });
+
+ // Styled vertex
+ private styledVertex = this.RULE('styledVertex', () => {
+ this.SUBRULE(this.vertex);
+ // TODO: Add style separator support when implementing styling
+ });
+
+ // Vertex - following JISON pattern
+ private vertex = this.RULE('vertex', () => {
+ this.OR([
+ // idString SQS text SQE
+ {
+ ALT: () => {
+ this.SUBRULE(this.nodeId);
+ this.CONSUME(tokens.SquareStart);
+ this.SUBRULE(this.nodeText);
+ this.CONSUME(tokens.SquareEnd);
+ },
+ },
+ // idString DoubleCircleStart text DoubleCircleEnd
+ {
+ ALT: () => {
+ this.SUBRULE2(this.nodeId);
+ this.CONSUME(tokens.DoubleCircleStart);
+ this.SUBRULE2(this.nodeText);
+ this.CONSUME(tokens.DoubleCircleEnd);
+ },
+ },
+ // idString CircleStart text CircleEnd
+ {
+ ALT: () => {
+ this.SUBRULE3(this.nodeId);
+ this.CONSUME(tokens.CircleStart);
+ this.SUBRULE3(this.nodeText);
+ this.CONSUME(tokens.CircleEnd);
+ },
+ },
+ // idString PS text PE
+ {
+ ALT: () => {
+ this.SUBRULE4(this.nodeId);
+ this.CONSUME(tokens.PS);
+ this.SUBRULE4(this.nodeText);
+ this.CONSUME(tokens.PE);
+ },
+ },
+ // idString HexagonStart text HexagonEnd
+ {
+ ALT: () => {
+ this.SUBRULE5(this.nodeId);
+ this.CONSUME(tokens.HexagonStart);
+ this.SUBRULE5(this.nodeText);
+ this.CONSUME(tokens.HexagonEnd);
+ },
+ },
+ // idString DIAMOND_START text DIAMOND_STOP
+ {
+ ALT: () => {
+ this.SUBRULE6(this.nodeId);
+ this.CONSUME(tokens.DiamondStart);
+ this.SUBRULE6(this.nodeText);
+ this.CONSUME(tokens.DiamondEnd);
+ },
+ },
+ // idString (plain node)
+ { ALT: () => this.SUBRULE7(this.nodeId) },
+ ]);
+ });
+
+ // Node definition (legacy)
+ private nodeDefinition = this.RULE('nodeDefinition', () => {
+ this.SUBRULE(this.nodeId);
+ this.OPTION(() => {
+ this.SUBRULE(this.nodeShape);
+ });
+ // TODO: Add style separator support when implementing styling
+ });
+
+ // Node ID - handles both simple and compound node IDs
+ private nodeId = this.RULE('nodeId', () => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME(tokens.NumberToken) },
+
+ // Allow special characters as standalone node IDs (matching JISON parser behavior)
+ { ALT: () => this.CONSUME2(tokens.Ampersand) },
+ { ALT: () => this.CONSUME2(tokens.Minus) },
+ { ALT: () => this.CONSUME2(tokens.DirectionValue) },
+ { ALT: () => this.CONSUME(tokens.Colon) },
+ { ALT: () => this.CONSUME(tokens.Comma) },
+ // Only allow 'default' as node ID when not followed by statement patterns
+ {
+ ALT: () => this.CONSUME(tokens.Default),
+ GATE: () => this.LA(2).tokenType !== tokens.DirectionValue,
+ },
+ ]);
+ });
+
+ // Node shape
+ private nodeShape = this.RULE('nodeShape', () => {
+ this.OR([
+ { ALT: () => this.SUBRULE(this.squareShape) },
+ { ALT: () => this.SUBRULE(this.circleShape) },
+ { ALT: () => this.SUBRULE(this.diamondShape) },
+ ]);
+ });
+
+ // Shape definitions
+ private squareShape = this.RULE('squareShape', () => {
+ this.CONSUME(tokens.SquareStart);
+ this.SUBRULE(this.nodeText);
+ this.CONSUME(tokens.SquareEnd);
+ });
+
+ private circleShape = this.RULE('circleShape', () => {
+ this.CONSUME(tokens.PS);
+ this.SUBRULE(this.nodeText);
+ this.CONSUME(tokens.PE);
+ });
+
+ private diamondShape = this.RULE('diamondShape', () => {
+ this.CONSUME(tokens.DiamondStart);
+ this.SUBRULE(this.nodeText);
+ this.CONSUME(tokens.DiamondEnd);
+ });
+
+ // Node text
+ private nodeText = this.RULE('nodeText', () => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.TextContent) },
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME(tokens.QuotedString) },
+ { ALT: () => this.CONSUME(tokens.NumberToken) },
+ ]);
+ });
+
+ // Link chain
+ private linkChain = this.RULE('linkChain', () => {
+ this.AT_LEAST_ONE(() => {
+ this.SUBRULE(this.link);
+ this.SUBRULE(this.nodeDefinition);
+ });
+ });
+
+ // Link - following JISON structure
+ private link = this.RULE('link', () => {
+ this.OR([
+ { ALT: () => this.SUBRULE(this.linkWithEdgeText) },
+ { ALT: () => this.SUBRULE(this.linkWithArrowText) },
+ { ALT: () => this.SUBRULE(this.linkStatement) },
+ ]);
+ });
+
+ // Link with arrow text - LINK arrowText
+ private linkWithArrowText = this.RULE('linkWithArrowText', () => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.LINK) },
+ { ALT: () => this.CONSUME(tokens.THICK_LINK) },
+ { ALT: () => this.CONSUME(tokens.DOTTED_LINK) },
+ ]);
+ this.SUBRULE(this.arrowText);
+ });
+
+ // Link statement
+ private linkStatement = this.RULE('linkStatement', () => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.LINK) },
+ { ALT: () => this.CONSUME(tokens.THICK_LINK) },
+ { ALT: () => this.CONSUME(tokens.DOTTED_LINK) },
+ ]);
+ });
+
+ // Link with edge text - START_LINK/START_DOTTED_LINK/START_THICK_LINK edgeText EdgeTextEnd
+ private linkWithEdgeText = this.RULE('linkWithEdgeText', () => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.START_LINK) },
+ { ALT: () => this.CONSUME(tokens.START_DOTTED_LINK) },
+ { ALT: () => this.CONSUME(tokens.START_THICK_LINK) },
+ ]);
+ this.SUBRULE(this.edgeText);
+ this.CONSUME(tokens.EdgeTextEnd);
+ });
+
+ // Edge text
+ private edgeText = this.RULE('edgeText', () => {
+ this.MANY(() => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.EdgeTextContent) },
+ { ALT: () => this.CONSUME(tokens.EdgeTextPipe) },
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME(tokens.QuotedString) },
+ ]);
+ });
+ });
+
+ // Arrow text - PIPE text PIPE
+ private arrowText = this.RULE('arrowText', () => {
+ this.CONSUME(tokens.Pipe);
+ this.SUBRULE(this.text);
+ this.CONSUME2(tokens.Pipe);
+ });
+
+ // Text rule - following JISON pattern
+ private text = this.RULE('text', () => {
+ this.AT_LEAST_ONE(() => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.TextContent) },
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME(tokens.NumberToken) },
+ { ALT: () => this.CONSUME(tokens.WhiteSpace) },
+ { ALT: () => this.CONSUME(tokens.Colon) },
+ { ALT: () => this.CONSUME(tokens.Minus) },
+ { ALT: () => this.CONSUME(tokens.Ampersand) },
+ { ALT: () => this.CONSUME(tokens.QuotedString) },
+ ]);
+ });
+ });
+
+ // Link text
+ private linkText = this.RULE('linkText', () => {
+ this.AT_LEAST_ONE(() => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.TextContent) },
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ ]);
+ });
+ });
+
+ // Style statement
+ private styleStatement = this.RULE('styleStatement', () => {
+ this.CONSUME(tokens.Style);
+ this.SUBRULE(this.nodeId);
+ this.SUBRULE(this.styleList);
+ this.SUBRULE(this.statementSeparator);
+ });
+
+ // Link style statement
+ private linkStyleStatement = this.RULE('linkStyleStatement', () => {
+ this.CONSUME(tokens.LinkStyle);
+ this.SUBRULE(this.linkIndexList);
+ this.SUBRULE(this.styleList);
+ this.SUBRULE(this.statementSeparator);
+ });
+
+ // Class definition statement
+ private classDefStatement = this.RULE('classDefStatement', () => {
+ this.CONSUME(tokens.ClassDef);
+ this.SUBRULE(this.className);
+ this.SUBRULE(this.styleList);
+ this.SUBRULE(this.statementSeparator);
+ });
+
+ // Class statement
+ private classStatement = this.RULE('classStatement', () => {
+ this.CONSUME(tokens.Class);
+ this.SUBRULE(this.nodeIdList);
+ this.SUBRULE(this.className);
+ this.SUBRULE(this.statementSeparator);
+ });
+
+ // Click statement
+ private clickStatement = this.RULE('clickStatement', () => {
+ this.CONSUME(tokens.Click);
+ this.SUBRULE(this.nodeId);
+ this.OR([
+ { ALT: () => this.SUBRULE(this.clickHref) },
+ { ALT: () => this.SUBRULE(this.clickCall) },
+ ]);
+ this.OPTION(() => {
+ this.OR2([
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME(tokens.QuotedString) },
+ ]);
+ });
+ this.OPTION2(() => {
+ this.SUBRULE(this.statementSeparator);
+ });
+ });
+
+ // Click href
+ private clickHref = this.RULE('clickHref', () => {
+ this.CONSUME(tokens.Href);
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME(tokens.QuotedString) },
+ ]);
+ });
+
+ // Click call
+ private clickCall = this.RULE('clickCall', () => {
+ this.OR([
+ {
+ ALT: () => {
+ this.CONSUME(tokens.Call);
+ this.OR2([
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME(tokens.QuotedString) },
+ ]);
+ this.OPTION(() => {
+ this.CONSUME(tokens.Pipe);
+ // Parse arguments
+ this.CONSUME2(tokens.Pipe);
+ });
+ },
+ },
+ {
+ ALT: () => {
+ this.CONSUME(tokens.Callback);
+ this.OR3([
+ { ALT: () => this.CONSUME2(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME2(tokens.QuotedString) },
+ {
+ ALT: () => {
+ this.CONSUME(tokens.StringStart);
+ this.CONSUME(tokens.StringContent);
+ this.CONSUME(tokens.StringEnd);
+ },
+ },
+ ]);
+ },
+ },
+ ]);
+ });
+
+ // Subgraph statement
+ private subgraphStatement = this.RULE('subgraphStatement', () => {
+ this.CONSUME(tokens.Subgraph);
+ this.OPTION(() => {
+ this.SUBRULE(this.subgraphId);
+ });
+ this.OPTION2(() => {
+ this.OR([
+ {
+ ALT: () => {
+ this.CONSUME(tokens.SquareStart);
+ this.SUBRULE(this.nodeText);
+ this.CONSUME(tokens.SquareEnd);
+ },
+ },
+ {
+ ALT: () => {
+ this.CONSUME(tokens.QuotedString);
+ },
+ },
+ ]);
+ });
+ this.OPTION3(() => {
+ this.SUBRULE(this.statementSeparator);
+ });
+ this.MANY(() => {
+ this.OR2([
+ { ALT: () => this.SUBRULE2(this.statement) },
+ { ALT: () => this.SUBRULE2(this.statementSeparator) },
+ ]);
+ });
+ this.CONSUME(tokens.End);
+ this.OPTION4(() => {
+ this.SUBRULE3(this.statementSeparator);
+ });
+ });
+
+ // Direction statement
+ private directionStatement = this.RULE('directionStatement', () => {
+ // TODO: Add direction keyword token
+ this.CONSUME(tokens.DirectionValue);
+ this.SUBRULE(this.statementSeparator);
+ });
+
+ // Helper rules
+ private className = this.RULE('className', () => {
+ this.CONSUME(tokens.NODE_STRING);
+ });
+
+ private subgraphId = this.RULE('subgraphId', () => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME(tokens.QuotedString) },
+ {
+ ALT: () => {
+ this.CONSUME(tokens.StringStart);
+ this.CONSUME(tokens.StringContent);
+ this.CONSUME(tokens.StringEnd);
+ },
+ },
+ ]);
+ });
+
+ private nodeIdList = this.RULE('nodeIdList', () => {
+ this.SUBRULE(this.nodeId);
+ this.MANY(() => {
+ this.CONSUME(tokens.Comma);
+ this.SUBRULE2(this.nodeId);
+ });
+ });
+
+ private linkIndexList = this.RULE('linkIndexList', () => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) }, // "default"
+ { ALT: () => this.SUBRULE(this.numberList) },
+ ]);
+ });
+
+ private numberList = this.RULE('numberList', () => {
+ this.CONSUME(tokens.NumberToken);
+ this.MANY(() => {
+ this.CONSUME(tokens.Comma);
+ this.CONSUME2(tokens.NumberToken);
+ });
+ });
+
+ private styleList = this.RULE('styleList', () => {
+ this.SUBRULE(this.style);
+ this.MANY(() => {
+ this.CONSUME(tokens.Comma);
+ this.SUBRULE2(this.style);
+ });
+ });
+
+ private style = this.RULE('style', () => {
+ this.AT_LEAST_ONE(() => {
+ this.OR([
+ { ALT: () => this.CONSUME(tokens.NODE_STRING) },
+ { ALT: () => this.CONSUME(tokens.NumberToken) },
+ { ALT: () => this.CONSUME(tokens.Colon) },
+ { ALT: () => this.CONSUME(tokens.Semicolon) },
+ { ALT: () => this.CONSUME(tokens.Minus) },
+ ]);
+ });
+ });
+
+ // Standalone link statement
+ private standaloneLinkStatement = this.RULE('standaloneLinkStatement', () => {
+ this.SUBRULE(this.nodeId);
+ this.SUBRULE(this.link);
+ this.SUBRULE2(this.nodeId);
+ });
+
+ // Accessibility statement
+ private accStatement = this.RULE('accStatement', () => {
+ this.OR([
+ {
+ ALT: () => {
+ this.CONSUME(tokens.AccTitle);
+ this.CONSUME(tokens.AccTitleValue);
+ },
+ },
+ {
+ ALT: () => {
+ this.CONSUME(tokens.AccDescr);
+ this.CONSUME(tokens.AccDescrValue);
+ },
+ },
+ {
+ ALT: () => {
+ this.CONSUME(tokens.AccDescrMultiline);
+ this.CONSUME(tokens.AccDescrMultilineValue);
+ this.CONSUME(tokens.AccDescrMultilineEnd);
+ },
+ },
+ ]);
+ });
+}
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flowParserAdapter.ts b/packages/mermaid/src/diagrams/flowchart/parser/flowParserAdapter.ts
new file mode 100644
index 000000000..6a596a7ca
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flowParserAdapter.ts
@@ -0,0 +1,363 @@
+import { FlowchartLexer } from './flowLexer.js';
+import { FlowchartParser } from './flowParser.js';
+import { FlowchartAstVisitor } from './flowAst.js';
+
+// Interface matching existing Mermaid flowDb expectations
+export interface FlowDb {
+ vertices: Record;
+ edges: any[];
+ classes: Record;
+ subGraphs: any[];
+ direction: string;
+ tooltips: Record;
+ clickEvents: any[];
+ firstGraph: () => boolean;
+ setDirection: (dir: string) => void;
+ addVertex: (
+ id: string,
+ text?: string,
+ type?: string,
+ style?: string,
+ classes?: string[],
+ dir?: string,
+ props?: any
+ ) => void;
+ addLink: (start: string | string[], end: string | string[], linkData: any) => void;
+ addClass: (id: string, style: string) => void;
+ setClass: (ids: string | string[], className: string) => void;
+ setClickEvent: (id: string, functionName: string, functionArgs?: string) => void;
+ setLink: (id: string, link: string, target?: string) => void;
+ addSubGraph: (id: string, list: any[], title: string) => string;
+ getVertices: () => Record;
+ getEdges: () => any[];
+ getClasses: () => Record;
+ clear: () => void;
+ setAccTitle: (title: string) => void;
+ setAccDescription: (description: string) => void;
+}
+
+class FlowchartParserAdapter {
+ public lexer: any;
+ public parser: FlowchartParser;
+ public visitor: FlowchartAstVisitor;
+
+ // Mermaid compatibility
+ public yy: FlowDb;
+
+ constructor() {
+ this.lexer = FlowchartLexer;
+ this.parser = new FlowchartParser();
+ this.visitor = new FlowchartAstVisitor();
+
+ // Initialize yy object for Mermaid compatibility
+ this.yy = this.createYY();
+ }
+
+ public createYY(): FlowDb {
+ const state = {
+ vertices: new Map(),
+ edges: [] as any[],
+ classes: {} as Record,
+ subGraphs: [] as any[],
+ direction: 'TB',
+ tooltips: {} as Record,
+ clickEvents: [] as any[],
+ subCount: 0,
+ accTitle: '',
+ accDescription: '',
+ };
+
+ return {
+ vertices: state.vertices,
+ edges: state.edges,
+ classes: state.classes,
+ subGraphs: state.subGraphs,
+ direction: state.direction,
+ tooltips: state.tooltips,
+ clickEvents: state.clickEvents,
+
+ firstGraph: () => true,
+
+ setDirection: (dir: string) => {
+ state.direction = dir;
+ },
+
+ addVertex: (
+ id: string,
+ text?: string,
+ type?: string,
+ style?: string,
+ classes?: string[],
+ dir?: string,
+ props?: any
+ ) => {
+ state.vertices.set(id, {
+ id,
+ text: text || id,
+ type: type || 'default',
+ style,
+ classes,
+ dir,
+ props,
+ });
+ },
+
+ addLink: (start: string | string[], end: string | string[], linkData: any) => {
+ state.edges.push({
+ start: Array.isArray(start) ? start[start.length - 1] : start,
+ end: Array.isArray(end) ? end[end.length - 1] : end,
+ type: linkData.type || 'arrow',
+ stroke: linkData.stroke || 'normal',
+ length: linkData.length,
+ text: linkData.text,
+ });
+ },
+
+ addClass: (id: string, style: string) => {
+ state.classes[id] = style;
+ },
+
+ setClass: (ids: string | string[], className: string) => {
+ const idArray = Array.isArray(ids) ? ids : [ids];
+ idArray.forEach((id) => {
+ const vertex = state.vertices.get(id);
+ if (vertex) {
+ vertex.classes = [className];
+ }
+ });
+ },
+
+ setClickEvent: (id: string, functionName: string, functionArgs?: string) => {
+ state.clickEvents.push({
+ id,
+ functionName,
+ functionArgs,
+ });
+ },
+
+ setLink: (id: string, link: string, target?: string) => {
+ state.clickEvents.push({
+ id,
+ link,
+ target,
+ });
+ },
+
+ addSubGraph: (id: string, list: any[], title: string) => {
+ const sgId = id || `subGraph${state.subCount++}`;
+ state.subGraphs.push({
+ id: sgId,
+ nodes: list,
+ title: title || sgId,
+ });
+ return sgId;
+ },
+
+ getVertices: () => state.vertices,
+ getEdges: () => state.edges,
+ getClasses: () => state.classes,
+
+ clear: () => {
+ state.vertices.clear();
+ state.edges.length = 0;
+ state.classes = {};
+ state.subGraphs = [];
+ state.direction = 'TB';
+ state.tooltips = {};
+ state.clickEvents = [];
+ state.subCount = 0;
+ state.accTitle = '';
+ state.accDescription = '';
+ },
+
+ setAccTitle: (title: string) => {
+ state.accTitle = title;
+ },
+
+ setAccDescription: (description: string) => {
+ state.accDescription = description;
+ },
+ };
+ }
+
+ parse(text: string): any {
+ // Clear previous state
+ this.yy.clear();
+
+ // Tokenize
+ const lexResult = this.lexer.tokenize(text);
+
+ if (lexResult.errors.length > 0) {
+ const error = lexResult.errors[0];
+ throw new Error(
+ `Lexing error at line ${error.line}, column ${error.column}: ${error.message}`
+ );
+ }
+
+ // Parse
+ this.parser.input = lexResult.tokens;
+ const cst = this.parser.flowchart();
+
+ if (this.parser.errors.length > 0) {
+ const error = this.parser.errors[0];
+ throw new Error(`Parse error: ${error.message}`);
+ }
+
+ // Visit CST and build AST
+ const ast = this.visitor.visit(cst);
+
+ // Update yy state with parsed data
+ // Convert plain object vertices to Map
+ Object.entries(ast.vertices).forEach(([id, vertex]) => {
+ this.yy.vertices.set(id, vertex);
+ });
+ this.yy.edges.push(...ast.edges);
+ Object.assign(this.yy.classes, ast.classes);
+ this.yy.subGraphs.push(...ast.subGraphs);
+ this.yy.direction = ast.direction;
+ Object.assign(this.yy.tooltips, ast.tooltips);
+ this.yy.clickEvents.push(...ast.clickEvents);
+
+ return ast;
+ }
+
+ // Compatibility method for Mermaid
+ getYY(): FlowDb {
+ return this.yy;
+ }
+}
+
+// Export a singleton instance for compatibility
+const parserInstance = new FlowchartParserAdapter();
+
+// Create a flow object that can have its yy property reassigned
+const flow = {
+ parser: parserInstance,
+ yy: parserInstance.yy,
+ parse: (text: string) => {
+ // Use the current yy object (which might have been reassigned by tests)
+ const targetYY = flow.yy;
+
+ // Clear previous state
+ targetYY.clear();
+ parserInstance.visitor.clear();
+
+ // Tokenize
+ const lexResult = parserInstance.lexer.tokenize(text);
+
+ if (lexResult.errors.length > 0) {
+ const error = lexResult.errors[0];
+ throw new Error(
+ `Lexing error at line ${error.line}, column ${error.column}: ${error.message}`
+ );
+ }
+
+ // Parse
+ parserInstance.parser.input = lexResult.tokens;
+ const cst = parserInstance.parser.flowchart();
+
+ if (parserInstance.parser.errors.length > 0) {
+ const error = parserInstance.parser.errors[0];
+ throw new Error(`Parse error: ${error.message}`);
+ }
+
+ // Visit CST and build AST
+ const ast = parserInstance.visitor.visit(cst);
+
+ // Update yy state with parsed data
+ // Convert plain object vertices to Map
+ Object.entries(ast.vertices).forEach(([id, vertex]) => {
+ // Use addVertex method if available, otherwise set directly
+ if (typeof targetYY.addVertex === 'function') {
+ // Create textObj structure expected by FlowDB
+ const textObj = vertex.text ? { text: vertex.text, type: 'text' } : undefined;
+ targetYY.addVertex(
+ id,
+ textObj,
+ vertex.type,
+ vertex.style || [],
+ vertex.classes || [],
+ vertex.dir,
+ vertex.props || {},
+ undefined // metadata
+ );
+ } else {
+ targetYY.vertices.set(id, vertex);
+ }
+ });
+
+ // Add edges
+ ast.edges.forEach((edge) => {
+ if (typeof targetYY.addLink === 'function') {
+ // Create the linkData structure expected by FlowDB
+ const linkData = {
+ type: edge.type,
+ stroke: edge.stroke,
+ length: edge.length,
+ text: edge.text ? { text: edge.text, type: 'text' } : undefined,
+ };
+ targetYY.addLink([edge.start], [edge.end], linkData);
+ } else {
+ targetYY.edges.push(edge);
+ }
+ });
+
+ // Add classes
+ Object.entries(ast.classes).forEach(([id, className]) => {
+ if (typeof targetYY.addClass === 'function') {
+ // FlowDB.addClass expects an array of style strings, not a single string
+ const styleArray = className.split(',').map((s) => s.trim());
+ targetYY.addClass(id, styleArray);
+ } else {
+ targetYY.classes[id] = className;
+ }
+ });
+
+ // Add subgraphs
+ if (targetYY.subGraphs) {
+ targetYY.subGraphs.push(...ast.subGraphs);
+ }
+
+ // Set direction
+ if (typeof targetYY.setDirection === 'function') {
+ targetYY.setDirection(ast.direction);
+ } else {
+ targetYY.direction = ast.direction;
+ }
+
+ // Add tooltips
+ Object.entries(ast.tooltips).forEach(([id, tooltip]) => {
+ if (typeof targetYY.setTooltip === 'function') {
+ targetYY.setTooltip(id, tooltip);
+ } else if (targetYY.tooltips) {
+ targetYY.tooltips[id] = tooltip;
+ }
+ });
+
+ // Add accessibility information
+ if (ast.accTitle && typeof targetYY.setAccTitle === 'function') {
+ targetYY.setAccTitle(ast.accTitle);
+ }
+ if (ast.accDescription && typeof targetYY.setAccDescription === 'function') {
+ targetYY.setAccDescription(ast.accDescription);
+ }
+
+ // Add click events
+ ast.clickEvents.forEach((clickEvent) => {
+ if (typeof targetYY.setClickEvent === 'function') {
+ targetYY.setClickEvent(clickEvent.id, clickEvent.functionName, clickEvent.functionArgs);
+ } else if (targetYY.clickEvents) {
+ targetYY.clickEvents.push(clickEvent);
+ }
+ });
+
+ return ast;
+ },
+};
+
+// Mermaid expects these exports
+export const parser = parserInstance;
+export const yy = parserInstance.yy;
+
+// Default export for modern imports
+export default flow;
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js
index 9339a6e2c..31ebc1e79 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js
+++ b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js
@@ -1,5 +1,5 @@
import { FlowDB } from '../flowDb.js';
-import flow from './flowParser.ts';
+import flow from './flowParserAdapter.js';
import { setConfig } from '../../../config.js';
setConfig({
@@ -8,13 +8,13 @@ setConfig({
describe('when parsing subgraphs', function () {
beforeEach(function () {
- flow.parser.yy = new FlowDB();
- flow.parser.yy.clear();
- flow.parser.yy.setGen('gen-2');
+ flow.yy = new FlowDB();
+ flow.yy.clear();
+ flow.yy.setGen('gen-2');
});
it('should handle subgraph with tab indentation', function () {
- const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2\nend');
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const res = flow.parse('graph TB\nsubgraph One\n\ta1-->a2\nend');
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
@@ -25,8 +25,8 @@ describe('when parsing subgraphs', function () {
expect(subgraph.id).toBe('One');
});
it('should handle subgraph with chaining nodes indentation', function () {
- const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2-->a3\nend');
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const res = flow.parse('graph TB\nsubgraph One\n\ta1-->a2-->a3\nend');
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(3);
@@ -38,8 +38,8 @@ describe('when parsing subgraphs', function () {
});
it('should handle subgraph with multiple words in title', function () {
- const res = flow.parser.parse('graph TB\nsubgraph "Some Title"\n\ta1-->a2\nend');
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const res = flow.parse('graph TB\nsubgraph "Some Title"\n\ta1-->a2\nend');
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2);
@@ -50,8 +50,8 @@ describe('when parsing subgraphs', function () {
});
it('should handle subgraph with id and title notation', function () {
- const res = flow.parser.parse('graph TB\nsubgraph some-id[Some Title]\n\ta1-->a2\nend');
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const res = flow.parse('graph TB\nsubgraph some-id[Some Title]\n\ta1-->a2\nend');
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2);
@@ -62,8 +62,8 @@ describe('when parsing subgraphs', function () {
});
it.skip('should handle subgraph without id and space in title', function () {
- const res = flow.parser.parse('graph TB\nsubgraph Some Title\n\ta1-->a2\nend');
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const res = flow.parse('graph TB\nsubgraph Some Title\n\ta1-->a2\nend');
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(2);
@@ -74,13 +74,13 @@ describe('when parsing subgraphs', function () {
});
it('should handle subgraph id starting with a number', function () {
- const res = flow.parser.parse(`graph TD
+ const res = flow.parse(`graph TD
A[Christmas] -->|Get money| B(Go shopping)
subgraph 1test
A
end`);
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
expect(subgraph.nodes.length).toBe(1);
@@ -89,20 +89,20 @@ describe('when parsing subgraphs', function () {
});
it('should handle subgraphs1', function () {
- const res = flow.parser.parse('graph TD;A-->B;subgraph myTitle;c-->d;end;');
+ const res = flow.parse('graph TD;A-->B;subgraph myTitle;c-->d;end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
it('should handle subgraphs with title in quotes', function () {
- const res = flow.parser.parse('graph TD;A-->B;subgraph "title in quotes";c-->d;end;');
+ const res = flow.parse('graph TD;A-->B;subgraph "title in quotes";c-->d;end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
@@ -111,12 +111,12 @@ describe('when parsing subgraphs', function () {
expect(edges[0].type).toBe('arrow_point');
});
it('should handle subgraphs in old style that was broken', function () {
- const res = flow.parser.parse('graph TD;A-->B;subgraph old style that is broken;c-->d;end;');
+ const res = flow.parse('graph TD;A-->B;subgraph old style that is broken;c-->d;end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
@@ -125,12 +125,12 @@ describe('when parsing subgraphs', function () {
expect(edges[0].type).toBe('arrow_point');
});
it('should handle subgraphs with dashes in the title', function () {
- const res = flow.parser.parse('graph TD;A-->B;subgraph a-b-c;c-->d;end;');
+ const res = flow.parse('graph TD;A-->B;subgraph a-b-c;c-->d;end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
@@ -139,12 +139,12 @@ describe('when parsing subgraphs', function () {
expect(edges[0].type).toBe('arrow_point');
});
it('should handle subgraphs with id and title in brackets', function () {
- const res = flow.parser.parse('graph TD;A-->B;subgraph uid1[text of doom];c-->d;end;');
+ const res = flow.parse('graph TD;A-->B;subgraph uid1[text of doom];c-->d;end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
@@ -154,12 +154,12 @@ describe('when parsing subgraphs', function () {
expect(edges[0].type).toBe('arrow_point');
});
it('should handle subgraphs with id and title in brackets and quotes', function () {
- const res = flow.parser.parse('graph TD;A-->B;subgraph uid2["text of doom"];c-->d;end;');
+ const res = flow.parse('graph TD;A-->B;subgraph uid2["text of doom"];c-->d;end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
@@ -169,12 +169,12 @@ describe('when parsing subgraphs', function () {
expect(edges[0].type).toBe('arrow_point');
});
it('should handle subgraphs with id and title in brackets without spaces', function () {
- const res = flow.parser.parse('graph TD;A-->B;subgraph uid2[textofdoom];c-->d;end;');
+ const res = flow.parse('graph TD;A-->B;subgraph uid2[textofdoom];c-->d;end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(1);
const subgraph = subgraphs[0];
@@ -185,19 +185,19 @@ describe('when parsing subgraphs', function () {
});
it('should handle subgraphs2', function () {
- const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\n\n c-->d \nend\n');
+ const res = flow.parse('graph TD\nA-->B\nsubgraph myTitle\n\n c-->d \nend\n');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
it('should handle subgraphs3', function () {
- const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle \n\n c-->d \nend\n');
+ const res = flow.parse('graph TD\nA-->B\nsubgraph myTitle \n\n c-->d \nend\n');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
@@ -211,36 +211,36 @@ describe('when parsing subgraphs', function () {
' subgraph inner\n\n e-->f \n end \n\n' +
' subgraph inner\n\n h-->i \n end \n\n' +
'end\n';
- const res = flow.parser.parse(str);
+ const res = flow.parse(str);
});
it('should handle subgraphs4', function () {
- const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-->d\nend;');
+ const res = flow.parse('graph TD\nA-->B\nsubgraph myTitle\nc-->d\nend;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
it('should handle subgraphs5', function () {
- const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-- text -->d\nd-->e\n end;');
+ const res = flow.parse('graph TD\nA-->B\nsubgraph myTitle\nc-- text -->d\nd-->e\n end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
it('should handle subgraphs with multi node statements in it', function () {
- const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\na & b --> c & e\n end;');
+ const res = flow.parse('graph TD\nA-->B\nsubgraph myTitle\na & b --> c & e\n end;');
- const vert = flow.parser.yy.getVertices();
- const edges = flow.parser.yy.getEdges();
+ const vert = flow.yy.getVertices();
+ const edges = flow.yy.getEdges();
expect(edges[0].type).toBe('arrow_point');
});
it('should handle nested subgraphs 1', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
subgraph A
b-->B
a
@@ -250,7 +250,7 @@ describe('when parsing subgraphs', function () {
c
end`);
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(2);
const subgraphA = subgraphs.find((o) => o.id === 'A');
@@ -263,7 +263,7 @@ describe('when parsing subgraphs', function () {
expect(subgraphA.nodes).not.toContain('c');
});
it('should handle nested subgraphs 2', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
b-->B
a-->c
subgraph B
@@ -275,7 +275,7 @@ describe('when parsing subgraphs', function () {
B
end`);
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(2);
const subgraphA = subgraphs.find((o) => o.id === 'A');
@@ -288,7 +288,7 @@ describe('when parsing subgraphs', function () {
expect(subgraphA.nodes).not.toContain('c');
});
it('should handle nested subgraphs 3', function () {
- const res = flow.parser.parse(`flowchart TB
+ const res = flow.parse(`flowchart TB
subgraph B
c
end
@@ -298,7 +298,7 @@ describe('when parsing subgraphs', function () {
a
end`);
- const subgraphs = flow.parser.yy.getSubGraphs();
+ const subgraphs = flow.yy.getSubGraphs();
expect(subgraphs.length).toBe(2);
const subgraphA = subgraphs.find((o) => o.id === 'A');
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/test-chevrotain.ts b/packages/mermaid/src/diagrams/flowchart/parser/test-chevrotain.ts
new file mode 100644
index 000000000..07e5c1ca8
--- /dev/null
+++ b/packages/mermaid/src/diagrams/flowchart/parser/test-chevrotain.ts
@@ -0,0 +1,40 @@
+import { FlowchartLexer } from './flowLexer.js';
+import { FlowchartParser } from './flowParser.js';
+import { FlowchartAstVisitor } from './flowAst.js';
+
+// Simple test function
+function testChevrotainParser() {
+ // Test simple flowchart
+ const input = `
+ graph TD
+ A[Start] --> B{Decision}
+ B -->|Yes| C[Process]
+ B -->|No| D[End]
+ C --> D
+ `;
+
+ // Tokenize
+ const lexResult = FlowchartLexer.tokenize(input);
+
+ if (lexResult.errors.length > 0) {
+ throw new Error(`Lexing errors: ${lexResult.errors.map((e) => e.message).join(', ')}`);
+ }
+
+ // Parse
+ const parser = new FlowchartParser();
+ parser.input = lexResult.tokens;
+ const cst = parser.flowchart();
+
+ if (parser.errors.length > 0) {
+ throw new Error(`Parse errors: ${parser.errors.map((e) => e.message).join(', ')}`);
+ }
+
+ // Visit CST and build AST
+ const visitor = new FlowchartAstVisitor();
+ const ast = visitor.visit(cst);
+
+ return ast;
+}
+
+// Export for testing
+export { testChevrotainParser };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 40039466f..3143931fe 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -229,6 +229,9 @@ importers:
'@types/d3':
specifier: ^7.4.3
version: 7.4.3
+ chevrotain:
+ specifier: ^11.0.3
+ version: 11.0.3
cytoscape:
specifier: ^3.29.3
version: 3.31.0
@@ -508,6 +511,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.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(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.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(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.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))
+ unplugin-vue-components:
+ specifier: ^28.4.0
+ version: 28.4.0(@babel/parser@7.27.2)(vue@3.5.13(typescript@5.7.3))
+ vite:
+ specifier: ^6.1.1
+ version: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+ vite-plugin-pwa:
+ specifier: ^0.21.1
+ version: 0.21.2(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(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:
@@ -3627,6 +3691,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:
@@ -9594,6 +9667,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'}
@@ -14191,6 +14276,16 @@ snapshots:
transitivePeerDependencies:
- vue
+ '@unocss/astro@66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))':
+ dependencies:
+ '@unocss/core': 66.0.0
+ '@unocss/reset': 66.0.0
+ '@unocss/vite': 66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))
+ optionalDependencies:
+ vite: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+ transitivePeerDependencies:
+ - vue
+
'@unocss/cli@66.0.0':
dependencies:
'@ampproject/remapping': 2.3.0
@@ -14326,6 +14421,24 @@ snapshots:
transitivePeerDependencies:
- vue
+ '@unocss/vite@66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))':
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@unocss/config': 66.0.0
+ '@unocss/core': 66.0.0
+ '@unocss/inspector': 66.0.0(vue@3.5.13(typescript@5.7.3))
+ chokidar: 3.6.0
+ magic-string: 0.30.17
+ tinyglobby: 0.2.12
+ unplugin-utils: 0.2.4
+ vite: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+ transitivePeerDependencies:
+ - vue
+
+ '@vite-pwa/vitepress@0.5.4(vite-plugin-pwa@0.21.2(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(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.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(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.1))(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.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.3.0)
@@ -14340,6 +14453,11 @@ snapshots:
vite: 6.1.1(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
vue: 3.5.13(typescript@5.7.3)
+ '@vitejs/plugin-vue@5.2.1(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))':
+ dependencies:
+ vite: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+ vue: 3.5.13(typescript@5.7.3)
+
'@vitest/coverage-v8@3.0.6(vitest@3.0.6)':
dependencies:
'@ampproject/remapping': 2.3.0
@@ -21455,6 +21573,33 @@ snapshots:
- supports-color
- vue
+ unocss@66.0.0(postcss@8.5.3)(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3)):
+ dependencies:
+ '@unocss/astro': 66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))
+ '@unocss/cli': 66.0.0
+ '@unocss/core': 66.0.0
+ '@unocss/postcss': 66.0.0(postcss@8.5.3)
+ '@unocss/preset-attributify': 66.0.0
+ '@unocss/preset-icons': 66.0.0
+ '@unocss/preset-mini': 66.0.0
+ '@unocss/preset-tagify': 66.0.0
+ '@unocss/preset-typography': 66.0.0
+ '@unocss/preset-uno': 66.0.0
+ '@unocss/preset-web-fonts': 66.0.0
+ '@unocss/preset-wind': 66.0.0
+ '@unocss/preset-wind3': 66.0.0
+ '@unocss/transformer-attributify-jsx': 66.0.0
+ '@unocss/transformer-compile-class': 66.0.0
+ '@unocss/transformer-directives': 66.0.0
+ '@unocss/transformer-variant-group': 66.0.0
+ '@unocss/vite': 66.0.0(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(vue@3.5.13(typescript@5.7.3))
+ optionalDependencies:
+ vite: 6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+ transitivePeerDependencies:
+ - postcss
+ - supports-color
+ - vue
+
unpipe@1.0.0: {}
unplugin-utils@0.2.4:
@@ -21570,6 +21715,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ vite-plugin-pwa@0.21.2(vite@6.1.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1))(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.6(@types/node@22.13.5)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.1)
+ 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.1))(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)