From 8127d01db7257e7cefad769aa83b9ca3ce14dbac Mon Sep 17 00:00:00 2001 From: Justin Greywolf Date: Tue, 21 Oct 2025 13:01:53 -0700 Subject: [PATCH] Fix flowchart arrow parsing to support lowercase, digits, and edge text - Added lookahead patterns for arrows followed by lowercase letters (fixes #2492) - Added lookahead patterns for arrows followed by digits (fixes Sample 4 & 5) - Preserved edge text functionality with pipe delimiter lookahead - Applied fix to all arrow types: solid (--), thick (==), dotted (-.-) - Added comprehensive test coverage (21 tests total) --- .../flowchart/parser/flow-arrows.spec.ts | 35 +++++++++++++++++++ .../src/diagrams/flowchart/parser/flow.jison | 35 +++++++++---------- 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-arrows.spec.ts b/packages/mermaid/src/diagrams/flowchart/parser/flow-arrows.spec.ts index 1b0cf0766..1b48c8d17 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow-arrows.spec.ts +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-arrows.spec.ts @@ -91,4 +91,39 @@ describe('Flowchart arrow parsing - Issue #2492', () => { expect(() => flow.parser.parse(diagram)).not.toThrow(); }); }); + + describe('Arrows with edge text', () => { + it('should parse arrow with edge text followed by uppercase node', () => { + const diagram = 'graph TD\nA-->|text|B'; + expect(() => flow.parser.parse(diagram)).not.toThrow(); + }); + + it('should parse arrow with edge text followed by lowercase node', () => { + const diagram = 'graph TD\nA-->|text|b'; + expect(() => flow.parser.parse(diagram)).not.toThrow(); + }); + + it('should parse multiple arrows with edge text (regression test)', () => { + const diagram = 'graph TD\nA-->|Get money|B\nB-->C\nC-->|One|D\nC-->|Two|E'; + expect(() => flow.parser.parse(diagram)).not.toThrow(); + }); + }); + + describe('Arrows followed by digits', () => { + it('should parse --> followed by digit', () => { + const diagram = 'graph LR\n47-->48'; + expect(() => flow.parser.parse(diagram)).not.toThrow(); + }); + + it('should parse --> followed by node starting with digit', () => { + const diagram = 'graph LR\nA-->48(Node)'; + expect(() => flow.parser.parse(diagram)).not.toThrow(); + }); + + it('should parse complex diagram with digit node IDs (Sample 4)', () => { + const diagram = + 'graph LR\n47(SAM.CommonFA.FMESummary)-->48(SAM.CommonFA.CommonFAFinanceBudget)\n37(SAM.CommonFA.BudgetSubserviceLineVolume)-->48(SAM.CommonFA.CommonFAFinanceBudget)'; + expect(() => flow.parser.parse(diagram)).not.toThrow(); + }); + }); }); diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison index 356efac18..09f091e18 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison @@ -152,30 +152,27 @@ that id. "," return 'COMMA'; "*" return 'MULT'; -\s*[xo<]?\-\-+[xo>]\s+ { this.popState(); return 'LINK'; } -\s*[xo<]?\-\-+[xo>](?=[A-Z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\-\-+[xo>](?=[a-z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\-\-+[-](?=[A-Z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\-\-+[-]\s+ { this.popState(); return 'LINK'; } -\s*[xo<]?\-\-+[-](?=[a-z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\-\-\s* { this.pushState("edgeText"); return 'START_LINK'; } +\s*[xo<]?\-\-+[-xo>]\s+ { this.popState(); return 'LINK'; } +\s*[xo<]?\-\-+[-xo>](?=[A-Z]) { return 'LINK'; } +\s*[xo<]?\-\-+[-xo>](?=[a-z]) { return 'LINK'; } +\s*[xo<]?\-\-+[-xo>](?=[0-9]) { return 'LINK'; } +\s*[xo<]?\-\-+[-xo>](?=\s*\|) { this.popState(); return 'LINK'; } +\s*[xo<]?\-\-\s* { this.pushState("edgeText"); return 'START_LINK'; } [^-]|\-(?!\-)+ return 'EDGE_TEXT'; -\s*[xo<]?\=\=+[xo>]\s+ { this.popState(); return 'LINK'; } -\s*[xo<]?\=\=+[xo>](?=[A-Z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\=\=+[xo>](?=[a-z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\=\=+[=](?=[A-Z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\=\=+[=]\s+ { this.popState(); return 'LINK'; } -\s*[xo<]?\=\=+[=](?=[a-z]) { this.popState(); return 'LINK'; } +\s*[xo<]?\=\=+[=xo>]\s+ { this.popState(); return 'LINK'; } +\s*[xo<]?\=\=+[=xo>](?=[A-Z]) { return 'LINK'; } +\s*[xo<]?\=\=+[=xo>](?=[a-z]) { return 'LINK'; } +\s*[xo<]?\=\=+[=xo>](?=[0-9]) { return 'LINK'; } +\s*[xo<]?\=\=+[=xo>](?=\s*\|) { this.popState(); return 'LINK'; } \s*[xo<]?\=\=\s* { this.pushState("thickEdgeText"); return 'START_LINK'; } [^=]|\=(?!=) return 'EDGE_TEXT'; -\s*[xo<]?\-?\.+\-[xo>]\s+ { this.popState(); return 'LINK'; } -\s*[xo<]?\-?\.+\-[xo>](?=[A-Z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\-?\.+\-[xo>](?=[a-z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\-?\.+\-(?=[A-Z]) { this.popState(); return 'LINK'; } -\s*[xo<]?\-?\.+\-\s+ { this.popState(); return 'LINK'; } -\s*[xo<]?\-?\.+\-(?=[a-z]) { this.popState(); return 'LINK'; } +\s*[xo<]?\-?\.+\-[xo>]?\s+ { this.popState(); return 'LINK'; } +\s*[xo<]?\-?\.+\-[xo>]?(?=[A-Z]) { return 'LINK'; } +\s*[xo<]?\-?\.+\-[xo>]?(?=[a-z]) { return 'LINK'; } +\s*[xo<]?\-?\.+\-[xo>]?(?=[0-9]) { return 'LINK'; } +\s*[xo<]?\-?\.+\-[xo>]?(?=\s*\|) { this.popState(); return 'LINK'; } \s*[xo<]?\-\.\s* { this.pushState("dottedEdgeText"); return 'START_LINK'; } [^\.]|\.(?!-) return 'EDGE_TEXT';