mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-07 06:14:14 +01:00
More robust type detection
This commit is contained in:
@@ -14,6 +14,7 @@ gantt
|
|||||||
gitgraph
|
gitgraph
|
||||||
gzipped
|
gzipped
|
||||||
handDrawn
|
handDrawn
|
||||||
|
headerless
|
||||||
kanban
|
kanban
|
||||||
marginx
|
marginx
|
||||||
marginy
|
marginy
|
||||||
|
|||||||
@@ -34,17 +34,33 @@ export const detectors: Record<string, DetectorRecord> = {};
|
|||||||
* @returns A graph definition key
|
* @returns A graph definition key
|
||||||
*/
|
*/
|
||||||
export const detectType = function (text: string, config?: MermaidConfig): string {
|
export const detectType = function (text: string, config?: MermaidConfig): string {
|
||||||
|
// Strip header prelude (front matter, directives, comments, blank lines) only at the top
|
||||||
|
// Then detect based on the first significant keyword to avoid false positives in labels/strings
|
||||||
|
const headerlessText = stripHeaderPrelude(text);
|
||||||
const cleanedText = text
|
const cleanedText = text
|
||||||
.replace(frontMatterRegex, '')
|
.replace(frontMatterRegex, '') // no-op after stripHeaderPrelude, but safe
|
||||||
.replace(directiveRegex, '')
|
.replace(directiveRegex, '') // defensive if any directive remains at the top
|
||||||
.replace(anyCommentRegex, '\n');
|
.replace(anyCommentRegex, '\n');
|
||||||
|
|
||||||
|
// Robust anchored check for sequence only (after header prelude);
|
||||||
|
// keep inside the loop so that detection before diagram registration still throws
|
||||||
|
if (detectors.flowchart.detector(headerlessText, config)) {
|
||||||
|
return 'flowchart';
|
||||||
|
}
|
||||||
|
if (detectors.sequence.detector(headerlessText, config)) {
|
||||||
|
return 'sequence';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detectors.classDiagram.detector(headerlessText, config)) {
|
||||||
|
return 'classDiagram';
|
||||||
|
}
|
||||||
|
if (detectors.class.detector(headerlessText, config)) {
|
||||||
|
return 'class';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to registered detectors in order
|
||||||
for (const [key, { detector }] of Object.entries(detectors)) {
|
for (const [key, { detector }] of Object.entries(detectors)) {
|
||||||
const diagram = detector(cleanedText, config);
|
const diagram = detector(cleanedText, config);
|
||||||
const isSequence = /sequenceDiagram/.exec(cleanedText);
|
|
||||||
if (isSequence) {
|
|
||||||
return 'sequence';
|
|
||||||
}
|
|
||||||
if (diagram) {
|
if (diagram) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
@@ -55,6 +71,36 @@ export const detectType = function (text: string, config?: MermaidConfig): strin
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Remove header prelude (front matter, directives, comments, blank lines) from the start only
|
||||||
|
function stripHeaderPrelude(input: string): string {
|
||||||
|
let s = input;
|
||||||
|
|
||||||
|
// Remove leading BOM if present
|
||||||
|
s = s.replace(/^\uFEFF/, '');
|
||||||
|
|
||||||
|
// Remove Jekyll-style front matter at the very top
|
||||||
|
s = s.replace(frontMatterRegex, '');
|
||||||
|
|
||||||
|
// Iteratively remove top-of-file blocks: directives, comment lines, and blank lines
|
||||||
|
// - Directives: %%{ ... }%% possibly multiline
|
||||||
|
// - Comment lines starting with %% or #
|
||||||
|
// - Blank lines
|
||||||
|
const headerPattern = /^(?:\s*%%{[\S\s]*?}%{2}\s*|\s*%%.*\r?\n|\s*#.*\r?\n|\s*\r?\n)*/;
|
||||||
|
const before = s;
|
||||||
|
s = s.replace(headerPattern, '');
|
||||||
|
|
||||||
|
// If nothing changed, return; otherwise, there could be another front matter after directives (rare)
|
||||||
|
if (s === before) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One extra pass for safety (handles stacked front matter blocks or multiple directives)
|
||||||
|
s = s.replace(frontMatterRegex, '');
|
||||||
|
s = s.replace(headerPattern, '');
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers lazy-loaded diagrams to Mermaid.
|
* Registers lazy-loaded diagrams to Mermaid.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -114,5 +114,21 @@ describe('diagram-orchestration', () => {
|
|||||||
)
|
)
|
||||||
).toBe('er');
|
).toBe('er');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should detect sequence/state even if config strings contain other diagram names', () => {
|
||||||
|
// sequenceDiagram with config string mentioning stateDiagram
|
||||||
|
expect(
|
||||||
|
detectType(
|
||||||
|
`---
|
||||||
|
title: Hello Title
|
||||||
|
config:
|
||||||
|
theme: base
|
||||||
|
themeVariables:
|
||||||
|
primaryColor: "#00ff00"
|
||||||
|
---
|
||||||
|
sequenceDiagram\nA->B: hi`
|
||||||
|
)
|
||||||
|
).toBe('sequence');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import architecture from '../diagrams/architecture/architectureDetector.js';
|
|||||||
import { registerLazyLoadedDiagrams } from './detectType.js';
|
import { registerLazyLoadedDiagrams } from './detectType.js';
|
||||||
import { registerDiagram } from './diagramAPI.js';
|
import { registerDiagram } from './diagramAPI.js';
|
||||||
import { treemap } from '../diagrams/treemap/detector.js';
|
import { treemap } from '../diagrams/treemap/detector.js';
|
||||||
import { frontMatterRegex } from './regexes.js';
|
|
||||||
import '../type.d.ts';
|
import '../type.d.ts';
|
||||||
|
|
||||||
let hasLoadedDiagrams = false;
|
let hasLoadedDiagrams = false;
|
||||||
@@ -69,21 +68,7 @@ export const addDiagrams = () => {
|
|||||||
init: () => null, // no op
|
init: () => null, // no op
|
||||||
},
|
},
|
||||||
(text) => {
|
(text) => {
|
||||||
const trimmed = text.trimStart();
|
return text.toLowerCase().trimStart().startsWith('---');
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
// Note that JS doesn't support the "\A" anchor, which means we can't use
|
// Note that JS doesn't support the "\A" anchor, which means we can't use
|
||||||
// multiline mode.
|
// multiline mode.
|
||||||
// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents
|
// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents
|
||||||
export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
|
export const frontMatterRegex =
|
||||||
|
/^\uFEFF?[\t ]*-{3}[\t ]*\r?\n([\S\s]*?)\r?\n[\t ]*-{3}[\t ]*(?:\r?\n|$)/;
|
||||||
|
|
||||||
export const directiveRegex =
|
export const directiveRegex =
|
||||||
/%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
/%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
||||||
|
|||||||
@@ -1821,7 +1821,7 @@ Alice->Bob: Hello Bob, how are you?`;
|
|||||||
expect(bounds.stopy).toBe(models.lastMessage().stopy + 10);
|
expect(bounds.stopy).toBe(models.lastMessage().stopy + 10);
|
||||||
expect(msgs.every((v) => v.wrap)).toBe(true);
|
expect(msgs.every((v) => v.wrap)).toBe(true);
|
||||||
});
|
});
|
||||||
it('should handle YAML front matter before sequenceDiagram', async () => {
|
it('should handle YAML front matter before sequenceDiagram XXX12', async () => {
|
||||||
const str = `---
|
const str = `---
|
||||||
title: Front matter title
|
title: Front matter title
|
||||||
config:
|
config:
|
||||||
|
|||||||
Reference in New Issue
Block a user