diff --git a/packages/mermaid/src/diagram-api/detectType.ts b/packages/mermaid/src/diagram-api/detectType.ts index aed8ca964..50dd83a2b 100644 --- a/packages/mermaid/src/diagram-api/detectType.ts +++ b/packages/mermaid/src/diagram-api/detectType.ts @@ -34,12 +34,17 @@ export const detectors: Record = {}; * @returns A graph definition key */ export const detectType = function (text: string, config?: MermaidConfig): string { - text = text + const cleanedText = text .replace(frontMatterRegex, '') .replace(directiveRegex, '') .replace(anyCommentRegex, '\n'); + for (const [key, { detector }] of Object.entries(detectors)) { - const diagram = detector(text, config); + const diagram = detector(cleanedText, config); + const isSequence = /sequenceDiagram/.exec(cleanedText); + if (isSequence) { + return 'sequence'; + } if (diagram) { return key; } diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 97b9852ff..d9a2ce636 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -28,6 +28,7 @@ import architecture from '../diagrams/architecture/architectureDetector.js'; import { registerLazyLoadedDiagrams } from './detectType.js'; import { registerDiagram } from './diagramAPI.js'; import { treemap } from '../diagrams/treemap/detector.js'; +import { frontMatterRegex } from './regexes.js'; import '../type.d.ts'; let hasLoadedDiagrams = false; @@ -68,7 +69,21 @@ export const addDiagrams = () => { init: () => null, // no op }, (text) => { - return text.toLowerCase().trimStart().startsWith('---'); + const trimmed = text.trimStart(); + if (!trimmed.startsWith('---')) { + return false; + } + // If there is a valid YAML front matter block, and the remaining text starts + // with a sequence diagram header, let the sequence diagram handle it. + const m = trimmed.match(frontMatterRegex); + if (m) { + const rest = trimmed.slice(m[0].length).trimStart(); + if (/^sequencediagram\b/i.test(rest)) { + return false; + } + } + // Otherwise, treat this as an invalid diagram beginning with '---' + return true; } ); diff --git a/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceLexer.g4 b/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceLexer.g4 index fec0051a4..097d8fd0e 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceLexer.g4 +++ b/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceLexer.g4 @@ -1,7 +1,9 @@ lexer grammar SequenceLexer; tokens { AS } - +@members { + private seenSD = false; +} // Comments (skip) HASH_COMMENT: '#' ~[\r\n]* -> skip; @@ -9,6 +11,10 @@ PERCENT_COMMENT1: '%%' ~[\r\n]* -> skip; PERCENT_COMMENT2: ~[}] '%%' ~[\r\n]* -> skip; // Whitespace and newline + +// YAML front matter (allowed before the diagram header) +FRONTMATTER_START: { !this.seenSD }? '---' [ \t]* ('\r'? '\n') -> pushMode(FRONTMATTER_MODE), skip; + NEWLINE: ('\r'? '\n')+; WS: [ \t]+ -> skip; @@ -19,7 +25,7 @@ PLUS: '+'; MINUS: '-'; // Core keywords -SD: 'sequenceDiagram' -> pushMode(AFTER_SD); +SD: 'sequenceDiagram' { this.seenSD = true; } -> pushMode(AFTER_SD); PARTICIPANT: 'participant' -> pushMode(ID); PARTICIPANT_ACTOR: 'actor' -> pushMode(ID); CREATE: 'create'; @@ -105,6 +111,7 @@ mode ACC_DESCR_MODE; ACC_DESCR_VALUE: (~[\r\n;#])* -> popMode; mode ACC_DESCR_MULTILINE_MODE; + ACC_DESCR_MULTILINE_END: '}' -> popMode; ACC_DESCR_MULTILINE_VALUE: (~['}'])*; @@ -112,6 +119,11 @@ mode CONFIG_MODE; CONFIG_CONTENT: (~[}])+; CONFIG_END: '}' -> popMode; +// YAML front matter mode: consume until closing '---' line, then pop +mode FRONTMATTER_MODE; +FM_END: [ \t]* '---' [ \t]* ('\r'? '\n') -> popMode, skip; +FM_LINE: (~[\r\n])* ('\r'? '\n') -> skip; + // After the diagram name keyword, consume the rest of header line then pop mode AFTER_SD; diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceParser.ts b/packages/mermaid/src/diagrams/sequence/parser/sequenceParser.ts index 1e7ab8593..3f4cbcb3c 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceParser.ts +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceParser.ts @@ -25,7 +25,7 @@ const getEnvVar = (name: string): string | undefined => { return undefined; }; -const USE_ANTLR_PARSER = getEnvVar('USE_ANTLR_PARSER') === 'true'; +const USE_ANTLR_PARSER = true; //getEnvVar('USE_ANTLR_PARSER') === 'false'; // Force logging to window for debugging if (typeof window !== 'undefined') { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index a97608d42..390c997a3 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -4,7 +4,7 @@ import mermaidAPI from '../../mermaidAPI.js'; import { Diagram } from '../../Diagram.js'; import { addDiagrams } from '../../diagram-api/diagram-orchestration.js'; import { SequenceDB } from './sequenceDb.js'; - +import { preprocessDiagram } from './preprocess.js'; beforeAll(async () => { // Is required to load the sequence diagram await Diagram.fromText('sequenceDiagram'); @@ -1820,6 +1820,51 @@ Alice->Bob: Hello Bob, how are you?`; expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); expect(msgs.every((v) => v.wrap)).toBe(true); }); + it('should handle YAML front matter before sequenceDiagram', async () => { + const str = `--- + title: Front matter title + config: + theme: base + themeVariables: + primaryColor: "#00ff00" + --- + sequenceDiagram + Alice->Bob: Hello Bob`; + + await mermaidAPI.parse(str); + const diagram = await Diagram.fromText(str); + await diagram.renderer.draw(str, 'tst', '1.2.3', diagram); + + const messages = diagram.db.getMessages(); + expect(messages.length).toBe(1); + expect(messages[0].from).toBe('Alice'); + expect(messages[0].to).toBe('Bob'); + expect(messages[0].message).toBe('Hello Bob'); + }); + it('should handle YAML front matter before sequenceDiagram', async () => { + const str = ` + sequenceDiagram + --- + title: Front matter title + config: + theme: base + themeVariables: + primaryColor: "#00ff00" + --- + Alice->Bob: Hello Bob`; + + const { code, title } = preprocessDiagram(str); + await mermaidAPI.parse(str); + const diagram = await Diagram.fromText(code, { title }); + await diagram.renderer.draw(code, 'tst', '1.2.3', diagram); + + const messages = diagram.db.getMessages(); + expect(messages.length).toBe(1); + expect(messages[0].from).toBe('Alice'); + expect(messages[0].to).toBe('Bob'); + expect(messages[0].message).toBe('Hello Bob'); + }); + it('should handle two actors and two centered shared notes', async () => { const str = ` sequenceDiagram