Handling of YAML blocks for sequence diagrams

This commit is contained in:
Knut Sveidqvist
2025-09-18 13:37:19 +02:00
parent 574d76c674
commit d1bec402b6
5 changed files with 84 additions and 7 deletions

View File

@@ -34,12 +34,17 @@ export const detectors: Record<string, DetectorRecord> = {};
* @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;
}

View File

@@ -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;
}
);

View File

@@ -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;

View File

@@ -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') {

View File

@@ -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