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)