Skip to content

Commit

Permalink
fix(#4137): Cleanup comments before parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
sidharthv96 committed Mar 30, 2023
1 parent e4a2c74 commit 48d267c
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 21 deletions.
2 changes: 1 addition & 1 deletion docs/config/setup/modules/mermaidAPI.md
Expand Up @@ -95,7 +95,7 @@ mermaid.initialize(config);

#### Defined in

[mermaidAPI.ts:662](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L662)
[mermaidAPI.ts:659](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L659)

## Functions

Expand Down
4 changes: 3 additions & 1 deletion packages/mermaid/src/Diagram.ts
Expand Up @@ -5,6 +5,7 @@ import { detectType, getDiagramLoader } from './diagram-api/detectType';
import { extractFrontMatter } from './diagram-api/frontmatter';
import { UnknownDiagramError } from './errors';
import { DetailedError } from './utils';
import { cleanupComments } from './diagram-api/comments';

export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void;

Expand Down Expand Up @@ -43,7 +44,8 @@ export class Diagram {
// Similarly, we can't do this in getDiagramFromText() because some code
// calls diagram.db.clear(), which would reset anything set by
// extractFrontMatter().
this.parser.parse = (text: string) => originalParse(extractFrontMatter(text, this.db));
this.parser.parse = (text: string) =>
originalParse(cleanupComments(extractFrontMatter(text, this.db)));
this.parser.parser.yy = this.db;
if (diagram.init) {
diagram.init(cnf);
Expand Down
70 changes: 70 additions & 0 deletions packages/mermaid/src/diagram-api/comments.spec.ts
@@ -0,0 +1,70 @@
// tests to check that comments are removed

import { cleanupComments } from './comments';
import { describe, it, expect } from 'vitest';

describe('comments', () => {
it('should remove comments', () => {
const text = `
%% This is a comment
%% This is another comment
graph TD
A-->B
%% This is a comment
`;
expect(cleanupComments(text)).toMatchInlineSnapshot(`
"
graph TD
A-->B
"
`);
});

it('should keep init statements when removing comments', () => {
const text = `
%% This is a comment
%% This is another comment
%%{init: {'theme': 'forest'}}%%
%%{init: {'theme': 'space after ending'}}%%
graph TD
A-->B
B-->C
%% This is a comment
`;
expect(cleanupComments(text)).toMatchInlineSnapshot(`
"
%%{init: {'theme': 'forest'}}%%
%%{init: {'theme': 'space after ending'}}%%
graph TD
A-->B
B-->C
"
`);
});

it('should remove indented comments', () => {
const text = `
%% This is a comment
graph TD
A-->B
%% This is a comment
C-->D
`;
expect(cleanupComments(text)).toMatchInlineSnapshot(`
"
graph TD
A-->B
C-->D
"
`);
});
});
8 changes: 8 additions & 0 deletions packages/mermaid/src/diagram-api/comments.ts
@@ -0,0 +1,8 @@
/**
* Remove all lines starting with `%%` from the text that don't contain a `%%{`
* @param text - The text to remove comments from
* @returns cleaned text
*/
export const cleanupComments = (text: string): string => {
return text.replace(/^\s*%%(?!{)[^\n]+/gm, '');
};
4 changes: 0 additions & 4 deletions packages/mermaid/src/diagrams/er/parser/erDiagram.jison
Expand Up @@ -19,8 +19,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[\n]+ return 'NEWLINE';
\s+ /* skip whitespace */
[\s]+ return 'SPACE';
Expand All @@ -35,8 +33,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
<block>[A-Za-z_][A-Za-z0-9\-_\[\]\(\)]* return 'ATTRIBUTE_WORD'
<block>\"[^"]*\" return 'COMMENT';
<block>[\n]+ /* nothing */
<block>\%%(?!\{)[^\n]* /* skip comments in attribute block */
<block>[^\}]\%\%[^\n]* /* skip comments in attribute block */
<block>"}" { this.popState(); return 'BLOCK_STOP'; }
<block>. return yytext[0];

Expand Down
@@ -1,6 +1,7 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
import { cleanupComments } from '../../../diagram-api/comments';

setConfig({
securityLevel: 'strict',
Expand All @@ -13,7 +14,7 @@ describe('[Comments] when parsing', () => {
});

it('should handle comments', function () {
const res = flow.parser.parse('graph TD;\n%% Comment\n A-->B;');
const res = flow.parser.parse(cleanupComments('graph TD;\n%% Comment\n A-->B;'));

const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
Expand All @@ -28,7 +29,7 @@ describe('[Comments] when parsing', () => {
});

it('should handle comments at the start', function () {
const res = flow.parser.parse('%% Comment\ngraph TD;\n A-->B;');
const res = flow.parser.parse(cleanupComments('%% Comment\ngraph TD;\n A-->B;'));

const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
Expand All @@ -43,7 +44,7 @@ describe('[Comments] when parsing', () => {
});

it('should handle comments at the end', function () {
const res = flow.parser.parse('graph TD;\n A-->B\n %% Comment at the end\n');
const res = flow.parser.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();
Expand All @@ -58,7 +59,7 @@ describe('[Comments] when parsing', () => {
});

it('should handle comments at the end no trailing newline', function () {
const res = flow.parser.parse('graph TD;\n A-->B\n%% Comment');
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment'));

const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
Expand All @@ -73,7 +74,7 @@ describe('[Comments] when parsing', () => {
});

it('should handle comments at the end many trailing newlines', function () {
const res = flow.parser.parse('graph TD;\n A-->B\n%% Comment\n\n\n');
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n%% Comment\n\n\n'));

const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
Expand All @@ -88,7 +89,7 @@ describe('[Comments] when parsing', () => {
});

it('should handle no trailing newlines', function () {
const res = flow.parser.parse('graph TD;\n A-->B');
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B'));

const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
Expand All @@ -103,7 +104,7 @@ describe('[Comments] when parsing', () => {
});

it('should handle many trailing newlines', function () {
const res = flow.parser.parse('graph TD;\n A-->B\n\n');
const res = flow.parser.parse(cleanupComments('graph TD;\n A-->B\n\n'));

const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
Expand All @@ -118,7 +119,7 @@ describe('[Comments] when parsing', () => {
});

it('should handle a comment with blank rows in-between', function () {
const res = flow.parser.parse('graph TD;\n\n\n %% Comment\n A-->B;');
const res = flow.parser.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B;'));

const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
Expand All @@ -134,7 +135,9 @@ describe('[Comments] when parsing', () => {

it('should handle a comment with mermaid flowchart code in them', function () {
const res = flow.parser.parse(
'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line<br>edge comment|ro;\n A-->B;'
cleanupComments(
'graph TD;\n\n\n %% Test od>Odd shape]-->|Two line<br>edge comment|ro;\n A-->B;'
)
);

const vert = flow.parser.yy.getVertices();
Expand Down
2 changes: 0 additions & 2 deletions packages/mermaid/src/diagrams/flowchart/parser/flow.jison
Expand Up @@ -27,8 +27,6 @@
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
Expand Down
3 changes: 2 additions & 1 deletion packages/mermaid/src/diagrams/flowchart/parser/flow.spec.js
@@ -1,6 +1,7 @@
import flowDb from '../flowDb';
import flow from './flow';
import { setConfig } from '../../../config';
import { cleanupComments } from '../../../diagram-api/comments';

setConfig({
securityLevel: 'strict',
Expand All @@ -13,7 +14,7 @@ describe('parsing a flow chart', function () {
});

it('should handle a trailing whitespaces after statements', function () {
const res = flow.parser.parse('graph TD;\n\n\n %% Comment\n A-->B; \n B-->C;');
const res = flow.parser.parse(cleanupComments('graph TD;\n\n\n %% Comment\n A-->B; \n B-->C;'));

const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
Expand Down
3 changes: 0 additions & 3 deletions packages/mermaid/src/mermaidAPI.ts
Expand Up @@ -399,9 +399,6 @@ const render = async function (
// clean up text CRLFs
text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;;

// eslint-disable-next-line unicorn/better-regex
text = text.replace(/\s*%%[^{\ninit].*\n/gm, '\n'); // remove comments from text to avoid issues with parser

const idSelector = '#' + id;
const iFrameID = 'i' + id;
const iFrameID_selector = '#' + iFrameID;
Expand Down

0 comments on commit 48d267c

Please sign in to comment.