zombie-mermaid 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -5
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +96 -87
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
- package/src/__tests__/architecture-integration.test.ts +217 -0
- package/src/__tests__/ascii-ansi.test.ts +22 -0
- package/src/__tests__/c4-integration.test.ts +161 -0
- package/src/__tests__/c4-orientation.test.ts +162 -0
- package/src/__tests__/c4-upstream-integration.test.ts +211 -0
- package/src/__tests__/integration.test.ts +27 -0
- package/src/__tests__/interactivity-config.test.ts +8 -5
- package/src/__tests__/mcp-render-svg-tool.test.ts +3 -1
- package/src/__tests__/state-styling.test.ts +150 -0
- package/src/__tests__/svg-accessible-name-conformance.test.ts +4 -0
- package/src/__tests__/xychart-layout.test.ts +38 -0
- package/src/parser.ts +65 -7
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Ultra-fast, fully themeable, zero DOM dependencies. A maintained fork of [`beaut
|
|
|
18
18
|
[](https://github.com/dfadler/zombie-mermaid/actions/workflows/ci.yml)
|
|
19
19
|
[](https://codecov.io/gh/dfadler/zombie-mermaid)
|
|
20
20
|
[](#bundle-size)
|
|
21
|
-
[](https://socket.dev/npm/package/zombie-mermaid)
|
|
22
22
|
[](LICENSE)
|
|
23
23
|
|
|
24
24
|
</div>
|
|
@@ -47,9 +47,12 @@ Diagrams are essential for AI-assisted programming. When you're working with an
|
|
|
47
47
|
### Bundle Size
|
|
48
48
|
|
|
49
49
|
The **Bundle Size** badge above tracks the gzipped size of `zombie-mermaid`'s
|
|
50
|
-
main entry point (`dist/index.js`)
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
main entry point (`dist/index.js`) together with the `@zombie-mermaid/*`
|
|
51
|
+
workspace packages it imports (third-party dependencies such as ELK.js are not
|
|
52
|
+
counted), and updates automatically with every release — the same
|
|
53
|
+
badge/CI-tracked pattern already backing the coverage and CI status badges.
|
|
54
|
+
The standalone renderer packages under [Packages](#packages) get the same
|
|
55
|
+
measurement for their own dependency closure. Need ASCII rendering only? `import { renderMermaidASCII }
|
|
53
56
|
from 'zombie-mermaid/ascii'` skips [ELK.js](https://github.com/kieler/elkjs),
|
|
54
57
|
the layout engine the SVG renderer depends on, for a substantially smaller
|
|
55
58
|
bundle.
|
|
@@ -77,6 +80,8 @@ bun add zombie-mermaid
|
|
|
77
80
|
pnpm add zombie-mermaid
|
|
78
81
|
```
|
|
79
82
|
|
|
83
|
+
Only need one output format? `@zombie-mermaid/ascii-renderer` and `@zombie-mermaid/svg-renderer` are published standalone — see [Packages](#packages) below.
|
|
84
|
+
|
|
80
85
|
## Quick Start
|
|
81
86
|
|
|
82
87
|
### SVG Output
|
|
@@ -163,7 +168,7 @@ hyperlinks — off by default, since terminal and pager support varies (see
|
|
|
163
168
|
|
|
164
169
|
> 🧪 **Experimental — shipped to gauge interest, not a finished or best-effort implementation.** This is a first cut covering the common case; the tool surface may change based on feedback. Try it and [open an issue](https://github.com/dfadler/zombie-mermaid/issues/new) with what you'd want from it.
|
|
165
170
|
|
|
166
|
-
`zombie-mermaid mcp` starts a [Model Context Protocol](https://modelcontextprotocol.io/) server on stdio, exposing the library's rendering as
|
|
171
|
+
`zombie-mermaid mcp` starts a [Model Context Protocol](https://modelcontextprotocol.io/) server on stdio, exposing the library's rendering as six tools: `render_mermaid_svg`, `render_mermaid_ascii`, `check_mermaid_sequence_activations`, `fix_mermaid_sequence_activations`, `list_themes`, and `list_diagram_types`. `render_mermaid_svg` also takes optional `bg`/`fg` hex color overrides (applied on top of `theme`) and an optional `outputPath` that writes the SVG to disk and returns `{ saved, size }` instead of the SVG text. `outputPath` must end in `.svg` and resolve inside the server's working directory (the directory your MCP client launched it from); its parent directory must already exist, symlinks and non-regular files are refused, and an existing `.svg` file at that path is overwritten. Point an MCP client (Claude Desktop, Claude Code, or anything else that speaks MCP) at it to render Mermaid diagrams directly in a conversation, without shelling out to the CLI or importing the library.
|
|
167
172
|
|
|
168
173
|
Example Claude Desktop / Claude Code MCP server config:
|
|
169
174
|
|
|
@@ -212,6 +217,20 @@ the package.
|
|
|
212
217
|
|
|
213
218
|
---
|
|
214
219
|
|
|
220
|
+
## Packages
|
|
221
|
+
|
|
222
|
+
This repo is a pnpm-workspace monorepo. Most consumers only need the umbrella `zombie-mermaid` package above; internally it's built from workspace packages under `packages/`, all published under the `@zombie-mermaid` npm scope and version-locked with it:
|
|
223
|
+
|
|
224
|
+
- **[`@zombie-mermaid/ascii-renderer`](packages/ascii-renderer)** — the ASCII/Unicode renderer, published standalone for anyone who wants terminal output without `svg-renderer`'s `elkjs` dependency. The exact code `zombie-mermaid/ascii` re-exports.
|
|
225
|
+
[](https://www.npmjs.com/package/@zombie-mermaid/ascii-renderer) [](#bundle-size)
|
|
226
|
+
- **[`@zombie-mermaid/svg-renderer`](packages/svg-renderer)** — the ELK.js-backed SVG layout and rendering engine, also published standalone.
|
|
227
|
+
[](https://www.npmjs.com/package/@zombie-mermaid/svg-renderer) [](#bundle-size)
|
|
228
|
+
- `@zombie-mermaid/core`, `@zombie-mermaid/mermaid-parser`, `@zombie-mermaid/mcp` — shared types/theming, the diagram parsers, and the MCP server implementation, respectively. Published under the scope (so the names can't be squatted) and version-locked with the rest, but internal-only: no standalone support commitment beyond backing the umbrella and the two renderer packages above.
|
|
229
|
+
|
|
230
|
+
See [docs/decisions/monorepo-conversion.md](docs/decisions/monorepo-conversion.md) for the full package-split and publish-strategy history.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
215
234
|
## Docs
|
|
216
235
|
|
|
217
236
|
- [Guides](docs/guides/) — task-oriented walkthroughs: [browsing the samples](docs/guides/samples.md), [choosing a theme](docs/guides/theming.md), [using the HTML viewer](docs/guides/html-viewer.md)
|
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@zombie-mermaid/core"),t=require("@zombie-mermaid/mermaid-parser"),n=require("@zombie-mermaid/ascii-renderer"),r=require("@zombie-mermaid/svg-renderer");function i(e){let t=e.trim();return t.length>=2&&(t[0]===`"`&&t[t.length-1]===`"`||t[0]===`'`&&t[t.length-1]===`'`)?t.slice(1,-1):t}var a=`"graph <dir>"/"flowchart <dir>" (dir: TD, TB, LR, BT, RL), "stateDiagram-v2", "sequenceDiagram", "classDiagram", "erDiagram", "xychart-beta"`;function o(e){let t=e.trim().toLowerCase();if(/^sequence/.test(t))return`sequenceDiagram`;if(/^class/.test(t))return`classDiagram`;if(/^er/.test(t))return`erDiagram`;if(/^xychart/.test(t))return`xychart-beta`;if(/^state/.test(t))return`stateDiagram-v2`}var s=/^(?:[\w-]+@)?<?(?:-{2,}|={2,}|-\.+-?|~{3,})/;function c(e){let t=[];for(let n of e)n.forEach((e,n)=>{if(n===0&&t.length>0&&s.test(e.text)){let n=t[t.length-1];t[t.length-1]={text:`${n.text} ${e.text}`,line:n.line}}else t.push(e)});return t}function l(t){let n=(0,e.extractInitConfig)(t.split(`
|
|
2
|
-
`).map(e=>e.trim())),r=c((0,e.splitStatementsByLine)(t));if(r.length===0)throw Error(`Empty mermaid diagram`);let i=r[0],a=/^stateDiagram(-v2)?\s*$/i.test(i.text)?d(r):u(r);return a.initConfig=n,a}function u(t){let n=t[0];if(n===void 0)throw Error(`parseFlowchart called with no lines`);let r=n.text,s=r.match(/^(?:graph|flowchart)\s+(TD|TB|LR|BT|RL)\s*$/i);if(!s){let e=r.match(/^(?:graph|flowchart)\b\s*(.*)$/i);if(e){let t=e[1].trim();throw Error(t.length>0?`Line ${n.line}: Invalid direction "${t}" in header "${r}". Expected one of: TD, TB, LR, BT, RL.`:`Line ${n.line}: Missing direction in header "${r}". Expected e.g. "graph TD" or "flowchart LR" — one of: TD, TB, LR, BT, RL.`)}let t=o(r),i=t&&t.toLowerCase()!==r.trim().toLowerCase()?` Did you mean "${t}"?`:``;throw Error(`Line ${n.line}: Invalid mermaid header: "${r}".${i} Supported headers: ${a}.`)}let c={direction:(0,e.toDirection)(s[1]),nodes:new Map,edges:[],subgraphs:[],classDefs:new Map,classAssignments:new Map,nodeStyles:new Map,linkStyles:new Map,interactions:new Map},l=[];for(let n=1;n<t.length;n++){let r=t[n].text;if((0,e.tryApplyClassDef)(r,c)||(0,e.tryApplyClassAssignment)(r,c)||(0,e.tryApplyStyleStatement)(r,c))continue;if(/^click\s+/i.test(r)){C(r,c);continue}if(T(r,c))continue;let a=r.match(/^linkStyle\s+(default|[\d,\s]+)\s+(.+)$/);if(a){let t=a[1].trim(),n=(0,e.parseStyleProps)(a[2]);if(t==="default")c.linkStyles.set(`default`,{...c.linkStyles.get(`default`),...n});else{let e=t.split(`,`).map(e=>parseInt(e.trim(),10));for(let t of e)isNaN(t)||c.linkStyles.set(t,{...c.linkStyles.get(t),...n})}continue}let o=r.match(/^direction\s+(TD|TB|LR|BT|RL)\s*$/i);if(o&&l.length>0){l[l.length-1].direction=(0,e.toDirection)(o[1]);continue}let s=r.match(/^subgraph\s+(.+)$/);if(s){let t=s[1].trim(),n=t.match(/^([^\s[]+)\s*\[(.+)\]$/),r,a;n?(r=n[1],a=(0,e.normalizeBrTags)(i(n[2]))):(a=(0,e.normalizeBrTags)(i(t)),r=a.replace(/\s+/g,`_`).replace(/[^\p{L}\p{N}_-]/gu,``));let o={id:r,label:a,nodeIds:[],children:[]};l.push(o);continue}if(r===`end`){let e=l.pop();e&&(l.length>0?l[l.length-1].children.push(e):c.subgraphs.push(e));continue}k(r,c,l)}return c}function d(t){let n={direction:`TD`,nodes:new Map,edges:[],subgraphs:[],classDefs:new Map,classAssignments:new Map,nodeStyles:new Map,linkStyles:new Map,interactions:new Map},r=[],i=new Set,a=0,o=0;for(let s=1;s<t.length;s++){let c=t[s].text,l=c.match(/^direction\s+(TD|TB|LR|BT|RL)\s*$/i);if(l){r.length>0?r[r.length-1].direction=(0,e.toDirection)(l[1]):n.direction=(0,e.toDirection)(l[1]);continue}let u=c.match(/^linkStyle\s+(default|[\d,\s]+)\s+(.+)$/);if(u){let t=u[1].trim(),r=(0,e.parseStyleProps)(u[2]);if(t==="default")n.linkStyles.set(`default`,{...n.linkStyles.get(`default`),...r});else{let e=t.split(`,`).map(e=>parseInt(e.trim(),10));for(let t of e)isNaN(t)||n.linkStyles.set(t,{...n.linkStyles.get(t),...r})}continue}if(T(c,n))continue;let d=c.match(/^state\s+(?:"([^"]+)"\s+as\s+)?([\w\p{L}]+)\s*\{$/u);if(d){let e=d[1]??d[2],t=d[2],a={id:t,label:e,nodeIds:[],children:[]};r.push(a),i.add(t),n.nodes.delete(t);continue}if(c===`}`){let e=r.pop();e&&(r.length>0?r[r.length-1].children.push(e):n.subgraphs.push(e));continue}let m=c.match(/^state\s+"([^"]+)"\s+as\s+([\w\p{L}]+)\s*$/u);if(m){let t=(0,e.normalizeBrTags)(m[1]),i=m[2];f(n,r,{id:i,label:t,shape:`rounded`});continue}let h=c.match(/^(\[\*\]|[\w\p{L}-]+)\s*(?:([\w-]+)@)?-->\s*(\[\*\]|[\w\p{L}-]+)(?:\s*:\s*(.+))?$/u);if(h){let t=h[1],s=h[2],c=h[3],l=h[4]?.trim(),u=l?(0,e.normalizeBrTags)(l):void 0;t===`[*]`?(a++,t=`_start${a>1?a:``}`,f(n,r,{id:t,label:``,shape:`state-start`})):i.has(t)||p(n,r,t),c===`[*]`?(o++,c=`_end${o>1?o:``}`,f(n,r,{id:c,label:``,shape:`state-end`})):i.has(c)||p(n,r,c),n.edges.push({source:t,target:c,label:u,style:`solid`,hasArrowStart:!1,hasArrowEnd:!0,...s===void 0?{}:{id:s}});continue}let g=c.match(/^([\w\p{L}-]+)\s*:\s*(.+)$/u);if(g){let t=g[1];f(n,r,{id:t,label:(0,e.normalizeBrTags)(g[2].trim()),shape:`rounded`});continue}}return n}function f(e,t,n){if(e.nodes.has(n.id)||e.nodes.set(n.id,n),t.length>0){let e=t[t.length-1];e.nodeIds.includes(n.id)||e.nodeIds.push(n.id)}}function p(e,t,n){if(!e.nodes.has(n))f(e,t,{id:n,label:n,shape:`rounded`});else if(t.length>0){let e=t[t.length-1];e.nodeIds.includes(n)||e.nodeIds.push(n)}}var m=/^(<|o|x)?(-{2,}|={2,}|-\.+-|~{3,})(>|o|x)?(?:\|([^|]*)\|)?/,h=new Set([`--`,`==`]);function g(e){if(e===`o`)return`circle`;if(e===`x`)return`cross`}var _=/^(<|o|x)?(--|-\.|==)\s+(.+?)\s+(-{2,}[>ox]|={2,}[>ox]|\.+-[>ox]|-{3,}|={3,}|-\.+-)/,v=[{regex:/^([\w\p{L}-]+)\(\(\(((?:"[^"]*"|(?!\)\)\)).)+)\)\)\)/u,shape:`doublecircle`},{regex:/^([\w\p{L}-]+)\(\[((?:"[^"]*"|(?!\]\)).)+)\]\)/u,shape:`stadium`},{regex:/^([\w\p{L}-]+)\(\(((?:"[^"]*"|(?!\)\)).)+)\)\)/u,shape:`circle`},{regex:/^([\w\p{L}-]+)\[\[((?:"[^"]*"|(?!\]\]).)+)\]\]/u,shape:`subroutine`},{regex:/^([\w\p{L}-]+)\[\(((?:"[^"]*"|(?!\)\]).)+)\)\]/u,shape:`cylinder`},{regex:/^([\w\p{L}-]+)\[\/((?:"[^"]*"|(?![\\/]\]).)+)([\\/])\]/u,shape:e=>e===`\\`?`trapezoid`:`parallelogram`},{regex:/^([\w\p{L}-]+)\[\\((?:"[^"]*"|(?![\\/]\]).)+)([\\/])\]/u,shape:e=>e===`/`?`trapezoid-alt`:`parallelogram-alt`},{regex:/^([\w\p{L}-]+)>((?:"[^"]*"|(?!\]).)+)\]/u,shape:`asymmetric`},{regex:/^([\w\p{L}-]+)\{\{((?:"[^"]*"|(?!\}\}).)+)\}\}/u,shape:`hexagon`},{regex:/^([\w\p{L}-]+)\[((?:"[^"]*"|(?!\]).)+)\]/u,shape:`rectangle`},{regex:/^([\w\p{L}-]+)\(((?:"[^"]*"|(?!\)).)+)\)/u,shape:`rounded`},{regex:/^([\w\p{L}-]+)\{((?:"[^"]*"|(?!\}).)+)\}/u,shape:`diamond`}],y=/^([\w\p{L}]+(?:-[\w\p{L}]+)*)/u,b=/^([\w\p{L}-]+)(?=@\{)/u;function x(e){if(e.shape)return(0,t.resolveShapeName)(e.shape)??`rectangle`;if(e.icon!==void 0||e.img!==void 0)switch(e.form?.toLowerCase()){case`circle`:return`circle`;case`rounded`:return`rounded`;default:return`rectangle`}return`rectangle`}function S(e,t){return t.label!==void 0&&t.label.length>0?t.label:t.icon?t.icon:t.img?t.img:e}function C(t,n){(0,e.applyClickStatement)(t,n.interactions)}var w=/^([\w-]+)(?=@\{)/;function T(e,n){let r=e.match(w);if(!r||!n.edges.some(e=>e.id===r[1]))return!1;let i=(0,t.matchExpandedBlock)(e.slice(r[1].length));return i?(E(n,r[1],(0,t.parseExpandedMeta)(i.body)),!0):!1}function E(e,t,n){for(let r of e.edges)r.id===t&&(n.animate!==void 0&&(r.animate=n.animate.toLowerCase()!==`false`),n.animation!==void 0&&(r.animate=!0))}var D=/^:::([\w][\w-]*)/,O=/^([\w\p{L}-]+):::([\w][\w-]*)/u;function k(t,n,r){let i=t.trim(),a=A(i,n,r);if(!a||a.ids.length===0)return;i=a.remaining.trim();let o=a.ids;for(;i.length>0;){let t,a,s,c,l,u,d,f=i.match(/^([\w-]+)@(?=[-=<~ox])/);f&&(d=f[1],i=i.slice(f[0].length));let p=i.match(m),v=p?.[2],y=p?.[1],b=p?.[3];if(p&&v!==void 0&&!(y===void 0&&b===void 0&&h.has(v))){t=y!==void 0,l=g(y);let n=p[4]?.trim();c=n?(0,e.normalizeBrTags)(n):void 0,i=i.slice(p[0].length).trim(),a=P(v),s=b!==void 0,u=g(b)}else{let n=i.match(_);if(!n)break;t=!!n[1],l=g(n[1]);let r=n[3].trim();c=r?(0,e.normalizeBrTags)(r):void 0;let o=n[2],d=n[4];i=i.slice(n[0].length).trim(),a=F(o,d),u=g(d.slice(-1)),s=d.endsWith(`>`)||u!==void 0}let x=A(i,n,r);if(!x||x.ids.length===0)break;i=x.remaining.trim();for(let e of o)for(let r of x.ids)n.edges.push({source:e,target:r,label:c,style:a,hasArrowStart:t,hasArrowEnd:s,...l===void 0?{}:{startMarker:l},...u===void 0?{}:{endMarker:u},...d===void 0?{}:{id:d}});o=x.ids}}function A(e,t,n){let r=j(e,t,n);if(!r)return null;let i=[r.id],a=r.remaining.trim();for(;a.startsWith(`&`);){a=a.slice(1).trim();let e=j(a,t,n);if(!e)break;i.push(e.id),a=e.remaining.trim()}return{ids:i,remaining:a}}function j(n,r,i){let a=null,o=n,s,c=o.match(O);c&&(s=c[2],o=c[1]+o.slice(c[0].length));let l=o.match(b);if(l){let n=l[1],s=(0,t.matchExpandedBlock)(o.slice(n.length));if(s){let c=(0,t.parseExpandedMeta)(s.body);M(r,i,{id:n,label:(0,e.normalizeBrTags)(S(n,c)),shape:x(c)}),a=n,o=o.slice(n.length+s.length)}}if(a===null)for(let{regex:t,shape:n}of v){let s=o.match(t);if(s){a=s[1];let t=(0,e.normalizeBrTags)(s[2]),c=typeof n==`function`?n(s[3]??``):n;M(r,i,{id:a,label:t,shape:c}),o=o.slice(s[0].length);break}}if(a===null){let e=o.match(y);e&&(a=e[1],r.nodes.has(a)||M(r,i,{id:a,label:a,shape:`rectangle`}),o=o.slice(e[0].length))}if(a===null)return null;s&&r.classAssignments.set(a,s);let u=o.match(D);return u&&(r.classAssignments.set(a,u[1]),o=o.slice(u[0].length)),{id:a,remaining:o}}function M(e,t,n){e.nodes.has(n.id)||e.nodes.set(n.id,n),N(t,n.id)}function N(e,t){if(e.length>0){let n=e[e.length-1];n.nodeIds.includes(t)||n.nodeIds.push(t)}}function P(e){return e.startsWith(`~`)?`invisible`:e.includes(`.`)?`dotted`:e.startsWith(`=`)?`thick`:`solid`}function F(e,t){return e.includes(`.`)||t.includes(`.`)?`dotted`:e.startsWith(`=`)||t.startsWith(`=`)?`thick`:`solid`}Object.defineProperty(exports,"DEFAULTS",{enumerable:!0,get:function(){return e.DEFAULTS}}),Object.defineProperty(exports,"THEMES",{enumerable:!0,get:function(){return e.THEMES}}),Object.defineProperty(exports,"createLayoutCache",{enumerable:!0,get:function(){return r.createLayoutCache}}),Object.defineProperty(exports,"fromShikiTheme",{enumerable:!0,get:function(){return e.fromShikiTheme}}),exports.parseMermaid=l,Object.defineProperty(exports,"renderMermaid",{enumerable:!0,get:function(){return r.renderMermaid}}),Object.defineProperty(exports,"renderMermaidASCII",{enumerable:!0,get:function(){return n.renderMermaidASCII}}),Object.defineProperty(exports,"renderMermaidAscii",{enumerable:!0,get:function(){return n.renderMermaidAscii}}),Object.defineProperty(exports,"renderMermaidSVG",{enumerable:!0,get:function(){return r.renderMermaidSVG}}),Object.defineProperty(exports,"renderMermaidSVGAsync",{enumerable:!0,get:function(){return r.renderMermaidSVGAsync}}),Object.defineProperty(exports,"renderMermaidSync",{enumerable:!0,get:function(){return r.renderMermaidSync}}),Object.defineProperty(exports,"themeCssVariables",{enumerable:!0,get:function(){return r.themeCssVariables}});
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@zombie-mermaid/core"),t=require("@zombie-mermaid/mermaid-parser"),n=require("@zombie-mermaid/ascii-renderer"),r=require("@zombie-mermaid/svg-renderer");function i(e){let t=e.trim();return t.length>=2&&(t[0]===`"`&&t[t.length-1]===`"`||t[0]===`'`&&t[t.length-1]===`'`)?t.slice(1,-1):t}var a=`"graph <dir>"/"flowchart <dir>" (dir: TD, TB, LR, BT, RL), "stateDiagram-v2", "sequenceDiagram", "classDiagram", "erDiagram", "xychart-beta", "C4Context"/"C4Container"/"C4Component"/"C4Dynamic"/"C4Deployment", "architecture-beta"`;function o(e){let t=e.trim().toLowerCase();if(/^sequence/.test(t))return`sequenceDiagram`;if(/^class/.test(t))return`classDiagram`;if(/^er/.test(t))return`erDiagram`;if(/^xychart/.test(t))return`xychart-beta`;if(/^state/.test(t))return`stateDiagram-v2`}var s=/^(?:[\w-]+@)?<?(?:-{2,}|={2,}|-\.+-?|~{3,})/;function c(e){let t=[];for(let n of e)n.forEach((e,n)=>{if(n===0&&t.length>0&&s.test(e.text)){let n=t[t.length-1];t[t.length-1]={text:`${n.text} ${e.text}`,line:n.line}}else t.push(e)});return t}function l(t){let n=(0,e.extractInitConfig)(t.split(`
|
|
2
|
+
`).map(e=>e.trim())),r=c((0,e.splitStatementsByLine)(t));if(r.length===0)throw Error(`Empty mermaid diagram`);let i=r[0],a=/^stateDiagram(-v2)?\s*$/i.test(i.text)?d(r):u(r);return a.initConfig=n,a}function u(t){let n=t[0];if(n===void 0)throw Error(`parseFlowchart called with no lines`);let r=n.text,s=r.match(/^(?:graph|flowchart)\s+(TD|TB|LR|BT|RL)\s*$/i);if(!s){let e=r.match(/^(?:graph|flowchart)\b\s*(.*)$/i);if(e){let t=e[1].trim();throw Error(t.length>0?`Line ${n.line}: Invalid direction "${t}" in header "${r}". Expected one of: TD, TB, LR, BT, RL.`:`Line ${n.line}: Missing direction in header "${r}". Expected e.g. "graph TD" or "flowchart LR" — one of: TD, TB, LR, BT, RL.`)}let t=o(r),i=t&&t.toLowerCase()!==r.trim().toLowerCase()?` Did you mean "${t}"?`:``;throw Error(`Line ${n.line}: Invalid mermaid header: "${r}".${i} Supported headers: ${a}.`)}let c={direction:(0,e.toDirection)(s[1]),nodes:new Map,edges:[],subgraphs:[],classDefs:new Map,classAssignments:new Map,nodeStyles:new Map,linkStyles:new Map,interactions:new Map},l=[];for(let n=1;n<t.length;n++){let r=t[n].text;if((0,e.tryApplyClassDef)(r,c)||(0,e.tryApplyClassAssignment)(r,c)||(0,e.tryApplyStyleStatement)(r,c))continue;if(/^click\s+/i.test(r)){w(r,c);continue}if(E(r,c))continue;let a=r.match(/^linkStyle\s+(default|[\d,\s]+)\s+(.+)$/);if(a){let t=a[1].trim(),n=(0,e.parseStyleProps)(a[2]);if(t==="default")c.linkStyles.set(`default`,{...c.linkStyles.get(`default`),...n});else{let e=t.split(`,`).map(e=>parseInt(e.trim(),10));for(let t of e)isNaN(t)||c.linkStyles.set(t,{...c.linkStyles.get(t),...n})}continue}let o=r.match(/^direction\s+(TD|TB|LR|BT|RL)\s*$/i);if(o&&l.length>0){l[l.length-1].direction=(0,e.toDirection)(o[1]);continue}let s=r.match(/^subgraph\s+(.+)$/);if(s){let t=s[1].trim(),n=t.match(/^([^\s[]+)\s*\[(.+)\]$/),r,a;n?(r=n[1],a=(0,e.normalizeBrTags)(i(n[2]))):(a=(0,e.normalizeBrTags)(i(t)),r=a.replace(/\s+/g,`_`).replace(/[^\p{L}\p{N}_-]/gu,``));let o={id:r,label:a,nodeIds:[],children:[]};l.push(o);continue}if(r===`end`){let e=l.pop();e&&(l.length>0?l[l.length-1].children.push(e):c.subgraphs.push(e));continue}A(r,c,l)}return c}function d(t){let n={direction:`TD`,nodes:new Map,edges:[],subgraphs:[],classDefs:new Map,classAssignments:new Map,nodeStyles:new Map,linkStyles:new Map,interactions:new Map},r=[],i=new Set,a=0,o=0;for(let s=1;s<t.length;s++){let c=t[s].text;if((0,e.tryApplyClassDef)(c,n)||(0,e.tryApplyClassAssignment)(c,n)||(0,e.tryApplyStyleStatement)(c,n))continue;let l=c.match(/^direction\s+(TD|TB|LR|BT|RL)\s*$/i);if(l){r.length>0?r[r.length-1].direction=(0,e.toDirection)(l[1]):n.direction=(0,e.toDirection)(l[1]);continue}let u=c.match(/^linkStyle\s+(default|[\d,\s]+)\s+(.+)$/);if(u){let t=u[1].trim(),r=(0,e.parseStyleProps)(u[2]);if(t==="default")n.linkStyles.set(`default`,{...n.linkStyles.get(`default`),...r});else{let e=t.split(`,`).map(e=>parseInt(e.trim(),10));for(let t of e)isNaN(t)||n.linkStyles.set(t,{...n.linkStyles.get(t),...r})}continue}if(E(c,n))continue;let d=c.match(/^state\s+(?:"([^"]+)"\s+as\s+)?([\w\p{L}]+)\s*\{$/u);if(d){let e=d[1]??d[2],t=d[2],a={id:t,label:e,nodeIds:[],children:[]};r.push(a),i.add(t),n.nodes.delete(t);continue}if(c===`}`){let e=r.pop();e&&(r.length>0?r[r.length-1].children.push(e):n.subgraphs.push(e));continue}let h=c.match(/^state\s+"([^"]+)"\s+as\s+([\w\p{L}]+)\s*$/u);if(h){let t=(0,e.normalizeBrTags)(h[1]),i=h[2];f(n,r,i,t);continue}let g=c.match(/^(\[\*\]|[\w\p{L}-]+)(?::::([\w][\w-]*))?\s*(?:([\w-]+)@)?-->\s*(\[\*\]|[\w\p{L}-]+)(?::::([\w][\w-]*))?(?:\s*:\s*(.+))?$/u);if(g){let t=g[1],s=g[2],c=g[3],l=g[4],u=g[5],d=g[6]?.trim(),f=d?(0,e.normalizeBrTags)(d):void 0;t===`[*]`?(a++,t=`_start${a>1?a:``}`,p(n,r,{id:t,label:``,shape:`state-start`})):i.has(t)||m(n,r,t),l===`[*]`?(o++,l=`_end${o>1?o:``}`,p(n,r,{id:l,label:``,shape:`state-end`})):i.has(l)||m(n,r,l),s!==void 0&&n.classAssignments.set(t,s),u!==void 0&&n.classAssignments.set(l,u),n.edges.push({source:t,target:l,label:f,style:`solid`,hasArrowStart:!1,hasArrowEnd:!0,...c===void 0?{}:{id:c}});continue}let _=c.match(/^([\w\p{L}-]+):::([\w][\w-]*)\s*$/u);if(_){let e=_[1];i.has(e)||m(n,r,e),n.classAssignments.set(e,_[2]);continue}let v=c.match(/^([\w\p{L}-]+)\s*:\s*(.+)$/u);if(v){let t=v[1];f(n,r,t,(0,e.normalizeBrTags)(v[2].trim()));continue}}for(let e of n.classAssignments.keys())!n.nodes.has(e)&&!i.has(e)&&m(n,[],e);return n}function f(e,t,n,r){let i=e.nodes.get(n);i&&(i.label=r),p(e,t,{id:n,label:r,shape:`rounded`})}function p(e,t,n){if(e.nodes.has(n.id)||e.nodes.set(n.id,n),t.length>0){let e=t[t.length-1];e.nodeIds.includes(n.id)||e.nodeIds.push(n.id)}}function m(e,t,n){if(!e.nodes.has(n))p(e,t,{id:n,label:n,shape:`rounded`});else if(t.length>0){let e=t[t.length-1];e.nodeIds.includes(n)||e.nodeIds.push(n)}}var h=/^(<|o|x)?(-{2,}|={2,}|-\.+-|~{3,})(>|o|x)?(?:\|([^|]*)\|)?/,g=new Set([`--`,`==`]);function _(e){if(e===`o`)return`circle`;if(e===`x`)return`cross`}var v=/^(<|o|x)?(--|-\.|==)\s+(.+?)\s+(-{2,}[>ox]|={2,}[>ox]|\.+-[>ox]|-{3,}|={3,}|-\.+-)/,y=[{regex:/^([\w\p{L}-]+)\(\(\(((?:"[^"]*"|(?!\)\)\)).)+)\)\)\)/u,shape:`doublecircle`},{regex:/^([\w\p{L}-]+)\(\[((?:"[^"]*"|(?!\]\)).)+)\]\)/u,shape:`stadium`},{regex:/^([\w\p{L}-]+)\(\(((?:"[^"]*"|(?!\)\)).)+)\)\)/u,shape:`circle`},{regex:/^([\w\p{L}-]+)\[\[((?:"[^"]*"|(?!\]\]).)+)\]\]/u,shape:`subroutine`},{regex:/^([\w\p{L}-]+)\[\(((?:"[^"]*"|(?!\)\]).)+)\)\]/u,shape:`cylinder`},{regex:/^([\w\p{L}-]+)\[\/((?:"[^"]*"|(?![\\/]\]).)+)([\\/])\]/u,shape:e=>e===`\\`?`trapezoid`:`parallelogram`},{regex:/^([\w\p{L}-]+)\[\\((?:"[^"]*"|(?![\\/]\]).)+)([\\/])\]/u,shape:e=>e===`/`?`trapezoid-alt`:`parallelogram-alt`},{regex:/^([\w\p{L}-]+)>((?:"[^"]*"|(?!\]).)+)\]/u,shape:`asymmetric`},{regex:/^([\w\p{L}-]+)\{\{((?:"[^"]*"|(?!\}\}).)+)\}\}/u,shape:`hexagon`},{regex:/^([\w\p{L}-]+)\[((?:"[^"]*"|(?!\]).)+)\]/u,shape:`rectangle`},{regex:/^([\w\p{L}-]+)\(((?:"[^"]*"|(?!\)).)+)\)/u,shape:`rounded`},{regex:/^([\w\p{L}-]+)\{((?:"[^"]*"|(?!\}).)+)\}/u,shape:`diamond`}],b=/^([\w\p{L}]+(?:-[\w\p{L}]+)*)/u,x=/^([\w\p{L}-]+)(?=@\{)/u;function S(e){if(e.shape)return(0,t.resolveShapeName)(e.shape)??`rectangle`;if(e.icon!==void 0||e.img!==void 0)switch(e.form?.toLowerCase()){case`circle`:return`circle`;case`rounded`:return`rounded`;default:return`rectangle`}return`rectangle`}function C(e,t){return t.label!==void 0&&t.label.length>0?t.label:t.icon?t.icon:t.img?t.img:e}function w(t,n){(0,e.applyClickStatement)(t,n.interactions)}var T=/^([\w-]+)(?=@\{)/;function E(e,n){let r=e.match(T);if(!r||!n.edges.some(e=>e.id===r[1]))return!1;let i=(0,t.matchExpandedBlock)(e.slice(r[1].length));return i?(D(n,r[1],(0,t.parseExpandedMeta)(i.body)),!0):!1}function D(e,t,n){for(let r of e.edges)r.id===t&&(n.animate!==void 0&&(r.animate=n.animate.toLowerCase()!==`false`),n.animation!==void 0&&(r.animate=!0))}var O=/^:::([\w][\w-]*)/,k=/^([\w\p{L}-]+):::([\w][\w-]*)/u;function A(t,n,r){let i=t.trim(),a=j(i,n,r);if(!a||a.ids.length===0)return;i=a.remaining.trim();let o=a.ids;for(;i.length>0;){let t,a,s,c,l,u,d,f=i.match(/^([\w-]+)@(?=[-=<~ox])/);f&&(d=f[1],i=i.slice(f[0].length));let p=i.match(h),m=p?.[2],y=p?.[1],b=p?.[3];if(p&&m!==void 0&&!(y===void 0&&b===void 0&&g.has(m))){t=y!==void 0,l=_(y);let n=p[4]?.trim();c=n?(0,e.normalizeBrTags)(n):void 0,i=i.slice(p[0].length).trim(),a=F(m),s=b!==void 0,u=_(b)}else{let n=i.match(v);if(!n)break;t=!!n[1],l=_(n[1]);let r=n[3].trim();c=r?(0,e.normalizeBrTags)(r):void 0;let o=n[2],d=n[4];i=i.slice(n[0].length).trim(),a=I(o,d),u=_(d.slice(-1)),s=d.endsWith(`>`)||u!==void 0}let x=j(i,n,r);if(!x||x.ids.length===0)break;i=x.remaining.trim();for(let e of o)for(let r of x.ids)n.edges.push({source:e,target:r,label:c,style:a,hasArrowStart:t,hasArrowEnd:s,...l===void 0?{}:{startMarker:l},...u===void 0?{}:{endMarker:u},...d===void 0?{}:{id:d}});o=x.ids}}function j(e,t,n){let r=M(e,t,n);if(!r)return null;let i=[r.id],a=r.remaining.trim();for(;a.startsWith(`&`);){a=a.slice(1).trim();let e=M(a,t,n);if(!e)break;i.push(e.id),a=e.remaining.trim()}return{ids:i,remaining:a}}function M(n,r,i){let a=null,o=n,s,c=o.match(k);c&&(s=c[2],o=c[1]+o.slice(c[0].length));let l=o.match(x);if(l){let n=l[1],s=(0,t.matchExpandedBlock)(o.slice(n.length));if(s){let c=(0,t.parseExpandedMeta)(s.body);N(r,i,{id:n,label:(0,e.normalizeBrTags)(C(n,c)),shape:S(c)}),a=n,o=o.slice(n.length+s.length)}}if(a===null)for(let{regex:t,shape:n}of y){let s=o.match(t);if(s){a=s[1];let t=(0,e.normalizeBrTags)(s[2]),c=typeof n==`function`?n(s[3]??``):n;N(r,i,{id:a,label:t,shape:c}),o=o.slice(s[0].length);break}}if(a===null){let e=o.match(b);e&&(a=e[1],r.nodes.has(a)||N(r,i,{id:a,label:a,shape:`rectangle`}),o=o.slice(e[0].length))}if(a===null)return null;s&&r.classAssignments.set(a,s);let u=o.match(O);return u&&(r.classAssignments.set(a,u[1]),o=o.slice(u[0].length)),{id:a,remaining:o}}function N(e,t,n){e.nodes.has(n.id)||e.nodes.set(n.id,n),P(t,n.id)}function P(e,t){if(e.length>0){let n=e[e.length-1];n.nodeIds.includes(t)||n.nodeIds.push(t)}}function F(e){return e.startsWith(`~`)?`invisible`:e.includes(`.`)?`dotted`:e.startsWith(`=`)?`thick`:`solid`}function I(e,t){return e.includes(`.`)||t.includes(`.`)?`dotted`:e.startsWith(`=`)||t.startsWith(`=`)?`thick`:`solid`}Object.defineProperty(exports,"DEFAULTS",{enumerable:!0,get:function(){return e.DEFAULTS}}),Object.defineProperty(exports,"THEMES",{enumerable:!0,get:function(){return e.THEMES}}),Object.defineProperty(exports,"createLayoutCache",{enumerable:!0,get:function(){return r.createLayoutCache}}),Object.defineProperty(exports,"fromShikiTheme",{enumerable:!0,get:function(){return e.fromShikiTheme}}),exports.parseMermaid=l,Object.defineProperty(exports,"renderMermaid",{enumerable:!0,get:function(){return r.renderMermaid}}),Object.defineProperty(exports,"renderMermaidASCII",{enumerable:!0,get:function(){return n.renderMermaidASCII}}),Object.defineProperty(exports,"renderMermaidAscii",{enumerable:!0,get:function(){return n.renderMermaidAscii}}),Object.defineProperty(exports,"renderMermaidSVG",{enumerable:!0,get:function(){return r.renderMermaidSVG}}),Object.defineProperty(exports,"renderMermaidSVGAsync",{enumerable:!0,get:function(){return r.renderMermaidSVGAsync}}),Object.defineProperty(exports,"renderMermaidSync",{enumerable:!0,get:function(){return r.renderMermaidSync}}),Object.defineProperty(exports,"themeCssVariables",{enumerable:!0,get:function(){return r.themeCssVariables}});
|
|
3
3
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/parser.ts"],"sourcesContent":["import type {\n MermaidGraph,\n MermaidNode,\n MermaidSubgraph,\n NodeShape,\n EdgeStyle,\n Statement,\n} from '@zombie-mermaid/core'\nimport {\n toDirection,\n normalizeBrTags,\n splitStatementsByLine,\n extractInitConfig,\n applyClickStatement as applyClickStatementShared,\n parseStyleProps,\n tryApplyClassDef,\n tryApplyClassAssignment,\n tryApplyStyleStatement,\n} from '@zombie-mermaid/core'\n\nimport {\n matchExpandedBlock,\n parseExpandedMeta,\n resolveShapeName,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { ExpandedNodeMeta } from '@zombie-mermaid/mermaid-parser'\n/** Remove a single layer of matching wrapping quotes (`\"…\"` or `'…'`). */\nfunction stripWrappingQuotes(s: string): string {\n const t = s.trim()\n if (\n t.length >= 2 &&\n ((t[0] === '\"' && t[t.length - 1] === '\"') ||\n (t[0] === \"'\" && t[t.length - 1] === \"'\"))\n ) {\n return t.slice(1, -1)\n }\n return t\n}\n\n// ============================================================================\n// Mermaid parser — flowcharts and state diagrams\n//\n// Supports:\n// Flowcharts: graph TD / flowchart LR\n// State diagrams: stateDiagram-v2\n//\n// Line-by-line regex approach — the grammar is regular enough\n// that we don't need a grammar generator or full parser combinator.\n// ============================================================================\n\n// `toDirection` used to be defined here, with this file as its only caller.\n// #624 gave it a second caller — `packages/mermaid-parser/src/er/parser.ts`,\n// which cannot import this umbrella file without creating a cycle\n// (mermaid-parser -> umbrella -> mermaid-parser) — so it moved to\n// `packages/core/src/direction.ts` alongside `isDirection`, for the same\n// reason that one moved under #625. Re-exported here so existing importers\n// of `toDirection` from `./parser.ts` (this file's own tests included) keep\n// working unchanged.\nexport { toDirection } from '@zombie-mermaid/core'\n\n/**\n * All diagram-type headers this library recognizes, for the \"supported\n * headers\" list in the invalid-header error below. Kept in one place so a\n * newly-added diagram type doesn't get forgotten in the error message the\n * way sequence/class/ER/xychart were before issue #541.\n */\nconst SUPPORTED_HEADERS =\n '\"graph <dir>\"/\"flowchart <dir>\" (dir: TD, TB, LR, BT, RL), \"stateDiagram-v2\", \"sequenceDiagram\", \"classDiagram\", \"erDiagram\", \"xychart-beta\"'\n\n/**\n * Best-guess canonical header for a header line that looks like an attempt\n * at one of the *other* diagram types this library supports, but didn't\n * match `detectDiagramType`'s exact-match regex (a typo, extra trailing\n * text, wrong case pattern, etc.) and so fell through to this flowchart\n * parser as the default. Returns undefined when `header` doesn't look like\n * any of those — in which case the generic \"supported headers\" list in the\n * thrown error is the best we can do.\n *\n * This only recognizes the four *other* multi-word headers\n * (sequenceDiagram/classDiagram/erDiagram/xychart-beta/stateDiagram-v2);\n * \"graph\"/\"flowchart\" typos are handled by the direction-specific message\n * below instead, since those already matched the diagram-type keyword and\n * just have a bad or missing direction token.\n */\nfunction suggestedHeaderFor(header: string): string | undefined {\n const lower = header.trim().toLowerCase()\n if (/^sequence/.test(lower)) return 'sequenceDiagram'\n if (/^class/.test(lower)) return 'classDiagram'\n if (/^er/.test(lower)) return 'erDiagram'\n if (/^xychart/.test(lower)) return 'xychart-beta'\n if (/^state/.test(lower)) return 'stateDiagram-v2'\n return undefined\n}\n\n/**\n * Matches the start of a statement that continues the *previous* one's edge\n * chain rather than beginning a new one: Mermaid lets a vertex-chain\n * statement break across lines, with the link operator leading the next\n * line — e.g.\n *\n * start([Start])\n * ==> green([Change some code])\n * ==> finish([Finish])\n *\n * is one chained statement (`start ==> green ==> finish`), identical to\n * writing it on one line. `splitStatementsByLine` only knows about newlines\n * and `;` as separators, so without this it hands `parseFlowchart` three\n * unrelated-looking statements — the continuation lines start with a bare\n * arrow and no node group, so `parseEdgeLine` can't find a source node and\n * drops them entirely (see issue mermaid-js/mermaid#6049's repro, reported\n * against this parser).\n *\n * Covers every arrow opener `parseEdgeLine` itself recognizes (ARROW_REGEX /\n * TEXT_ARROW_REGEX below): a solid/thick run (`--`, `===`), a dotted run\n * (`-.`, `-.-`), a `~~~` run, each optionally preceded by a `<` marker and/or\n * an edge id (`e1@-->`).\n *\n * Deliberately excludes the `o`/`x` start markers ARROW_REGEX also accepts\n * (`o--o`, `x--x`): unlike `<`, both are also valid bare node ids, so a\n * first-body-line statement like `x-->Y` would otherwise be misread as an\n * `x`-marked continuation of the header and merged into it, corrupting the\n * header line (`flowchart TD x-->Y`) instead of parsing `x` as its own node.\n * A genuine marked-start continuation line is rare enough that losing it is\n * the safer tradeoff.\n */\nconst CONTINUATION_START_REGEX = /^(?:[\\w-]+@)?<?(?:-{2,}|={2,}|-\\.+-?|~{3,})/\n\n/**\n * Rejoin a continuation statement onto the one it continues.\n *\n * `splitStatementsByLine` groups statements by their originating physical\n * line, which is what makes this safe: only the *first* statement in a\n * group is eligible to merge backward into the previous group's last\n * statement (a genuine multi-line continuation, see\n * `CONTINUATION_START_REGEX`). A later statement in the same group got\n * there via an explicit `;` on that line (`A --> B; --> C`) — that boundary\n * must stay a boundary, or `--> C` would wrongly become part of the chain\n * instead of the source-less fragment it actually is.\n */\nfunction mergeContinuationLines(groups: Statement[][]): Statement[] {\n const merged: Statement[] = []\n for (const group of groups) {\n group.forEach((statement, index) => {\n if (\n index === 0 &&\n merged.length > 0 &&\n CONTINUATION_START_REGEX.test(statement.text)\n ) {\n // Keep the *first* line's number: a merged statement is reported at\n // the line its logical statement began, not the continuation line.\n const prev = merged[merged.length - 1]!\n merged[merged.length - 1] = {\n text: `${prev.text} ${statement.text}`,\n line: prev.line,\n }\n } else {\n merged.push(statement)\n }\n })\n }\n return merged\n}\n\n/**\n * Parse Mermaid text into a logical graph structure.\n * Auto-detects diagram type (flowchart or state diagram).\n * Throws on invalid/unsupported input.\n */\nexport function parseMermaid(text: string): MermaidGraph {\n /*\n * Init directives must be read from the raw lines: `%%{init: ...}%%` begins\n * with `%%`, so splitStatementsByLine — which treats `%%` as a comment and\n * truncates the line there — would otherwise discard it along with real\n * comments before it could be seen.\n */\n const initConfig = extractInitConfig(text.split('\\n').map((l) => l.trim()))\n\n const lines = mergeContinuationLines(splitStatementsByLine(text))\n\n if (lines.length === 0) {\n throw new Error('Empty mermaid diagram')\n }\n\n // Detect diagram type from header\n const headerStmt = lines[0]!\n\n // State diagram: \"stateDiagram-v2\" or \"stateDiagram\"\n const graph = /^stateDiagram(-v2)?\\s*$/i.test(headerStmt.text)\n ? parseStateDiagram(lines)\n : parseFlowchart(lines)\n\n graph.initConfig = initConfig\n return graph\n}\n\n// ============================================================================\n// Flowchart parser\n// ============================================================================\n\nfunction parseFlowchart(lines: Statement[]): MermaidGraph {\n // parseFlowchart is only ever invoked by parseMermaid, which has already\n // verified `lines` is non-empty — but that invariant isn't visible to the\n // type checker across the function boundary, so validate it here instead\n // of asserting past it.\n const headerStmt = lines[0]\n if (headerStmt === undefined) {\n /* v8 ignore next */\n throw new Error('parseFlowchart called with no lines')\n }\n const header = headerStmt.text\n\n const headerMatch = header.match(\n /^(?:graph|flowchart)\\s+(TD|TB|LR|BT|RL)\\s*$/i,\n )\n if (!headerMatch) {\n // A header that starts with \"graph\"/\"flowchart\" but has a bad or\n // missing direction gets a targeted message about the direction token\n // specifically — it already picked the right diagram type.\n const partialFlowchartMatch = header.match(\n /^(?:graph|flowchart)\\b\\s*(.*)$/i,\n )\n if (partialFlowchartMatch) {\n const rest = partialFlowchartMatch[1]!.trim()\n throw new Error(\n rest.length > 0\n ? `Line ${headerStmt.line}: Invalid direction \"${rest}\" in header \"${header}\". Expected one of: TD, TB, LR, BT, RL.`\n : `Line ${headerStmt.line}: Missing direction in header \"${header}\". Expected e.g. \"graph TD\" or \"flowchart LR\" — one of: TD, TB, LR, BT, RL.`,\n )\n }\n\n // Otherwise this header didn't match any recognized diagram-type\n // keyword at all (typo, extra text, or genuinely unsupported syntax) —\n // point at the closest known diagram type when the text resembles one.\n const suggestion = suggestedHeaderFor(header)\n const hint =\n suggestion && suggestion.toLowerCase() !== header.trim().toLowerCase()\n ? ` Did you mean \"${suggestion}\"?`\n : ''\n throw new Error(\n `Line ${headerStmt.line}: Invalid mermaid header: \"${header}\".${hint} Supported headers: ${SUPPORTED_HEADERS}.`,\n )\n }\n\n const direction = toDirection(headerMatch[1])\n\n const graph: MermaidGraph = {\n direction,\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Subgraph stack for nested subgraphs.\n const subgraphStack: MermaidSubgraph[] = []\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- classDef / class assignment / style — shared with the class-diagram\n // parser, see packages/core/src/style-directives.ts ---\n if (tryApplyClassDef(line, graph)) continue\n if (tryApplyClassAssignment(line, graph)) continue\n if (tryApplyStyleStatement(line, graph)) continue\n\n // --- click interaction: `click A \"url\" \"tooltip\" _blank` / `click A call fn()` ---\n if (/^click\\s+/i.test(line)) {\n applyClickStatement(line, graph)\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseStateDiagram ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- direction override inside subgraph: `direction LR` ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch && subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n continue\n }\n\n // --- subgraph start: `subgraph Label` or `subgraph id [Label]` ---\n const subgraphMatch = line.match(/^subgraph\\s+(.+)$/)\n if (subgraphMatch) {\n const rest = subgraphMatch[1]!.trim()\n // Check for `subgraph id [Label]` / `subgraph id [\"Label\"]` form. The id\n // may contain non-ASCII chars (e.g. CJK), so match any run of non-space,\n // non-`[` chars rather than ASCII [\\w-]; strip optional quotes wrapping\n // the title.\n const bracketMatch = rest.match(/^([^\\s[]+)\\s*\\[(.+)\\]$/)\n let id: string\n let label: string\n if (bracketMatch) {\n id = bracketMatch[1]!\n label = normalizeBrTags(stripWrappingQuotes(bracketMatch[2]!))\n } else {\n // `subgraph Label` (optionally quoted): the label doubles as the id,\n // slugified — but preserve unicode letters/numbers so a CJK-only title\n // doesn't collapse to an empty id (which broke nested layout).\n label = normalizeBrTags(stripWrappingQuotes(rest))\n id = label.replace(/\\s+/g, '_').replace(/[^\\p{L}\\p{N}_-]/gu, '')\n }\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n subgraphStack.push(sg)\n continue\n }\n\n // --- subgraph end ---\n if (line === 'end') {\n const completed = subgraphStack.pop()\n if (completed) {\n if (subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- Edge/node definitions ---\n parseEdgeLine(line, graph, subgraphStack)\n }\n\n return graph\n}\n\n// ============================================================================\n// State diagram parser\n//\n// Supported syntax:\n// stateDiagram-v2\n// s1 : Description\n// state \"Description\" as s1\n// s1 --> s2 : label\n// [*] --> s1 (start pseudostate)\n// s1 --> [*] (end pseudostate)\n// state CompositeState {\n// inner1 --> inner2\n// }\n// ============================================================================\n\nfunction parseStateDiagram(lines: Statement[]): MermaidGraph {\n const graph: MermaidGraph = {\n direction: 'TD',\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Track composite state nesting (like subgraphs)\n const compositeStack: MermaidSubgraph[] = []\n // Track all composite state IDs to avoid creating duplicate nodes\n const compositeStateIds = new Set<string>()\n // Counter for unique [*] pseudostate IDs\n let startCount = 0\n let endCount = 0\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- direction override ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n } else {\n graph.direction = toDirection(dirMatch[1])\n }\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseFlowchart ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- composite state start: `state CompositeState {` ---\n const compositeMatch = line.match(\n /^state\\s+(?:\"([^\"]+)\"\\s+as\\s+)?([\\w\\p{L}]+)\\s*\\{$/u,\n )\n if (compositeMatch) {\n const label = compositeMatch[1] ?? compositeMatch[2]!\n const id = compositeMatch[2]!\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n compositeStack.push(sg)\n // Track this ID to avoid creating a duplicate node for the composite state\n compositeStateIds.add(id)\n // Remove any existing node that was created when parsing transitions before\n // this composite state definition (e.g., \"A --> Processing\" before \"state Processing {\")\n graph.nodes.delete(id)\n continue\n }\n\n // --- composite state end ---\n if (line === '}') {\n const completed = compositeStack.pop()\n if (completed) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- state alias: `state \"Description\" as s1` (without brace) ---\n const stateAliasMatch = line.match(\n /^state\\s+\"([^\"]+)\"\\s+as\\s+([\\w\\p{L}]+)\\s*$/u,\n )\n if (stateAliasMatch) {\n const label = normalizeBrTags(stateAliasMatch[1]!)\n const id = stateAliasMatch[2]!\n registerStateNode(graph, compositeStack, { id, label, shape: 'rounded' })\n continue\n }\n\n /*\n * --- transition: `s1 --> s2`, `s1 --> s2 : label`, `[*] --> s1`, or\n * with an edge id (Mermaid v11.10.0+): `s1 e1@--> s2` ---\n *\n * State-diagram transitions only ever use `-->` (unlike flowchart's\n * many arrow variants), so the edge id — same `id@` prefix syntax as\n * flowchart's `A e1@--> B` — is captured inline in this one regex\n * rather than needing flowchart's separate pre-arrow scan.\n */\n const transitionMatch = line.match(\n /^(\\[\\*\\]|[\\w\\p{L}-]+)\\s*(?:([\\w-]+)@)?-->\\s*(\\[\\*\\]|[\\w\\p{L}-]+)(?:\\s*:\\s*(.+))?$/u,\n )\n if (transitionMatch) {\n let sourceId = transitionMatch[1]!\n const edgeId = transitionMatch[2]\n let targetId = transitionMatch[3]!\n const rawTransitionLabel = transitionMatch[4]?.trim()\n const edgeLabel = rawTransitionLabel\n ? normalizeBrTags(rawTransitionLabel)\n : undefined\n\n // Handle [*] pseudostates — each occurrence gets a unique ID\n if (sourceId === '[*]') {\n startCount++\n sourceId = `_start${startCount > 1 ? startCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: sourceId,\n label: '',\n shape: 'state-start',\n })\n } else if (!compositeStateIds.has(sourceId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, sourceId)\n }\n\n if (targetId === '[*]') {\n endCount++\n targetId = `_end${endCount > 1 ? endCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: targetId,\n label: '',\n shape: 'state-end',\n })\n } else if (!compositeStateIds.has(targetId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, targetId)\n }\n\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style: 'solid',\n hasArrowStart: false,\n hasArrowEnd: true,\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n continue\n }\n\n // --- state description: `s1 : Description` ---\n const stateDescMatch = line.match(/^([\\w\\p{L}-]+)\\s*:\\s*(.+)$/u)\n if (stateDescMatch) {\n const id = stateDescMatch[1]!\n const label = normalizeBrTags(stateDescMatch[2]!.trim())\n registerStateNode(graph, compositeStack, { id, label, shape: 'rounded' })\n continue\n }\n }\n\n return graph\n}\n\n/** Register a state node and track in composite state if applicable */\nfunction registerStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(node.id)) {\n current.nodeIds.push(node.id)\n }\n }\n}\n\n/** Ensure a state node exists with default rounded shape */\nfunction ensureStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n id: string,\n): void {\n if (!graph.nodes.has(id)) {\n registerStateNode(graph, compositeStack, {\n id,\n label: id,\n shape: 'rounded',\n })\n } else {\n // Track in composite if applicable\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(id)) {\n current.nodeIds.push(id)\n }\n }\n }\n}\n\n// ============================================================================\n// Flowchart edge line parser\n//\n// Handles chained edges like: A[Label] --> B(Label) -.-> C{Label}\n// Also handles & parallel links: A & B --> C & D\n// ============================================================================\n\n/**\n * Arrow regex — matches all arrow operators with optional labels.\n *\n * Supported operators:\n * --> --- solid arrow / solid line\n * -.-> -.- dotted arrow / dotted line\n * ==> === thick arrow / thick line\n * <--> <-.-> <==> bidirectional variants\n * --o --x circle-end / cross-end arrow (see issue #65)\n * o-- x-- circle-start / cross-start arrow\n * o--o x--x circle/cross at both ends\n *\n * The doubled `o--o`/`x--x` forms must be tried before the single-sided\n * `o--`/`x--` forms — regex alternation picks the first alternative that\n * matches, not the longest, so `o--` listed first would consume just the\n * start marker and strand the trailing `o` as a bogus token. Capturing the\n * markers as their own groups (rather than baking them into an alternation\n * of whole tokens) sidesteps that ordering problem entirely.\n *\n * The body is a variable-length run: Mermaid uses extra characters as a\n * layout-rank hint (`A ---- B` pushes B one rank further than `A --> B`).\n * A fixed alternation of `-->|---|==>` mis-tokenized anything longer,\n * stranding the surplus characters and corrupting the following token.\n * The run length is parsed so the edge survives; the rank hint it encodes\n * is not yet modelled by the layout engine.\n *\n * `~~~` is Mermaid's invisible link: it participates in layout but draws\n * nothing.\n *\n * Optional label: -->|label text|\n */\nconst ARROW_REGEX = /^(<|o|x)?(-{2,}|={2,}|-\\.+-|~{3,})(>|o|x)?(?:\\|([^|]*)\\|)?/\n\n/**\n * Link bodies that are only a link when a start or end marker accompanies\n * them.\n *\n * A bare `--` (or `==`) opens Mermaid's text-embedded label syntax —\n * `A -- Yes --> B` — which TEXT_ARROW_REGEX handles. Treating it as an\n * unmarked open link here would consume the opener and strand the label.\n * Mermaid itself requires three characters for an unmarked open link\n * (`A --- B`), so this only rejects what Mermaid also rejects.\n */\nconst AMBIGUOUS_UNMARKED_BODIES = new Set(['--', '=='])\n\n/**\n * Map a raw start/end marker character (`<`, `>`, `o`, `x`, or undefined) to\n * the distinct terminator shape it draws, if any.\n *\n * `<`/`>` are plain arrowheads — already handled by `hasArrowStart`/\n * `hasArrowEnd` — so they map to `undefined` here, same as no marker at all.\n * Only `o`/`x` need their own shape (circle/cross) recorded, so the ASCII\n * renderer can draw something other than the default arrowhead glyph — see\n * issue #330.\n */\nfunction markerKind(\n marker: string | undefined,\n): 'circle' | 'cross' | undefined {\n if (marker === 'o') return 'circle'\n if (marker === 'x') return 'cross'\n return undefined\n}\n\n/**\n * Text-embedded label regex — matches \"-- label -->\", \"-. label .->\", \"== label ==>\" syntax.\n * Tried as fallback when ARROW_REGEX doesn't match.\n *\n * The closing operator is a variable-length run, matching ARROW_REGEX. While\n * it was a fixed alternation, `A -- label ----> B` consumed only `---` from\n * `---->`, leaving `-> B`, which forms no node group — so the edge and its\n * target node were both dropped silently. Exactly the failure mode the\n * variable-length work exists to remove.\n *\n * Based on PR #36 by @liuxiaopai-ai (https://github.com/lukilabs/beautiful-mermaid/pull/36)\n */\nconst TEXT_ARROW_REGEX =\n /^(<|o|x)?(--|-\\.|==)\\s+(.+?)\\s+(-{2,}[>ox]|={2,}[>ox]|\\.+-[>ox]|-{3,}|={3,}|-\\.+-)/\n\n/**\n * Node shape patterns — ordered from most specific delimiters to least.\n * Multi-char delimiters must be tried before single-char to avoid false matches.\n *\n * The label-content group for each shape is quote-aware: it matches either a\n * complete `\"...\"` quoted span (any character, including this shape's own\n * closing delimiter chars, is fine inside quotes) or a single character that\n * isn't the start of the closing delimiter. This stops the scanner from\n * treating a `]`/`)`/`}` etc. *inside* a quoted label as the node's real\n * closing bracket (see issue #61) — without this, `A[\"test [] brackets\"]`\n * would stop at the first `]`, which lives inside the quoted string.\n */\n/**\n * A node shape pattern.\n *\n * `shape` is usually a fixed `NodeShape`. For the slash-bracket family it is\n * a function instead, because those four shapes are distinguished by which\n * delimiter *closes* them — which the regex has to capture rather than\n * hard-code. See SLASH_BRACKET note below.\n */\ninterface NodePattern {\n regex: RegExp\n shape: NodeShape | ((closingDelimiter: string) => NodeShape)\n}\n\nconst NODE_PATTERNS: NodePattern[] = [\n // Triple delimiters (must be first)\n {\n regex: /^([\\w\\p{L}-]+)\\(\\(\\(((?:\"[^\"]*\"|(?!\\)\\)\\)).)+)\\)\\)\\)/u,\n shape: 'doublecircle',\n }, // A(((text)))\n\n // Double delimiters with mixed brackets\n {\n regex: /^([\\w\\p{L}-]+)\\(\\[((?:\"[^\"]*\"|(?!\\]\\)).)+)\\]\\)/u,\n shape: 'stadium',\n }, // A([text])\n { regex: /^([\\w\\p{L}-]+)\\(\\(((?:\"[^\"]*\"|(?!\\)\\)).)+)\\)\\)/u, shape: 'circle' }, // A((text))\n {\n regex: /^([\\w\\p{L}-]+)\\[\\[((?:\"[^\"]*\"|(?!\\]\\]).)+)\\]\\]/u,\n shape: 'subroutine',\n }, // A[[text]]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\(((?:\"[^\"]*\"|(?!\\)\\]).)+)\\)\\]/u,\n shape: 'cylinder',\n }, // A[(text)]\n\n /*\n * SLASH_BRACKET family — must come before plain [text].\n *\n * Four shapes share a `[` + slash opener and differ only in which\n * delimiter closes them:\n *\n * A[/text\\] trapezoid A[/text/] parallelogram\n * A[\\text/] trapezoid-alt A[\\text\\] parallelogram-alt\n *\n * They CANNOT be four separate patterns each negated against its own\n * closing pair. A pattern for `[/…\\]` whose content merely excludes `\\]`\n * will happily run past a `/]` to reach a `\\]` later on the same line, so\n *\n * A[/parallelogram/] --> B[\\alt\\]\n *\n * matched as a single trapezoid whose label was the entire statement —\n * silently swallowing the edge and the second node. Ordering the patterns\n * differently only moves which input breaks.\n *\n * Instead, one pattern per opener stops at whichever slash-close comes\n * first and captures it, and the captured delimiter selects the shape.\n */\n {\n regex: /^([\\w\\p{L}-]+)\\[\\/((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '\\\\' ? 'trapezoid' : 'parallelogram'),\n }, // A[/text\\] or A[/text/]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\\\((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '/' ? 'trapezoid-alt' : 'parallelogram-alt'),\n }, // A[\\text/] or A[\\text\\]\n\n // Asymmetric flag shape\n { regex: /^([\\w\\p{L}-]+)>((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'asymmetric' }, // A>text]\n\n // Double curly braces (hexagon) — must come before single {text}\n {\n regex: /^([\\w\\p{L}-]+)\\{\\{((?:\"[^\"]*\"|(?!\\}\\}).)+)\\}\\}/u,\n shape: 'hexagon',\n }, // A{{text}}\n\n // Single-char delimiters (last — most common, least specific)\n { regex: /^([\\w\\p{L}-]+)\\[((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'rectangle' }, // A[text]\n { regex: /^([\\w\\p{L}-]+)\\(((?:\"[^\"]*\"|(?!\\)).)+)\\)/u, shape: 'rounded' }, // A(text)\n { regex: /^([\\w\\p{L}-]+)\\{((?:\"[^\"]*\"|(?!\\}).)+)\\}/u, shape: 'diamond' }, // A{text}\n]\n\n/**\n * Regex for a bare node reference (just an ID, no shape brackets).\n *\n * Only allows a hyphen when it's sandwiched between word characters\n * (`foo-bar`, `step-1-b`), never a bare/trailing/doubled hyphen. This keeps\n * legitimately hyphenated ids intact while stopping the id from swallowing\n * the leading dashes of an immediately-following arrow when there's no\n * whitespace before it — e.g. `A-->B` (see issue #61): the naive `[\\w-]+`\n * greedily consumed `A--`, leaving a bogus node and no edge. Arrow tokens\n * always start with `-`/`=`/`<` followed by another non-word character\n * (`-`, `.`, `=`, `>`), which this pattern never matches into, so it now\n * stops cleanly at `A` and lets the arrow regex take over.\n *\n * `\\p{L}` (any Unicode letter, alongside plain `\\w`) is included so a bare\n * non-ASCII name (`Lasaña`, `日本`) matches in full instead of truncating at\n * the first non-ASCII character and silently stranding the rest of the line\n * as unparsed text — see issue #328. This mirrors the state-diagram\n * transition regex below, which already allows `\\p{L}` for the same reason.\n */\nconst BARE_NODE_REGEX = /^([\\w\\p{L}]+(?:-[\\w\\p{L}]+)*)/u\n\n/**\n * Node id immediately followed by the expanded-syntax opener: `A@{`.\n *\n * Only the id is captured — the block itself needs depth- and quote-aware\n * scanning (a label may contain `}`), which a regex would do badly, so\n * `matchExpandedBlock` takes over from here.\n */\nconst EXPANDED_NODE_ID_REGEX = /^([\\w\\p{L}-]+)(?=@\\{)/u\n\n/**\n * Resolve the geometry for an `A@{ ... }` node.\n *\n * An `icon:` or `img:` node has no `shape:` of its own — its outline comes\n * from `form:` instead (Mermaid defaults to a square). An unrecognized shape\n * name falls back to a rectangle rather than throwing: Mermaid adds shape\n * names regularly, and rendering a plain box beats failing the whole diagram\n * over one unknown name.\n */\nfunction expandedNodeShape(meta: ExpandedNodeMeta): NodeShape {\n if (meta.shape) {\n return resolveShapeName(meta.shape) ?? 'rectangle'\n }\n\n if (meta.icon !== undefined || meta.img !== undefined) {\n switch (meta.form?.toLowerCase()) {\n case 'circle':\n return 'circle'\n case 'rounded':\n return 'rounded'\n default:\n return 'rectangle'\n }\n }\n\n return 'rectangle'\n}\n\n/**\n * Resolve the display label for an `A@{ ... }` node.\n *\n * Falls back to the node id when no `label:` is given, matching how the\n * bracket syntax treats a bare `A`. For an icon or image node with no label,\n * the icon/image reference itself is shown: this renderer draws neither\n * FontAwesome glyphs nor remote images, so showing the reference is more\n * useful than an empty box, and it keeps the node identifiable.\n */\nfunction expandedNodeLabel(id: string, meta: ExpandedNodeMeta): string {\n if (meta.label !== undefined && meta.label.length > 0) return meta.label\n if (meta.icon) return meta.icon\n if (meta.img) return meta.img\n return id\n}\n\n/**\n * Apply a `click` statement to the graph.\n *\n * Mermaid's forms:\n * click A \"https://example.com\"\n * click A \"https://example.com\" \"Tooltip\"\n * click A \"https://example.com\" _blank\n * click A href \"https://example.com\" \"Tooltip\" _blank\n * click A call myCallback()\n * click A callback \"Tooltip\"\n *\n * A callback is recorded but never invoked — this renderer emits static SVG\n * and does not execute script supplied by a diagram. An href, by contrast, is\n * genuinely actionable: the node is wrapped in an SVG <a>, which works in any\n * browser without script.\n *\n * The actual grammar and href-safety rules live in packages/core/src/click-directive.ts,\n * shared with the class diagram parser\n * (packages/mermaid-parser/src/class/parser.ts) — this is a thin wrapper\n * binding it to this parser's `graph.interactions` map.\n */\nfunction applyClickStatement(line: string, graph: MermaidGraph): void {\n applyClickStatementShared(line, graph.interactions)\n}\n\n/**\n * Id immediately followed by the expanded-syntax opener on a standalone\n * metadata line: `e1@{ ... }`.\n *\n * Shared by both parsers (`tryApplyEdgeMetaLine`) since the syntax and its\n * \"must already be a known edge id\" disambiguation rule are identical for\n * flowcharts and state diagrams — only how edge ids get declared differs\n * (`A e1@--> B` vs. `A e1@--> B` with a simpler arrow set).\n */\nconst EDGE_META_ID_REGEX = /^([\\w-]+)(?=@\\{)/\n\n/**\n * Try to parse and apply a standalone `e1@{ animate: true }` edge-metadata\n * line.\n *\n * Told apart from a node's `A@{ shape: ... }` by whether the id was already\n * declared as an edge id (`A e1@--> B`) — checked before node parsing so an\n * edge id is never registered as a stray node. Shared between\n * `parseFlowchart` and `parseStateDiagram`; returns `true` if the line was\n * consumed as edge metadata (caller should `continue` its loop).\n */\nfunction tryApplyEdgeMetaLine(line: string, graph: MermaidGraph): boolean {\n const edgeMetaMatch = line.match(EDGE_META_ID_REGEX)\n if (!edgeMetaMatch || !graph.edges.some((e) => e.id === edgeMetaMatch[1])) {\n return false\n }\n const block = matchExpandedBlock(line.slice(edgeMetaMatch[1]!.length))\n if (!block) return false\n applyEdgeMeta(graph, edgeMetaMatch[1]!, parseExpandedMeta(block.body))\n return true\n}\n\n/**\n * Apply an `e1@{ ... }` metadata block to the edge with that id.\n *\n * Only `animate` is acted on; Mermaid's other edge keys (`animation`,\n * `curve`) are recorded as no-ops rather than errors, matching how an\n * unknown node shape degrades.\n */\nfunction applyEdgeMeta(\n graph: MermaidGraph,\n edgeId: string,\n meta: Record<string, string | undefined>,\n): void {\n for (const edge of graph.edges) {\n if (edge.id !== edgeId) continue\n if (meta.animate !== undefined) {\n edge.animate = meta.animate.toLowerCase() !== 'false'\n }\n // Mermaid's `animation: fast|slow` is a speed hint on top of animate.\n if (meta.animation !== undefined) edge.animate = true\n }\n}\n\n/** Regex for ::: class shorthand suffix — matches :::className immediately after a node */\nconst CLASS_SHORTHAND_REGEX = /^:::([\\w][\\w-]*)/\n\n/**\n * Regex for ::: class shorthand appearing BEFORE the shape brackets,\n * e.g. A:::external[Label]. Captures the bare id and the class name so\n * the shorthand can be stripped out before shape-pattern matching runs.\n *\n * The id group allows `\\p{L}` (see BARE_NODE_REGEX above) so a non-ASCII\n * id keeps working with this shorthand instead of falling through to\n * BARE_NODE_REGEX with the `:::className` suffix left dangling as\n * unparsed text. The class-name group stays ASCII-only — Mermaid class\n * names follow CSS identifier conventions, not node-name conventions.\n */\nconst PRE_CLASS_SHORTHAND_REGEX = /^([\\w\\p{L}-]+):::([\\w][\\w-]*)/u\n\n/**\n * Parse a line that contains node definitions and edges.\n * Handles chaining: A --> B --> C produces edges A→B and B→C.\n * Handles parallel links: A & B --> C & D produces 4 edges.\n */\nfunction parseEdgeLine(\n line: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): void {\n let remaining = line.trim()\n\n // Parse the first node group (possibly with & separators)\n const firstGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!firstGroup || firstGroup.ids.length === 0) return\n\n remaining = firstGroup.remaining.trim()\n let prevGroupIds = firstGroup.ids\n\n // Parse arrow + node-group pairs until the line is exhausted\n while (remaining.length > 0) {\n let hasArrowStart: boolean\n let style: EdgeStyle\n let hasArrowEnd: boolean\n let edgeLabel: string | undefined\n let startMarkerKind: 'circle' | 'cross' | undefined\n let endMarkerKind: 'circle' | 'cross' | undefined\n\n /*\n * Optional edge id prefix: `A e1@--> B` (Mermaid v11.10.0+). Consumed\n * before the arrow so ARROW_REGEX still sees the link token at position\n * zero. `@` is not otherwise valid ahead of a link, so this cannot\n * shadow existing syntax.\n */\n let edgeId: string | undefined\n const edgeIdMatch = remaining.match(/^([\\w-]+)@(?=[-=<~ox])/)\n if (edgeIdMatch) {\n edgeId = edgeIdMatch[1]\n remaining = remaining.slice(edgeIdMatch[0].length)\n }\n\n const arrowMatch = remaining.match(ARROW_REGEX)\n const arrowBody = arrowMatch?.[2]\n const startMarker = arrowMatch?.[1]\n const endMarker = arrowMatch?.[3]\n\n if (\n arrowMatch &&\n arrowBody !== undefined &&\n // An unmarked `--`/`==` is the text-label opener, not a link.\n !(\n startMarker === undefined &&\n endMarker === undefined &&\n AMBIGUOUS_UNMARKED_BODIES.has(arrowBody)\n )\n ) {\n // `o`/`x` mark a circle/cross terminator (alongside `<` for a reversed\n // arrow) — `markerKind` records which, so the ASCII renderer can draw\n // a distinct glyph instead of the default arrowhead (see issue #330,\n // a follow-up to issue #65 which first stopped these from being\n // dropped entirely).\n hasArrowStart = startMarker !== undefined\n startMarkerKind = markerKind(startMarker)\n const rawEdgeLabel = arrowMatch[4]?.trim()\n edgeLabel = rawEdgeLabel ? normalizeBrTags(rawEdgeLabel) : undefined\n remaining = remaining.slice(arrowMatch[0].length).trim()\n style = arrowStyleFromBody(arrowBody)\n hasArrowEnd = endMarker !== undefined\n endMarkerKind = markerKind(endMarker)\n } else {\n // Fallback: text-embedded label syntax (-- Yes -->, -. Maybe .->, == Sure ==>)\n const textMatch = remaining.match(TEXT_ARROW_REGEX)\n if (!textMatch) break\n hasArrowStart = Boolean(textMatch[1])\n startMarkerKind = markerKind(textMatch[1])\n const rawLabel = textMatch[3]!.trim()\n edgeLabel = rawLabel ? normalizeBrTags(rawLabel) : undefined\n const openOp = textMatch[2]!\n const closeOp = textMatch[4]!\n remaining = remaining.slice(textMatch[0].length).trim()\n style = textArrowStyleFromOps(openOp, closeOp)\n // closeOp is a variable-length run (e.g. \"---o\", \"==x\", \"-.-.->\"); only\n // its final character can be a circle/cross marker. A circle/cross\n // terminator is its own kind of arrow ending — like `>` — so it counts\n // toward hasArrowEnd too; an unmarked run (`---`, `===`, `-.-`) is the\n // only case with no arrowhead of any kind at the close.\n endMarkerKind = markerKind(closeOp.slice(-1))\n hasArrowEnd = closeOp.endsWith('>') || endMarkerKind !== undefined\n }\n\n // Parse the next node group\n const nextGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!nextGroup || nextGroup.ids.length === 0) break\n\n remaining = nextGroup.remaining.trim()\n\n // Emit Cartesian product of edges: every source × every target\n for (const sourceId of prevGroupIds) {\n for (const targetId of nextGroup.ids) {\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style,\n hasArrowStart,\n hasArrowEnd,\n ...(startMarkerKind !== undefined\n ? { startMarker: startMarkerKind }\n : {}),\n ...(endMarkerKind !== undefined ? { endMarker: endMarkerKind } : {}),\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n }\n }\n\n prevGroupIds = nextGroup.ids\n }\n}\n\ninterface ConsumedNodeGroup {\n ids: string[]\n remaining: string\n}\n\n/**\n * Consume one or more nodes separated by `&`.\n * E.g. \"A & B & C --> ...\" returns ids: ['A', 'B', 'C']\n */\nfunction consumeNodeGroup(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNodeGroup | null {\n const first = consumeNode(text, graph, subgraphStack)\n if (!first) return null\n\n const ids = [first.id]\n let remaining = first.remaining.trim()\n\n // Check for & separators\n while (remaining.startsWith('&')) {\n remaining = remaining.slice(1).trim()\n const next = consumeNode(remaining, graph, subgraphStack)\n if (!next) break\n ids.push(next.id)\n remaining = next.remaining.trim()\n }\n\n return { ids, remaining }\n}\n\ninterface ConsumedNode {\n id: string\n remaining: string\n}\n\n/**\n * Try to consume a node definition from the start of `text`.\n * If the node has a shape+label (e.g. A[Text]), it's registered in the graph.\n * If it's a bare reference (e.g. A), we look it up or create a default.\n * Also handles ::: class shorthand suffix.\n */\nfunction consumeNode(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNode | null {\n let id: string | null = null\n let remaining: string = text\n\n // Check for ::: class shorthand appearing BEFORE the shape brackets\n // (e.g. A:::external[Label]). Strip it out so shape-pattern matching\n // below still sees the id directly adjacent to its brackets.\n let preClassName: string | undefined\n const preClassMatch = remaining.match(PRE_CLASS_SHORTHAND_REGEX)\n if (preClassMatch) {\n preClassName = preClassMatch[2]!\n remaining = preClassMatch[1]! + remaining.slice(preClassMatch[0].length)\n }\n\n /*\n * Expanded syntax: `A@{ shape: doc, label: \"Report\" }` (Mermaid v11.3.0+).\n *\n * Tried before the bracket patterns because the id is followed by `@{`,\n * which none of them match — without this the id would fall through to\n * BARE_NODE_REGEX and the whole metadata block would be stranded as\n * unparsed text.\n */\n const expandedIdMatch = remaining.match(EXPANDED_NODE_ID_REGEX)\n if (expandedIdMatch) {\n const expandedId = expandedIdMatch[1]!\n const block = matchExpandedBlock(remaining.slice(expandedId.length))\n if (block) {\n const meta = parseExpandedMeta(block.body)\n registerNode(graph, subgraphStack, {\n id: expandedId,\n label: normalizeBrTags(expandedNodeLabel(expandedId, meta)),\n shape: expandedNodeShape(meta),\n })\n id = expandedId\n remaining = remaining.slice(expandedId.length + block.length)\n }\n }\n\n // Try each node pattern (shape-qualified)\n if (id === null) {\n for (const { regex, shape } of NODE_PATTERNS) {\n const match = remaining.match(regex)\n if (match) {\n id = match[1]!\n const label = normalizeBrTags(match[2]!)\n // The slash-bracket family resolves its shape from the closing\n // delimiter it captured in group 3; every other pattern has a fixed\n // shape. See the SLASH_BRACKET note in NODE_PATTERNS.\n const resolvedShape =\n typeof shape === 'function' ? shape(match[3] ?? '') : shape\n registerNode(graph, subgraphStack, { id, label, shape: resolvedShape })\n remaining = remaining.slice(match[0].length)\n break\n }\n }\n }\n\n // Bare node reference — only register if node doesn't exist yet.\n // If it already exists, do NOT track it in the current subgraph;\n // nodes belong to the subgraph where they're first defined.\n if (id === null) {\n const bareMatch = remaining.match(BARE_NODE_REGEX)\n if (bareMatch) {\n id = bareMatch[1]!\n if (!graph.nodes.has(id)) {\n registerNode(graph, subgraphStack, {\n id,\n label: id,\n shape: 'rectangle',\n })\n }\n remaining = remaining.slice(bareMatch[0].length)\n }\n }\n\n if (id === null) return null\n\n if (preClassName) {\n graph.classAssignments.set(id, preClassName)\n }\n\n // Check for ::: class shorthand suffix immediately after the node\n const classMatch = remaining.match(CLASS_SHORTHAND_REGEX)\n if (classMatch) {\n graph.classAssignments.set(id, classMatch[1]!)\n remaining = remaining.slice(classMatch[0].length)\n }\n\n return { id, remaining }\n}\n\n/** Register a node in the graph and track it in the current subgraph */\nfunction registerNode(\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n trackInSubgraph(subgraphStack, node.id)\n}\n\n/** Add node ID to the innermost subgraph if we're inside one */\nfunction trackInSubgraph(\n subgraphStack: MermaidSubgraph[],\n nodeId: string,\n): void {\n if (subgraphStack.length > 0) {\n const current = subgraphStack[subgraphStack.length - 1]!\n if (!current.nodeIds.includes(nodeId)) {\n current.nodeIds.push(nodeId)\n }\n }\n}\n\n/**\n * Map a link body to its edge style, ignoring direction and run length.\n *\n * The body is the run between the optional start/end markers: `--`, `----`,\n * `==`, `-.-`, `-..-`, `~~~`, etc. Classification is by the characters used,\n * not the length, so every run length of a given style behaves identically.\n */\nfunction arrowStyleFromBody(body: string): EdgeStyle {\n if (body.startsWith('~')) return 'invisible'\n if (body.includes('.')) return 'dotted'\n if (body.startsWith('=')) return 'thick'\n return 'solid'\n}\n\n/** Map text-embedded arrow open/close operators to edge style */\nfunction textArrowStyleFromOps(openOp: string, closeOp: string): EdgeStyle {\n // Classify by the characters used, not by exact token, so every run length\n // of a given style resolves identically — the same rule arrowStyleFromBody\n // applies to the plain arrow forms.\n if (openOp.includes('.') || closeOp.includes('.')) return 'dotted'\n if (openOp.startsWith('=') || closeOp.startsWith('=')) return 'thick'\n return 'solid'\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/parser.ts"],"sourcesContent":["import type {\n MermaidGraph,\n MermaidNode,\n MermaidSubgraph,\n NodeShape,\n EdgeStyle,\n Statement,\n} from '@zombie-mermaid/core'\nimport {\n toDirection,\n normalizeBrTags,\n splitStatementsByLine,\n extractInitConfig,\n applyClickStatement as applyClickStatementShared,\n parseStyleProps,\n tryApplyClassDef,\n tryApplyClassAssignment,\n tryApplyStyleStatement,\n} from '@zombie-mermaid/core'\n\nimport {\n matchExpandedBlock,\n parseExpandedMeta,\n resolveShapeName,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { ExpandedNodeMeta } from '@zombie-mermaid/mermaid-parser'\n/** Remove a single layer of matching wrapping quotes (`\"…\"` or `'…'`). */\nfunction stripWrappingQuotes(s: string): string {\n const t = s.trim()\n if (\n t.length >= 2 &&\n ((t[0] === '\"' && t[t.length - 1] === '\"') ||\n (t[0] === \"'\" && t[t.length - 1] === \"'\"))\n ) {\n return t.slice(1, -1)\n }\n return t\n}\n\n// ============================================================================\n// Mermaid parser — flowcharts and state diagrams\n//\n// Supports:\n// Flowcharts: graph TD / flowchart LR\n// State diagrams: stateDiagram-v2\n//\n// Line-by-line regex approach — the grammar is regular enough\n// that we don't need a grammar generator or full parser combinator.\n// ============================================================================\n\n// `toDirection` used to be defined here, with this file as its only caller.\n// #624 gave it a second caller — `packages/mermaid-parser/src/er/parser.ts`,\n// which cannot import this umbrella file without creating a cycle\n// (mermaid-parser -> umbrella -> mermaid-parser) — so it moved to\n// `packages/core/src/direction.ts` alongside `isDirection`, for the same\n// reason that one moved under #625. Re-exported here so existing importers\n// of `toDirection` from `./parser.ts` (this file's own tests included) keep\n// working unchanged.\nexport { toDirection } from '@zombie-mermaid/core'\n\n/**\n * All diagram-type headers this library recognizes, for the \"supported\n * headers\" list in the invalid-header error below. Kept in one place so a\n * newly-added diagram type doesn't get forgotten in the error message the\n * way sequence/class/ER/xychart were before issue #541.\n */\nconst SUPPORTED_HEADERS =\n '\"graph <dir>\"/\"flowchart <dir>\" (dir: TD, TB, LR, BT, RL), \"stateDiagram-v2\", \"sequenceDiagram\", \"classDiagram\", \"erDiagram\", \"xychart-beta\", \"C4Context\"/\"C4Container\"/\"C4Component\"/\"C4Dynamic\"/\"C4Deployment\", \"architecture-beta\"'\n\n/**\n * Best-guess canonical header for a header line that looks like an attempt\n * at one of the *other* diagram types this library supports, but didn't\n * match `detectDiagramType`'s exact-match regex (a typo, extra trailing\n * text, wrong case pattern, etc.) and so fell through to this flowchart\n * parser as the default. Returns undefined when `header` doesn't look like\n * any of those — in which case the generic \"supported headers\" list in the\n * thrown error is the best we can do.\n *\n * This only recognizes the four *other* multi-word headers\n * (sequenceDiagram/classDiagram/erDiagram/xychart-beta/stateDiagram-v2);\n * \"graph\"/\"flowchart\" typos are handled by the direction-specific message\n * below instead, since those already matched the diagram-type keyword and\n * just have a bad or missing direction token.\n */\nfunction suggestedHeaderFor(header: string): string | undefined {\n const lower = header.trim().toLowerCase()\n if (/^sequence/.test(lower)) return 'sequenceDiagram'\n if (/^class/.test(lower)) return 'classDiagram'\n if (/^er/.test(lower)) return 'erDiagram'\n if (/^xychart/.test(lower)) return 'xychart-beta'\n if (/^state/.test(lower)) return 'stateDiagram-v2'\n return undefined\n}\n\n/**\n * Matches the start of a statement that continues the *previous* one's edge\n * chain rather than beginning a new one: Mermaid lets a vertex-chain\n * statement break across lines, with the link operator leading the next\n * line — e.g.\n *\n * start([Start])\n * ==> green([Change some code])\n * ==> finish([Finish])\n *\n * is one chained statement (`start ==> green ==> finish`), identical to\n * writing it on one line. `splitStatementsByLine` only knows about newlines\n * and `;` as separators, so without this it hands `parseFlowchart` three\n * unrelated-looking statements — the continuation lines start with a bare\n * arrow and no node group, so `parseEdgeLine` can't find a source node and\n * drops them entirely (see issue mermaid-js/mermaid#6049's repro, reported\n * against this parser).\n *\n * Covers every arrow opener `parseEdgeLine` itself recognizes (ARROW_REGEX /\n * TEXT_ARROW_REGEX below): a solid/thick run (`--`, `===`), a dotted run\n * (`-.`, `-.-`), a `~~~` run, each optionally preceded by a `<` marker and/or\n * an edge id (`e1@-->`).\n *\n * Deliberately excludes the `o`/`x` start markers ARROW_REGEX also accepts\n * (`o--o`, `x--x`): unlike `<`, both are also valid bare node ids, so a\n * first-body-line statement like `x-->Y` would otherwise be misread as an\n * `x`-marked continuation of the header and merged into it, corrupting the\n * header line (`flowchart TD x-->Y`) instead of parsing `x` as its own node.\n * A genuine marked-start continuation line is rare enough that losing it is\n * the safer tradeoff.\n */\nconst CONTINUATION_START_REGEX = /^(?:[\\w-]+@)?<?(?:-{2,}|={2,}|-\\.+-?|~{3,})/\n\n/**\n * Rejoin a continuation statement onto the one it continues.\n *\n * `splitStatementsByLine` groups statements by their originating physical\n * line, which is what makes this safe: only the *first* statement in a\n * group is eligible to merge backward into the previous group's last\n * statement (a genuine multi-line continuation, see\n * `CONTINUATION_START_REGEX`). A later statement in the same group got\n * there via an explicit `;` on that line (`A --> B; --> C`) — that boundary\n * must stay a boundary, or `--> C` would wrongly become part of the chain\n * instead of the source-less fragment it actually is.\n */\nfunction mergeContinuationLines(groups: Statement[][]): Statement[] {\n const merged: Statement[] = []\n for (const group of groups) {\n group.forEach((statement, index) => {\n if (\n index === 0 &&\n merged.length > 0 &&\n CONTINUATION_START_REGEX.test(statement.text)\n ) {\n // Keep the *first* line's number: a merged statement is reported at\n // the line its logical statement began, not the continuation line.\n const prev = merged[merged.length - 1]!\n merged[merged.length - 1] = {\n text: `${prev.text} ${statement.text}`,\n line: prev.line,\n }\n } else {\n merged.push(statement)\n }\n })\n }\n return merged\n}\n\n/**\n * Parse Mermaid text into a logical graph structure.\n * Auto-detects diagram type (flowchart or state diagram).\n * Throws on invalid/unsupported input.\n */\nexport function parseMermaid(text: string): MermaidGraph {\n /*\n * Init directives must be read from the raw lines: `%%{init: ...}%%` begins\n * with `%%`, so splitStatementsByLine — which treats `%%` as a comment and\n * truncates the line there — would otherwise discard it along with real\n * comments before it could be seen.\n */\n const initConfig = extractInitConfig(text.split('\\n').map((l) => l.trim()))\n\n const lines = mergeContinuationLines(splitStatementsByLine(text))\n\n if (lines.length === 0) {\n throw new Error('Empty mermaid diagram')\n }\n\n // Detect diagram type from header\n const headerStmt = lines[0]!\n\n // State diagram: \"stateDiagram-v2\" or \"stateDiagram\"\n const graph = /^stateDiagram(-v2)?\\s*$/i.test(headerStmt.text)\n ? parseStateDiagram(lines)\n : parseFlowchart(lines)\n\n graph.initConfig = initConfig\n return graph\n}\n\n// ============================================================================\n// Flowchart parser\n// ============================================================================\n\nfunction parseFlowchart(lines: Statement[]): MermaidGraph {\n // parseFlowchart is only ever invoked by parseMermaid, which has already\n // verified `lines` is non-empty — but that invariant isn't visible to the\n // type checker across the function boundary, so validate it here instead\n // of asserting past it.\n const headerStmt = lines[0]\n if (headerStmt === undefined) {\n /* v8 ignore next */\n throw new Error('parseFlowchart called with no lines')\n }\n const header = headerStmt.text\n\n const headerMatch = header.match(\n /^(?:graph|flowchart)\\s+(TD|TB|LR|BT|RL)\\s*$/i,\n )\n if (!headerMatch) {\n // A header that starts with \"graph\"/\"flowchart\" but has a bad or\n // missing direction gets a targeted message about the direction token\n // specifically — it already picked the right diagram type.\n const partialFlowchartMatch = header.match(\n /^(?:graph|flowchart)\\b\\s*(.*)$/i,\n )\n if (partialFlowchartMatch) {\n const rest = partialFlowchartMatch[1]!.trim()\n throw new Error(\n rest.length > 0\n ? `Line ${headerStmt.line}: Invalid direction \"${rest}\" in header \"${header}\". Expected one of: TD, TB, LR, BT, RL.`\n : `Line ${headerStmt.line}: Missing direction in header \"${header}\". Expected e.g. \"graph TD\" or \"flowchart LR\" — one of: TD, TB, LR, BT, RL.`,\n )\n }\n\n // Otherwise this header didn't match any recognized diagram-type\n // keyword at all (typo, extra text, or genuinely unsupported syntax) —\n // point at the closest known diagram type when the text resembles one.\n const suggestion = suggestedHeaderFor(header)\n const hint =\n suggestion && suggestion.toLowerCase() !== header.trim().toLowerCase()\n ? ` Did you mean \"${suggestion}\"?`\n : ''\n throw new Error(\n `Line ${headerStmt.line}: Invalid mermaid header: \"${header}\".${hint} Supported headers: ${SUPPORTED_HEADERS}.`,\n )\n }\n\n const direction = toDirection(headerMatch[1])\n\n const graph: MermaidGraph = {\n direction,\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Subgraph stack for nested subgraphs.\n const subgraphStack: MermaidSubgraph[] = []\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- classDef / class assignment / style — shared with the class-diagram\n // parser, see packages/core/src/style-directives.ts ---\n if (tryApplyClassDef(line, graph)) continue\n if (tryApplyClassAssignment(line, graph)) continue\n if (tryApplyStyleStatement(line, graph)) continue\n\n // --- click interaction: `click A \"url\" \"tooltip\" _blank` / `click A call fn()` ---\n if (/^click\\s+/i.test(line)) {\n applyClickStatement(line, graph)\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseStateDiagram ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- direction override inside subgraph: `direction LR` ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch && subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n continue\n }\n\n // --- subgraph start: `subgraph Label` or `subgraph id [Label]` ---\n const subgraphMatch = line.match(/^subgraph\\s+(.+)$/)\n if (subgraphMatch) {\n const rest = subgraphMatch[1]!.trim()\n // Check for `subgraph id [Label]` / `subgraph id [\"Label\"]` form. The id\n // may contain non-ASCII chars (e.g. CJK), so match any run of non-space,\n // non-`[` chars rather than ASCII [\\w-]; strip optional quotes wrapping\n // the title.\n const bracketMatch = rest.match(/^([^\\s[]+)\\s*\\[(.+)\\]$/)\n let id: string\n let label: string\n if (bracketMatch) {\n id = bracketMatch[1]!\n label = normalizeBrTags(stripWrappingQuotes(bracketMatch[2]!))\n } else {\n // `subgraph Label` (optionally quoted): the label doubles as the id,\n // slugified — but preserve unicode letters/numbers so a CJK-only title\n // doesn't collapse to an empty id (which broke nested layout).\n label = normalizeBrTags(stripWrappingQuotes(rest))\n id = label.replace(/\\s+/g, '_').replace(/[^\\p{L}\\p{N}_-]/gu, '')\n }\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n subgraphStack.push(sg)\n continue\n }\n\n // --- subgraph end ---\n if (line === 'end') {\n const completed = subgraphStack.pop()\n if (completed) {\n if (subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- Edge/node definitions ---\n parseEdgeLine(line, graph, subgraphStack)\n }\n\n return graph\n}\n\n// ============================================================================\n// State diagram parser\n//\n// Supported syntax:\n// stateDiagram-v2\n// s1 : Description\n// state \"Description\" as s1\n// s1 --> s2 : label\n// [*] --> s1 (start pseudostate)\n// s1 --> [*] (end pseudostate)\n// state CompositeState {\n// inner1 --> inner2\n// }\n// classDef name fill:#f00 (style class — shared helpers)\n// class s1,s2 name\n// s1:::name --> s2 (inline class shorthand)\n// ============================================================================\n\nfunction parseStateDiagram(lines: Statement[]): MermaidGraph {\n const graph: MermaidGraph = {\n direction: 'TD',\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Track composite state nesting (like subgraphs)\n const compositeStack: MermaidSubgraph[] = []\n // Track all composite state IDs to avoid creating duplicate nodes\n const compositeStateIds = new Set<string>()\n // Counter for unique [*] pseudostate IDs\n let startCount = 0\n let endCount = 0\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- classDef / class assignment / style — shared with the flowchart and\n // class-diagram parsers, see packages/core/src/style-directives.ts.\n // Mermaid can't style `[*]` or composite states yet, so an assignment to\n // one of those ids is stored but never matches a rendered node. ---\n if (tryApplyClassDef(line, graph)) continue\n if (tryApplyClassAssignment(line, graph)) continue\n if (tryApplyStyleStatement(line, graph)) continue\n\n // --- direction override ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n } else {\n graph.direction = toDirection(dirMatch[1])\n }\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseFlowchart ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- composite state start: `state CompositeState {` ---\n const compositeMatch = line.match(\n /^state\\s+(?:\"([^\"]+)\"\\s+as\\s+)?([\\w\\p{L}]+)\\s*\\{$/u,\n )\n if (compositeMatch) {\n const label = compositeMatch[1] ?? compositeMatch[2]!\n const id = compositeMatch[2]!\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n compositeStack.push(sg)\n // Track this ID to avoid creating a duplicate node for the composite state\n compositeStateIds.add(id)\n // Remove any existing node that was created when parsing transitions before\n // this composite state definition (e.g., \"A --> Processing\" before \"state Processing {\")\n graph.nodes.delete(id)\n continue\n }\n\n // --- composite state end ---\n if (line === '}') {\n const completed = compositeStack.pop()\n if (completed) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- state alias: `state \"Description\" as s1` (without brace) ---\n const stateAliasMatch = line.match(\n /^state\\s+\"([^\"]+)\"\\s+as\\s+([\\w\\p{L}]+)\\s*$/u,\n )\n if (stateAliasMatch) {\n const label = normalizeBrTags(stateAliasMatch[1]!)\n const id = stateAliasMatch[2]!\n registerStateDescription(graph, compositeStack, id, label)\n continue\n }\n\n /*\n * --- transition: `s1 --> s2`, `s1 --> s2 : label`, `[*] --> s1`, or\n * with an edge id (Mermaid v11.10.0+): `s1 e1@--> s2` ---\n *\n * State-diagram transitions only ever use `-->` (unlike flowchart's\n * many arrow variants), so the edge id — same `id@` prefix syntax as\n * flowchart's `A e1@--> B` — is captured inline in this one regex\n * rather than needing flowchart's separate pre-arrow scan.\n */\n const transitionMatch = line.match(\n /^(\\[\\*\\]|[\\w\\p{L}-]+)(?::::([\\w][\\w-]*))?\\s*(?:([\\w-]+)@)?-->\\s*(\\[\\*\\]|[\\w\\p{L}-]+)(?::::([\\w][\\w-]*))?(?:\\s*:\\s*(.+))?$/u,\n )\n if (transitionMatch) {\n let sourceId = transitionMatch[1]!\n const sourceClass = transitionMatch[2]\n const edgeId = transitionMatch[3]\n let targetId = transitionMatch[4]!\n const targetClass = transitionMatch[5]\n const rawTransitionLabel = transitionMatch[6]?.trim()\n const edgeLabel = rawTransitionLabel\n ? normalizeBrTags(rawTransitionLabel)\n : undefined\n\n // Handle [*] pseudostates — each occurrence gets a unique ID\n if (sourceId === '[*]') {\n startCount++\n sourceId = `_start${startCount > 1 ? startCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: sourceId,\n label: '',\n shape: 'state-start',\n })\n } else if (!compositeStateIds.has(sourceId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, sourceId)\n }\n\n if (targetId === '[*]') {\n endCount++\n targetId = `_end${endCount > 1 ? endCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: targetId,\n label: '',\n shape: 'state-end',\n })\n } else if (!compositeStateIds.has(targetId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, targetId)\n }\n\n // `S1:::name --> S2:::name` — inline class shorthand on either end\n if (sourceClass !== undefined) {\n graph.classAssignments.set(sourceId, sourceClass)\n }\n if (targetClass !== undefined) {\n graph.classAssignments.set(targetId, targetClass)\n }\n\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style: 'solid',\n hasArrowStart: false,\n hasArrowEnd: true,\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n continue\n }\n\n // --- bare class shorthand: `S2:::name`. Must precede the description\n // rule below, which would otherwise read it as `S2 : ::name`. ---\n const stateClassMatch = line.match(/^([\\w\\p{L}-]+):::([\\w][\\w-]*)\\s*$/u)\n if (stateClassMatch) {\n const id = stateClassMatch[1]!\n if (!compositeStateIds.has(id)) {\n ensureStateNode(graph, compositeStack, id)\n }\n graph.classAssignments.set(id, stateClassMatch[2]!)\n continue\n }\n\n // --- state description: `s1 : Description` ---\n const stateDescMatch = line.match(/^([\\w\\p{L}-]+)\\s*:\\s*(.+)$/u)\n if (stateDescMatch) {\n const id = stateDescMatch[1]!\n const label = normalizeBrTags(stateDescMatch[2]!.trim())\n registerStateDescription(graph, compositeStack, id, label)\n continue\n }\n }\n\n // `class S2 foo` on a state that no transition mentions still declares it\n // (Mermaid creates the state on first reference). Composite ids are skipped:\n // they render as clusters, not nodes.\n for (const id of graph.classAssignments.keys()) {\n if (!graph.nodes.has(id) && !compositeStateIds.has(id)) {\n ensureStateNode(graph, [], id)\n }\n }\n\n return graph\n}\n\n/**\n * Register a state with an explicit description (`s1 : text` or\n * `state \"text\" as s1`). Unlike a bare reference, this replaces the\n * placeholder label an earlier `s1 --> s2` or `s1:::name` gave the node.\n */\nfunction registerStateDescription(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n id: string,\n label: string,\n): void {\n const existing = graph.nodes.get(id)\n if (existing) existing.label = label\n registerStateNode(graph, compositeStack, { id, label, shape: 'rounded' })\n}\n\n/** Register a state node and track in composite state if applicable */\nfunction registerStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(node.id)) {\n current.nodeIds.push(node.id)\n }\n }\n}\n\n/** Ensure a state node exists with default rounded shape */\nfunction ensureStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n id: string,\n): void {\n if (!graph.nodes.has(id)) {\n registerStateNode(graph, compositeStack, {\n id,\n label: id,\n shape: 'rounded',\n })\n } else {\n // Track in composite if applicable\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(id)) {\n current.nodeIds.push(id)\n }\n }\n }\n}\n\n// ============================================================================\n// Flowchart edge line parser\n//\n// Handles chained edges like: A[Label] --> B(Label) -.-> C{Label}\n// Also handles & parallel links: A & B --> C & D\n// ============================================================================\n\n/**\n * Arrow regex — matches all arrow operators with optional labels.\n *\n * Supported operators:\n * --> --- solid arrow / solid line\n * -.-> -.- dotted arrow / dotted line\n * ==> === thick arrow / thick line\n * <--> <-.-> <==> bidirectional variants\n * --o --x circle-end / cross-end arrow (see issue #65)\n * o-- x-- circle-start / cross-start arrow\n * o--o x--x circle/cross at both ends\n *\n * The doubled `o--o`/`x--x` forms must be tried before the single-sided\n * `o--`/`x--` forms — regex alternation picks the first alternative that\n * matches, not the longest, so `o--` listed first would consume just the\n * start marker and strand the trailing `o` as a bogus token. Capturing the\n * markers as their own groups (rather than baking them into an alternation\n * of whole tokens) sidesteps that ordering problem entirely.\n *\n * The body is a variable-length run: Mermaid uses extra characters as a\n * layout-rank hint (`A ---- B` pushes B one rank further than `A --> B`).\n * A fixed alternation of `-->|---|==>` mis-tokenized anything longer,\n * stranding the surplus characters and corrupting the following token.\n * The run length is parsed so the edge survives; the rank hint it encodes\n * is not yet modelled by the layout engine.\n *\n * `~~~` is Mermaid's invisible link: it participates in layout but draws\n * nothing.\n *\n * Optional label: -->|label text|\n */\nconst ARROW_REGEX = /^(<|o|x)?(-{2,}|={2,}|-\\.+-|~{3,})(>|o|x)?(?:\\|([^|]*)\\|)?/\n\n/**\n * Link bodies that are only a link when a start or end marker accompanies\n * them.\n *\n * A bare `--` (or `==`) opens Mermaid's text-embedded label syntax —\n * `A -- Yes --> B` — which TEXT_ARROW_REGEX handles. Treating it as an\n * unmarked open link here would consume the opener and strand the label.\n * Mermaid itself requires three characters for an unmarked open link\n * (`A --- B`), so this only rejects what Mermaid also rejects.\n */\nconst AMBIGUOUS_UNMARKED_BODIES = new Set(['--', '=='])\n\n/**\n * Map a raw start/end marker character (`<`, `>`, `o`, `x`, or undefined) to\n * the distinct terminator shape it draws, if any.\n *\n * `<`/`>` are plain arrowheads — already handled by `hasArrowStart`/\n * `hasArrowEnd` — so they map to `undefined` here, same as no marker at all.\n * Only `o`/`x` need their own shape (circle/cross) recorded, so the ASCII\n * renderer can draw something other than the default arrowhead glyph — see\n * issue #330.\n */\nfunction markerKind(\n marker: string | undefined,\n): 'circle' | 'cross' | undefined {\n if (marker === 'o') return 'circle'\n if (marker === 'x') return 'cross'\n return undefined\n}\n\n/**\n * Text-embedded label regex — matches \"-- label -->\", \"-. label .->\", \"== label ==>\" syntax.\n * Tried as fallback when ARROW_REGEX doesn't match.\n *\n * The closing operator is a variable-length run, matching ARROW_REGEX. While\n * it was a fixed alternation, `A -- label ----> B` consumed only `---` from\n * `---->`, leaving `-> B`, which forms no node group — so the edge and its\n * target node were both dropped silently. Exactly the failure mode the\n * variable-length work exists to remove.\n *\n * Based on PR #36 by @liuxiaopai-ai (https://github.com/lukilabs/beautiful-mermaid/pull/36)\n */\nconst TEXT_ARROW_REGEX =\n /^(<|o|x)?(--|-\\.|==)\\s+(.+?)\\s+(-{2,}[>ox]|={2,}[>ox]|\\.+-[>ox]|-{3,}|={3,}|-\\.+-)/\n\n/**\n * Node shape patterns — ordered from most specific delimiters to least.\n * Multi-char delimiters must be tried before single-char to avoid false matches.\n *\n * The label-content group for each shape is quote-aware: it matches either a\n * complete `\"...\"` quoted span (any character, including this shape's own\n * closing delimiter chars, is fine inside quotes) or a single character that\n * isn't the start of the closing delimiter. This stops the scanner from\n * treating a `]`/`)`/`}` etc. *inside* a quoted label as the node's real\n * closing bracket (see issue #61) — without this, `A[\"test [] brackets\"]`\n * would stop at the first `]`, which lives inside the quoted string.\n */\n/**\n * A node shape pattern.\n *\n * `shape` is usually a fixed `NodeShape`. For the slash-bracket family it is\n * a function instead, because those four shapes are distinguished by which\n * delimiter *closes* them — which the regex has to capture rather than\n * hard-code. See SLASH_BRACKET note below.\n */\ninterface NodePattern {\n regex: RegExp\n shape: NodeShape | ((closingDelimiter: string) => NodeShape)\n}\n\nconst NODE_PATTERNS: NodePattern[] = [\n // Triple delimiters (must be first)\n {\n regex: /^([\\w\\p{L}-]+)\\(\\(\\(((?:\"[^\"]*\"|(?!\\)\\)\\)).)+)\\)\\)\\)/u,\n shape: 'doublecircle',\n }, // A(((text)))\n\n // Double delimiters with mixed brackets\n {\n regex: /^([\\w\\p{L}-]+)\\(\\[((?:\"[^\"]*\"|(?!\\]\\)).)+)\\]\\)/u,\n shape: 'stadium',\n }, // A([text])\n { regex: /^([\\w\\p{L}-]+)\\(\\(((?:\"[^\"]*\"|(?!\\)\\)).)+)\\)\\)/u, shape: 'circle' }, // A((text))\n {\n regex: /^([\\w\\p{L}-]+)\\[\\[((?:\"[^\"]*\"|(?!\\]\\]).)+)\\]\\]/u,\n shape: 'subroutine',\n }, // A[[text]]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\(((?:\"[^\"]*\"|(?!\\)\\]).)+)\\)\\]/u,\n shape: 'cylinder',\n }, // A[(text)]\n\n /*\n * SLASH_BRACKET family — must come before plain [text].\n *\n * Four shapes share a `[` + slash opener and differ only in which\n * delimiter closes them:\n *\n * A[/text\\] trapezoid A[/text/] parallelogram\n * A[\\text/] trapezoid-alt A[\\text\\] parallelogram-alt\n *\n * They CANNOT be four separate patterns each negated against its own\n * closing pair. A pattern for `[/…\\]` whose content merely excludes `\\]`\n * will happily run past a `/]` to reach a `\\]` later on the same line, so\n *\n * A[/parallelogram/] --> B[\\alt\\]\n *\n * matched as a single trapezoid whose label was the entire statement —\n * silently swallowing the edge and the second node. Ordering the patterns\n * differently only moves which input breaks.\n *\n * Instead, one pattern per opener stops at whichever slash-close comes\n * first and captures it, and the captured delimiter selects the shape.\n */\n {\n regex: /^([\\w\\p{L}-]+)\\[\\/((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '\\\\' ? 'trapezoid' : 'parallelogram'),\n }, // A[/text\\] or A[/text/]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\\\((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '/' ? 'trapezoid-alt' : 'parallelogram-alt'),\n }, // A[\\text/] or A[\\text\\]\n\n // Asymmetric flag shape\n { regex: /^([\\w\\p{L}-]+)>((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'asymmetric' }, // A>text]\n\n // Double curly braces (hexagon) — must come before single {text}\n {\n regex: /^([\\w\\p{L}-]+)\\{\\{((?:\"[^\"]*\"|(?!\\}\\}).)+)\\}\\}/u,\n shape: 'hexagon',\n }, // A{{text}}\n\n // Single-char delimiters (last — most common, least specific)\n { regex: /^([\\w\\p{L}-]+)\\[((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'rectangle' }, // A[text]\n { regex: /^([\\w\\p{L}-]+)\\(((?:\"[^\"]*\"|(?!\\)).)+)\\)/u, shape: 'rounded' }, // A(text)\n { regex: /^([\\w\\p{L}-]+)\\{((?:\"[^\"]*\"|(?!\\}).)+)\\}/u, shape: 'diamond' }, // A{text}\n]\n\n/**\n * Regex for a bare node reference (just an ID, no shape brackets).\n *\n * Only allows a hyphen when it's sandwiched between word characters\n * (`foo-bar`, `step-1-b`), never a bare/trailing/doubled hyphen. This keeps\n * legitimately hyphenated ids intact while stopping the id from swallowing\n * the leading dashes of an immediately-following arrow when there's no\n * whitespace before it — e.g. `A-->B` (see issue #61): the naive `[\\w-]+`\n * greedily consumed `A--`, leaving a bogus node and no edge. Arrow tokens\n * always start with `-`/`=`/`<` followed by another non-word character\n * (`-`, `.`, `=`, `>`), which this pattern never matches into, so it now\n * stops cleanly at `A` and lets the arrow regex take over.\n *\n * `\\p{L}` (any Unicode letter, alongside plain `\\w`) is included so a bare\n * non-ASCII name (`Lasaña`, `日本`) matches in full instead of truncating at\n * the first non-ASCII character and silently stranding the rest of the line\n * as unparsed text — see issue #328. This mirrors the state-diagram\n * transition regex below, which already allows `\\p{L}` for the same reason.\n */\nconst BARE_NODE_REGEX = /^([\\w\\p{L}]+(?:-[\\w\\p{L}]+)*)/u\n\n/**\n * Node id immediately followed by the expanded-syntax opener: `A@{`.\n *\n * Only the id is captured — the block itself needs depth- and quote-aware\n * scanning (a label may contain `}`), which a regex would do badly, so\n * `matchExpandedBlock` takes over from here.\n */\nconst EXPANDED_NODE_ID_REGEX = /^([\\w\\p{L}-]+)(?=@\\{)/u\n\n/**\n * Resolve the geometry for an `A@{ ... }` node.\n *\n * An `icon:` or `img:` node has no `shape:` of its own — its outline comes\n * from `form:` instead (Mermaid defaults to a square). An unrecognized shape\n * name falls back to a rectangle rather than throwing: Mermaid adds shape\n * names regularly, and rendering a plain box beats failing the whole diagram\n * over one unknown name.\n */\nfunction expandedNodeShape(meta: ExpandedNodeMeta): NodeShape {\n if (meta.shape) {\n return resolveShapeName(meta.shape) ?? 'rectangle'\n }\n\n if (meta.icon !== undefined || meta.img !== undefined) {\n switch (meta.form?.toLowerCase()) {\n case 'circle':\n return 'circle'\n case 'rounded':\n return 'rounded'\n default:\n return 'rectangle'\n }\n }\n\n return 'rectangle'\n}\n\n/**\n * Resolve the display label for an `A@{ ... }` node.\n *\n * Falls back to the node id when no `label:` is given, matching how the\n * bracket syntax treats a bare `A`. For an icon or image node with no label,\n * the icon/image reference itself is shown: this renderer draws neither\n * FontAwesome glyphs nor remote images, so showing the reference is more\n * useful than an empty box, and it keeps the node identifiable.\n */\nfunction expandedNodeLabel(id: string, meta: ExpandedNodeMeta): string {\n if (meta.label !== undefined && meta.label.length > 0) return meta.label\n if (meta.icon) return meta.icon\n if (meta.img) return meta.img\n return id\n}\n\n/**\n * Apply a `click` statement to the graph.\n *\n * Mermaid's forms:\n * click A \"https://example.com\"\n * click A \"https://example.com\" \"Tooltip\"\n * click A \"https://example.com\" _blank\n * click A href \"https://example.com\" \"Tooltip\" _blank\n * click A call myCallback()\n * click A callback \"Tooltip\"\n *\n * A callback is recorded but never invoked — this renderer emits static SVG\n * and does not execute script supplied by a diagram. An href, by contrast, is\n * genuinely actionable: the node is wrapped in an SVG <a>, which works in any\n * browser without script.\n *\n * The actual grammar and href-safety rules live in packages/core/src/click-directive.ts,\n * shared with the class diagram parser\n * (packages/mermaid-parser/src/class/parser.ts) — this is a thin wrapper\n * binding it to this parser's `graph.interactions` map.\n */\nfunction applyClickStatement(line: string, graph: MermaidGraph): void {\n applyClickStatementShared(line, graph.interactions)\n}\n\n/**\n * Id immediately followed by the expanded-syntax opener on a standalone\n * metadata line: `e1@{ ... }`.\n *\n * Shared by both parsers (`tryApplyEdgeMetaLine`) since the syntax and its\n * \"must already be a known edge id\" disambiguation rule are identical for\n * flowcharts and state diagrams — only how edge ids get declared differs\n * (`A e1@--> B` vs. `A e1@--> B` with a simpler arrow set).\n */\nconst EDGE_META_ID_REGEX = /^([\\w-]+)(?=@\\{)/\n\n/**\n * Try to parse and apply a standalone `e1@{ animate: true }` edge-metadata\n * line.\n *\n * Told apart from a node's `A@{ shape: ... }` by whether the id was already\n * declared as an edge id (`A e1@--> B`) — checked before node parsing so an\n * edge id is never registered as a stray node. Shared between\n * `parseFlowchart` and `parseStateDiagram`; returns `true` if the line was\n * consumed as edge metadata (caller should `continue` its loop).\n */\nfunction tryApplyEdgeMetaLine(line: string, graph: MermaidGraph): boolean {\n const edgeMetaMatch = line.match(EDGE_META_ID_REGEX)\n if (!edgeMetaMatch || !graph.edges.some((e) => e.id === edgeMetaMatch[1])) {\n return false\n }\n const block = matchExpandedBlock(line.slice(edgeMetaMatch[1]!.length))\n if (!block) return false\n applyEdgeMeta(graph, edgeMetaMatch[1]!, parseExpandedMeta(block.body))\n return true\n}\n\n/**\n * Apply an `e1@{ ... }` metadata block to the edge with that id.\n *\n * Only `animate` is acted on; Mermaid's other edge keys (`animation`,\n * `curve`) are recorded as no-ops rather than errors, matching how an\n * unknown node shape degrades.\n */\nfunction applyEdgeMeta(\n graph: MermaidGraph,\n edgeId: string,\n meta: Record<string, string | undefined>,\n): void {\n for (const edge of graph.edges) {\n if (edge.id !== edgeId) continue\n if (meta.animate !== undefined) {\n edge.animate = meta.animate.toLowerCase() !== 'false'\n }\n // Mermaid's `animation: fast|slow` is a speed hint on top of animate.\n if (meta.animation !== undefined) edge.animate = true\n }\n}\n\n/** Regex for ::: class shorthand suffix — matches :::className immediately after a node */\nconst CLASS_SHORTHAND_REGEX = /^:::([\\w][\\w-]*)/\n\n/**\n * Regex for ::: class shorthand appearing BEFORE the shape brackets,\n * e.g. A:::external[Label]. Captures the bare id and the class name so\n * the shorthand can be stripped out before shape-pattern matching runs.\n *\n * The id group allows `\\p{L}` (see BARE_NODE_REGEX above) so a non-ASCII\n * id keeps working with this shorthand instead of falling through to\n * BARE_NODE_REGEX with the `:::className` suffix left dangling as\n * unparsed text. The class-name group stays ASCII-only — Mermaid class\n * names follow CSS identifier conventions, not node-name conventions.\n */\nconst PRE_CLASS_SHORTHAND_REGEX = /^([\\w\\p{L}-]+):::([\\w][\\w-]*)/u\n\n/**\n * Parse a line that contains node definitions and edges.\n * Handles chaining: A --> B --> C produces edges A→B and B→C.\n * Handles parallel links: A & B --> C & D produces 4 edges.\n */\nfunction parseEdgeLine(\n line: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): void {\n let remaining = line.trim()\n\n // Parse the first node group (possibly with & separators)\n const firstGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!firstGroup || firstGroup.ids.length === 0) return\n\n remaining = firstGroup.remaining.trim()\n let prevGroupIds = firstGroup.ids\n\n // Parse arrow + node-group pairs until the line is exhausted\n while (remaining.length > 0) {\n let hasArrowStart: boolean\n let style: EdgeStyle\n let hasArrowEnd: boolean\n let edgeLabel: string | undefined\n let startMarkerKind: 'circle' | 'cross' | undefined\n let endMarkerKind: 'circle' | 'cross' | undefined\n\n /*\n * Optional edge id prefix: `A e1@--> B` (Mermaid v11.10.0+). Consumed\n * before the arrow so ARROW_REGEX still sees the link token at position\n * zero. `@` is not otherwise valid ahead of a link, so this cannot\n * shadow existing syntax.\n */\n let edgeId: string | undefined\n const edgeIdMatch = remaining.match(/^([\\w-]+)@(?=[-=<~ox])/)\n if (edgeIdMatch) {\n edgeId = edgeIdMatch[1]\n remaining = remaining.slice(edgeIdMatch[0].length)\n }\n\n const arrowMatch = remaining.match(ARROW_REGEX)\n const arrowBody = arrowMatch?.[2]\n const startMarker = arrowMatch?.[1]\n const endMarker = arrowMatch?.[3]\n\n if (\n arrowMatch &&\n arrowBody !== undefined &&\n // An unmarked `--`/`==` is the text-label opener, not a link.\n !(\n startMarker === undefined &&\n endMarker === undefined &&\n AMBIGUOUS_UNMARKED_BODIES.has(arrowBody)\n )\n ) {\n // `o`/`x` mark a circle/cross terminator (alongside `<` for a reversed\n // arrow) — `markerKind` records which, so the ASCII renderer can draw\n // a distinct glyph instead of the default arrowhead (see issue #330,\n // a follow-up to issue #65 which first stopped these from being\n // dropped entirely).\n hasArrowStart = startMarker !== undefined\n startMarkerKind = markerKind(startMarker)\n const rawEdgeLabel = arrowMatch[4]?.trim()\n edgeLabel = rawEdgeLabel ? normalizeBrTags(rawEdgeLabel) : undefined\n remaining = remaining.slice(arrowMatch[0].length).trim()\n style = arrowStyleFromBody(arrowBody)\n hasArrowEnd = endMarker !== undefined\n endMarkerKind = markerKind(endMarker)\n } else {\n // Fallback: text-embedded label syntax (-- Yes -->, -. Maybe .->, == Sure ==>)\n const textMatch = remaining.match(TEXT_ARROW_REGEX)\n if (!textMatch) break\n hasArrowStart = Boolean(textMatch[1])\n startMarkerKind = markerKind(textMatch[1])\n const rawLabel = textMatch[3]!.trim()\n edgeLabel = rawLabel ? normalizeBrTags(rawLabel) : undefined\n const openOp = textMatch[2]!\n const closeOp = textMatch[4]!\n remaining = remaining.slice(textMatch[0].length).trim()\n style = textArrowStyleFromOps(openOp, closeOp)\n // closeOp is a variable-length run (e.g. \"---o\", \"==x\", \"-.-.->\"); only\n // its final character can be a circle/cross marker. A circle/cross\n // terminator is its own kind of arrow ending — like `>` — so it counts\n // toward hasArrowEnd too; an unmarked run (`---`, `===`, `-.-`) is the\n // only case with no arrowhead of any kind at the close.\n endMarkerKind = markerKind(closeOp.slice(-1))\n hasArrowEnd = closeOp.endsWith('>') || endMarkerKind !== undefined\n }\n\n // Parse the next node group\n const nextGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!nextGroup || nextGroup.ids.length === 0) break\n\n remaining = nextGroup.remaining.trim()\n\n // Emit Cartesian product of edges: every source × every target\n for (const sourceId of prevGroupIds) {\n for (const targetId of nextGroup.ids) {\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style,\n hasArrowStart,\n hasArrowEnd,\n ...(startMarkerKind !== undefined\n ? { startMarker: startMarkerKind }\n : {}),\n ...(endMarkerKind !== undefined ? { endMarker: endMarkerKind } : {}),\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n }\n }\n\n prevGroupIds = nextGroup.ids\n }\n}\n\ninterface ConsumedNodeGroup {\n ids: string[]\n remaining: string\n}\n\n/**\n * Consume one or more nodes separated by `&`.\n * E.g. \"A & B & C --> ...\" returns ids: ['A', 'B', 'C']\n */\nfunction consumeNodeGroup(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNodeGroup | null {\n const first = consumeNode(text, graph, subgraphStack)\n if (!first) return null\n\n const ids = [first.id]\n let remaining = first.remaining.trim()\n\n // Check for & separators\n while (remaining.startsWith('&')) {\n remaining = remaining.slice(1).trim()\n const next = consumeNode(remaining, graph, subgraphStack)\n if (!next) break\n ids.push(next.id)\n remaining = next.remaining.trim()\n }\n\n return { ids, remaining }\n}\n\ninterface ConsumedNode {\n id: string\n remaining: string\n}\n\n/**\n * Try to consume a node definition from the start of `text`.\n * If the node has a shape+label (e.g. A[Text]), it's registered in the graph.\n * If it's a bare reference (e.g. A), we look it up or create a default.\n * Also handles ::: class shorthand suffix.\n */\nfunction consumeNode(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNode | null {\n let id: string | null = null\n let remaining: string = text\n\n // Check for ::: class shorthand appearing BEFORE the shape brackets\n // (e.g. A:::external[Label]). Strip it out so shape-pattern matching\n // below still sees the id directly adjacent to its brackets.\n let preClassName: string | undefined\n const preClassMatch = remaining.match(PRE_CLASS_SHORTHAND_REGEX)\n if (preClassMatch) {\n preClassName = preClassMatch[2]!\n remaining = preClassMatch[1]! + remaining.slice(preClassMatch[0].length)\n }\n\n /*\n * Expanded syntax: `A@{ shape: doc, label: \"Report\" }` (Mermaid v11.3.0+).\n *\n * Tried before the bracket patterns because the id is followed by `@{`,\n * which none of them match — without this the id would fall through to\n * BARE_NODE_REGEX and the whole metadata block would be stranded as\n * unparsed text.\n */\n const expandedIdMatch = remaining.match(EXPANDED_NODE_ID_REGEX)\n if (expandedIdMatch) {\n const expandedId = expandedIdMatch[1]!\n const block = matchExpandedBlock(remaining.slice(expandedId.length))\n if (block) {\n const meta = parseExpandedMeta(block.body)\n registerNode(graph, subgraphStack, {\n id: expandedId,\n label: normalizeBrTags(expandedNodeLabel(expandedId, meta)),\n shape: expandedNodeShape(meta),\n })\n id = expandedId\n remaining = remaining.slice(expandedId.length + block.length)\n }\n }\n\n // Try each node pattern (shape-qualified)\n if (id === null) {\n for (const { regex, shape } of NODE_PATTERNS) {\n const match = remaining.match(regex)\n if (match) {\n id = match[1]!\n const label = normalizeBrTags(match[2]!)\n // The slash-bracket family resolves its shape from the closing\n // delimiter it captured in group 3; every other pattern has a fixed\n // shape. See the SLASH_BRACKET note in NODE_PATTERNS.\n const resolvedShape =\n typeof shape === 'function' ? shape(match[3] ?? '') : shape\n registerNode(graph, subgraphStack, { id, label, shape: resolvedShape })\n remaining = remaining.slice(match[0].length)\n break\n }\n }\n }\n\n // Bare node reference — only register if node doesn't exist yet.\n // If it already exists, do NOT track it in the current subgraph;\n // nodes belong to the subgraph where they're first defined.\n if (id === null) {\n const bareMatch = remaining.match(BARE_NODE_REGEX)\n if (bareMatch) {\n id = bareMatch[1]!\n if (!graph.nodes.has(id)) {\n registerNode(graph, subgraphStack, {\n id,\n label: id,\n shape: 'rectangle',\n })\n }\n remaining = remaining.slice(bareMatch[0].length)\n }\n }\n\n if (id === null) return null\n\n if (preClassName) {\n graph.classAssignments.set(id, preClassName)\n }\n\n // Check for ::: class shorthand suffix immediately after the node\n const classMatch = remaining.match(CLASS_SHORTHAND_REGEX)\n if (classMatch) {\n graph.classAssignments.set(id, classMatch[1]!)\n remaining = remaining.slice(classMatch[0].length)\n }\n\n return { id, remaining }\n}\n\n/** Register a node in the graph and track it in the current subgraph */\nfunction registerNode(\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n trackInSubgraph(subgraphStack, node.id)\n}\n\n/** Add node ID to the innermost subgraph if we're inside one */\nfunction trackInSubgraph(\n subgraphStack: MermaidSubgraph[],\n nodeId: string,\n): void {\n if (subgraphStack.length > 0) {\n const current = subgraphStack[subgraphStack.length - 1]!\n if (!current.nodeIds.includes(nodeId)) {\n current.nodeIds.push(nodeId)\n }\n }\n}\n\n/**\n * Map a link body to its edge style, ignoring direction and run length.\n *\n * The body is the run between the optional start/end markers: `--`, `----`,\n * `==`, `-.-`, `-..-`, `~~~`, etc. Classification is by the characters used,\n * not the length, so every run length of a given style behaves identically.\n */\nfunction arrowStyleFromBody(body: string): EdgeStyle {\n if (body.startsWith('~')) return 'invisible'\n if (body.includes('.')) return 'dotted'\n if (body.startsWith('=')) return 'thick'\n return 'solid'\n}\n\n/** Map text-embedded arrow open/close operators to edge style */\nfunction textArrowStyleFromOps(openOp: string, closeOp: string): EdgeStyle {\n // Classify by the characters used, not by exact token, so every run length\n // of a given style resolves identically — the same rule arrowStyleFromBody\n // applies to the plain arrow forms.\n if (openOp.includes('.') || closeOp.includes('.')) return 'dotted'\n if (openOp.startsWith('=') || closeOp.startsWith('=')) return 'thick'\n return 'solid'\n}\n"],"mappings":"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"}
|