From 2fef44b43f128dfbe958591b453666b391646d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Dourado?= Date: Wed, 25 Mar 2026 19:15:20 +0000 Subject: [PATCH 1/8] fix #7091: Ensure only one word is allowed between 'state' and '{' The parser allowed multiple words between the 'state' keyword and the '{' character, leading to incorrect parsing of state diagrams. Added a new rule to the parser to enforce a single-word constraint between 'state' and '{'. This ensures invalid syntax is rejected with an appropriate error message. --- .../diagrams/state/parser/state-parser.spec.js | 15 +++++++++++++-- .../src/diagrams/state/parser/stateDiagram.jison | 6 ++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js index c498b4b1a3d..988e0d6b568 100644 --- a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js +++ b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js @@ -14,6 +14,17 @@ describe('state parser can parse...', () => { stateDiagram.parser.yy.clear(); }); + describe('invalid name between state and curly bracket', () => { + it('should throw error when name has multiple words', () => { + const diagramText = `stateDiagram-v2 + state invalid syntax { X }`; + + expect(() => { + stateDiagram.parser.parse(diagramText); + }).toThrow('Error: State name must be a single word.'); + }); + }); + describe('states with id displayed as a (name)', () => { describe('syntax 1: stateID as "name in quotes"', () => { it('stateID as "some name"', () => { @@ -55,8 +66,8 @@ describe('state parser can parse...', () => { const diagramText = `stateDiagram-v2 assemble assemblies - state assemble - state assemblies + state assemble { innerState1 } + state assemblies {innerState2 } `; stateDiagram.parser.parse(diagramText); const states = stateDiagram.parser.yy.getStates(); diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison index 59cb3cd11b8..97b053c41e7 100644 --- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison +++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison @@ -167,6 +167,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili /lex %left '^' +%right COMPOSIT_STATE /* fix the shift/reduce conflicts from new rule */ %start start @@ -229,6 +230,11 @@ statement // console.log('Adding document for state without id ', $1); $$={ stmt: 'state', id: $1, type: 'default', description: '', doc: $3 } } + | COMPOSIT_STATE COMPOSIT_STATE STRUCT_START document STRUCT_STOP + { + /* Rejects invalid syntax: multiple words between 'state' and '{' */ + throw new Error('Error: State name must be a single word.'); + } | STATE_DESCR AS ID { var id=$3; var description = $1.trim(); From ccc030d4f99c82fc8b4b3714aeb867472cac6fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Dourado?= Date: Fri, 3 Apr 2026 18:31:18 +0100 Subject: [PATCH 2/8] fix #7091: adjust rules to fit existing tests --- .../mermaid/src/diagrams/state/parser/state-parser.spec.js | 4 ++-- .../mermaid/src/diagrams/state/parser/stateDiagram.jison | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js index 988e0d6b568..d1fbbbaba7b 100644 --- a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js +++ b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js @@ -66,8 +66,8 @@ describe('state parser can parse...', () => { const diagramText = `stateDiagram-v2 assemble assemblies - state assemble { innerState1 } - state assemblies {innerState2 } + state assemble + state assemblies `; stateDiagram.parser.parse(diagramText); const states = stateDiagram.parser.yy.getStates(); diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison index 97b053c41e7..b9adfceb6e8 100644 --- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison +++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison @@ -131,7 +131,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili ["] { this.popState(); } [^"]* { /* console.log('Long description:', yytext); */ return "STATE_DESCR"; } [^\n\s\{]+ { /* console.log('COMPOSIT_STATE', yytext); */ return 'COMPOSIT_STATE'; } -\n { this.popState(); } +\n { this.popState(); return 'NL'; } \{ { this.popState(); this.pushState('struct'); /* console.log('begin struct', yytext); */ return 'STRUCT_START'; } \} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';} } [\n] /* nothing */ @@ -167,6 +167,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili /lex %left '^' +%left NL %right COMPOSIT_STATE /* fix the shift/reduce conflicts from new rule */ %start start @@ -225,6 +226,10 @@ statement | HIDE_EMPTY | scale WIDTH | COMPOSIT_STATE + | COMPOSIT_STATE NL + { + $$={ stmt: 'state', id: $1, type: 'default', description: '', doc: [] } + } | COMPOSIT_STATE STRUCT_START document STRUCT_STOP { // console.log('Adding document for state without id ', $1); From d323bb45cb1b0238d09ccc4fa1cde913ae84d84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Dourado?= Date: Fri, 3 Apr 2026 19:30:22 +0100 Subject: [PATCH 3/8] fix #7091: added/removed rules to pass failing tests --- packages/mermaid/src/diagrams/state/parser/stateDiagram.jison | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison index b9adfceb6e8..21d1bfb2058 100644 --- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison +++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison @@ -130,6 +130,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili [^\n\{]* { if (!processId()) return; this.popState(); /* console.log('STATE_ID', yytext); */ return "ID"; } ["] { this.popState(); } [^"]* { /* console.log('Long description:', yytext); */ return "STATE_DESCR"; } +\s+"state"\s+ { this.popState(); this.pushState('STATE'); yytext='state '; return 'NL'; } [^\n\s\{]+ { /* console.log('COMPOSIT_STATE', yytext); */ return 'COMPOSIT_STATE'; } \n { this.popState(); return 'NL'; } \{ { this.popState(); this.pushState('struct'); /* console.log('begin struct', yytext); */ return 'STRUCT_START'; } @@ -167,8 +168,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili /lex %left '^' -%left NL -%right COMPOSIT_STATE /* fix the shift/reduce conflicts from new rule */ %start start @@ -225,7 +224,6 @@ statement } | HIDE_EMPTY | scale WIDTH - | COMPOSIT_STATE | COMPOSIT_STATE NL { $$={ stmt: 'state', id: $1, type: 'default', description: '', doc: [] } From ecc8c6e83bc5b350405457f4a4fa04c41d2eb28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Dourado?= Date: Thu, 23 Apr 2026 17:29:45 +0100 Subject: [PATCH 4/8] fix #7091: Moved fix from grammar to lexer Added test case for 3+ word case --- .../diagrams/state/parser/state-parser.spec.js | 3 ++- .../src/diagrams/state/parser/stateDiagram.jison | 16 ++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js index d1fbbbaba7b..da2a050c2d8 100644 --- a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js +++ b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js @@ -17,7 +17,8 @@ describe('state parser can parse...', () => { describe('invalid name between state and curly bracket', () => { it('should throw error when name has multiple words', () => { const diagramText = `stateDiagram-v2 - state invalid syntax { X }`; + state invalid syntax { X } + state invalid syntax with more than 2 words { Y }`; expect(() => { stateDiagram.parser.parse(diagramText); diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison index 21d1bfb2058..604111c2350 100644 --- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison +++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison @@ -130,9 +130,9 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili [^\n\{]* { if (!processId()) return; this.popState(); /* console.log('STATE_ID', yytext); */ return "ID"; } ["] { this.popState(); } [^"]* { /* console.log('Long description:', yytext); */ return "STATE_DESCR"; } -\s+"state"\s+ { this.popState(); this.pushState('STATE'); yytext='state '; return 'NL'; } +\w+\s+\w+.*?\{ { throw new Error('Error: State name must be a single word. Found: "' + yytext.trim() + '"'); } [^\n\s\{]+ { /* console.log('COMPOSIT_STATE', yytext); */ return 'COMPOSIT_STATE'; } -\n { this.popState(); return 'NL'; } +\n { this.popState(); } \{ { this.popState(); this.pushState('struct'); /* console.log('begin struct', yytext); */ return 'STRUCT_START'; } \} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';} } [\n] /* nothing */ @@ -224,20 +224,12 @@ statement } | HIDE_EMPTY | scale WIDTH - | COMPOSIT_STATE NL - { - $$={ stmt: 'state', id: $1, type: 'default', description: '', doc: [] } - } + | COMPOSIT_STATE | COMPOSIT_STATE STRUCT_START document STRUCT_STOP { // console.log('Adding document for state without id ', $1); $$={ stmt: 'state', id: $1, type: 'default', description: '', doc: $3 } } - | COMPOSIT_STATE COMPOSIT_STATE STRUCT_START document STRUCT_STOP - { - /* Rejects invalid syntax: multiple words between 'state' and '{' */ - throw new Error('Error: State name must be a single word.'); - } | STATE_DESCR AS ID { var id=$3; var description = $1.trim(); @@ -358,4 +350,4 @@ notePosition | right_of ; -%% +%% \ No newline at end of file From c2305df424963c0263d1c75804248db2969ee17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Dourado?= Date: Thu, 23 Apr 2026 17:42:43 +0100 Subject: [PATCH 5/8] Chore: Add changeset for parsing bug fix --- .changeset/tired-rockets-rule.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tired-rockets-rule.md diff --git a/.changeset/tired-rockets-rule.md b/.changeset/tired-rockets-rule.md new file mode 100644 index 00000000000..53c7d41308f --- /dev/null +++ b/.changeset/tired-rockets-rule.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +Fix invalid syntax between state and '}' From ee4aafdeb19a6258d45051305d93b26dd102f4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Dourado?= Date: Thu, 30 Apr 2026 15:52:03 +0100 Subject: [PATCH 6/8] fix #7091: Separated tests Added a still-works case Fixed a typo in changeset --- .changeset/tired-rockets-rule.md | 2 +- .../state/parser/state-parser.spec.js | 34 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/.changeset/tired-rockets-rule.md b/.changeset/tired-rockets-rule.md index 53c7d41308f..5d850562222 100644 --- a/.changeset/tired-rockets-rule.md +++ b/.changeset/tired-rockets-rule.md @@ -2,4 +2,4 @@ 'mermaid': patch --- -Fix invalid syntax between state and '}' +Fix invalid syntax between state and '{' diff --git a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js index da2a050c2d8..a9964b84d52 100644 --- a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js +++ b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js @@ -15,14 +15,36 @@ describe('state parser can parse...', () => { }); describe('invalid name between state and curly bracket', () => { - it('should throw error when name has multiple words', () => { - const diagramText = `stateDiagram-v2 - state invalid syntax { X } - state invalid syntax with more than 2 words { Y }`; + describe('valid syntax', () => { + it('should only accept 1 word', () => { + const diagramText = `stateDiagram-v2 + state valid { X }`; - expect(() => { stateDiagram.parser.parse(diagramText); - }).toThrow('Error: State name must be a single word.'); + + const states = stateDiagram.parser.yy.getStates(); + expect(states.get('valid')).not.toBeUndefined(); + }); + }); + + describe('invalid syntax', () => { + it('should throw error with 2 words', () => { + const diagramText = `stateDiagram-v2 + state invalid syntax { Y }`; + + expect(() => { + stateDiagram.parser.parse(diagramText); + }).toThrow('Error: State name must be a single word.'); + }); + + it('should also throw with more than 2 words', () => { + const diagramText = `stateDiagram-v2 + state invalid syntax with more than 2 words { Z }`; + + expect(() => { + stateDiagram.parser.parse(diagramText); + }).toThrow('Error: State name must be a single word.'); + }); }); }); From d7ff861c0c29d426ccb800e0ad076a183991a33c Mon Sep 17 00:00:00 2001 From: Daniel Drori Date: Wed, 3 Jun 2026 20:23:50 +0300 Subject: [PATCH 7/8] docs: add configuration parameters table to classDiagram --- packages/mermaid/src/docs/syntax/classDiagram.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index 1eab023d613..aebe98d175a 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -768,6 +768,14 @@ It is possible to hide the empty members box of a class node. This is done by changing the **hideEmptyMembersBox** value of the class diagram configuration. For more information on how to edit the Mermaid configuration see the [configuration page.](https://mermaid.js.org/config/configuration.html) +### Possible configuration parameters: + +| Parameter | Description | Default value | +| --- | --- | --- | +| hideEmptyMembersBox | Hides the empty members box of a class node when set to true | false | + + + ```mermaid-example --- config: From 9a66de51b3c68b353dbbe3a74d36be8e98893f5e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 3 Jun 2026 17:31:22 +0000 Subject: [PATCH 8/8] [autofix.ci] apply automated fixes --- docs/syntax/classDiagram.md | 6 ++++++ packages/mermaid/src/docs/syntax/classDiagram.md | 8 +++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 682754810e7..2906ce034ba 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -1165,6 +1165,12 @@ It is possible to hide the empty members box of a class node. This is done by changing the **hideEmptyMembersBox** value of the class diagram configuration. For more information on how to edit the Mermaid configuration see the [configuration page.](https://mermaid.js.org/config/configuration.html) +### Possible configuration parameters: + +| Parameter | Description | Default value | +| ------------------- | ------------------------------------------------------------ | ------------- | +| hideEmptyMembersBox | Hides the empty members box of a class node when set to true | false | + ```mermaid-example --- config: diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index aebe98d175a..e981e4885d6 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -770,11 +770,9 @@ This is done by changing the **hideEmptyMembersBox** value of the class diagram ### Possible configuration parameters: -| Parameter | Description | Default value | -| --- | --- | --- | -| hideEmptyMembersBox | Hides the empty members box of a class node when set to true | false | - - +| Parameter | Description | Default value | +| ------------------- | ------------------------------------------------------------ | ------------- | +| hideEmptyMembersBox | Hides the empty members box of a class node when set to true | false | ```mermaid-example ---