Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/yummy-moose-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': patch
---

fix: updated the parser to only treat comments starting with %%.
4 changes: 1 addition & 3 deletions docs/syntax/stateDiagram.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,9 +437,7 @@ stateDiagram

## Comments

Comments can be entered within a state diagram chart, which will be ignored by the parser. Comments need to be on their
own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next
newline will be treated as a comment, including any diagram syntax
Comments can be entered within a state diagram chart, which will be ignored by the parser. Comments must be prefaced with `%%` (double percent signs) and any text after their start to the next newline will be treated as a comment, including any diagram syntax. They can be on their own line or at the end of a statement.

```mermaid-example
stateDiagram-v2
Expand Down
114 changes: 114 additions & 0 deletions packages/mermaid/src/diagrams/state/parser/state-style.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,120 @@ describe('ClassDefs and classes when parsing a State diagram', () => {

expect(states.get('Moving').doc.length).toEqual(1);
});

it('should handle comments correctly', function () {
let diagram = '';
diagram += 'stateDiagram-v2\n';
diagram += '%%initial_comment\n';
diagram += '[*] --> Moving %%inline_comment\n';
diagram += '%%final_comment\n';

stateDiagram.parser.parse(diagram);

const states = stateDiagram.parser.yy.getStates();

expect(states.size).toEqual(2);
expect(states.get('%%initial_comment')).toBeUndefined();
expect(states.get('%%inline_comment')).toBeUndefined();
expect(states.get('%%final_comment')).toBeUndefined();
});

it('should handle comments correctly inside states', function () {
let diagram = '';
diagram += 'stateDiagram-v2\n';
diagram += 'state Moving {\n';
diagram += '%%comment_inside_state\n';
diagram += 'slow --> fast %%inline_comment\n';
diagram += '}\n';

stateDiagram.parser.parse(diagram);

const states = stateDiagram.parser.yy.getStates();

const movingDoc = states.get('Moving').doc;
const state1 = movingDoc.find((d) => d.id === '%%comment_inside_state');
expect(state1).toBeUndefined();
const state2 = movingDoc.find((d) => d.id === '%%inline_comment');
expect(state2).toBeUndefined();
});

it('should handle comments correctly after a blank line', () => {
let diagram = '';
diagram += 'stateDiagram-v2\n';
diagram += '[*] --> Moving\n';
diagram += '\n';
diagram += '%% comment after a blank line\n';
diagram += 'Moving --> Still\n';

stateDiagram.parser.parse(diagram);

const states = stateDiagram.parser.yy.getStates();

expect(states.size).toEqual(3);
});

it('should parse single % as normal syntax, not a comment', () => {
let diagram = '';
diagram += 'stateDiagram-v2\n';
diagram += '% not a comment\n';
diagram += 'Moving --> Still %inline\n';

stateDiagram.parser.parse(diagram);

const states = stateDiagram.parser.yy.getStates();

expect(states.size).toEqual(7);
expect(states.get('%inline')).toBeDefined();
});

it('should skip multiple consecutive comment lines', () => {
let diagram = '';
diagram += 'stateDiagram-v2\n';
diagram += '%% first comment\n';
diagram += '%% second comment\n';
diagram += '%% third comment\n';
diagram += '[*] --> Moving\n';

stateDiagram.parser.parse(diagram);

const states = stateDiagram.parser.yy.getStates();

expect(states.size).toEqual(2);
});

it('transitions should be preserved when separated by a comment', () => {
let diagram = '';
diagram += 'stateDiagram-v2\n';
diagram += 'Moving --> Crash\n';
diagram += '%% comment between\n';
diagram += 'Moving --> Still\n';

stateDiagram.parser.parse(diagram);

const relationships = stateDiagram.parser.yy.getRelations();
const states = stateDiagram.parser.yy.getStates();

expect(states.size).toEqual(3);
expect(relationships).toHaveLength(2);
expect(relationships[0].id1).toEqual('Moving');
expect(relationships[0].id2).toEqual('Crash');
expect(relationships[1].id1).toEqual('Moving');
expect(relationships[1].id2).toEqual('Still');
});

it('should correctly handle inline %% comments in state syntax', () => {
let diagram = '';
diagram += 'stateDiagram-v2\n';
diagram += '[*] --> Moving%% this is a comment\n';
diagram += 'Moving --> Still%Active\n';

stateDiagram.parser.parse(diagram);

const states = stateDiagram.parser.yy.getStates();

expect(states.get('Moving')).toBeDefined();
expect(states.get('Still%Active')).toBeDefined();
});
});
});
});
Expand Down
34 changes: 25 additions & 9 deletions packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@
// A special state for grabbing text up to the first comment/newline
%x LINE

// Function to split ID from inline %% comment
%{
function processId() {
const idx = yytext.indexOf('%%');
if (idx === 0) {
return false;
}
if (idx > 0) {
const before = yytext.slice(0, idx);
const after = yytext.slice(idx);

if (after) {
yy.lexer.unput(after);
}
yytext = before;
}
return true;
}
%}

%%

"click" return 'CLICK';
Expand All @@ -57,14 +77,11 @@
.*direction\s+RL[^\n]* return 'direction_rl';
.*direction\s+LR[^\n]* return 'direction_lr';

\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */{ /*console.log('Crap after close');*/ }

[\n]+ return 'NL';
[\s]+ /* skip all whitespace */
<ID,STATE,struct,LINE>((?!\n)\s)+ /* skip same-line whitespace */
<INITIAL,ID,STATE,struct,LINE>\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
<INITIAL,ID,STATE,struct,LINE>\%\%(?!\{)[^\n]* /* skip comments */
"scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; }
<SCALE>\d+ return 'WIDTH';
<SCALE>\s+"width" { this.popState(); }
Expand Down Expand Up @@ -110,13 +127,12 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili

<STATE>["] { /* console.log('Starting STATE_STRING'); */ this.pushState("STATE_STRING"); }
<STATE>\s*"as"\s+ { this.pushState('STATE_ID'); /* console.log('pushState(STATE_ID)'); */ return "AS"; }
<STATE_ID>[^\n\{]* { this.popState(); /* console.log('STATE_ID', yytext); */ return "ID"; }
<STATE_ID>[^\n\{]* { if (!processId()) return; this.popState(); /* console.log('STATE_ID', yytext); */ return "ID"; }
<STATE_STRING>["] { this.popState(); }
<STATE_STRING>[^"]* { /* console.log('Long description:', yytext); */ return "STATE_DESCR"; }
<STATE>[^\n\s\{]+ { /* console.log('COMPOSIT_STATE', yytext); */ return 'COMPOSIT_STATE'; }
<STATE>\n { this.popState(); }
<INITIAL,STATE>\{ { this.popState(); this.pushState('struct'); /* console.log('begin struct', yytext); */ return 'STRUCT_START'; }
<struct>\%\%(?!\{)[^\n]* /* skip comments inside state*/
<struct>\} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';} }
<struct>[\n] /* nothing */

Expand All @@ -127,8 +143,8 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
<FLOATING_NOTE>\s*"as"\s* { this.popState(); this.pushState('FLOATING_NOTE_ID'); return "AS"; }
<FLOATING_NOTE>["] /**/
<FLOATING_NOTE>[^"]* { /* console.log('Floating note text: ', yytext); */ return "NOTE_TEXT"; }
<FLOATING_NOTE_ID>[^\n]* { this.popState(); /* console.log('Floating note ID', yytext);*/ return "ID"; }
<NOTE_ID>\s*[^:\n\s\-]+ { this.popState(); this.pushState('NOTE_TEXT'); /*console.log('Got ID for note', yytext);*/ return 'ID'; }
<FLOATING_NOTE_ID>[^\n]* { if (!processId()) return; this.popState(); /* console.log('Floating note ID', yytext);*/ return "ID"; }
<NOTE_ID>\s*[^:\n\s\-]+ { if (!processId()) return; this.popState(); this.pushState('NOTE_TEXT'); /*console.log('Got ID for note', yytext);*/ return 'ID'; }
<NOTE_TEXT>\s*":"[^:\n;]+ { this.popState(); /* console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.substr(2).trim(); return 'NOTE_TEXT'; }
<NOTE_TEXT>[\s\S]*?"end note" { this.popState(); /* console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.slice(0,-8).trim(); return 'NOTE_TEXT'; }

Expand All @@ -138,7 +154,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
"hide empty description" { /* console.log('HIDE_EMPTY', yytext,'#'); */ return 'HIDE_EMPTY'; }

<INITIAL,struct>"[*]" { /* console.log('EDGE_STATE=',yytext); */ return 'EDGE_STATE'; }
<INITIAL,struct>[^:\n\s\-\{]+ { /* console.log('=>ID=',yytext); */ return 'ID'; }
<INITIAL,struct>[^:\n\s\-\{]+ { if (!processId()) return; /* console.log('=>ID=',yytext); */ return 'ID'; }
// <INITIAL,struct>\s*":"[^\+\->:\n;]+ { yytext = yytext.trim(); /* console.log('Descr = ', yytext); */ return 'DESCR'; }
<INITIAL,struct>\s*":"(?:[^:\n;]|":"[^:\n;])+ { yytext = yytext.trim(); /* console.log('Descr = ', yytext); */ return 'DESCR'; }

Expand Down
4 changes: 1 addition & 3 deletions packages/mermaid/src/docs/syntax/stateDiagram.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,7 @@ stateDiagram

## Comments

Comments can be entered within a state diagram chart, which will be ignored by the parser. Comments need to be on their
own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next
newline will be treated as a comment, including any diagram syntax
Comments can be entered within a state diagram chart, which will be ignored by the parser. Comments must be prefaced with `%%` (double percent signs) and any text after their start to the next newline will be treated as a comment, including any diagram syntax. They can be on their own line or at the end of a statement.

```mermaid
stateDiagram-v2
Expand Down
Loading