@zombie-mermaid/mermaid-parser 4.1.0 → 4.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/dist/index.cjs +9 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.js +340 -179
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/flowchart-edge-label-space-before-pipe.test.ts +54 -0
- package/src/__tests__/pie-mermaid-spec.test.ts +308 -0
- package/src/__tests__/pie-parser.test.ts +452 -0
- package/src/flowchart-parser.ts +7 -4
- package/src/index.ts +3 -0
- package/src/pie/parser.ts +393 -0
- package/src/pie/types.ts +109 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/architecture/parser.ts","../src/architecture/to-graph.ts","../src/c4/parser.ts","../src/c4/format.ts","../src/class/parser.ts","../src/class/format.ts","../src/er/parser.ts","../src/sequence/box-color.ts","../src/sequence/parser.ts","../src/sequence/activation-check.ts","../src/sequence/activation-fix.ts","../src/xychart/parser.ts","../src/xychart/colors.ts","../src/expanded-shapes.ts","../src/flowchart-parser.ts"],"sourcesContent":["import { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport type {\n ArchitectureDiagram,\n ArchitectureEdge,\n ArchitecturePort,\n} from './types.ts'\n\n// ============================================================================\n// Architecture diagram parser\n//\n// Parses Mermaid's `architecture-beta` diagram\n// (https://mermaid.ai/open-source/syntax/architecture.html):\n//\n// architecture-beta\n// group api(cloud)[API]\n// service db(database)[Database] in api\n// service server(server)[Server] in api\n// junction j1 in api\n// db:L -- R:server\n// server{group}:B --> T:client{group}\n//\n// Grammar:\n// group <id>[(icon)][[Title]] [in <parent>]\n// service <id>[(icon)][[Title]] [in <parent>]\n// junction <id> [in <parent>]\n// <id>[{group}]:<L|R|T|B> <--|-->|<-->|--> <L|R|T|B>:<id>[{group}]\n// align row|column <id> <id> ... (accepted, no effect: see to-graph)\n//\n// Unlike Mermaid's own parser, every malformed statement throws with its line\n// number rather than being dropped.\n// ============================================================================\n\nconst ID = String.raw`[\\w-]+`\nconst DECL_RE = new RegExp(\n String.raw`^(group|service)\\s+(${ID})\\s*(?:\\(([^)]*)\\))?\\s*(?:\\[([^\\]]*)\\])?(?:\\s+in\\s+(${ID}))?$`,\n)\nconst JUNCTION_RE = new RegExp(\n String.raw`^junction\\s+(${ID})(?:\\s+in\\s+(${ID}))?$`,\n)\nconst EDGE_RE = new RegExp(\n String.raw`^(${ID})(\\{group\\})?:([LRTB])\\s+(<)?--(>)?\\s+([LRTB]):(${ID})(\\{group\\})?$`,\n)\nconst ALIGN_RE = new RegExp(\n String.raw`^align\\s+(?:row|column)\\s+${ID}(?:\\s+${ID})+$`,\n)\n\ntype Kind = 'group' | 'service' | 'junction'\n\nfunction fail(stmt: Statement, msg: string): never {\n throw new Error(\n `Architecture diagram, line ${stmt.line}: ${msg} — \"${stmt.text}\"`,\n )\n}\n\n/** Strip the optional quotes around a custom icon name like `\"logos:aws\"`. */\nfunction cleanIcon(raw: string | undefined): string | undefined {\n const icon = raw?.trim().replace(/^\"(.*)\"$/, '$1')\n return icon || undefined\n}\n\n/**\n * Parse a Mermaid architecture diagram.\n * Expects the first statement to be the `architecture-beta` header.\n */\nexport function parseArchitecture(lines: Statement[]): ArchitectureDiagram {\n const diagram: ArchitectureDiagram = {\n groups: [],\n services: [],\n junctions: [],\n edges: [],\n }\n const kinds = new Map<string, Kind>()\n // `in` may name a group declared later, so parents and edge endpoints are\n // resolved after the loop.\n const parentRefs: { stmt: Statement; parent: string }[] = []\n const pendingEdges: { stmt: Statement; edge: ArchitectureEdge }[] = []\n\n const declare = (\n stmt: Statement,\n id: string,\n kind: Kind,\n parent: string | undefined,\n ): void => {\n if (kinds.has(id)) fail(stmt, `duplicate id \"${id}\"`)\n if (parent === id) fail(stmt, `\"${id}\" cannot be inside itself`)\n kinds.set(id, kind)\n if (parent) parentRefs.push({ stmt, parent })\n }\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const text = stmt.text\n\n const decl = text.match(DECL_RE)\n if (decl) {\n const kind = decl[1] as 'group' | 'service'\n const id = decl[2]!\n const title = decl[4]\n const parent = decl[5]\n declare(stmt, id, kind, parent)\n const entry = {\n id,\n icon: cleanIcon(decl[3]),\n title: title !== undefined ? normalizeBrTags(title.trim()) : id,\n parent,\n }\n if (kind === 'group') diagram.groups.push(entry)\n else diagram.services.push(entry)\n continue\n }\n\n const junction = text.match(JUNCTION_RE)\n if (junction) {\n const id = junction[1]!\n const parent = junction[2]\n declare(stmt, id, 'junction', parent)\n diagram.junctions.push({ id, parent })\n continue\n }\n\n const edge = text.match(EDGE_RE)\n if (edge) {\n pendingEdges.push({\n stmt,\n edge: {\n source: edge[1]!,\n sourceGroup: edge[2] !== undefined,\n sourcePort: edge[3] as ArchitecturePort,\n arrowStart: edge[4] !== undefined,\n arrowEnd: edge[5] !== undefined,\n targetPort: edge[6] as ArchitecturePort,\n target: edge[7]!,\n targetGroup: edge[8] !== undefined,\n },\n })\n continue\n }\n\n // Alignment constraints (Mermaid v11.16+) have no flowchart equivalent.\n if (ALIGN_RE.test(text)) continue\n\n fail(stmt, 'unrecognized statement')\n }\n\n for (const { stmt, parent } of parentRefs) {\n if (kinds.get(parent) !== 'group') {\n fail(stmt, `\"in ${parent}\" must name a declared group`)\n }\n }\n\n const parentOf = new Map<string, string>()\n for (const g of diagram.groups) if (g.parent) parentOf.set(g.id, g.parent)\n for (const g of diagram.groups) {\n const seen = new Set([g.id])\n for (let p = parentOf.get(g.id); p; p = parentOf.get(p)) {\n if (seen.has(p)) {\n throw new Error(\n `Architecture diagram: group \"${g.id}\" is nested inside itself`,\n )\n }\n seen.add(p)\n }\n }\n\n for (const { stmt, edge } of pendingEdges) {\n const ends: [string, boolean][] = [\n [edge.source, edge.sourceGroup],\n [edge.target, edge.targetGroup],\n ]\n for (const [id, viaGroup] of ends) {\n const kind = kinds.get(id)\n if (!kind) fail(stmt, `unknown id \"${id}\"`)\n if (kind === 'group') {\n fail(stmt, `edges connect services or junctions, not the group \"${id}\"`)\n }\n const parent = [...diagram.services, ...diagram.junctions].find(\n (n) => n.id === id,\n )?.parent\n if (viaGroup && !parent) {\n fail(stmt, `\"{group}\" needs \"${id}\" to be declared inside a group`)\n }\n }\n diagram.edges.push(edge)\n }\n\n return diagram\n}\n","import type {\n Direction,\n MermaidEdge,\n MermaidGraph,\n MermaidNode,\n MermaidSubgraph,\n NodeShape,\n} from '@zombie-mermaid/core'\nimport type {\n ArchitectureDiagram,\n ArchitectureEdge,\n ArchitecturePort,\n} from './types.ts'\n\n// ============================================================================\n// Architecture -> flowchart lowering\n//\n// `architecture-beta` is boxes in nested boxes joined by edges, which the\n// flowchart pipeline already lays out and draws (ELK for SVG, the grid router\n// for ASCII). Like the other lowered types it becomes a `MermaidGraph`:\n//\n// group -> subgraph (nested via `in`)\n// service -> node; the icon picks a shape\n// junction -> small filled circle, no label\n// edge -> edge; `<`/`>` become arrowheads\n// `{group}` end -> the edge attaches to the service's enclosing group\n//\n// What is not carried over: Mermaid places services on a grid from the edge\n// ports, so `a:R -- L:b` means \"b is right of a\". Here ports only choose the\n// flow direction (majority axis) and which way each edge points in the\n// layout; exact placement is the flowchart layout's. Icons are not drawn\n// (only the database/disk shapes differ) and `align` is ignored.\n// ============================================================================\n\n/** Built-in Mermaid icons that have a distinct flowchart shape. */\nconst ICON_SHAPE: Record<string, NodeShape> = {\n database: 'cylinder',\n disk: 'cylinder',\n cloud: 'stadium',\n internet: 'circle',\n}\n\nconst HORIZONTAL: ReadonlySet<ArchitecturePort> = new Set(['L', 'R'])\n\n/** Flow direction: vertical only when more edges leave via T/B than L/R. */\nfunction pickDirection(edges: readonly ArchitectureEdge[]): Direction {\n let horizontal = 0\n let vertical = 0\n for (const e of edges) {\n for (const p of [e.sourcePort, e.targetPort]) {\n if (HORIZONTAL.has(p)) horizontal++\n else vertical++\n }\n }\n return vertical > horizontal ? 'TB' : 'LR'\n}\n\n/**\n * Whether the edge, as written, runs against the layout flow. `a:L -- R:b`\n * puts `a` to the right of `b`, so in a left-to-right layout the edge has to\n * be emitted reversed for `a` to land on the right.\n */\nfunction runsBackwards(e: ArchitectureEdge, direction: Direction): boolean {\n const horizontal = direction === 'LR' || direction === 'RL'\n const [back, forward]: ArchitecturePort[] = horizontal\n ? ['L', 'R']\n : ['T', 'B']\n // Source port on the far side of the flow, or target port on the near side.\n if (e.sourcePort === forward || e.targetPort === back) return false\n return e.sourcePort === back || e.targetPort === forward\n}\n\nexport function architectureToGraph(\n diagram: ArchitectureDiagram,\n): MermaidGraph {\n const nodes = new Map<string, MermaidNode>()\n const parentOf = new Map<string, string | undefined>()\n\n for (const s of diagram.services) {\n nodes.set(s.id, {\n id: s.id,\n label: s.title,\n shape: (s.icon && ICON_SHAPE[s.icon]) || 'rectangle',\n })\n parentOf.set(s.id, s.parent)\n }\n for (const j of diagram.junctions) {\n nodes.set(j.id, { id: j.id, label: '', shape: 'filled-circle' })\n parentOf.set(j.id, j.parent)\n }\n\n // Subgraphs, nested. Children are attached in declaration order.\n const subgraphs = new Map<string, MermaidSubgraph>()\n for (const g of diagram.groups) {\n subgraphs.set(g.id, {\n id: g.id,\n label: g.title,\n nodeIds: [],\n children: [],\n })\n }\n const roots: MermaidSubgraph[] = []\n for (const g of diagram.groups) {\n const sg = subgraphs.get(g.id)!\n const parent = g.parent ? subgraphs.get(g.parent) : undefined\n if (parent) parent.children.push(sg)\n else roots.push(sg)\n }\n for (const [id, parent] of parentOf) {\n subgraphs.get(parent ?? '')?.nodeIds.push(id)\n }\n\n const direction = pickDirection(diagram.edges)\n const edges: MermaidEdge[] = diagram.edges.map((e) => {\n const end = (id: string, viaGroup: boolean): string =>\n (viaGroup ? parentOf.get(id) : undefined) ?? id\n const forward = !runsBackwards(e, direction)\n const [source, target] = forward\n ? [end(e.source, e.sourceGroup), end(e.target, e.targetGroup)]\n : [end(e.target, e.targetGroup), end(e.source, e.sourceGroup)]\n return {\n source,\n target,\n style: 'solid',\n hasArrowStart: forward ? e.arrowStart : e.arrowEnd,\n hasArrowEnd: forward ? e.arrowEnd : e.arrowStart,\n }\n })\n\n return {\n direction,\n nodes,\n edges,\n subgraphs: roots,\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n}\n","import { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport type {\n C4Boundary,\n C4Diagram,\n C4Element,\n C4ElementKind,\n C4LayoutHint,\n C4ElementShape,\n C4Relationship,\n C4Variant,\n} from './types.ts'\n\n// ============================================================================\n// C4 diagram parser\n//\n// Supported syntax (Mermaid C4 macros):\n// Person / Person_Ext (alias, label, descr)\n// System[Db|Queue][_Ext] (alias, label, descr)\n// Container[Db|Queue][_Ext] (alias, label, techn, descr)\n// Component[Db|Queue][_Ext] (alias, label, techn, descr)\n// Boundary / Enterprise_Boundary / System_Boundary / Container_Boundary\n// (alias, label[, type]) { ... }\n// Deployment_Node / Node / Node_L / Node_R (alias, label, type, descr) { ... }\n// Rel / BiRel / Rel_U|Up|D|Down|L|Left|R|Right|Back (from, to, label, techn)\n// RelIndex (index, from, to, label, techn)\n// title <text>\n//\n// `Rel_U/D/L/R` record a placement hint (`C4Relationship.layout`) the\n// renderers use to steer layout. Accepted and ignored (styling / layout\n// config this renderer does not honor): UpdateElementStyle, UpdateRelStyle, UpdateLayoutConfig, LAYOUT_*,\n// SHOW_LEGEND, SHOW_FLOATING_LEGEND, AddElementTag, AddRelTag,\n// AddBoundaryTag, RoleTag. Named arguments (`$tags=\"x\"`, `$link=...`) are\n// dropped.\n// ============================================================================\n\nconst HEADERS: Record<string, C4Variant> = {\n c4context: 'context',\n c4container: 'container',\n c4component: 'component',\n c4dynamic: 'dynamic',\n c4deployment: 'deployment',\n}\n\nconst IGNORED_MACRO =\n /^(?:UpdateElementStyle|UpdateRelStyle|UpdateLayoutConfig|LAYOUT_[A-Z_]+|SHOW_LEGEND|SHOW_FLOATING_LEGEND|AddElementTag|AddRelTag|AddBoundaryTag|RoleTag)\\b/\n\nconst ELEMENT_NAME = /^(Person|System|Container|Component)(Db|Queue)?(_Ext)?$/\nconst BOUNDARY_NAME =\n /^(?:Boundary|Enterprise_Boundary|System_Boundary|Container_Boundary)$/\nconst NODE_NAME = /^(?:Deployment_Node|Node|Node_L|Node_R)$/\nconst REL_NAME =\n /^(Rel|BiRel|Rel_U|Rel_Up|Rel_D|Rel_Down|Rel_L|Rel_Left|Rel_R|Rel_Right|Rel_Back|RelIndex)$/\n\nconst BOUNDARY_TYPES: Record<string, string> = {\n Enterprise_Boundary: 'ENTERPRISE',\n System_Boundary: 'SYSTEM',\n Container_Boundary: 'CONTAINER',\n}\n\nconst REL_HINTS: Record<string, C4LayoutHint> = {\n Rel_U: 'up',\n Rel_Up: 'up',\n Rel_D: 'down',\n Rel_Down: 'down',\n Rel_L: 'left',\n Rel_Left: 'left',\n Rel_R: 'right',\n Rel_Right: 'right',\n}\n\nfunction unquote(a: string): string {\n const t = a.trim()\n const inner =\n t.length >= 2 && t.startsWith('\"') && t.endsWith('\"') ? t.slice(1, -1) : t\n return normalizeBrTags(inner)\n}\n\n/**\n * Split a macro's argument list on top-level commas. Double-quoted strings\n * may contain commas and parentheses; `$name=value` named arguments are\n * dropped. Returns null when a quote is unbalanced.\n */\nfunction splitArgs(inner: string): string[] | null {\n const raw: string[] = []\n let cur = ''\n let inQuote = false\n for (const ch of inner) {\n if (ch === '\"') inQuote = !inQuote\n if (ch === ',' && !inQuote) {\n raw.push(cur)\n cur = ''\n } else {\n cur += ch\n }\n }\n if (inQuote) return null\n raw.push(cur)\n return raw\n .map((a) => a.trim())\n .filter((a) => !a.startsWith('$'))\n .map(unquote)\n}\n\n/** `Name(args)` optionally followed by ` {`. */\nconst MACRO = /^([A-Za-z_][A-Za-z0-9_]*)\\s*\\((.*)\\)\\s*(\\{)?\\s*$/\n\nfunction fail(stmt: Statement, msg: string): never {\n throw new Error(`C4 diagram, line ${stmt.line}: ${msg} — \"${stmt.text}\"`)\n}\n\n/**\n * Parse a Mermaid C4 diagram. Expects the first statement to be a\n * `C4Context` / `C4Container` / `C4Component` / `C4Dynamic` /\n * `C4Deployment` header.\n */\nexport function parseC4Diagram(lines: Statement[]): C4Diagram {\n const variant = HEADERS[lines[0]?.text.trim().toLowerCase() ?? '']\n if (!variant) {\n throw new Error(\n `C4 diagram: expected a header of C4Context, C4Container, C4Component, C4Dynamic or C4Deployment, got \"${lines[0]?.text ?? ''}\"`,\n )\n }\n\n const diagram: C4Diagram = {\n variant,\n elements: [],\n boundaries: [],\n relationships: [],\n }\n const aliases = new Set<string>()\n const stack: C4Boundary[] = []\n /** Boundary declared on the previous statement without an inline `{`. */\n let pendingBoundary: C4Boundary | undefined\n\n const claim = (stmt: Statement, alias: string | undefined): string => {\n if (!alias) fail(stmt, 'missing alias (first argument)')\n if (aliases.has(alias)) fail(stmt, `duplicate alias \"${alias}\"`)\n aliases.add(alias)\n return alias\n }\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n if (line === '}') {\n if (stack.length === 0) fail(stmt, 'unmatched \"}\"')\n stack.pop()\n continue\n }\n // Brace on its own line, directly after a boundary macro.\n if (line === '{') {\n if (!pendingBoundary) fail(stmt, 'unexpected \"{\"')\n stack.push(pendingBoundary)\n pendingBoundary = undefined\n continue\n }\n pendingBoundary = undefined\n\n const titleMatch = line.match(/^title(?:\\s+(.*))?$/i)\n if (titleMatch) {\n diagram.title = normalizeBrTags((titleMatch[1] ?? '').trim())\n continue\n }\n\n if (IGNORED_MACRO.test(line)) continue\n\n const m = line.match(MACRO)\n if (!m) fail(stmt, 'unrecognized statement')\n const name = m[1]!\n const opensBlock = m[3] === '{'\n const args = splitArgs(m[2]!)\n if (!args) fail(stmt, 'unbalanced quotes')\n\n const el = name.match(ELEMENT_NAME)\n if (el) {\n if (opensBlock) fail(stmt, `\"${name}\" cannot contain a block`)\n const kind = el[1]!.toLowerCase() as C4ElementKind\n const shape: C4ElementShape =\n el[2] === 'Db' ? 'db' : el[2] === 'Queue' ? 'queue' : 'default'\n const hasTech = kind === 'container' || kind === 'component'\n const alias = claim(stmt, args[0])\n const element: C4Element = {\n alias,\n kind,\n shape,\n external: el[3] !== undefined,\n label: args[1] || alias,\n }\n const technology = hasTech ? args[2] : undefined\n const description = hasTech ? args[3] : args[2]\n if (technology) element.technology = technology\n if (description) element.description = description\n diagram.elements.push(element)\n stack.at(-1)?.elementAliases.push(alias)\n continue\n }\n\n if (BOUNDARY_NAME.test(name) || NODE_NAME.test(name)) {\n const isNode = NODE_NAME.test(name)\n const alias = claim(stmt, args[0])\n const boundary: C4Boundary = {\n alias,\n label: args[1] || alias,\n elementAliases: [],\n children: [],\n }\n // Mermaid labels every frame with a type: the macro's own for the\n // typed boundaries, else the explicit argument, else a default.\n // (System_/Container_/Enterprise_Boundary take no type arg.)\n const typed = BOUNDARY_TYPES[name]\n if (typed) boundary.type = typed\n else if (args[2] && (isNode || name === 'Boundary'))\n boundary.type = args[2]\n else boundary.type = isNode ? 'node' : 'system'\n if (isNode && args[3]) boundary.description = args[3]\n const parent = stack.at(-1)\n if (parent) parent.children.push(boundary)\n else diagram.boundaries.push(boundary)\n if (opensBlock) stack.push(boundary)\n else pendingBoundary = boundary\n continue\n }\n\n const rel = name.match(REL_NAME)\n if (rel) {\n const indexed = name === 'RelIndex'\n const a = indexed ? args.slice(1) : args\n if (!a[0] || !a[1]) {\n fail(stmt, 'a relationship needs \"from\" and \"to\" aliases')\n }\n const r: C4Relationship = {\n from: a[0],\n to: a[1],\n label: a[2] ?? '',\n bidirectional: name === 'BiRel',\n }\n if (a[3]) r.technology = a[3]\n if (name === 'Rel_Back') r.reversed = true\n const hint = REL_HINTS[name]\n if (hint) r.layout = hint\n diagram.relationships.push(r)\n continue\n }\n\n fail(stmt, `unknown C4 macro \"${name}\"`)\n }\n\n if (stack.length > 0) {\n throw new Error(\n `C4 diagram: boundary \"${stack.at(-1)!.alias}\" is missing its closing \"}\"`,\n )\n }\n // Mermaid ignores RelIndex's index argument: in C4Dynamic every relationship\n // is numbered by its position in the source, and no other variant numbers.\n if (diagram.variant === 'dynamic') {\n diagram.relationships.forEach((r, i) => {\n r.index = String(i + 1)\n })\n }\n for (const r of diagram.relationships) {\n for (const end of [r.from, r.to]) {\n if (!aliases.has(end)) {\n throw new Error(\n `C4 diagram: relationship refers to undeclared alias \"${end}\"`,\n )\n }\n }\n }\n return diagram\n}\n","import type { C4Boundary, C4Element, C4Relationship } from './types.ts'\n\n// ============================================================================\n// C4 text and layout-hint helpers shared by the SVG and ASCII renderers, so\n// both draw the same words and honor the same `Rel_U/D/L/R` hints.\n// ============================================================================\n\n/** Greedy word wrap; a word longer than `width` stays on its own line. */\nexport function wrapC4Text(text: string, width: number): string[] {\n const out: string[] = []\n for (const paragraph of text.split('\\n')) {\n let cur = ''\n for (const word of paragraph.split(/\\s+/).filter(Boolean)) {\n if (cur && cur.length + 1 + word.length > width) {\n out.push(cur)\n cur = word\n } else {\n cur = cur ? `${cur} ${word}` : word\n }\n }\n out.push(cur)\n }\n return out\n}\n\nconst STEREOTYPES: Record<C4Element['kind'], string> = {\n person: 'Person',\n system: 'Software System',\n container: 'Container',\n component: 'Component',\n}\n\n/**\n * The bracketed type line under an element's name, as Mermaid draws it:\n * `[Person]`, `[Software System]`, `[Container: PostgreSQL]`. External,\n * database and queue variants share their base kind's line; the fill and the\n * shape tell them apart.\n */\nexport function c4TypeLine(el: C4Element): string {\n const tech =\n (el.kind === 'container' || el.kind === 'component') && el.technology\n ? `: ${el.technology}`\n : ''\n return `[${STEREOTYPES[el.kind]}${tech}]`\n}\n\n/** A boundary's type line (`[ENTERPRISE]`, `[Ubuntu]`), when it has a type. */\nexport function c4BoundaryTypeLine(b: C4Boundary): string | undefined {\n return b.type ? `[${b.type}]` : undefined\n}\n\n/**\n * The text lines of a relationship label: the label itself (prefixed with its\n * sequence number in a C4Dynamic diagram), then the technology in brackets on its\n * own line. Empty when the relationship has neither.\n */\nexport function c4RelLabelLines(rel: C4Relationship): string[] {\n const lines: string[] = []\n const head = [rel.index ? `${rel.index}:` : '', rel.label]\n .filter(Boolean)\n .join(' ')\n if (head) lines.push(head)\n if (rel.technology) lines.push(`[${rel.technology}]`)\n return lines\n}\n\n/**\n * How a relationship constrains placement. `source` is meant to sit before\n * `target` along `axis`: above it for `'vertical'`, left of it for\n * `'horizontal'`. `Rel_U`/`Rel_L` reverse the pair (the target sits above /\n * left of the source); plain `Rel` is a vertical hint, since C4 diagrams flow\n * top to bottom by default.\n */\nexport interface C4Placement {\n source: string\n target: string\n axis: 'vertical' | 'horizontal'\n /** True when the hint came from an explicit `Rel_U/D/L/R`. */\n explicit: boolean\n}\n\nexport function c4Placement(rel: C4Relationship): C4Placement {\n switch (rel.layout) {\n case 'up':\n return {\n source: rel.to,\n target: rel.from,\n axis: 'vertical',\n explicit: true,\n }\n case 'left':\n return {\n source: rel.to,\n target: rel.from,\n axis: 'horizontal',\n explicit: true,\n }\n case 'right':\n return {\n source: rel.from,\n target: rel.to,\n axis: 'horizontal',\n explicit: true,\n }\n case 'down':\n return {\n source: rel.from,\n target: rel.to,\n axis: 'vertical',\n explicit: true,\n }\n default:\n return {\n source: rel.from,\n target: rel.to,\n axis: 'vertical',\n explicit: false,\n }\n }\n}\n","import type {\n ClassDiagram,\n ClassNode,\n ClassRelationship,\n ClassMember,\n RelationshipType,\n ClassNamespace,\n} from './types.ts'\nimport {\n normalizeBrTags,\n applyClickStatement,\n splitClassShorthand,\n tryApplyClassAssignment,\n tryApplyClassDef,\n tryApplyCssClass,\n tryApplyStyleStatement,\n type Statement,\n} from '@zombie-mermaid/core'\n\n// ============================================================================\n// Class diagram parser\n//\n// Parses Mermaid classDiagram syntax into a ClassDiagram structure.\n//\n// Supported syntax:\n// class Animal { +String name; +eat() void }\n// class Shape { <<abstract>> }\n// Animal <|-- Dog (inheritance)\n// Car *-- Engine (composition)\n// Car o-- Wheel (aggregation)\n// A --> B (association)\n// A ..> B (dependency)\n// A ..|> B (realization)\n// A \"1\" --> \"*\" B : label (with cardinality + label)\n// Animal : +String name (inline attribute)\n// namespace MyNamespace { class A { } }\n// click Animal \"https://example.com\" \"Tooltip\" _blank (see docs/decisions/no-script-interactivity.md)\n// classDef name fill:#f96 / style Animal fill:#f96 / cssClass \"A,B\" name\n// class Animal:::name (style-class shorthand, also on relationship ends)\n// note \"text\" / note for Animal \"line1\\nline2\"\n// ============================================================================\n\n/**\n * The set of relationship-arrow tokens `parseRelationship` recognizes,\n * shared with the \"does this line even look like a relationship attempt\"\n * check below so the two can't drift apart. Order matters for regex\n * alternation (longer/more-specific tokens like `--|>` must be tried before\n * the bare `--` fallback).\n */\nconst RELATIONSHIP_ARROW_SOURCE =\n '<\\\\|--|<\\\\|\\\\.\\\\.|\\\\*--|o--|-->|--\\\\*|--o|--\\\\|>|\\\\.\\\\.>|\\\\.\\\\.\\\\|>|<--|<\\\\.\\\\.?|--'\n\n/**\n * Matches a line that *contains* a relationship-arrow token somewhere, even\n * if the overall shape (FROM/TO, cardinalities) doesn't parse. Used to\n * scope the \"malformed relationship\" error (issue #761) to lines that\n * already look like an attempt at this construct — mirroring the same\n * \"attempt, then validate\" pattern #722 used for the ER and xychart-beta\n * parsers, so a genuinely unrelated line (no arrow at all) still falls\n * through silently rather than throwing on totally unrecognized syntax.\n */\nconst RELATIONSHIP_ARROW_MARKER = new RegExp(RELATIONSHIP_ARROW_SOURCE)\n\n/**\n * Parse a Mermaid class diagram.\n * Expects the first line to be \"classDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// either a bounds-checked loop-index array access (`lines[i]!` inside a\n// `for (let i = 1; i < lines.length; ...)` loop) or a regex-mandatory-\n// capture-group access after `.match()` (a group that isn't wrapped in an\n// optional `(?:...)?`, so it always participates when the overall match\n// succeeds). Both are the same idioms already accepted as justified\n// elsewhere in this codebase (see src/parser.ts, PR #158, and this\n// subsystem's earlier layout.ts/renderer.ts audit, PR #147, which fixed\n// the genuinely risky assertions but didn't reach this file) —\n// `noUncheckedIndexedAccess` can't see either guarantee, but removing the\n// `!` would only replace a proven-safe assertion with an unreachable guard.\n// Left as-is; no behavior change.\nexport function parseClassDiagram(lines: Statement[]): ClassDiagram {\n const diagram: ClassDiagram = {\n classes: [],\n relationships: [],\n namespaces: [],\n interactions: new Map(),\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n notes: [],\n }\n\n // Track classes by ID for deduplication\n const classMap = new Map<string, ClassNode>()\n // Track namespace nesting\n let currentNamespace: ClassNamespace | null = null\n // Track class body parsing\n let currentClass: ClassNode | null = null\n let braceDepth = 0\n // Line the currently-open class body started on, for the unclosed-body\n // error below (issue #761) — `currentClass`/`braceDepth` alone don't\n // carry position.\n let openClassLine: number | undefined\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- Inside a class body block ---\n if (currentClass && braceDepth > 0) {\n if (line === '}') {\n braceDepth--\n if (braceDepth === 0) {\n currentClass = null\n openClassLine = undefined\n }\n continue\n }\n\n // Check for annotation like <<interface>>\n const annotMatch = line.match(/^<<(\\w+)>>$/)\n if (annotMatch) {\n currentClass.annotation = annotMatch[1]!\n continue\n }\n\n // A line that starts with `<<` but doesn't match the full\n // `<<name>>` shape is an attempted annotation with broken syntax\n // (e.g. a missing closing `>>`) rather than a member — surface that\n // instead of silently mis-parsing it as a member named \"<<foo\" (#761).\n if (line.startsWith('<<')) {\n throw new Error(\n `Line ${stmt.line}: Malformed class annotation \"${line}\" — expected \"<<name>>\" (e.g. \"<<interface>>\").`,\n )\n }\n\n // Parse member: visibility, name, type, optional parens for method\n addMember(currentClass, line, stmt.line)\n continue\n }\n\n // --- click interaction: `click ClassName \"url\" \"tooltip\" _blank` / `click ClassName call fn()` ---\n if (/^click\\s+/i.test(line)) {\n applyClickStatement(line, diagram.interactions)\n continue\n }\n\n // --- Notes: `note \"text\"` / `note for ClassName \"text\"` ---\n // Mermaid's grammar takes a quoted string (`noteText: STR`) and its\n // renderer splits the text on `\\n` after JSON-parsing it (svgDraw.js\n // drawNote), so a literal `\\n` in the source is a line break;\n // normalizeBrTags handles that and `<br/>`, the form the rest of this\n // repo's labels use. The class need not be declared first (or at all).\n const noteMatch = line.match(/^note\\s+(?:for\\s+(\\S+)\\s+)?\"([^\"]*)\"\\s*$/)\n if (noteMatch) {\n const forClass = noteMatch[1]\n diagram.notes.push({\n text: normalizeBrTags(noteMatch[2]!),\n ...(forClass ? { forClass } : {}),\n })\n continue\n }\n\n // --- Styling: `classDef`, `style`, `cssClass \"A,B\" name`, `class A,B name` ---\n // Shared with the flowchart parser (packages/core/src/style-directives.ts). The\n // `class A,B name` assignment form is checked before the declaration\n // regexes below: a declaration never has a second bare-word token after\n // the class id (`class Animal`, `class Animal~T~`, `class Animal {`), so\n // the two can't collide. Mermaid's own class grammar only spells the\n // attachment as `cssClass \"A,B\" name` / `A:::name`; the flowchart-style\n // `class A,B name` is accepted here for parity with flowcharts.\n if (tryApplyClassDef(line, diagram)) continue\n if (tryApplyStyleStatement(line, diagram)) continue\n if (tryApplyCssClass(line, diagram)) continue\n if (tryApplyClassAssignment(line, diagram)) continue\n\n // --- Namespace block start ---\n const nsMatch = line.match(/^namespace\\s+(\\S+)\\s*\\{$/)\n if (nsMatch) {\n currentNamespace = { name: nsMatch[1]!, classIds: [] }\n continue\n }\n\n // --- Namespace end ---\n if (line === '}' && currentNamespace) {\n diagram.namespaces.push(currentNamespace)\n currentNamespace = null\n continue\n }\n\n // --- Class block start: `class ClassName {` or `class ClassName:::style {` ---\n const classBlockMatch = line.match(/^class\\s+(\\S+?)(?:\\s*~(\\w+)~)?\\s*\\{$/)\n if (classBlockMatch) {\n const id = declareClass(classBlockMatch[1]!, classBlockMatch[2])\n currentClass = classMap.get(id) ?? null\n braceDepth = 1\n openClassLine = stmt.line\n continue\n }\n\n // --- Standalone class declaration (no body): `class ClassName` / `class ClassName:::style` ---\n const classOnlyMatch = line.match(/^class\\s+(\\S+?)(?:\\s*~(\\w+)~)?\\s*$/)\n if (classOnlyMatch) {\n declareClass(classOnlyMatch[1]!, classOnlyMatch[2])\n continue\n }\n\n // --- Single-line body: `class ClassName { <<interface>> }` / `class ClassName:::style { -int size }` ---\n // One annotation or one member; `;`-separated members on a single line\n // are already split into separate statements upstream, so a multi-member\n // body can't reach here intact.\n const inlineBodyMatch = line.match(/^class\\s+(\\S+?)\\s*\\{\\s*(.*?)\\s*\\}$/)\n if (inlineBodyMatch) {\n const id = declareClass(inlineBodyMatch[1]!, undefined)\n const cls = classMap.get(id)\n const body = inlineBodyMatch[2]!\n const annotMatch = body.match(/^<<(\\w+)>>$/)\n if (cls && annotMatch) {\n cls.annotation = annotMatch[1]!\n } else if (cls && body.startsWith('<<')) {\n // Same broken-annotation-attempt case as the multi-line body\n // branch above, just on the single-line form (#761).\n throw new Error(\n `Line ${stmt.line}: Malformed class annotation \"${body}\" — expected \"<<name>>\" (e.g. \"<<interface>>\").`,\n )\n } else if (cls && body) {\n addMember(cls, body, stmt.line)\n }\n continue\n }\n\n // --- Inline attribute: `ClassName : +String name` ---\n const inlineAttrMatch = line.match(/^(\\S+?)\\s*:\\s*(.+)$/)\n if (inlineAttrMatch) {\n // Make sure this isn't a relationship line (those have arrows)\n const rest = inlineAttrMatch[2]!\n if (!rest.match(/<\\|--|--|\\*--|o--|-->|\\.\\.>|\\.\\.\\|>/)) {\n addMember(ensureClass(classMap, inlineAttrMatch[1]!), rest, stmt.line)\n continue\n }\n }\n\n // --- Relationship ---\n // Pattern: [FROM] [\"card\"] ARROW [\"card\"] [TO] [: label]\n // Arrows: <|--, *--, o--, -->, ..|>, ..>\n // Can also be reversed: --o, --*, --|>\n const rel = parseRelationship(line)\n if (rel) {\n // Ensure both classes exist. Either end may carry the `:::style`\n // shorthand (Mermaid's docs warn against combining it with a relation\n // statement, but it's better to strip it than to mint a class whose id\n // is the literal `Animal:::someclass`).\n rel.from = declareClass(rel.from, undefined, false)\n rel.to = declareClass(rel.to, undefined, false)\n diagram.relationships.push(rel)\n continue\n }\n\n // A line containing a relationship-arrow token that still didn't parse\n // above is a malformed/dangling relationship attempt — e.g. \"Animal\n // <|--\" with no target, or an unsupported arrow shape — rather than an\n // unrelated line that merely falls through. Scoped to lines that\n // already look like an attempt (same \"attempt, then validate\" pattern\n // #722 used for ER/xychart-beta) so genuinely unrecognized syntax with\n // no arrow at all keeps falling through silently (#761).\n if (RELATIONSHIP_ARROW_MARKER.test(line)) {\n throw new Error(\n `Line ${stmt.line}: Malformed class-diagram relationship \"${line}\". Expected \"FROM ARROW TO\" (optionally with cardinalities and a \": label\"), e.g. \"Animal <|-- Dog\" or 'A \"1\" --> \"*\" B : label'. ARROW must be one of <|--, <|.., *--, o--, -->, --*, --o, --|>, ..>, ..|>, <--, <.., or --.`,\n )\n }\n }\n\n if (currentClass !== null) {\n throw new Error(\n `Line ${openClassLine}: Unclosed class body for \"${currentClass.id}\" — expected a closing \"}\" before the diagram ends.`,\n )\n }\n\n diagram.classes = [...classMap.values()]\n return diagram\n\n /**\n * Register a class from a declaration token, splitting off any `:::style`\n * shorthand into `diagram.classAssignments`. Returns the bare id.\n *\n * @param generic - `~T~` generic parameter captured by the declaration regex\n * @param inNamespace - whether to record the class in the open namespace\n * (declarations do; relationship endpoints don't)\n */\n function declareClass(\n token: string,\n generic: string | undefined,\n inNamespace: boolean = true,\n ): string {\n const { id: rawId, className } = splitClassShorthand(token)\n let id = rawId\n let resolvedGeneric = generic\n if (!resolvedGeneric) {\n // The declaration regexes only split a `~T~` generic off the bare id\n // when nothing else trails it. When the `:::style` shorthand trails\n // the generic instead (`Foo~T~:::hot`), the generic gets swallowed\n // into the shorthand-stripped id here (`splitClassShorthand` returns\n // `Foo~T~`) — strip it off now so the class is declared as `Foo`\n // rather than the literal `Foo~T~` (#506).\n const genericMatch = rawId.match(/^(.+?)~(\\w+)~$/)\n if (genericMatch) {\n id = genericMatch[1]!\n resolvedGeneric = genericMatch[2]!\n }\n }\n const cls = ensureClass(classMap, id)\n if (resolvedGeneric) {\n cls.label = `${id}<${resolvedGeneric}>`\n }\n if (className) {\n diagram.classAssignments.set(id, className)\n }\n if (inNamespace && currentNamespace) {\n currentNamespace.classIds.push(id)\n }\n return id\n }\n}\n\n/** Parse one member line and file it under the class's attributes or methods. */\nfunction addMember(cls: ClassNode, line: string, lineNumber: number): void {\n const member = parseMember(line, lineNumber)\n if (!member) return\n if (member.isMethod) {\n cls.methods.push(member.member)\n } else {\n cls.attributes.push(member.member)\n }\n}\n\n/** Ensure a class exists in the map, creating a default if needed */\nfunction ensureClass(classMap: Map<string, ClassNode>, id: string): ClassNode {\n let cls = classMap.get(id)\n if (!cls) {\n cls = { id, label: id, attributes: [], methods: [] }\n classMap.set(id, cls)\n }\n return cls\n}\n\n/** Count occurrences of `needle` in `input`. */\nfunction countOccurrence(input: string, needle: string): number {\n return Math.max(0, input.split(needle).length - 1)\n}\n\n/**\n * Convert one comma-free (or already re-joined) segment's `~`-delimited\n * generics to angle brackets, pairing the outermost `~`s first so nested\n * generics like `List~List~T~~` become `List<List<T>>`. A segment with a\n * single `~` is left alone — there's nothing to pair it with — and an odd\n * count with a leading `~` keeps that leading one as-is (mermaid treats it\n * as literal text, not a delimiter). Mirrors mermaid's `processSet`.\n */\nfunction convertGenericSegment(input: string): string {\n const tildeCount = countOccurrence(input, '~')\n if (tildeCount <= 1) return input\n\n let text = input\n let keepLeadingTilde = false\n if (tildeCount % 2 !== 0 && text.startsWith('~')) {\n text = text.slice(1)\n keepLeadingTilde = true\n }\n\n const chars = [...text]\n let open = chars.indexOf('~')\n let close = chars.lastIndexOf('~')\n while (open !== -1 && close !== -1 && open !== close) {\n chars[open] = '<'\n chars[close] = '>'\n open = chars.indexOf('~')\n close = chars.lastIndexOf('~')\n }\n if (keepLeadingTilde) chars.unshift('~')\n return chars.join('')\n}\n\n/**\n * Convert mermaid's `~T~` generic syntax to the `<T>` form mermaid itself\n * renders — `List~Observer~` → `List<Observer>`, `Map~K,V~` → `Map<K,V>`,\n * `List~List~T~~` → `List<List<T>>`.\n *\n * Port of mermaid's `parseGenericTypes` (packages/mermaid/src/diagrams/\n * common/common.ts): the text is split on commas so a comma *inside* a\n * generic (`Map~K,V~`) can be re-joined with its neighbors — two adjacent\n * comma-separated pieces that each carry exactly one `~` are the two halves\n * of one generic — before each piece's `~` pairs are converted outermost-first.\n */\nexport function parseGenericTypes(input: string): string {\n const pieces = input.split(/(,)/)\n const output: string[] = []\n for (let i = 0; i < pieces.length; i++) {\n let piece = pieces[i]!\n if (piece === ',' && i > 0 && i + 1 < pieces.length) {\n const previous = pieces[i - 1]!\n const next = pieces[i + 1]!\n if (\n countOccurrence(previous, '~') === 1 &&\n countOccurrence(next, '~') === 1\n ) {\n piece = `${previous},${next}`\n i++\n output.pop()\n }\n }\n output.push(convertGenericSegment(piece))\n }\n return output.join('')\n}\n\n/** Parse a class member line (attribute or method) */\nfunction parseMember(\n line: string,\n lineNumber: number,\n): { member: ClassMember; isMethod: boolean } | null {\n const trimmed = line.trim().replace(/;$/, '')\n if (!trimmed) return null\n\n // Extract visibility prefix\n let visibility: ClassMember['visibility'] = ''\n let rest = trimmed\n const visibilityChar = rest[0]\n if (\n visibilityChar === '+' ||\n visibilityChar === '-' ||\n visibilityChar === '#' ||\n visibilityChar === '~'\n ) {\n visibility = visibilityChar\n rest = rest.slice(1).trim()\n }\n\n // An attempted method signature with an opening \"(\" but no closing \")\"\n // at all can't be a genuine attribute either (an attribute name/type\n // never legitimately contains a bare, unclosed paren) — surface it\n // instead of silently parsing \"eat(void\" as a member literally named\n // that (#761).\n if (rest.includes('(') && !rest.includes(')')) {\n throw new Error(\n `Line ${lineNumber}: Malformed class member \"${trimmed}\" — unclosed \"(\" in a method signature. Expected e.g. \"+eat() void\" or \"+eat(Food f) void\".`,\n )\n }\n\n // Mermaid renders `List~Observer~` as `List<Observer>` (its own\n // `parseGenericTypes`). It's applied per field below — name, params and\n // return type separately, the way mermaid's ClassMember does — rather\n // than to the whole line, since the comma re-join inside\n // parseGenericTypes would otherwise pair a `~` in the params with one in\n // the return type. Doing it after the visibility prefix is stripped also\n // keeps a package-visibility `~` from pairing with a generic's.\n\n // Check if it's a method (has parentheses)\n const methodMatch = rest.match(/^(.+?)\\(([^)]*)\\)(?:\\s*(.+))?$/)\n if (methodMatch) {\n const name = parseGenericTypes(methodMatch[1]!.trim())\n const rawParams = methodMatch[2]?.trim()\n const params = rawParams ? parseGenericTypes(rawParams) : undefined // Store the parameter string\n const rawType = methodMatch[3]?.trim()\n const type = rawType ? parseGenericTypes(rawType) : undefined\n // Check for static ($) or abstract (*) markers\n const isStatic = name.endsWith('$') || rest.includes('$')\n const isAbstract = name.endsWith('*') || rest.includes('*')\n return {\n member: {\n visibility,\n name: name.replace(/[$*]$/, ''),\n type: type || undefined,\n isStatic,\n isAbstract,\n isMethod: true,\n params,\n },\n isMethod: true,\n }\n }\n\n // It's an attribute. Mermaid renders attribute text verbatim (its\n // ClassMember keeps everything after the visibility marker as one id and\n // only splits methods into name/params/return type), so `String name`,\n // `name Type` and `name: Type` all display as written. Splitting two\n // tokens into Type + name guessed the order and flipped `-data Map` into\n // `- Map: data` (#1265).\n const name = parseGenericTypes(rest)\n const type: string | undefined = undefined\n\n const isStatic = name.endsWith('$')\n const isAbstract = name.endsWith('*')\n\n return {\n member: {\n visibility,\n name: name.replace(/[$*]$/, ''),\n type: type || undefined,\n isStatic,\n isAbstract,\n isMethod: false,\n },\n isMethod: false,\n }\n}\n\n/** Parse a relationship line into a ClassRelationship */\nfunction parseRelationship(line: string): ClassRelationship | null {\n // Relationship regex — handles all arrow types with optional cardinality and labels\n // Pattern: FROM [\"card\"] ARROW [\"card\"] TO[:::style] [: label]\n // The `:::style` shorthand on TO is matched as its own group so the `:`\n // label separator that follows can't swallow it as a label of `::style`;\n // it's re-joined onto the id here and split off again by the caller.\n const match = line.match(\n new RegExp(\n `^(\\\\S+?)\\\\s+(?:\"([^\"]*?)\"\\\\s+)?(${RELATIONSHIP_ARROW_SOURCE})\\\\s+(?:\"([^\"]*?)\"\\\\s+)?(\\\\S+?)(?::::([\\\\w][\\\\w-]*))?(?:\\\\s*:\\\\s*(.+))?$`,\n ),\n )\n if (!match) return null\n\n const from = match[1]!\n const rawFromCardinality = match[2]\n const fromCardinality = rawFromCardinality\n ? normalizeBrTags(rawFromCardinality)\n : undefined\n const arrow = match[3]!.trim()\n const rawToCardinality = match[4]\n const toCardinality = rawToCardinality\n ? normalizeBrTags(rawToCardinality)\n : undefined\n const to = match[6] ? `${match[5]!}:::${match[6]}` : match[5]!\n const rawLabel = match[7]?.trim()\n const label = rawLabel ? normalizeBrTags(rawLabel) : undefined\n\n const parsed = parseArrow(arrow)\n if (!parsed) return null\n\n return {\n from,\n to,\n type: parsed.type,\n markerAt: parsed.markerAt,\n label,\n fromCardinality,\n toCardinality,\n }\n}\n\n/**\n * Map arrow syntax to relationship type and marker placement side.\n * Prefix markers (`<|--`, `*--`, `o--`) place the UML shape at the 'from' end.\n * Suffix markers (`..|>`, `-->`, `..>`, `--*`, `--o`) place it at the 'to' end.\n */\nfunction parseArrow(\n arrow: string,\n): { type: RelationshipType; markerAt: 'from' | 'to' } | null {\n // Trim whitespace that might be captured by the regex\n const a = arrow.trim()\n switch (a) {\n case '<|--':\n return { type: 'inheritance', markerAt: 'from' }\n case '--|>':\n return { type: 'inheritance', markerAt: 'to' }\n case '<|..':\n return { type: 'realization', markerAt: 'from' }\n case '..|>':\n return { type: 'realization', markerAt: 'to' }\n case '*--':\n return { type: 'composition', markerAt: 'from' }\n case '--*':\n return { type: 'composition', markerAt: 'to' }\n case 'o--':\n return { type: 'aggregation', markerAt: 'from' }\n case '--o':\n return { type: 'aggregation', markerAt: 'to' }\n case '-->':\n return { type: 'association', markerAt: 'to' }\n case '<--':\n return { type: 'association', markerAt: 'from' }\n case '..>':\n return { type: 'dependency', markerAt: 'to' }\n case '<..':\n return { type: 'dependency', markerAt: 'from' }\n case '--':\n return { type: 'association', markerAt: 'to' }\n default:\n return null\n }\n}\n","// ============================================================================\n// Class member formatting — shared between the SVG (layout.ts) and ASCII\n// (ascii/class-diagram.ts) renderers so method parentheses/params can't drift\n// between the two again. See issue #290.\n// ============================================================================\n\nimport type { ClassMember } from './types.ts'\n\n/** Format a class member as a display string: visibility + name(+params for methods) + optional type */\nexport function formatClassMember(m: ClassMember): string {\n const vis = m.visibility ? `${m.visibility} ` : ''\n const name = m.isMethod ? `${m.name}(${m.params || ''})` : m.name\n const type = m.type ? `: ${m.type}` : ''\n return `${vis}${name}${type}`\n}\n","import type {\n ErDiagram,\n ErEntity,\n ErAttribute,\n ErRelationship,\n Cardinality,\n} from './types.ts'\nimport {\n normalizeBrTags,\n toDirection,\n type Statement,\n} from '@zombie-mermaid/core'\n\n// ============================================================================\n// ER diagram parser\n//\n// Parses Mermaid erDiagram syntax into an ErDiagram structure.\n//\n// Supported syntax:\n// CUSTOMER ||--o{ ORDER : places\n// CUSTOMER {\n// string name PK\n// int age\n// string email UK \"user email\"\n// }\n//\n// Cardinality notation:\n// || || exactly one\n// |o o| zero or one\n// }| |{ one or more\n// }o o{ zero or more\n//\n// Line style:\n// -- identifying (solid line)\n// .. non-identifying (dashed line)\n// ============================================================================\n\n/**\n * Parse a Mermaid ER diagram.\n * Expects the first line to be \"erDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// either a bounds-checked loop-index array access (`lines[i]!` inside a\n// `for (let i = 1; i < lines.length; ...)` loop) or a regex-mandatory-\n// capture-group access after `.match()` (a group that isn't wrapped in an\n// optional `(?:...)?`, so it always participates when the overall match\n// succeeds). Both are the same idioms already accepted as justified\n// elsewhere in this codebase (see src/parser.ts, PR #158, and this\n// subsystem's layout.ts/renderer.ts audit, PR #148, which fixed the\n// genuinely risky assertions there but didn't reach this file — PR #146\n// separately reviewed this file's `as` casts, which are a different\n// concern from these `!`s) — `noUncheckedIndexedAccess` can't see either\n// guarantee, but removing the `!` would only replace a proven-safe\n// assertion with an unreachable guard. Left as-is; no behavior change.\nexport function parseErDiagram(lines: Statement[]): ErDiagram {\n const diagram: ErDiagram = {\n entities: [],\n relationships: [],\n }\n\n // Track entities by ID for deduplication\n const entityMap = new Map<string, ErEntity>()\n // Track entity body parsing\n let currentEntity: ErEntity | null = null\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- Inside entity body ---\n if (currentEntity) {\n if (line === '}') {\n currentEntity = null\n continue\n }\n\n // Attribute line: type name [PK|FK|UK] [\"comment\"]\n const attr = parseAttribute(line)\n if (attr) {\n currentEntity.attributes.push(attr)\n }\n continue\n }\n\n // --- direction directive: `direction TB` / `direction LR` / etc. ---\n // ER diagrams have no subgraph nesting, so a single top-level direction\n // applies to the whole diagram (unlike flowcharts, where `direction` can\n // also appear per-subgraph).\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch) {\n diagram.direction = toDirection(dirMatch[1]!)\n continue\n }\n\n // --- Entity block start: `ENTITY_NAME {`, `id[Alias Label] {`, or\n // `id[\"Quoted Alias\"] {` — optionally with the body (and even the\n // closing `}`) inline on the same line, e.g. `p[Person] { string name }`.\n const entityBlockMatch = line.match(/^(\\S+?)(?:\\[(.+)\\])?\\s*\\{(.*)$/)\n if (entityBlockMatch) {\n const id = entityBlockMatch[1]!\n const aliasRaw = entityBlockMatch[2]\n const alias =\n aliasRaw !== undefined\n ? normalizeBrTags(aliasRaw.trim().replace(/^[\"']|[\"']$/g, ''))\n : undefined\n const entity = ensureEntity(entityMap, id, alias)\n\n const trailing = entityBlockMatch[3]!.trim()\n const closesInline = trailing.endsWith('}')\n const body = closesInline ? trailing.slice(0, -1).trim() : trailing\n for (const part of body\n .split(';')\n .map((s) => s.trim())\n .filter(Boolean)) {\n const attr = parseAttribute(part)\n if (attr) entity.attributes.push(attr)\n }\n\n // Only keep the block open for subsequent attribute lines when this\n // line didn't already close it.\n if (!closesInline) {\n currentEntity = entity\n }\n continue\n }\n\n // --- Relationship: `ENTITY1 cardinality1--cardinality2 ENTITY2 : label` ---\n const rel = parseRelationshipLine(line, stmt.line)\n if (rel) {\n // Ensure both entities exist\n ensureEntity(entityMap, rel.entity1)\n ensureEntity(entityMap, rel.entity2)\n diagram.relationships.push(rel)\n continue\n }\n }\n\n diagram.entities = [...entityMap.values()]\n return diagram\n}\n\n/**\n * Ensure an entity exists in the map. When `alias` is provided (from an\n * `id[Alias]` entity-block header), it becomes the display label while the\n * map key — and every relationship reference — stays keyed off the raw `id`.\n */\nfunction ensureEntity(\n entityMap: Map<string, ErEntity>,\n id: string,\n alias?: string,\n): ErEntity {\n let entity = entityMap.get(id)\n if (!entity) {\n entity = { id, label: alias ?? id, attributes: [] }\n entityMap.set(id, entity)\n } else if (alias !== undefined) {\n entity.label = alias\n }\n return entity\n}\n\n/** Parse an attribute line inside an entity block */\nfunction parseAttribute(line: string): ErAttribute | null {\n // Format: type name [PK|FK|UK [...]] [\"comment\"]\n const match = line.match(/^(\\S+)\\s+(\\S+)(?:\\s+(.+))?$/)\n if (!match) return null\n\n const type = match[1]!\n const name = match[2]!\n const rest = match[3]?.trim() ?? ''\n\n // Extract key constraints (PK, FK, UK) and optional comment\n const keys: ErAttribute['keys'] = []\n let comment: string | undefined\n\n // Extract quoted comment first (supports <br> tags)\n const commentMatch = rest.match(/\"([^\"]*)\"/)\n if (commentMatch) {\n comment = normalizeBrTags(commentMatch[1]!)\n }\n\n // Extract key constraints\n const restWithoutComment = rest.replace(/\"[^\"]*\"/, '').trim()\n for (const part of restWithoutComment.split(/\\s+/)) {\n const upper = part.toUpperCase()\n if (upper === 'PK' || upper === 'FK' || upper === 'UK') {\n keys.push(upper)\n }\n }\n\n return { type, name, keys, comment }\n}\n\n/**\n * Parse a relationship line.\n *\n * Cardinality symbols on each side of the line style:\n * Left side (entity1): || |o }| }o\n * Line: -- (identifying) or .. (non-identifying)\n * Right side (entity2): || o| |{ o{\n *\n * Full pattern example: CUSTOMER ||--o{ ORDER : places\n */\nfunction parseRelationshipLine(\n line: string,\n lineNumber: number,\n): ErRelationship | null {\n // Loosely match a line shaped like a relationship attempt: two bare\n // tokens flanking a `--`/`..`-based marker, with an optional `: label`.\n // A line with no such marker at all doesn't look like a relationship\n // attempt and falls through silently (returns null), same as before —\n // that part of the parser's leniency is unchanged.\n //\n // Everything that *does* match this shape is validated below and either\n // returns a fully-parsed relationship or throws an actionable error.\n // Before this pass, an invalid cardinality token or a missing/empty\n // label silently dropped the *entire* line (both entities included)\n // with zero indication anything was wrong — see issue #541 (parser\n // error-message quality audit — this parser had zero throw sites before\n // this pass).\n const attempt = line.match(\n /^(\\S+)\\s+(\\S*(?:--|\\.\\.)\\S*)\\s+(\\S+)\\s*(?::\\s*(.*))?$/,\n )\n if (!attempt) return null\n\n const entity1 = attempt[1]!\n const cardinalityStr = attempt[2]!\n const entity2 = attempt[3]!\n const hasLabel = attempt[4] !== undefined\n const rawLabel = attempt[4]?.trim() ?? ''\n\n // Cardinality symbols on each side of the line style:\n // Left side (entity1): || |o }| }o\n // Line: -- (identifying) or .. (non-identifying)\n // Right side (entity2): || o| |{ o{\n // `shapeMatch` only confirms the token is built from crow's-foot\n // characters around a line-style marker — `parseLeftCardinality`/\n // `parseRightCardinality` still reject a shape match that isn't one of\n // the four valid two-character combinations per side (e.g. a lone \"|\").\n const shapeMatch = cardinalityStr.match(/^([|o}{]*)(--|\\.\\.?)([|o}{]*)$/)\n const cardinality1 = shapeMatch ? parseLeftCardinality(shapeMatch[1]!) : null\n const cardinality2 = shapeMatch ? parseRightCardinality(shapeMatch[3]!) : null\n\n if (!cardinality1 || !cardinality2) {\n throw new Error(\n `Line ${lineNumber}: Invalid ER relationship cardinality \"${cardinalityStr}\" in \"${line}\". ` +\n 'Left side must be one of ||, |o, }|, }o; right side must be one of ||, o|, |{, o{ (e.g. \"||--o{\").',\n )\n }\n\n if (!hasLabel || rawLabel.length === 0) {\n throw new Error(\n `Line ${lineNumber}: ER relationship \"${entity1} ${cardinalityStr} ${entity2}\" is missing a \": label\" — expected e.g. \"${entity1} ${cardinalityStr} ${entity2} : label\".`,\n )\n }\n\n // Strip surrounding quotes if present, then normalize br tags.\n const label = normalizeBrTags(rawLabel.replace(/^[\"']|[\"']$/g, ''))\n const identifying = shapeMatch![2] === '--'\n\n return { entity1, entity2, cardinality1, cardinality2, label, identifying }\n}\n\n/**\n * Parse a left-side (entity1) cardinality notation string.\n * The crow's-foot character sits nearer the entity, so left- and\n * right-side notations are mirror images of each other and must be\n * matched exactly rather than order-normalized (sorting `}o` and `o{`\n * to the same key conflates \"zero or more\" with malformed input).\n */\nfunction parseLeftCardinality(str: string): Cardinality | null {\n if (str === '||') return 'one'\n if (str === '|o') return 'zero-one'\n if (str === '}|') return 'many'\n if (str === '}o') return 'zero-many'\n return null\n}\n\n/** Parse a right-side (entity2) cardinality notation string. */\nfunction parseRightCardinality(str: string): Cardinality | null {\n if (str === '||') return 'one'\n if (str === 'o|') return 'zero-one'\n if (str === '|{') return 'many'\n if (str === 'o{') return 'zero-many'\n return null\n}\n","// ============================================================================\n// `box <color?> <label?>` header parsing\n//\n// Mermaid's sequenceDb.parseBoxData takes the first word of the header (or a\n// leading `rgb(...)`/`hsl(...)` call), asks the browser whether it is a valid\n// CSS colour, and — if it isn't — treats the whole header as the label. There\n// is no browser here, so \"is a colour\" is decided by a fixed list of CSS\n// named colours plus the hex / rgb() / hsl() function forms. That list is\n// also what keeps a diagram-supplied string safe to emit inside an SVG\n// attribute: anything not matching it never reaches the renderer as a colour.\n// ============================================================================\n\n/** The CSS Color Module Level 4 named colours (plus `transparent`, handled separately). */\nconst CSS_NAMED_COLORS = new Set(\n (\n 'aliceblue antiquewhite aqua aquamarine azure beige bisque black ' +\n 'blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse ' +\n 'chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan ' +\n 'darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta ' +\n 'darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen ' +\n 'darkslateblue darkslategray darkslategrey darkturquoise darkviolet ' +\n 'deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite ' +\n 'forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green ' +\n 'greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender ' +\n 'lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan ' +\n 'lightgoldenrodyellow lightgray lightgreen lightgrey lightpink ' +\n 'lightsalmon lightseagreen lightskyblue lightslategray lightslategrey ' +\n 'lightsteelblue lightyellow lime limegreen linen magenta maroon ' +\n 'mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen ' +\n 'mediumslateblue mediumspringgreen mediumturquoise mediumvioletred ' +\n 'midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive ' +\n 'olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise ' +\n 'palevioletred papayawhip peachpuff peru pink plum powderblue purple ' +\n 'rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown ' +\n 'seagreen seashell sienna silver skyblue slateblue slategray slategrey ' +\n 'snow springgreen steelblue tan teal thistle tomato turquoise violet ' +\n 'wheat white whitesmoke yellow yellowgreen'\n ).split(' '),\n)\n\n// `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`. Mermaid itself can't accept hex\n// here (upstream's lexer treats `#` as a comment); this parser has no such\n// comment syntax, so hex is accepted as a strict superset.\nconst HEX_COLOR_RE = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i\n// `rgb(33,66,99)`, `rgba(33,66,99,0.5)`, `hsl(10, 40%, 90%)`, `hsla(...)`,\n// including the modern space-separated / slash-alpha forms. The argument\n// character class is the safety boundary: digits, `.`, `%`, `,`, `/`, and\n// whitespace only.\nconst FUNCTION_COLOR_RE =\n /^(?:rgba?|hsla?)\\(\\s*[\\d.%]+(?:[\\s,/]+[\\d.%]+){2,3}\\s*\\)$/i\n\n/** Whether `value` is a colour this renderer will emit as-is (case-insensitive). */\nexport function isCssColor(value: string): boolean {\n return (\n CSS_NAMED_COLORS.has(value.toLowerCase()) ||\n HEX_COLOR_RE.test(value) ||\n FUNCTION_COLOR_RE.test(value)\n )\n}\n\n/**\n * Split the text after the `box` keyword into a colour and a label, per\n * Mermaid's `parseBoxData`: the leading word (or function call) is the\n * colour if it is one, otherwise the whole header is the label. An explicit\n * `transparent` is consumed as \"no colour\" so `box transparent Aqua` yields\n * a colourless box labelled `Aqua`, exactly as upstream documents.\n */\nexport function parseBoxHeader(header: string): {\n color?: string\n label: string\n} {\n const trimmed = header.trim()\n const match = trimmed.match(\n /^((?:rgba?|hsla?)\\s*\\([^)]*\\)|#[0-9a-fA-F]+|\\S+)?(.*)$/,\n )\n const candidate = match?.[1] ?? ''\n const rest = match?.[2]?.trim() ?? ''\n if (candidate.toLowerCase() === 'transparent') {\n return { label: rest }\n }\n if (candidate !== '' && isCssColor(candidate)) {\n return { color: candidate, label: rest }\n }\n return { label: trimmed }\n}\n","import type { SequenceDiagram, Message, Block, Actor } from './types.ts'\nimport { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport { parseBoxHeader } from './box-color.ts'\n\n/**\n * Which `box … end` group (index into `diagram.boxes`) is currently open,\n * and which box each participant already belongs to. Threaded through the\n * actor-creating helpers so a participant first mentioned inside a box —\n * by a declaration, a `create`, or (leniently) a message — joins it, the\n * way Mermaid's `addActor` assigns `currentBox`.\n */\ninterface BoxContext {\n open: number | undefined\n membership: Map<string, number>\n}\n\n/**\n * Narrow a regex-captured block keyword to `Block['type']`. The capturing\n * regex at the call site uses the same `(loop|alt|opt|par|critical|break|\n * rect)` alternation, so the input is always one of these seven values in\n * practice — but the match itself is typed as `string`.\n *\n * Exported for direct unit testing (see\n * src/__tests__/sequence-parser.test.ts) — not otherwise part of this\n * module's public parsing API.\n */\nexport function isBlockType(value: string): value is Block['type'] {\n return (\n value === 'loop' ||\n value === 'alt' ||\n value === 'opt' ||\n value === 'par' ||\n value === 'critical' ||\n value === 'break' ||\n value === 'rect'\n )\n}\n\n/**\n * Narrow a regex-captured block keyword to `Block['type']`, throwing if it\n * somehow isn't one of the seven recognized keywords (see `isBlockType`\n * above — unreachable via the guarding regex in practice, but this keeps\n * the failure explicit rather than silently mistyping the value).\n *\n * Exported for direct unit testing (see\n * src/__tests__/sequence-parser.test.ts) — the throw branch is unreachable\n * through the public `parseSequenceDiagram` API (the regex that captures\n * the value already restricts it to the seven valid keywords), so it can\n * only be exercised by calling this function directly.\n */\nexport function toBlockType(value: string): Block['type'] {\n if (!isBlockType(value)) {\n throw new Error(`Invalid block type: \"${value}\"`)\n }\n return value\n}\n\n// ============================================================================\n// Sequence diagram parser\n//\n// Parses Mermaid sequenceDiagram syntax into a SequenceDiagram structure.\n//\n// Supported syntax:\n// participant A as Alice\n// actor B as Bob\n// A->>B: Solid arrow\n// A-->>B: Dashed arrow\n// A-)B: Open arrow\n// A--)B: Dashed open arrow\n// A->>+B: Activate target\n// A-->>-B: Deactivate source\n// activate A / deactivate A (standalone form of the +/- shorthand)\n// create participant C [as Label] / create actor C [as Label]\n// (on the line before C's first message)\n// destroy C (on the line before C's last message)\n// box <color?> <label?> ... end (participant grouping; see box-color.ts)\n// A<<->>B: Bidirectional solid arrow\n// A<<-->>B: Bidirectional dashed arrow\n// autonumber / autonumber <start> <step> / autonumber off\n// loop Label ... end\n// alt Label ... else Label ... end\n// opt Label ... end\n// par Label ... and Label ... end\n// Note left of A: Text\n// Note right of A: Text\n// Note over A,B: Text\n// ============================================================================\n\n// Message-line arrow regexes, shared by the two-pass match in the \"Message\"\n// branch of the parsing loop below (see the comment there for why there are\n// two, and issue #341 for the mis-split bug this two-pass approach fixes).\n// `MESSAGE_LONG_ARROW_RE` only recognizes the \"long\" arrow forms — anything\n// ending in `>` (`->`, `-->`, `->>`, `-->>`) plus the bidirectional tokens\n// (`<<->>`, `<<-->>`) — which essentially never occur by accident inside an\n// unquoted actor name. `MESSAGE_ANY_ARROW_RE` is the original, full\n// alternation (also matching the short open/cross forms `-)`, `--)`, `-x`,\n// `--x`), used as a fallback when a line has no long arrow at all.\nconst MESSAGE_LONG_ARROW_RE =\n /^(.+?)\\s*(<<->>|<<-->>|--?>?>)\\s*([+-]?)(.+?)\\s*:\\s*(.+)$/\nconst MESSAGE_ANY_ARROW_RE =\n /^(.+?)\\s*(<<->>|<<-->>|--?>?>|--?[)x]|--?>>|--?>)\\s*([+-]?)(.+?)\\s*:\\s*(.+)$/\n\n/**\n * Parse a Mermaid sequence diagram.\n * Expects the first line to be \"sequenceDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// one of three idioms already accepted as justified elsewhere in this\n// codebase — (1) a bounds-checked loop-index array access (`lines[i]!`\n// inside a `for (let i = 1; i < lines.length; ...)` loop), (2) a\n// regex-mandatory-capture-group access after `.match()` (a group not\n// wrapped in an optional `(?:...)?`, so it always participates when the\n// overall match succeeds), or (3) `blockStack[blockStack.length - 1]!` /\n// `blockStack.pop()!`, both guarded immediately above by an explicit\n// `blockStack.length > 0` check. See src/parser.ts (PR #158) and this\n// subsystem's layout.ts audit (PR #149), which fixed the one genuinely\n// risky assertion in the subsystem (a Map get/set race) but didn't reach\n// this file — PR #146 separately reviewed this file's `as` casts, a\n// different concern from these `!`s. `noUncheckedIndexedAccess` can't see\n// any of these guarantees, but removing the `!` would only replace a\n// proven-safe assertion with an unreachable guard. Left as-is; no\n// behavior change.\nexport function parseSequenceDiagram(lines: Statement[]): SequenceDiagram {\n const diagram: SequenceDiagram = {\n actors: [],\n messages: [],\n blocks: [],\n notes: [],\n activations: [],\n boxes: [],\n }\n\n const boxCtx: BoxContext = { open: undefined, membership: new Map() }\n // Block-stack depth at the moment the open box began. Mermaid's grammar\n // only allows participant declarations inside a box, so `end` there can\n // only mean the box — but this parser is lenient about other statements,\n // and a `loop` opened inside a box must still own the next `end`.\n let boxOpenedAtDepth = 0\n // Line the currently-open box started on, for the unclosed-box error at\n // EOF below (issue #762) — `boxCtx.open` alone doesn't carry position.\n let boxOpenLine: number | undefined\n\n // Track actor IDs to auto-create actors referenced in messages\n const actorIds = new Set<string>()\n // Track block nesting with a stack\n const blockStack: Array<{\n type: Block['type']\n label: string\n startIndex: number\n dividers: Block['dividers']\n line: number\n }> = []\n\n // `autonumber` state — a bare `autonumber` turns numbering on starting at 1\n // (step 1); `autonumber <start> <step>` sets both explicitly; `autonumber\n // off` turns it back off. Only messages consume a number — notes and\n // block/divider lines don't advance the counter.\n const autonumber = { enabled: false, next: 1, step: 1 }\n\n // `create participant X` / `destroy X` bind to the very next message —\n // Mermaid's sequenceDb enforces that the next message's recipient is the\n // created participant, and that the destroyed one is its sender or\n // recipient, throwing otherwise. Mirrored here (same error text) rather\n // than guessing which later message was meant. A directive with no\n // message after it at all is left as a plain declaration.\n let pendingCreate: string | undefined\n let pendingDestroy: string | undefined\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- box <color?> <label?> ---\n // Opens a participant group; closed by `end`. Boxes cannot nest\n // (Mermaid's grammar rejects it), so a second `box` while one is open\n // is an error rather than an implicit close.\n const boxMatch = line.match(/^box(?:\\s+(.*))?$/)\n if (boxMatch) {\n if (boxCtx.open !== undefined) {\n throw new Error(\n `Line ${stmt.line}: Sequence diagram: a box cannot be nested inside another box — close the open box with \"end\" first`,\n )\n }\n const { color, label } = parseBoxHeader(boxMatch[1] ?? '')\n const box: SequenceDiagram['boxes'][number] = {\n label: normalizeBrTags(label),\n actorIds: [],\n }\n if (color !== undefined) box.color = color\n diagram.boxes.push(box)\n boxCtx.open = diagram.boxes.length - 1\n boxOpenedAtDepth = blockStack.length\n boxOpenLine = stmt.line\n continue\n }\n\n // --- create participant / create actor ---\n // \"create participant C\" / \"create actor C as Label\" — the line before\n // C's first message. Same shape as a plain declaration, so the alias and\n // the participant/actor kind are kept, not dropped with the keyword.\n const createMatch = line.match(\n /^create\\s+(participant|actor)\\s+(\\S+?)(?:\\s+as\\s+(.+))?$/,\n )\n if (createMatch) {\n const type: 'participant' | 'actor' =\n createMatch[1] === 'actor' ? 'actor' : 'participant'\n const id = createMatch[2]!\n if (actorIds.has(id)) {\n // Mermaid's own wording (sequenceDb `createParticipant`).\n throw new Error(\n `Line ${stmt.line}: It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior`,\n )\n }\n actorIds.add(id)\n diagram.actors.push({\n id,\n label: normalizeBrTags(createMatch[3]?.trim() ?? id),\n type,\n })\n joinOpenBox(diagram, boxCtx, id, stmt.line)\n pendingCreate = id\n continue\n }\n\n // --- destroy ---\n // \"destroy C\" — the line before C's last message.\n const destroyMatch = line.match(/^destroy\\s+(.+)$/)\n if (destroyMatch) {\n const id = destroyMatch[1]!.trim()\n ensureActor(diagram, actorIds, boxCtx, id, stmt.line)\n pendingDestroy = id\n continue\n }\n\n // --- autonumber directive ---\n const autonumberMatch = line.match(\n /^autonumber(?:\\s+(off|\\d+(?:\\.\\d{1,2})?)(?:\\s+(\\d+(?:\\.\\d{1,2})?))?)?$/,\n )\n if (autonumberMatch) {\n const start = autonumberMatch[1]\n const step = autonumberMatch[2]\n if (start === 'off') {\n autonumber.enabled = false\n } else {\n autonumber.enabled = true\n autonumber.next = start !== undefined ? Number(start) : 1\n autonumber.step = step !== undefined ? Number(step) : 1\n }\n continue\n }\n\n // --- Participant / Actor declaration ---\n // \"participant A as Alice\" or \"participant Alice\"\n // \"actor B as Bob\" or \"actor Bob\"\n const actorMatch = line.match(\n /^(participant|actor)\\s+(\\S+?)(?:\\s+as\\s+(.+))?$/,\n )\n if (actorMatch) {\n // Group 1 is constrained by the regex alternation to 'participant' | 'actor'\n const type: 'participant' | 'actor' =\n actorMatch[1] === 'actor' ? 'actor' : 'participant'\n const id = actorMatch[2]!\n const rawLabel = actorMatch[3]?.trim() ?? id\n const label = normalizeBrTags(rawLabel)\n if (!actorIds.has(id)) {\n actorIds.add(id)\n diagram.actors.push({ id, label, type })\n }\n // A re-declaration inside a box joins it (or errors if it already\n // belongs to a different one) — Mermaid's `addActor` rule.\n joinOpenBox(diagram, boxCtx, id, stmt.line)\n continue\n }\n\n // --- Note ---\n // \"Note left of A: text\" / \"Note right of A: text\" / \"Note over A,B: text\"\n const noteMatch = line.match(\n /^Note\\s+(left of|right of|over)\\s+([^:]+):\\s*(.+)$/i,\n )\n if (noteMatch) {\n const posStr = noteMatch[1]!.toLowerCase()\n const actorsStr = noteMatch[2]!.trim()\n const text = normalizeBrTags(noteMatch[3]!.trim())\n const noteActorIds = actorsStr.split(',').map((s) => s.trim())\n\n // Ensure actors exist\n for (const aid of noteActorIds) {\n ensureActor(diagram, actorIds, boxCtx, aid, stmt.line)\n }\n\n let position: 'left' | 'right' | 'over' = 'over'\n if (posStr === 'left of') position = 'left'\n else if (posStr === 'right of') position = 'right'\n\n diagram.notes.push({\n actorIds: noteActorIds,\n text,\n position,\n afterIndex: diagram.messages.length - 1,\n })\n continue\n }\n\n // --- Block start: loop, alt, opt, par, critical, break, rect ---\n const blockMatch = line.match(\n /^(loop|alt|opt|par|critical|break|rect)\\s*(.*)$/,\n )\n if (blockMatch) {\n const blockType = toBlockType(blockMatch[1]!)\n const rawBlockLabel = blockMatch[2]?.trim() ?? ''\n const label = normalizeBrTags(rawBlockLabel)\n blockStack.push({\n type: blockType,\n label,\n startIndex: diagram.messages.length,\n dividers: [],\n line: stmt.line,\n })\n continue\n }\n\n // --- Block divider: else, and ---\n const dividerMatch = line.match(/^(else|and)\\s*(.*)$/)\n if (dividerMatch && blockStack.length > 0) {\n const rawDividerLabel = dividerMatch[2]?.trim() ?? ''\n const label = normalizeBrTags(rawDividerLabel)\n blockStack[blockStack.length - 1]!.dividers.push({\n index: diagram.messages.length,\n label,\n })\n continue\n }\n\n // --- Box end ---\n // `end` closes the open box unless a block was opened *inside* it,\n // in which case the block (innermost) owns this `end`.\n if (\n line === 'end' &&\n boxCtx.open !== undefined &&\n blockStack.length === boxOpenedAtDepth\n ) {\n boxCtx.open = undefined\n boxOpenLine = undefined\n continue\n }\n\n // --- Block end ---\n if (line === 'end' && blockStack.length > 0) {\n const completed = blockStack.pop()!\n diagram.blocks.push({\n type: completed.type,\n label: completed.label,\n startIndex: completed.startIndex,\n endIndex: Math.max(diagram.messages.length - 1, completed.startIndex),\n dividers: completed.dividers,\n })\n continue\n }\n\n // --- Unmatched end ---\n // Neither branch above claimed this \"end\" — no box and no block is\n // currently open, so there's nothing for it to close. Previously\n // silently ignored (#762); mirrors the \"box cannot be nested\" throw\n // above in surfacing a structural mistake instead of dropping it.\n if (line === 'end') {\n throw new Error(\n `Line ${stmt.line}: Sequence diagram: \"end\" does not match any open block (\"loop\"/\"alt\"/\"opt\"/\"par\"/\"critical\"/\"break\"/\"rect\") or \"box\" — nothing is currently open to close.`,\n )\n }\n\n // --- Standalone activate / deactivate ---\n // \"activate A\" / \"deactivate A\". Mermaid's own grammar expands the `+`/`-`\n // arrow shorthand into exactly this (message, then an activeStart for the\n // recipient / activeEnd for the sender), so record the same event the\n // shorthand implies and let layout.ts feed both through one activation\n // stack. Checked before the message regex: neither keyword contains an\n // arrow token, but an actor literally named `activate` used as a message\n // *source* (`activate->>B: x`) still reaches the message branch, since\n // the `\\s+` here requires whitespace after the keyword.\n const activationMatch = line.match(/^(activate|deactivate)\\s+(.+)$/)\n if (activationMatch) {\n const actorId = activationMatch[2]!.trim()\n ensureActor(diagram, actorIds, boxCtx, actorId, stmt.line)\n diagram.activations.push({\n actorId,\n kind: activationMatch[1] === 'activate' ? 'start' : 'end',\n afterIndex: diagram.messages.length - 1,\n })\n continue\n }\n\n // --- Message ---\n // Patterns: A->>B, A-->>B, A-)B, A--)B, A<<->>B, A<<-->>B, with optional\n // +/- activation. Format: FROM ARROW TO: LABEL\n //\n // FROM/TO are matched with a lazy `.+?` (not `\\S+?`) so an undeclared\n // actor name can contain spaces or internal hyphens — e.g. `cron\n // job->>customer-notifier: hi` — mirroring real Mermaid's own sequence\n // grammar, whose unquoted ACTOR token excludes only the characters that\n // start an arrow/label (`/ \\ + ( ) < - > :`) rather than all whitespace.\n // Because the quantifier is lazy and anchored by the arrow/colon tokens\n // that follow, this still resolves to the same minimal split as before\n // for plain single-word names.\n //\n // Two-pass match (issue #341): the lazy FROM capture stops as soon as\n // *any* position looks like a valid arrow+TO+`:`+LABEL tail, which is a\n // false positive when an unquoted actor name happens to contain a short\n // open/cross arrow substring (`-)`, `--)`, `-x`, `--x` — matched by the\n // `--?[)x]` alternative) ahead of the *real* arrow later in the line —\n // e.g. `foo-x-bar->>baz: hi` mis-split at the embedded `-x` instead of\n // the real `->>`. Those two-char forms are rare and highly ambiguous\n // inside a bare identifier, whereas the \"long\" forms (anything ending\n // in `>`, plus the bidirectional tokens) essentially never occur by\n // accident, since they require a literal `>` character in an unquoted\n // name. So: try the long forms only first, and only fall back to the\n // full alternation (including the short forms) if the line has no long\n // arrow at all — which is what keeps a genuinely short-arrow message\n // like `A-)B: msg` working. This doesn't attempt to disambiguate every\n // theoretically possible collision (e.g. a literal `->` substring\n // embedded before a real `->>`) — see the issue's own \"Scope\" section,\n // which limits the fix to the `-x`/`-)`/`--x`/`--)` substrings.\n const msgMatch =\n line.match(MESSAGE_LONG_ARROW_RE) ?? line.match(MESSAGE_ANY_ARROW_RE)\n if (msgMatch) {\n // Mermaid's own unquoted actor-name grammar excludes `>` and `)` from\n // ever starting an identifier (see the FROM/TO comment above) — so a\n // TO capture starting with either one means the real arrow in the\n // source is longer than what the alternation above actually matched\n // (e.g. \"->>>\" only matches as \"->>\", leaving a stray \">\" to be\n // absorbed into TO as part of a garbled actor name) rather than a\n // genuinely valid, if unusual, message. Reject it with an actionable\n // error instead of silently minting that garbled actor — see the\n // `Alice->>>Bob: Hello` example in issue #762.\n const arrowToken = msgMatch[2]!\n const toFirstChar = msgMatch[4]![0]\n if (toFirstChar === '>' || toFirstChar === ')') {\n throw new Error(\n `Line ${stmt.line}: Malformed sequence-diagram arrow in \"${line}\" — \"${arrowToken}${toFirstChar}\" is not a recognized arrow. Expected one of: ->, -->, ->>, -->>, -x, --x, -), --), <<->>, <<-->>.`,\n )\n }\n pushMessage(\n diagram,\n actorIds,\n boxCtx,\n autonumber,\n msgMatch[1]!,\n msgMatch[2]!,\n msgMatch[3],\n msgMatch[4]!,\n msgMatch[5]!,\n stmt.line,\n )\n const msgIndex = diagram.messages.length - 1\n const msg = diagram.messages[msgIndex]!\n if (pendingCreate !== undefined) {\n if (msg.to !== pendingCreate) {\n throw new Error(\n `Line ${stmt.line}: The created participant ${pendingCreate} does not have an associated creating message after its declaration. Please check the sequence diagram.`,\n )\n }\n findActor(diagram, pendingCreate).createdAt = msgIndex\n pendingCreate = undefined\n }\n if (pendingDestroy !== undefined) {\n if (msg.from !== pendingDestroy && msg.to !== pendingDestroy) {\n throw new Error(\n `Line ${stmt.line}: The destroyed participant ${pendingDestroy} does not have an associated destroying message after its declaration. Please check the sequence diagram.`,\n )\n }\n findActor(diagram, pendingDestroy).destroyedAt = msgIndex\n pendingDestroy = undefined\n }\n continue\n }\n }\n\n // A block/box left open at EOF is a structural mistake, not something to\n // silently accept as \"closed by end of input\" — mirrors the class-diagram\n // parser's unclosed-body check (#761) for the same reason: previously\n // this was silently ignored (#762). Checked innermost-first (the block\n // stack) since an unclosed block is the more specific, more actionable\n // thing to report when both are open.\n if (blockStack.length > 0) {\n const unclosed = blockStack[blockStack.length - 1]!\n throw new Error(\n `Line ${unclosed.line}: Sequence diagram: unclosed \"${unclosed.type}\" block — expected a matching \"end\" before the diagram ends.`,\n )\n }\n if (boxCtx.open !== undefined) {\n const openBox = diagram.boxes[boxCtx.open]!\n throw new Error(\n `Line ${boxOpenLine}: Sequence diagram: unclosed \"box${openBox.label ? ` ${openBox.label}` : ''}\" — expected a matching \"end\" before the diagram ends.`,\n )\n }\n\n return diagram\n}\n\n/**\n * Look up an actor the parser itself already registered (every caller\n * passes an id that went through `ensureActor` or the create branch first,\n * so the miss branch is a parser-invariant violation, not user error).\n */\nfunction findActor(diagram: SequenceDiagram, id: string): Actor {\n const actor = diagram.actors.find((a) => a.id === id)\n if (actor === undefined) {\n /* v8 ignore next */\n throw new Error(`Sequence diagram: unknown actor \"${id}\"`)\n }\n return actor\n}\n\n/** Ensure an actor exists, creating a default participant if not */\nfunction ensureActor(\n diagram: SequenceDiagram,\n actorIds: Set<string>,\n boxCtx: BoxContext,\n id: string,\n lineNumber: number,\n): void {\n if (!actorIds.has(id)) {\n actorIds.add(id)\n diagram.actors.push({ id, label: id, type: 'participant' })\n }\n joinOpenBox(diagram, boxCtx, id, lineNumber)\n}\n\n/**\n * Put `id` into the currently open `box`, if any. A participant already in\n * a *different* box is an error (Mermaid's own wording); one already in\n * this box, or no box open, is a no-op.\n */\nfunction joinOpenBox(\n diagram: SequenceDiagram,\n boxCtx: BoxContext,\n id: string,\n lineNumber: number,\n): void {\n const open = boxCtx.open\n if (open === undefined) return\n const existing = boxCtx.membership.get(id)\n if (existing === open) return\n if (existing !== undefined) {\n const from = diagram.boxes[existing]!.label\n const to = diagram.boxes[open]!.label\n throw new Error(\n `Line ${lineNumber}: A same participant should only be defined in one Box: ${id} can't be in '${from}' and in '${to}' at the same time.`,\n )\n }\n boxCtx.membership.set(id, open)\n diagram.boxes[open]!.actorIds.push(id)\n}\n\n/**\n * Build a `Message` from a matched arrow-message line and push it onto the\n * diagram. Shared by both message regexes in `parseSequenceDiagram` above so\n * the arrow → line-style/arrow-head/bidirectional mapping and `autonumber`\n * bookkeeping can't drift out of sync between them.\n */\nfunction pushMessage(\n diagram: SequenceDiagram,\n actorIds: Set<string>,\n boxCtx: BoxContext,\n autonumber: { enabled: boolean; next: number; step: number },\n from: string,\n arrow: string,\n activationMark: string | undefined,\n to: string,\n rawLabel: string,\n lineNumber: number,\n): void {\n ensureActor(diagram, actorIds, boxCtx, from, lineNumber)\n ensureActor(diagram, actorIds, boxCtx, to, lineNumber)\n\n const bidirectional = arrow === '<<->>' || arrow === '<<-->>'\n const lineStyle = bidirectional\n ? arrow === '<<-->>'\n ? 'dashed'\n : 'solid'\n : arrow.startsWith('--')\n ? 'dashed'\n : 'solid'\n // \">>\" = filled arrow, \")\" or \">\" alone = open arrow, \"x\" = cross (treat as\n // filled). Both bidirectional tokens end in \">>\", so they fall out as filled.\n const arrowHead =\n arrow.includes('>>') || arrow.includes('x') ? 'filled' : 'open'\n // \"x\"/\"--x\" is Mermaid's \"lost message\" terminator — a cross, not a plain\n // filled arrowhead. `arrow.includes('x')` is unambiguous here: the only\n // arrow tokens containing \"x\" are \"-x\"/\"--x\" (see the message regex's\n // `--?[)x]` alternative above), never \"->>\"/\"-->>\" or the bidirectional\n // forms.\n const isLost = arrow.includes('x')\n\n const msg: Message = {\n from,\n to,\n label: normalizeBrTags(rawLabel.trim()),\n lineStyle,\n arrowHead,\n }\n if (isLost) msg.isLost = true\n if (bidirectional) msg.bidirectional = true\n if (activationMark === '+') msg.activate = true\n if (activationMark === '-') msg.deactivate = true\n\n if (autonumber.enabled) {\n msg.seqNumber = autonumber.next\n autonumber.next =\n Math.round((autonumber.next + autonumber.step) * 100) / 100\n }\n\n diagram.messages.push(msg)\n}\n","// ============================================================================\n// Sequence diagram — activation/deactivation balance check\n//\n// A mechanical, deterministic semantic check: every `activate X` (or the\n// `+` arrow shorthand) must be closed by a matching `deactivate X` (or the\n// `-` shorthand) before the diagram ends. This doesn't need an LLM judge —\n// it's the same activation-stack bookkeeping\n// packages/svg-renderer/src/sequence/layout.ts already runs to *draw*\n// activation bars (see its `activationStacks` map), reused here to *report*\n// imbalance instead of silently rendering around it.\n//\n// Motivation (issue #539, split from #536): research on LLM-generated\n// Mermaid sequence diagrams found they fail mostly on activation handling\n// and error/status tracking, not basic syntax — which existing validators\n// (syntax checkers, and agentic-mermaid's structural/geometric/lint\n// `verify` tool: dangling edges, label overflow, duplicate/unreachable\n// nodes) already cover well. Activation balance is a concrete, narrow,\n// mechanically-checkable instance of that gap.\n// ============================================================================\n\nimport type { SequenceDiagram, Message } from './types.ts'\n\nexport type ActivationIssueCode =\n 'DANGLING_ACTIVATION' | 'UNMATCHED_DEACTIVATION'\n\nexport interface ActivationIssue {\n code: ActivationIssueCode\n /** Actor whose activation is unbalanced. */\n actorId: string\n /** Human-readable explanation, including a source-position hint. */\n message: string\n}\n\nexport interface ActivationCheckResult {\n /** true when every activation is balanced (no issues found). */\n ok: boolean\n issues: ActivationIssue[]\n}\n\n/** One `activate`/`deactivate` event in source order, whichever form wrote it. */\ninterface Event {\n actorId: string\n kind: 'start' | 'end'\n afterIndex: number\n}\n\n/**\n * Describe where an event occurred, for a human-readable issue message.\n * Mirrors the `afterIndex` semantics of `SequenceDiagram.activations` (see\n * types.ts): -1 means \"before the first message\", otherwise the index of\n * the message this event follows.\n */\nfunction describePosition(\n diagram: SequenceDiagram,\n afterIndex: number,\n): string {\n if (afterIndex < 0) return 'before the first message'\n const msg: Message | undefined = diagram.messages[afterIndex]\n if (!msg) return 'at an unresolved position'\n const label = msg.label ? `: ${msg.label}` : ''\n return `after message ${afterIndex + 1} (\"${msg.from} -> ${msg.to}${label}\")`\n}\n\n/**\n * Check a parsed sequence diagram for activation/deactivation imbalance.\n *\n * Merges the inline `+`/`-` arrow shorthand (`Message.activate` /\n * `Message.deactivate`) with standalone `activate X` / `deactivate X`\n * statements (`SequenceDiagram.activations`) into one chronological event\n * stream per actor — the same merge `layout.ts` performs to position\n * activation bars — then walks a stack per actor:\n *\n * - A `deactivate` with nothing open on that actor's stack is reported as\n * `UNMATCHED_DEACTIVATION` (the renderer silently ignores this case —\n * see `endActivation` in layout.ts — this check surfaces it instead).\n * - Anything left on a stack once every message has been processed is\n * reported as `DANGLING_ACTIVATION` (the renderer draws these extending\n * to the bottom of the diagram, which usually isn't what the author\n * intended).\n */\nexport function checkActivationBalance(\n diagram: SequenceDiagram,\n): ActivationCheckResult {\n // Standalone activations, grouped by the message index they follow — same\n // grouping layout.ts builds for its `activationEventsByAfterIndex`.\n const standaloneByAfterIndex = new Map<number, typeof diagram.activations>()\n for (const event of diagram.activations) {\n const list = standaloneByAfterIndex.get(event.afterIndex) ?? []\n list.push(event)\n standaloneByAfterIndex.set(event.afterIndex, list)\n }\n\n const events: Event[] = []\n function pushStandalone(afterIndex: number): void {\n for (const event of standaloneByAfterIndex.get(afterIndex) ?? []) {\n events.push({ actorId: event.actorId, kind: event.kind, afterIndex })\n }\n }\n\n // Events before the first message open first, same as layout.ts.\n pushStandalone(-1)\n for (let i = 0; i < diagram.messages.length; i++) {\n const msg = diagram.messages[i]!\n if (msg.activate) {\n events.push({ actorId: msg.to, kind: 'start', afterIndex: i })\n }\n if (msg.deactivate) {\n events.push({ actorId: msg.from, kind: 'end', afterIndex: i })\n }\n pushStandalone(i)\n }\n\n // actorId -> stack of afterIndex values where an activation was opened.\n const stacks = new Map<string, number[]>()\n const issues: ActivationIssue[] = []\n\n for (const event of events) {\n const stack = stacks.get(event.actorId) ?? []\n stacks.set(event.actorId, stack)\n if (event.kind === 'start') {\n stack.push(event.afterIndex)\n continue\n }\n const openedAt = stack.pop()\n if (openedAt === undefined) {\n issues.push({\n code: 'UNMATCHED_DEACTIVATION',\n actorId: event.actorId,\n message:\n `\"deactivate ${event.actorId}\" ${describePosition(diagram, event.afterIndex)} ` +\n `has no matching \"activate ${event.actorId}\" open at that point.`,\n })\n }\n }\n\n for (const [actorId, stack] of stacks) {\n for (const openedAt of stack) {\n issues.push({\n code: 'DANGLING_ACTIVATION',\n actorId,\n message:\n `\"activate ${actorId}\" opened ${describePosition(diagram, openedAt)} ` +\n `is never closed with a matching \"deactivate ${actorId}\".`,\n })\n }\n }\n\n return { ok: issues.length === 0, issues }\n}\n","// ============================================================================\n// Sequence diagram — activation/deactivation auto-fix\n//\n// Extends activation-check.ts (issue #539) with the \"optionally proposes a\n// fix\" half of that issue's scope. Deliberately narrow: only\n// `DANGLING_ACTIVATION` (an `activate X` never closed) is auto-fixable —\n// the mechanical, unambiguous repair is to append a matching `deactivate X`\n// after the diagram's last statement, which is always syntactically valid\n// regardless of block nesting (Mermaid's `deactivate` isn't block-scoped).\n//\n// `UNMATCHED_DEACTIVATION` (a `deactivate X` with nothing open) is\n// deliberately NOT auto-fixed: fixing it means either deleting that\n// specific statement or inserting a preceding `activate X`, and this\n// package doesn't track source line/position per activation statement (see\n// SequenceDiagram.activations — only a message-index `afterIndex`, no raw\n// line offset), so a text-level edit could not target the right occurrence\n// with confidence when the same statement text repeats. Rather than guess\n// and risk mangling unrelated source, this case is reported back as a\n// remaining (unfixable) issue for a human — or a fuzzier LLM-based layer,\n// per the issue's own caveat about unvalidated frontier-model behavior — to\n// resolve.\n// ============================================================================\n\nimport { detectDiagramType, splitStatements } from '@zombie-mermaid/core'\nimport { parseSequenceDiagram } from './parser.ts'\nimport {\n checkActivationBalance,\n type ActivationIssue,\n} from './activation-check.ts'\n\nexport interface ActivationFixResult {\n /** true when the returned diagram has no remaining activation issues. */\n ok: boolean\n /** Original diagram, unchanged if there was nothing fixable. */\n originalDiagram: string\n /**\n * Diagram with a \"deactivate X\" statement appended for every\n * DANGLING_ACTIVATION issue found. Identical to `originalDiagram` when\n * there was nothing to fix.\n */\n fixedDiagram: string\n /** Human-readable description of each fix actually applied. */\n fixesApplied: string[]\n /**\n * Issues that could not be auto-fixed (currently always\n * UNMATCHED_DEACTIVATION — see module header). Empty when `ok` is true.\n */\n remainingIssues: ActivationIssue[]\n}\n\n/**\n * Check a Mermaid sequence diagram for activation/deactivation imbalance\n * and, where mechanically safe, return a corrected version.\n *\n * Throws the same way `parseSequenceDiagram`/`detectDiagramType` do on\n * invalid or non-sequence input — callers (e.g. the MCP tool handler) are\n * expected to catch and translate, matching `check-sequence-activations.ts`'s\n * own error handling.\n */\nexport function fixActivationBalance(\n sourceDiagram: string,\n): ActivationFixResult {\n const diagramType = detectDiagramType(sourceDiagram)\n if (diagramType !== 'sequence') {\n throw new Error(\n 'fixActivationBalance only supports sequence diagrams (source must ' +\n `start with \"sequenceDiagram\"); detected diagram type: \"${diagramType}\".`,\n )\n }\n\n const lines = splitStatements(sourceDiagram)\n const diagram = parseSequenceDiagram(lines)\n const result = checkActivationBalance(diagram)\n\n if (result.ok) {\n return {\n ok: true,\n originalDiagram: sourceDiagram,\n fixedDiagram: sourceDiagram,\n fixesApplied: [],\n remainingIssues: [],\n }\n }\n\n const dangling = result.issues.filter(\n (issue) => issue.code === 'DANGLING_ACTIVATION',\n )\n const remainingIssues = result.issues.filter(\n (issue) => issue.code !== 'DANGLING_ACTIVATION',\n )\n\n const appended = dangling\n .map((issue) => ` deactivate ${issue.actorId}`)\n .join('\\n')\n const fixedDiagram =\n dangling.length === 0\n ? sourceDiagram\n : `${sourceDiagram.replace(/\\s+$/, '')}\\n${appended}`\n\n const fixesApplied = dangling.map(\n (issue) =>\n `Appended \"deactivate ${issue.actorId}\" to close: ${issue.message}`,\n )\n\n return {\n ok: remainingIssues.length === 0,\n originalDiagram: sourceDiagram,\n fixedDiagram,\n fixesApplied,\n remainingIssues,\n }\n}\n","import type { XYChart, XYAxis, XYChartSeries } from './types.ts'\nimport type { Statement } from '@zombie-mermaid/core'\n\n// ============================================================================\n// XY Chart parser\n//\n// Parses Mermaid xychart-beta syntax into a typed XYChart structure.\n//\n// Supported directives:\n// xychart-beta [horizontal]\n// title \"Chart Title\"\n// x-axis [label1, label2, ...] — categorical\n// x-axis min --> max — numeric range\n// x-axis \"Axis Title\" [label1, ...] — with title\n// x-axis \"Axis Title\" min --> max — with title\n// y-axis (same patterns)\n// bar [val1, val2, ...]\n// line [val1, val2, ...]\n// ============================================================================\n\n/**\n * Parse a Mermaid xychart-beta diagram from preprocessed lines.\n * Lines should already be trimmed and comment-stripped.\n */\nexport function parseXYChart(lines: Statement[]): XYChart {\n const xAxis: XYAxis = {}\n const yAxis: XYAxis = {}\n const series: XYChartSeries[] = []\n let title: string | undefined\n let horizontal = false\n\n for (const stmt of lines) {\n const line = stmt.text\n // Header line — detect horizontal\n if (/^xychart(-beta)?\\b/i.test(line)) {\n if (/\\bhorizontal\\b/i.test(line)) horizontal = true\n continue\n }\n\n // Title\n const titleMatch = line.match(/^title\\s+\"([^\"]+)\"/)\n if (titleMatch) {\n title = titleMatch[1]\n continue\n }\n\n // x-axis with categories: x-axis \"Title\" [a, b, c] or x-axis [a, b, c]\n const xCatMatch = line.match(/^x-axis\\s+(?:\"([^\"]*)\"\\s*)?\\[([^\\]]+)\\]/)\n if (xCatMatch) {\n if (xCatMatch[1]) xAxis.title = xCatMatch[1]\n xAxis.categories = splitCategoryList(xCatMatch[2]!).map((s) =>\n unquote(s.trim()),\n )\n continue\n }\n\n // x-axis with range: x-axis \"Title\" min --> max or x-axis min --> max\n const xRangeMatch = line.match(\n /^x-axis\\s+(?:\"([^\"]*)\"\\s+)?(-?\\d+(?:\\.\\d+)?)\\s*-->\\s*(-?\\d+(?:\\.\\d+)?)/,\n )\n if (xRangeMatch) {\n if (xRangeMatch[1]) xAxis.title = xRangeMatch[1]\n xAxis.range = {\n min: parseFloat(xRangeMatch[2]!),\n max: parseFloat(xRangeMatch[3]!),\n }\n continue\n }\n\n // y-axis with range: y-axis \"Title\" min --> max or y-axis min --> max\n const yRangeMatch = line.match(\n /^y-axis\\s+(?:\"([^\"]*)\"\\s+)?(-?\\d+(?:\\.\\d+)?)\\s*-->\\s*(-?\\d+(?:\\.\\d+)?)/,\n )\n if (yRangeMatch) {\n if (yRangeMatch[1]) yAxis.title = yRangeMatch[1]\n yAxis.range = {\n min: parseFloat(yRangeMatch[2]!),\n max: parseFloat(yRangeMatch[3]!),\n }\n continue\n }\n\n // y-axis with just title (no range)\n const yTitleOnly = line.match(/^y-axis\\s+\"([^\"]+)\"\\s*$/)\n if (yTitleOnly) {\n yAxis.title = yTitleOnly[1]\n continue\n }\n\n // bar [...]\n const barMatch = line.match(/^bar\\s+\\[([^\\]]+)\\]/)\n if (barMatch) {\n series.push({\n type: 'bar',\n data: parseNumericArray(barMatch[1]!, 'bar', line, stmt.line),\n })\n continue\n }\n\n // line [...]\n const lineMatch = line.match(/^line\\s+\\[([^\\]]+)\\]/)\n if (lineMatch) {\n series.push({\n type: 'line',\n data: parseNumericArray(lineMatch[1]!, 'line', line, stmt.line),\n })\n continue\n }\n\n // None of the recognized directive shapes matched. A line that doesn't\n // start with any known xychart-beta keyword at all falls through\n // silently below, same as before — but a line that *does* start with\n // one of the five keyworded directives (x-axis/y-axis/bar/line/title)\n // and still failed every pattern above is almost certainly an attempt\n // at that directive with broken syntax (an unclosed bracket, a missing\n // quote, a malformed range). Previously this was silently dropped —\n // the diagram would render with that axis/series just missing and no\n // indication why. Surface an actionable error instead. See issue #541\n // (parser error-message quality audit — this parser had zero throw\n // sites before this pass).\n const keywordMatch = line.match(/^(x-axis|y-axis|bar|line|title)\\b/i)\n if (keywordMatch) {\n throw malformedDirectiveError(\n keywordMatch[1]!.toLowerCase(),\n line,\n stmt.line,\n )\n }\n }\n\n // Auto-derive y-axis range from data if not specified\n if (!yAxis.range && series.length > 0) {\n const allValues = series.flatMap((s) => s.data)\n let min = Math.min(...allValues)\n let max = Math.max(...allValues)\n const span = max - min || 1\n // Add 10% padding\n min = min - span * 0.1\n max = max + span * 0.1\n // Floor to 0 if all values are positive and min is close to 0\n if (min > 0 && min < span * 0.5) min = 0\n yAxis.range = { min, max }\n }\n\n // Fallback y-axis range\n if (!yAxis.range) {\n yAxis.range = { min: 0, max: 100 }\n }\n\n return { title, horizontal, xAxis, yAxis, series }\n}\n\n/**\n * Split a categorical x-axis bracket's comma-separated contents into raw\n * item strings, treating a comma *inside* a double-quoted item as literal\n * text rather than a delimiter. A naive `.split(',')` would break\n * `[\"Jan, Feb\", \"Mar\"]` into three pieces instead of two — quoting a value\n * that itself contains a comma is exactly the case quoting exists for. Each\n * returned raw item is still trimmed and unquoted by the caller.\n */\nfunction splitCategoryList(str: string): string[] {\n const items: string[] = []\n let current = ''\n let inQuotes = false\n for (const char of str) {\n if (char === '\"') {\n inQuotes = !inQuotes\n current += char\n } else if (char === ',' && !inQuotes) {\n items.push(current)\n current = ''\n } else {\n current += char\n }\n }\n items.push(current)\n return items\n}\n\n/**\n * Strip one matching pair of leading/trailing double quotes from a category\n * item, if present — mirroring how an axis *title* capture group already\n * unquotes via its regex. `x-axis [A, B, C]` items are only split and\n * trimmed, so a quoted item like `\"CLI output / logs\"` previously kept its\n * literal quote characters in the rendered label. See issue #1087.\n */\nfunction unquote(value: string): string {\n if (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"')) {\n return value.slice(1, -1)\n }\n return value\n}\n\n/**\n * Parse a `bar`/`line` series' comma-separated bracket contents into\n * numbers, throwing on any element that isn't a valid number instead of\n * silently coercing it to `NaN` (which serializes as `null` and can\n * propagate into layout math with no indication anything was wrong — see\n * issue #541's audit finding on this exact parser).\n */\nfunction parseNumericArray(\n str: string,\n seriesType: 'bar' | 'line',\n line: string,\n lineNumber: number,\n): number[] {\n return str.split(',').map((raw, index) => {\n const trimmed = raw.trim()\n const value = parseFloat(trimmed)\n if (trimmed.length === 0 || Number.isNaN(value)) {\n throw new Error(\n `Line ${lineNumber}: Invalid numeric value ${JSON.stringify(trimmed)} at position ${\n index + 1\n } in \"${line}\". Every value in a ${seriesType} [...] list must be a number.`,\n )\n }\n return value\n })\n}\n\n/** Expected-syntax hint per xychart-beta keyword, for `malformedDirectiveError`. */\nconst XYCHART_DIRECTIVE_HELP: Record<string, string> = {\n 'x-axis':\n 'x-axis [A, B, C] (categories) or x-axis 0 --> 100 (numeric range), either optionally preceded by a quoted title',\n 'y-axis':\n 'y-axis 0 --> 100 (numeric range) or y-axis \"Title\" (title only), optionally preceded by a quoted title before a range',\n bar: 'bar [10, 20, 30] — a comma-separated numeric array in square brackets',\n line: 'line [10, 20, 30] — a comma-separated numeric array in square brackets',\n title: 'title \"Chart Title\" — a double-quoted string',\n}\n\n/**\n * Build the error for a line that starts with a recognized xychart-beta\n * keyword (x-axis/y-axis/bar/line/title) but doesn't match that keyword's\n * expected syntax in any of the forms this parser supports.\n */\nfunction malformedDirectiveError(\n keyword: string,\n line: string,\n lineNumber: number,\n): Error {\n const help = XYCHART_DIRECTIVE_HELP[keyword] ?? keyword\n return new Error(\n `Line ${lineNumber}: Malformed xychart-beta \"${keyword}\" directive: \"${line}\". Expected: ${help}.`,\n )\n}\n","// ============================================================================\n// XY Chart — shared color palette\n//\n// Generates monochromatic shades from the theme accent color.\n// Series 0 = accent (or blue fallback). Series 1+ are darker/lighter\n// shades of the same hue with subtle hue drift to stay in the same\n// color family (like navy ↔ cyan from blue). Hue drifts toward navy for\n// darker shades and toward cyan for lighter ones, so neighbouring series\n// do not collapse into two near-identical blues (#1244).\n//\n// Used by both the SVG and ASCII renderers.\n// ============================================================================\n\n/** Default accent for charts when the theme doesn't provide one. */\nexport const CHART_ACCENT_FALLBACK = '#3b82f6' // blue-500\n\n// ---------------------------------------------------------------------------\n// HSL ↔ Hex conversion\n// ---------------------------------------------------------------------------\n\nfunction hexToHsl(hex: string): [number, number, number] {\n const h = hex.replace('#', '')\n const ri = parseInt(h.substring(0, 2), 16) / 255\n const gi = parseInt(h.substring(2, 4), 16) / 255\n const bi = parseInt(h.substring(4, 6), 16) / 255\n\n const max = Math.max(ri, gi, bi)\n const min = Math.min(ri, gi, bi)\n const l = (max + min) / 2\n\n if (max === min) return [0, 0, l * 100]\n\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n\n let hue: number\n if (max === ri) hue = ((gi - bi) / d + (gi < bi ? 6 : 0)) / 6\n else if (max === gi) hue = ((bi - ri) / d + 2) / 6\n else hue = ((ri - gi) / d + 4) / 6\n\n return [hue * 360, s * 100, l * 100]\n}\n\nfunction hslToHex(h: number, s: number, l: number): string {\n const si = s / 100\n const li = l / 100\n\n const c = (1 - Math.abs(2 * li - 1)) * si\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n const m = li - c / 2\n\n let r: number, g: number, b: number\n if (h < 60) {\n r = c\n g = x\n b = 0\n } else if (h < 120) {\n r = x\n g = c\n b = 0\n } else if (h < 180) {\n r = 0\n g = c\n b = x\n } else if (h < 240) {\n r = 0\n g = x\n b = c\n } else if (h < 300) {\n r = x\n g = 0\n b = c\n } else {\n r = c\n g = 0\n b = x\n }\n\n const toHex = (v: number) =>\n Math.round((v + m) * 255)\n .toString(16)\n .padStart(2, '0')\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Check whether a string is a valid 6-digit hex color (e.g. \"#3b82f6\"). */\nexport function isValidHex(color: string): boolean {\n return /^#[0-9a-fA-F]{6}$/.test(color)\n}\n\n/**\n * Detect whether a background color is dark (lightness < 50%).\n */\nexport function isDarkBackground(bgHex: string): boolean {\n return hexToHsl(bgHex)[2] < 50\n}\n\n/**\n * Get the hex color for a series index.\n * Index 0 returns the accent color as-is.\n * Index 1+ alternate between darker and lighter shades of the same hue\n * with subtle hue drift (-8° per tier for darker, -12° for lighter) to stay in the same family.\n *\n * When `bgColor` is provided, shade direction adapts to the background:\n * - Light bg: odd = darker, even = lighter (default)\n * - Dark bg: odd = lighter, even = darker (so shades stay visible)\n */\nexport function getSeriesColor(\n index: number,\n accentColor: string,\n bgColor?: string,\n): string {\n if (index === 0) return accentColor\n // Fall back to defaults when inputs aren't valid hex (e.g. CSS variable refs like \"var(--accent)\")\n const safeAccent = isValidHex(accentColor)\n ? accentColor\n : CHART_ACCENT_FALLBACK\n const safeBg = bgColor && isValidHex(bgColor) ? bgColor : undefined\n const [h, s] = hexToHsl(safeAccent)\n const chartS = Math.max(55, Math.min(85, s))\n\n const tier = Math.ceil(index / 2)\n const oddIndex = index % 2 === 1\n\n // On dark backgrounds, flip: odd = lighter, even = darker\n const dark = safeBg && isDarkBackground(safeBg) ? !oddIndex : oddIndex\n const l = dark ? Math.max(25, 48 - tier * 13) : Math.min(78, 55 + tier * 11)\n\n // Subtle hue drift: darker shades shift slightly negative (toward navy),\n // lighter shades shift negative too (toward sky/cyan). The lighter shade\n // used to drift positive (toward indigo), which landed series 2 within a\n // few ΔE of series 0 (#1244); drifting the other way keeps the three first\n // series distinct in hue as well as lightness.\n const hShift = (dark ? -8 : -12) * tier\n const newH = (((h + hShift) % 360) + 360) % 360\n\n return hslToHex(newH, chartS, l)\n}\n","// ============================================================================\n// zombie-mermaid — expanded node syntax A@{ shape: ..., label: ... }\n//\n// Mermaid v11.3.0 added a metadata form for node definitions:\n//\n// A@{ shape: rounded, label: \"Start here\" }\n// B@{ shape: doc }\n// C@{ icon: \"fa:bell\", form: \"circle\", label: \"Alert\" }\n// D@{ img: \"https://example.com/a.png\", label: \"Diagram\", w: 120, h: 80 }\n//\n// It exposes ~30 semantic shape names (many with several aliases) that the\n// classic bracket syntax cannot express. This module owns both halves of\n// supporting it: parsing the metadata block, and resolving a semantic name to\n// the geometry this renderer draws.\n// ============================================================================\n\nimport type { NodeShape } from '@zombie-mermaid/core'\n\n/**\n * Every documented Mermaid shape name (and alias) mapped to the geometry this\n * renderer draws for it.\n *\n * Mermaid's list is deliberately semantic — `database`, `manual-input`,\n * `paper-tape` — while a renderer only has so many distinct outlines. Names\n * that share an outline map to the same `NodeShape`; that is a rendering\n * choice, not a parse failure, and it is documented in docs/diagrams.md so\n * the collapse is discoverable rather than surprising.\n *\n * Keys are lowercase; lookup lowercases its input.\n */\nconst SHAPE_ALIASES: Record<string, NodeShape> = {\n // --- Rectangle family ---\n rect: 'rectangle',\n rectangle: 'rectangle',\n proc: 'rectangle',\n process: 'rectangle',\n 'normal-rect': 'rectangle',\n\n // --- Rounded ---\n rounded: 'rounded',\n event: 'rounded',\n 'rounded-rect': 'rounded',\n\n // --- Stadium / terminal ---\n stadium: 'stadium',\n pill: 'stadium',\n terminal: 'stadium',\n\n // --- Subroutine / framed rectangle ---\n subproc: 'subroutine',\n subprocess: 'subroutine',\n subroutine: 'subroutine',\n 'framed-rectangle': 'subroutine',\n 'fr-rect': 'subroutine',\n\n // --- Cylinder / database ---\n cyl: 'cylinder',\n cylinder: 'cylinder',\n db: 'cylinder',\n database: 'cylinder',\n 'h-cyl': 'cylinder',\n das: 'cylinder',\n 'horizontal-cylinder': 'cylinder',\n 'lin-cyl': 'cylinder',\n 'lined-cylinder': 'cylinder',\n disk: 'cylinder',\n\n // --- Circle ---\n circ: 'circle',\n circle: 'circle',\n start: 'circle',\n\n // --- Double circle ---\n 'dbl-circ': 'doublecircle',\n 'double-circle': 'doublecircle',\n stop: 'doublecircle',\n\n // --- Filled / crossed circles ---\n 'f-circ': 'filled-circle',\n 'filled-circle': 'filled-circle',\n junction: 'filled-circle',\n 'cross-circ': 'crossed-circle',\n 'crossed-circle': 'crossed-circle',\n summary: 'crossed-circle',\n\n // --- Diamond / decision ---\n diam: 'diamond',\n diamond: 'diamond',\n decision: 'diamond',\n question: 'diamond',\n\n // --- Hexagon / prepare ---\n hex: 'hexagon',\n hexagon: 'hexagon',\n prepare: 'hexagon',\n\n // --- Asymmetric / odd ---\n odd: 'asymmetric',\n 'rect-left-inv-arrow': 'asymmetric',\n\n // --- Parallelograms ---\n 'lean-r': 'parallelogram',\n 'lean-right': 'parallelogram',\n 'in-out': 'parallelogram',\n 'lean-l': 'parallelogram-alt',\n 'lean-left': 'parallelogram-alt',\n 'out-in': 'parallelogram-alt',\n\n // --- Trapezoids ---\n 'trap-b': 'trapezoid',\n 'trapezoid-bottom': 'trapezoid',\n priority: 'trapezoid',\n 'trap-t': 'trapezoid-alt',\n 'trapezoid-top': 'trapezoid-alt',\n manual: 'trapezoid-alt',\n 'curv-trap': 'trapezoid-alt',\n 'curved-trapezoid': 'trapezoid-alt',\n display: 'trapezoid-alt',\n\n // --- Document family ---\n doc: 'document',\n document: 'document',\n 'lin-doc': 'document',\n 'lined-document': 'document',\n 'tag-doc': 'document',\n 'tagged-document': 'document',\n docs: 'stacked-document',\n documents: 'stacked-document',\n 'st-doc': 'stacked-document',\n 'stacked-document': 'stacked-document',\n\n // --- Card / notched rectangle ---\n 'notch-rect': 'card',\n card: 'card',\n 'notched-rectangle': 'card',\n\n // --- Lined / divided / tagged rectangles ---\n 'lin-rect': 'lined-process',\n 'lined-rectangle': 'lined-process',\n 'lin-proc': 'lined-process',\n 'shaded-process': 'lined-process',\n 'div-rect': 'divided-process',\n 'divided-rectangle': 'divided-process',\n 'div-proc': 'divided-process',\n 'tag-rect': 'rectangle',\n 'tagged-rectangle': 'rectangle',\n 'tag-proc': 'rectangle',\n procs: 'stacked-process',\n processes: 'stacked-process',\n 'st-rect': 'stacked-process',\n 'stacked-rectangle': 'stacked-process',\n\n // --- Triangles ---\n tri: 'triangle',\n triangle: 'triangle',\n extract: 'triangle',\n 'flip-tri': 'flipped-triangle',\n 'flipped-triangle': 'flipped-triangle',\n 'manual-file': 'flipped-triangle',\n\n // --- Window pane / internal storage ---\n 'win-pane': 'window-pane',\n 'window-pane': 'window-pane',\n 'internal-storage': 'window-pane',\n\n // --- Fork / join ---\n fork: 'fork-join',\n join: 'fork-join',\n 'long-rect': 'fork-join',\n\n // --- Notched pentagon / loop limit ---\n 'notch-pent': 'notched-pentagon',\n 'loop-limit': 'notched-pentagon',\n 'notched-pentagon': 'notched-pentagon',\n\n // --- Sloped rectangle / manual input ---\n 'sl-rect': 'sloped-rectangle',\n 'sloped-rectangle': 'sloped-rectangle',\n 'manual-input': 'sloped-rectangle',\n\n // --- Flag / paper tape ---\n flag: 'flag',\n 'paper-tape': 'flag',\n\n // --- Bow-tie rectangle / stored data ---\n 'bow-rect': 'bow-tie-rectangle',\n 'bow-tie-rectangle': 'bow-tie-rectangle',\n 'stored-data': 'bow-tie-rectangle',\n\n // --- Delay / half-rounded rectangle ---\n delay: 'half-rounded-rectangle',\n 'half-rounded-rectangle': 'half-rounded-rectangle',\n\n // --- Braces / comment ---\n brace: 'brace',\n 'brace-l': 'brace',\n comment: 'brace',\n 'brace-r': 'brace-right',\n braces: 'braces',\n\n // --- Lightning bolt / communication link ---\n bolt: 'bolt',\n 'com-link': 'bolt',\n 'lightning-bolt': 'bolt',\n\n // --- Bare text, no outline ---\n text: 'text',\n\n // --- Anchor / hidden ---\n anchor: 'anchor',\n}\n\n/**\n * Resolve a Mermaid shape name to the geometry this renderer draws.\n * Returns `undefined` for an unrecognized name so the caller can decide\n * whether to fall back or report it.\n */\nexport function resolveShapeName(name: string): NodeShape | undefined {\n return SHAPE_ALIASES[name.trim().toLowerCase()]\n}\n\n/** Every shape name this renderer accepts, for documentation and tests. */\nexport function knownShapeNames(): string[] {\n return Object.keys(SHAPE_ALIASES).sort()\n}\n\n/** The metadata a `@{ ... }` block can carry. */\nexport interface ExpandedNodeMeta {\n shape?: string\n label?: string\n icon?: string\n img?: string\n /** Outline drawn around an icon or image: `square`, `circle`, `rounded`. */\n form?: string\n /** Explicit width/height for an image node. */\n w?: string\n h?: string\n /** Image fit mode Mermaid accepts alongside `img`. */\n constraint?: string\n /** Any other key seen, preserved rather than dropped. */\n [key: string]: string | undefined\n}\n\n/**\n * Parse the body of a `@{ ... }` block into key/value pairs.\n *\n * The body is a comma-separated list of `key: value`. Values may be quoted\n * with `\"` or `'`, and a quoted value may contain commas, colons, and braces\n * — which is why this is a scanner rather than a `split(',')`.\n *\n * Mermaid also accepts a bare value with no key as shorthand for the shape\n * (`A@{ rounded }`); that is handled by the caller, which sees an entry with\n * an empty key.\n */\nexport function parseExpandedMeta(body: string): ExpandedNodeMeta {\n const meta: ExpandedNodeMeta = {}\n\n for (const entry of splitTopLevel(body, ',')) {\n const trimmed = entry.trim()\n if (trimmed.length === 0) continue\n\n const colon = indexOfTopLevel(trimmed, ':')\n if (colon === -1) {\n // Bare value — Mermaid's shorthand for `shape: <value>`.\n meta.shape ??= stripQuotes(trimmed)\n continue\n }\n\n const key = trimmed.slice(0, colon).trim().toLowerCase()\n const value = stripQuotes(trimmed.slice(colon + 1).trim())\n if (key.length > 0) meta[key] = value\n }\n\n return meta\n}\n\n/** Split on `separator`, ignoring separators inside quotes. */\nfunction splitTopLevel(text: string, separator: string): string[] {\n const parts: string[] = []\n let current = ''\n let quote: string | null = null\n\n for (const ch of text) {\n if (quote !== null) {\n current += ch\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n current += ch\n continue\n }\n if (ch === separator) {\n parts.push(current)\n current = ''\n continue\n }\n current += ch\n }\n\n parts.push(current)\n return parts\n}\n\n/** Index of the first `needle` outside quotes, or -1. */\nfunction indexOfTopLevel(text: string, needle: string): number {\n let quote: string | null = null\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === needle) return i\n }\n return -1\n}\n\n/** Remove one layer of matching wrapping quotes. */\nfunction stripQuotes(value: string): string {\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n return value.slice(1, -1)\n }\n return value\n}\n\n/**\n * Find the `@{ ... }` block at the start of `text`, returning its body and\n * total length.\n *\n * Brace matching is depth-aware and quote-aware, so a label containing `}`\n * (`A@{ label: \"a } b\" }`) does not terminate the block early. Returns\n * `undefined` if `text` does not open with `@{` or the block is unterminated.\n */\nexport function matchExpandedBlock(\n text: string,\n): { body: string; length: number } | undefined {\n if (!text.startsWith('@{')) return undefined\n\n let depth = 0\n let quote: string | null = null\n\n for (let i = 1; i < text.length; i++) {\n const ch = text[i]!\n\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === '{') {\n depth++\n continue\n }\n if (ch === '}') {\n depth--\n if (depth === 0) {\n return { body: text.slice(2, i), length: i + 1 }\n }\n }\n }\n\n return undefined\n}\n","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 './expanded-shapes.ts'\nimport type { ExpandedNodeMeta } from './expanded-shapes.ts'\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 — register it if the node doesn't exist yet.\n // If it already exists, it stays in the subgraph that claimed it first\n // (nodes belong to the subgraph where they're first defined). A node that\n // no subgraph has claimed yet (declared earlier at the top level, e.g.\n // `X --> A` before `subgraph SA; A; end`) is adopted by the current one,\n // as Mermaid does; otherwise the frame would come out empty (#1289).\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 } else if (!isClaimedBySubgraph(graph, subgraphStack, id)) {\n trackInSubgraph(subgraphStack, id)\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/** True when any finished or still-open subgraph already lists `nodeId`. */\nfunction isClaimedBySubgraph(\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n nodeId: string,\n): boolean {\n const claims = (sg: MermaidSubgraph): boolean =>\n sg.nodeIds.includes(nodeId) || sg.children.some(claims)\n return graph.subgraphs.some(claims) || subgraphStack.some(claims)\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.js","names":[],"sources":["../src/architecture/parser.ts","../src/architecture/to-graph.ts","../src/c4/parser.ts","../src/c4/format.ts","../src/class/parser.ts","../src/class/format.ts","../src/er/parser.ts","../src/sequence/box-color.ts","../src/sequence/parser.ts","../src/sequence/activation-check.ts","../src/sequence/activation-fix.ts","../src/xychart/parser.ts","../src/xychart/colors.ts","../src/pie/parser.ts","../src/expanded-shapes.ts","../src/flowchart-parser.ts"],"sourcesContent":["import { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport type {\n ArchitectureDiagram,\n ArchitectureEdge,\n ArchitecturePort,\n} from './types.ts'\n\n// ============================================================================\n// Architecture diagram parser\n//\n// Parses Mermaid's `architecture-beta` diagram\n// (https://mermaid.ai/open-source/syntax/architecture.html):\n//\n// architecture-beta\n// group api(cloud)[API]\n// service db(database)[Database] in api\n// service server(server)[Server] in api\n// junction j1 in api\n// db:L -- R:server\n// server{group}:B --> T:client{group}\n//\n// Grammar:\n// group <id>[(icon)][[Title]] [in <parent>]\n// service <id>[(icon)][[Title]] [in <parent>]\n// junction <id> [in <parent>]\n// <id>[{group}]:<L|R|T|B> <--|-->|<-->|--> <L|R|T|B>:<id>[{group}]\n// align row|column <id> <id> ... (accepted, no effect: see to-graph)\n//\n// Unlike Mermaid's own parser, every malformed statement throws with its line\n// number rather than being dropped.\n// ============================================================================\n\nconst ID = String.raw`[\\w-]+`\nconst DECL_RE = new RegExp(\n String.raw`^(group|service)\\s+(${ID})\\s*(?:\\(([^)]*)\\))?\\s*(?:\\[([^\\]]*)\\])?(?:\\s+in\\s+(${ID}))?$`,\n)\nconst JUNCTION_RE = new RegExp(\n String.raw`^junction\\s+(${ID})(?:\\s+in\\s+(${ID}))?$`,\n)\nconst EDGE_RE = new RegExp(\n String.raw`^(${ID})(\\{group\\})?:([LRTB])\\s+(<)?--(>)?\\s+([LRTB]):(${ID})(\\{group\\})?$`,\n)\nconst ALIGN_RE = new RegExp(\n String.raw`^align\\s+(?:row|column)\\s+${ID}(?:\\s+${ID})+$`,\n)\n\ntype Kind = 'group' | 'service' | 'junction'\n\nfunction fail(stmt: Statement, msg: string): never {\n throw new Error(\n `Architecture diagram, line ${stmt.line}: ${msg} — \"${stmt.text}\"`,\n )\n}\n\n/** Strip the optional quotes around a custom icon name like `\"logos:aws\"`. */\nfunction cleanIcon(raw: string | undefined): string | undefined {\n const icon = raw?.trim().replace(/^\"(.*)\"$/, '$1')\n return icon || undefined\n}\n\n/**\n * Parse a Mermaid architecture diagram.\n * Expects the first statement to be the `architecture-beta` header.\n */\nexport function parseArchitecture(lines: Statement[]): ArchitectureDiagram {\n const diagram: ArchitectureDiagram = {\n groups: [],\n services: [],\n junctions: [],\n edges: [],\n }\n const kinds = new Map<string, Kind>()\n // `in` may name a group declared later, so parents and edge endpoints are\n // resolved after the loop.\n const parentRefs: { stmt: Statement; parent: string }[] = []\n const pendingEdges: { stmt: Statement; edge: ArchitectureEdge }[] = []\n\n const declare = (\n stmt: Statement,\n id: string,\n kind: Kind,\n parent: string | undefined,\n ): void => {\n if (kinds.has(id)) fail(stmt, `duplicate id \"${id}\"`)\n if (parent === id) fail(stmt, `\"${id}\" cannot be inside itself`)\n kinds.set(id, kind)\n if (parent) parentRefs.push({ stmt, parent })\n }\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const text = stmt.text\n\n const decl = text.match(DECL_RE)\n if (decl) {\n const kind = decl[1] as 'group' | 'service'\n const id = decl[2]!\n const title = decl[4]\n const parent = decl[5]\n declare(stmt, id, kind, parent)\n const entry = {\n id,\n icon: cleanIcon(decl[3]),\n title: title !== undefined ? normalizeBrTags(title.trim()) : id,\n parent,\n }\n if (kind === 'group') diagram.groups.push(entry)\n else diagram.services.push(entry)\n continue\n }\n\n const junction = text.match(JUNCTION_RE)\n if (junction) {\n const id = junction[1]!\n const parent = junction[2]\n declare(stmt, id, 'junction', parent)\n diagram.junctions.push({ id, parent })\n continue\n }\n\n const edge = text.match(EDGE_RE)\n if (edge) {\n pendingEdges.push({\n stmt,\n edge: {\n source: edge[1]!,\n sourceGroup: edge[2] !== undefined,\n sourcePort: edge[3] as ArchitecturePort,\n arrowStart: edge[4] !== undefined,\n arrowEnd: edge[5] !== undefined,\n targetPort: edge[6] as ArchitecturePort,\n target: edge[7]!,\n targetGroup: edge[8] !== undefined,\n },\n })\n continue\n }\n\n // Alignment constraints (Mermaid v11.16+) have no flowchart equivalent.\n if (ALIGN_RE.test(text)) continue\n\n fail(stmt, 'unrecognized statement')\n }\n\n for (const { stmt, parent } of parentRefs) {\n if (kinds.get(parent) !== 'group') {\n fail(stmt, `\"in ${parent}\" must name a declared group`)\n }\n }\n\n const parentOf = new Map<string, string>()\n for (const g of diagram.groups) if (g.parent) parentOf.set(g.id, g.parent)\n for (const g of diagram.groups) {\n const seen = new Set([g.id])\n for (let p = parentOf.get(g.id); p; p = parentOf.get(p)) {\n if (seen.has(p)) {\n throw new Error(\n `Architecture diagram: group \"${g.id}\" is nested inside itself`,\n )\n }\n seen.add(p)\n }\n }\n\n for (const { stmt, edge } of pendingEdges) {\n const ends: [string, boolean][] = [\n [edge.source, edge.sourceGroup],\n [edge.target, edge.targetGroup],\n ]\n for (const [id, viaGroup] of ends) {\n const kind = kinds.get(id)\n if (!kind) fail(stmt, `unknown id \"${id}\"`)\n if (kind === 'group') {\n fail(stmt, `edges connect services or junctions, not the group \"${id}\"`)\n }\n const parent = [...diagram.services, ...diagram.junctions].find(\n (n) => n.id === id,\n )?.parent\n if (viaGroup && !parent) {\n fail(stmt, `\"{group}\" needs \"${id}\" to be declared inside a group`)\n }\n }\n diagram.edges.push(edge)\n }\n\n return diagram\n}\n","import type {\n Direction,\n MermaidEdge,\n MermaidGraph,\n MermaidNode,\n MermaidSubgraph,\n NodeShape,\n} from '@zombie-mermaid/core'\nimport type {\n ArchitectureDiagram,\n ArchitectureEdge,\n ArchitecturePort,\n} from './types.ts'\n\n// ============================================================================\n// Architecture -> flowchart lowering\n//\n// `architecture-beta` is boxes in nested boxes joined by edges, which the\n// flowchart pipeline already lays out and draws (ELK for SVG, the grid router\n// for ASCII). Like the other lowered types it becomes a `MermaidGraph`:\n//\n// group -> subgraph (nested via `in`)\n// service -> node; the icon picks a shape\n// junction -> small filled circle, no label\n// edge -> edge; `<`/`>` become arrowheads\n// `{group}` end -> the edge attaches to the service's enclosing group\n//\n// What is not carried over: Mermaid places services on a grid from the edge\n// ports, so `a:R -- L:b` means \"b is right of a\". Here ports only choose the\n// flow direction (majority axis) and which way each edge points in the\n// layout; exact placement is the flowchart layout's. Icons are not drawn\n// (only the database/disk shapes differ) and `align` is ignored.\n// ============================================================================\n\n/** Built-in Mermaid icons that have a distinct flowchart shape. */\nconst ICON_SHAPE: Record<string, NodeShape> = {\n database: 'cylinder',\n disk: 'cylinder',\n cloud: 'stadium',\n internet: 'circle',\n}\n\nconst HORIZONTAL: ReadonlySet<ArchitecturePort> = new Set(['L', 'R'])\n\n/** Flow direction: vertical only when more edges leave via T/B than L/R. */\nfunction pickDirection(edges: readonly ArchitectureEdge[]): Direction {\n let horizontal = 0\n let vertical = 0\n for (const e of edges) {\n for (const p of [e.sourcePort, e.targetPort]) {\n if (HORIZONTAL.has(p)) horizontal++\n else vertical++\n }\n }\n return vertical > horizontal ? 'TB' : 'LR'\n}\n\n/**\n * Whether the edge, as written, runs against the layout flow. `a:L -- R:b`\n * puts `a` to the right of `b`, so in a left-to-right layout the edge has to\n * be emitted reversed for `a` to land on the right.\n */\nfunction runsBackwards(e: ArchitectureEdge, direction: Direction): boolean {\n const horizontal = direction === 'LR' || direction === 'RL'\n const [back, forward]: ArchitecturePort[] = horizontal\n ? ['L', 'R']\n : ['T', 'B']\n // Source port on the far side of the flow, or target port on the near side.\n if (e.sourcePort === forward || e.targetPort === back) return false\n return e.sourcePort === back || e.targetPort === forward\n}\n\nexport function architectureToGraph(\n diagram: ArchitectureDiagram,\n): MermaidGraph {\n const nodes = new Map<string, MermaidNode>()\n const parentOf = new Map<string, string | undefined>()\n\n for (const s of diagram.services) {\n nodes.set(s.id, {\n id: s.id,\n label: s.title,\n shape: (s.icon && ICON_SHAPE[s.icon]) || 'rectangle',\n })\n parentOf.set(s.id, s.parent)\n }\n for (const j of diagram.junctions) {\n nodes.set(j.id, { id: j.id, label: '', shape: 'filled-circle' })\n parentOf.set(j.id, j.parent)\n }\n\n // Subgraphs, nested. Children are attached in declaration order.\n const subgraphs = new Map<string, MermaidSubgraph>()\n for (const g of diagram.groups) {\n subgraphs.set(g.id, {\n id: g.id,\n label: g.title,\n nodeIds: [],\n children: [],\n })\n }\n const roots: MermaidSubgraph[] = []\n for (const g of diagram.groups) {\n const sg = subgraphs.get(g.id)!\n const parent = g.parent ? subgraphs.get(g.parent) : undefined\n if (parent) parent.children.push(sg)\n else roots.push(sg)\n }\n for (const [id, parent] of parentOf) {\n subgraphs.get(parent ?? '')?.nodeIds.push(id)\n }\n\n const direction = pickDirection(diagram.edges)\n const edges: MermaidEdge[] = diagram.edges.map((e) => {\n const end = (id: string, viaGroup: boolean): string =>\n (viaGroup ? parentOf.get(id) : undefined) ?? id\n const forward = !runsBackwards(e, direction)\n const [source, target] = forward\n ? [end(e.source, e.sourceGroup), end(e.target, e.targetGroup)]\n : [end(e.target, e.targetGroup), end(e.source, e.sourceGroup)]\n return {\n source,\n target,\n style: 'solid',\n hasArrowStart: forward ? e.arrowStart : e.arrowEnd,\n hasArrowEnd: forward ? e.arrowEnd : e.arrowStart,\n }\n })\n\n return {\n direction,\n nodes,\n edges,\n subgraphs: roots,\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n}\n","import { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport type {\n C4Boundary,\n C4Diagram,\n C4Element,\n C4ElementKind,\n C4LayoutHint,\n C4ElementShape,\n C4Relationship,\n C4Variant,\n} from './types.ts'\n\n// ============================================================================\n// C4 diagram parser\n//\n// Supported syntax (Mermaid C4 macros):\n// Person / Person_Ext (alias, label, descr)\n// System[Db|Queue][_Ext] (alias, label, descr)\n// Container[Db|Queue][_Ext] (alias, label, techn, descr)\n// Component[Db|Queue][_Ext] (alias, label, techn, descr)\n// Boundary / Enterprise_Boundary / System_Boundary / Container_Boundary\n// (alias, label[, type]) { ... }\n// Deployment_Node / Node / Node_L / Node_R (alias, label, type, descr) { ... }\n// Rel / BiRel / Rel_U|Up|D|Down|L|Left|R|Right|Back (from, to, label, techn)\n// RelIndex (index, from, to, label, techn)\n// title <text>\n//\n// `Rel_U/D/L/R` record a placement hint (`C4Relationship.layout`) the\n// renderers use to steer layout. Accepted and ignored (styling / layout\n// config this renderer does not honor): UpdateElementStyle, UpdateRelStyle, UpdateLayoutConfig, LAYOUT_*,\n// SHOW_LEGEND, SHOW_FLOATING_LEGEND, AddElementTag, AddRelTag,\n// AddBoundaryTag, RoleTag. Named arguments (`$tags=\"x\"`, `$link=...`) are\n// dropped.\n// ============================================================================\n\nconst HEADERS: Record<string, C4Variant> = {\n c4context: 'context',\n c4container: 'container',\n c4component: 'component',\n c4dynamic: 'dynamic',\n c4deployment: 'deployment',\n}\n\nconst IGNORED_MACRO =\n /^(?:UpdateElementStyle|UpdateRelStyle|UpdateLayoutConfig|LAYOUT_[A-Z_]+|SHOW_LEGEND|SHOW_FLOATING_LEGEND|AddElementTag|AddRelTag|AddBoundaryTag|RoleTag)\\b/\n\nconst ELEMENT_NAME = /^(Person|System|Container|Component)(Db|Queue)?(_Ext)?$/\nconst BOUNDARY_NAME =\n /^(?:Boundary|Enterprise_Boundary|System_Boundary|Container_Boundary)$/\nconst NODE_NAME = /^(?:Deployment_Node|Node|Node_L|Node_R)$/\nconst REL_NAME =\n /^(Rel|BiRel|Rel_U|Rel_Up|Rel_D|Rel_Down|Rel_L|Rel_Left|Rel_R|Rel_Right|Rel_Back|RelIndex)$/\n\nconst BOUNDARY_TYPES: Record<string, string> = {\n Enterprise_Boundary: 'ENTERPRISE',\n System_Boundary: 'SYSTEM',\n Container_Boundary: 'CONTAINER',\n}\n\nconst REL_HINTS: Record<string, C4LayoutHint> = {\n Rel_U: 'up',\n Rel_Up: 'up',\n Rel_D: 'down',\n Rel_Down: 'down',\n Rel_L: 'left',\n Rel_Left: 'left',\n Rel_R: 'right',\n Rel_Right: 'right',\n}\n\nfunction unquote(a: string): string {\n const t = a.trim()\n const inner =\n t.length >= 2 && t.startsWith('\"') && t.endsWith('\"') ? t.slice(1, -1) : t\n return normalizeBrTags(inner)\n}\n\n/**\n * Split a macro's argument list on top-level commas. Double-quoted strings\n * may contain commas and parentheses; `$name=value` named arguments are\n * dropped. Returns null when a quote is unbalanced.\n */\nfunction splitArgs(inner: string): string[] | null {\n const raw: string[] = []\n let cur = ''\n let inQuote = false\n for (const ch of inner) {\n if (ch === '\"') inQuote = !inQuote\n if (ch === ',' && !inQuote) {\n raw.push(cur)\n cur = ''\n } else {\n cur += ch\n }\n }\n if (inQuote) return null\n raw.push(cur)\n return raw\n .map((a) => a.trim())\n .filter((a) => !a.startsWith('$'))\n .map(unquote)\n}\n\n/** `Name(args)` optionally followed by ` {`. */\nconst MACRO = /^([A-Za-z_][A-Za-z0-9_]*)\\s*\\((.*)\\)\\s*(\\{)?\\s*$/\n\nfunction fail(stmt: Statement, msg: string): never {\n throw new Error(`C4 diagram, line ${stmt.line}: ${msg} — \"${stmt.text}\"`)\n}\n\n/**\n * Parse a Mermaid C4 diagram. Expects the first statement to be a\n * `C4Context` / `C4Container` / `C4Component` / `C4Dynamic` /\n * `C4Deployment` header.\n */\nexport function parseC4Diagram(lines: Statement[]): C4Diagram {\n const variant = HEADERS[lines[0]?.text.trim().toLowerCase() ?? '']\n if (!variant) {\n throw new Error(\n `C4 diagram: expected a header of C4Context, C4Container, C4Component, C4Dynamic or C4Deployment, got \"${lines[0]?.text ?? ''}\"`,\n )\n }\n\n const diagram: C4Diagram = {\n variant,\n elements: [],\n boundaries: [],\n relationships: [],\n }\n const aliases = new Set<string>()\n const stack: C4Boundary[] = []\n /** Boundary declared on the previous statement without an inline `{`. */\n let pendingBoundary: C4Boundary | undefined\n\n const claim = (stmt: Statement, alias: string | undefined): string => {\n if (!alias) fail(stmt, 'missing alias (first argument)')\n if (aliases.has(alias)) fail(stmt, `duplicate alias \"${alias}\"`)\n aliases.add(alias)\n return alias\n }\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n if (line === '}') {\n if (stack.length === 0) fail(stmt, 'unmatched \"}\"')\n stack.pop()\n continue\n }\n // Brace on its own line, directly after a boundary macro.\n if (line === '{') {\n if (!pendingBoundary) fail(stmt, 'unexpected \"{\"')\n stack.push(pendingBoundary)\n pendingBoundary = undefined\n continue\n }\n pendingBoundary = undefined\n\n const titleMatch = line.match(/^title(?:\\s+(.*))?$/i)\n if (titleMatch) {\n diagram.title = normalizeBrTags((titleMatch[1] ?? '').trim())\n continue\n }\n\n if (IGNORED_MACRO.test(line)) continue\n\n const m = line.match(MACRO)\n if (!m) fail(stmt, 'unrecognized statement')\n const name = m[1]!\n const opensBlock = m[3] === '{'\n const args = splitArgs(m[2]!)\n if (!args) fail(stmt, 'unbalanced quotes')\n\n const el = name.match(ELEMENT_NAME)\n if (el) {\n if (opensBlock) fail(stmt, `\"${name}\" cannot contain a block`)\n const kind = el[1]!.toLowerCase() as C4ElementKind\n const shape: C4ElementShape =\n el[2] === 'Db' ? 'db' : el[2] === 'Queue' ? 'queue' : 'default'\n const hasTech = kind === 'container' || kind === 'component'\n const alias = claim(stmt, args[0])\n const element: C4Element = {\n alias,\n kind,\n shape,\n external: el[3] !== undefined,\n label: args[1] || alias,\n }\n const technology = hasTech ? args[2] : undefined\n const description = hasTech ? args[3] : args[2]\n if (technology) element.technology = technology\n if (description) element.description = description\n diagram.elements.push(element)\n stack.at(-1)?.elementAliases.push(alias)\n continue\n }\n\n if (BOUNDARY_NAME.test(name) || NODE_NAME.test(name)) {\n const isNode = NODE_NAME.test(name)\n const alias = claim(stmt, args[0])\n const boundary: C4Boundary = {\n alias,\n label: args[1] || alias,\n elementAliases: [],\n children: [],\n }\n // Mermaid labels every frame with a type: the macro's own for the\n // typed boundaries, else the explicit argument, else a default.\n // (System_/Container_/Enterprise_Boundary take no type arg.)\n const typed = BOUNDARY_TYPES[name]\n if (typed) boundary.type = typed\n else if (args[2] && (isNode || name === 'Boundary'))\n boundary.type = args[2]\n else boundary.type = isNode ? 'node' : 'system'\n if (isNode && args[3]) boundary.description = args[3]\n const parent = stack.at(-1)\n if (parent) parent.children.push(boundary)\n else diagram.boundaries.push(boundary)\n if (opensBlock) stack.push(boundary)\n else pendingBoundary = boundary\n continue\n }\n\n const rel = name.match(REL_NAME)\n if (rel) {\n const indexed = name === 'RelIndex'\n const a = indexed ? args.slice(1) : args\n if (!a[0] || !a[1]) {\n fail(stmt, 'a relationship needs \"from\" and \"to\" aliases')\n }\n const r: C4Relationship = {\n from: a[0],\n to: a[1],\n label: a[2] ?? '',\n bidirectional: name === 'BiRel',\n }\n if (a[3]) r.technology = a[3]\n if (name === 'Rel_Back') r.reversed = true\n const hint = REL_HINTS[name]\n if (hint) r.layout = hint\n diagram.relationships.push(r)\n continue\n }\n\n fail(stmt, `unknown C4 macro \"${name}\"`)\n }\n\n if (stack.length > 0) {\n throw new Error(\n `C4 diagram: boundary \"${stack.at(-1)!.alias}\" is missing its closing \"}\"`,\n )\n }\n // Mermaid ignores RelIndex's index argument: in C4Dynamic every relationship\n // is numbered by its position in the source, and no other variant numbers.\n if (diagram.variant === 'dynamic') {\n diagram.relationships.forEach((r, i) => {\n r.index = String(i + 1)\n })\n }\n for (const r of diagram.relationships) {\n for (const end of [r.from, r.to]) {\n if (!aliases.has(end)) {\n throw new Error(\n `C4 diagram: relationship refers to undeclared alias \"${end}\"`,\n )\n }\n }\n }\n return diagram\n}\n","import type { C4Boundary, C4Element, C4Relationship } from './types.ts'\n\n// ============================================================================\n// C4 text and layout-hint helpers shared by the SVG and ASCII renderers, so\n// both draw the same words and honor the same `Rel_U/D/L/R` hints.\n// ============================================================================\n\n/** Greedy word wrap; a word longer than `width` stays on its own line. */\nexport function wrapC4Text(text: string, width: number): string[] {\n const out: string[] = []\n for (const paragraph of text.split('\\n')) {\n let cur = ''\n for (const word of paragraph.split(/\\s+/).filter(Boolean)) {\n if (cur && cur.length + 1 + word.length > width) {\n out.push(cur)\n cur = word\n } else {\n cur = cur ? `${cur} ${word}` : word\n }\n }\n out.push(cur)\n }\n return out\n}\n\nconst STEREOTYPES: Record<C4Element['kind'], string> = {\n person: 'Person',\n system: 'Software System',\n container: 'Container',\n component: 'Component',\n}\n\n/**\n * The bracketed type line under an element's name, as Mermaid draws it:\n * `[Person]`, `[Software System]`, `[Container: PostgreSQL]`. External,\n * database and queue variants share their base kind's line; the fill and the\n * shape tell them apart.\n */\nexport function c4TypeLine(el: C4Element): string {\n const tech =\n (el.kind === 'container' || el.kind === 'component') && el.technology\n ? `: ${el.technology}`\n : ''\n return `[${STEREOTYPES[el.kind]}${tech}]`\n}\n\n/** A boundary's type line (`[ENTERPRISE]`, `[Ubuntu]`), when it has a type. */\nexport function c4BoundaryTypeLine(b: C4Boundary): string | undefined {\n return b.type ? `[${b.type}]` : undefined\n}\n\n/**\n * The text lines of a relationship label: the label itself (prefixed with its\n * sequence number in a C4Dynamic diagram), then the technology in brackets on its\n * own line. Empty when the relationship has neither.\n */\nexport function c4RelLabelLines(rel: C4Relationship): string[] {\n const lines: string[] = []\n const head = [rel.index ? `${rel.index}:` : '', rel.label]\n .filter(Boolean)\n .join(' ')\n if (head) lines.push(head)\n if (rel.technology) lines.push(`[${rel.technology}]`)\n return lines\n}\n\n/**\n * How a relationship constrains placement. `source` is meant to sit before\n * `target` along `axis`: above it for `'vertical'`, left of it for\n * `'horizontal'`. `Rel_U`/`Rel_L` reverse the pair (the target sits above /\n * left of the source); plain `Rel` is a vertical hint, since C4 diagrams flow\n * top to bottom by default.\n */\nexport interface C4Placement {\n source: string\n target: string\n axis: 'vertical' | 'horizontal'\n /** True when the hint came from an explicit `Rel_U/D/L/R`. */\n explicit: boolean\n}\n\nexport function c4Placement(rel: C4Relationship): C4Placement {\n switch (rel.layout) {\n case 'up':\n return {\n source: rel.to,\n target: rel.from,\n axis: 'vertical',\n explicit: true,\n }\n case 'left':\n return {\n source: rel.to,\n target: rel.from,\n axis: 'horizontal',\n explicit: true,\n }\n case 'right':\n return {\n source: rel.from,\n target: rel.to,\n axis: 'horizontal',\n explicit: true,\n }\n case 'down':\n return {\n source: rel.from,\n target: rel.to,\n axis: 'vertical',\n explicit: true,\n }\n default:\n return {\n source: rel.from,\n target: rel.to,\n axis: 'vertical',\n explicit: false,\n }\n }\n}\n","import type {\n ClassDiagram,\n ClassNode,\n ClassRelationship,\n ClassMember,\n RelationshipType,\n ClassNamespace,\n} from './types.ts'\nimport {\n normalizeBrTags,\n applyClickStatement,\n splitClassShorthand,\n tryApplyClassAssignment,\n tryApplyClassDef,\n tryApplyCssClass,\n tryApplyStyleStatement,\n type Statement,\n} from '@zombie-mermaid/core'\n\n// ============================================================================\n// Class diagram parser\n//\n// Parses Mermaid classDiagram syntax into a ClassDiagram structure.\n//\n// Supported syntax:\n// class Animal { +String name; +eat() void }\n// class Shape { <<abstract>> }\n// Animal <|-- Dog (inheritance)\n// Car *-- Engine (composition)\n// Car o-- Wheel (aggregation)\n// A --> B (association)\n// A ..> B (dependency)\n// A ..|> B (realization)\n// A \"1\" --> \"*\" B : label (with cardinality + label)\n// Animal : +String name (inline attribute)\n// namespace MyNamespace { class A { } }\n// click Animal \"https://example.com\" \"Tooltip\" _blank (see docs/decisions/no-script-interactivity.md)\n// classDef name fill:#f96 / style Animal fill:#f96 / cssClass \"A,B\" name\n// class Animal:::name (style-class shorthand, also on relationship ends)\n// note \"text\" / note for Animal \"line1\\nline2\"\n// ============================================================================\n\n/**\n * The set of relationship-arrow tokens `parseRelationship` recognizes,\n * shared with the \"does this line even look like a relationship attempt\"\n * check below so the two can't drift apart. Order matters for regex\n * alternation (longer/more-specific tokens like `--|>` must be tried before\n * the bare `--` fallback).\n */\nconst RELATIONSHIP_ARROW_SOURCE =\n '<\\\\|--|<\\\\|\\\\.\\\\.|\\\\*--|o--|-->|--\\\\*|--o|--\\\\|>|\\\\.\\\\.>|\\\\.\\\\.\\\\|>|<--|<\\\\.\\\\.?|--'\n\n/**\n * Matches a line that *contains* a relationship-arrow token somewhere, even\n * if the overall shape (FROM/TO, cardinalities) doesn't parse. Used to\n * scope the \"malformed relationship\" error (issue #761) to lines that\n * already look like an attempt at this construct — mirroring the same\n * \"attempt, then validate\" pattern #722 used for the ER and xychart-beta\n * parsers, so a genuinely unrelated line (no arrow at all) still falls\n * through silently rather than throwing on totally unrecognized syntax.\n */\nconst RELATIONSHIP_ARROW_MARKER = new RegExp(RELATIONSHIP_ARROW_SOURCE)\n\n/**\n * Parse a Mermaid class diagram.\n * Expects the first line to be \"classDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// either a bounds-checked loop-index array access (`lines[i]!` inside a\n// `for (let i = 1; i < lines.length; ...)` loop) or a regex-mandatory-\n// capture-group access after `.match()` (a group that isn't wrapped in an\n// optional `(?:...)?`, so it always participates when the overall match\n// succeeds). Both are the same idioms already accepted as justified\n// elsewhere in this codebase (see src/parser.ts, PR #158, and this\n// subsystem's earlier layout.ts/renderer.ts audit, PR #147, which fixed\n// the genuinely risky assertions but didn't reach this file) —\n// `noUncheckedIndexedAccess` can't see either guarantee, but removing the\n// `!` would only replace a proven-safe assertion with an unreachable guard.\n// Left as-is; no behavior change.\nexport function parseClassDiagram(lines: Statement[]): ClassDiagram {\n const diagram: ClassDiagram = {\n classes: [],\n relationships: [],\n namespaces: [],\n interactions: new Map(),\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n notes: [],\n }\n\n // Track classes by ID for deduplication\n const classMap = new Map<string, ClassNode>()\n // Track namespace nesting\n let currentNamespace: ClassNamespace | null = null\n // Track class body parsing\n let currentClass: ClassNode | null = null\n let braceDepth = 0\n // Line the currently-open class body started on, for the unclosed-body\n // error below (issue #761) — `currentClass`/`braceDepth` alone don't\n // carry position.\n let openClassLine: number | undefined\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- Inside a class body block ---\n if (currentClass && braceDepth > 0) {\n if (line === '}') {\n braceDepth--\n if (braceDepth === 0) {\n currentClass = null\n openClassLine = undefined\n }\n continue\n }\n\n // Check for annotation like <<interface>>\n const annotMatch = line.match(/^<<(\\w+)>>$/)\n if (annotMatch) {\n currentClass.annotation = annotMatch[1]!\n continue\n }\n\n // A line that starts with `<<` but doesn't match the full\n // `<<name>>` shape is an attempted annotation with broken syntax\n // (e.g. a missing closing `>>`) rather than a member — surface that\n // instead of silently mis-parsing it as a member named \"<<foo\" (#761).\n if (line.startsWith('<<')) {\n throw new Error(\n `Line ${stmt.line}: Malformed class annotation \"${line}\" — expected \"<<name>>\" (e.g. \"<<interface>>\").`,\n )\n }\n\n // Parse member: visibility, name, type, optional parens for method\n addMember(currentClass, line, stmt.line)\n continue\n }\n\n // --- click interaction: `click ClassName \"url\" \"tooltip\" _blank` / `click ClassName call fn()` ---\n if (/^click\\s+/i.test(line)) {\n applyClickStatement(line, diagram.interactions)\n continue\n }\n\n // --- Notes: `note \"text\"` / `note for ClassName \"text\"` ---\n // Mermaid's grammar takes a quoted string (`noteText: STR`) and its\n // renderer splits the text on `\\n` after JSON-parsing it (svgDraw.js\n // drawNote), so a literal `\\n` in the source is a line break;\n // normalizeBrTags handles that and `<br/>`, the form the rest of this\n // repo's labels use. The class need not be declared first (or at all).\n const noteMatch = line.match(/^note\\s+(?:for\\s+(\\S+)\\s+)?\"([^\"]*)\"\\s*$/)\n if (noteMatch) {\n const forClass = noteMatch[1]\n diagram.notes.push({\n text: normalizeBrTags(noteMatch[2]!),\n ...(forClass ? { forClass } : {}),\n })\n continue\n }\n\n // --- Styling: `classDef`, `style`, `cssClass \"A,B\" name`, `class A,B name` ---\n // Shared with the flowchart parser (packages/core/src/style-directives.ts). The\n // `class A,B name` assignment form is checked before the declaration\n // regexes below: a declaration never has a second bare-word token after\n // the class id (`class Animal`, `class Animal~T~`, `class Animal {`), so\n // the two can't collide. Mermaid's own class grammar only spells the\n // attachment as `cssClass \"A,B\" name` / `A:::name`; the flowchart-style\n // `class A,B name` is accepted here for parity with flowcharts.\n if (tryApplyClassDef(line, diagram)) continue\n if (tryApplyStyleStatement(line, diagram)) continue\n if (tryApplyCssClass(line, diagram)) continue\n if (tryApplyClassAssignment(line, diagram)) continue\n\n // --- Namespace block start ---\n const nsMatch = line.match(/^namespace\\s+(\\S+)\\s*\\{$/)\n if (nsMatch) {\n currentNamespace = { name: nsMatch[1]!, classIds: [] }\n continue\n }\n\n // --- Namespace end ---\n if (line === '}' && currentNamespace) {\n diagram.namespaces.push(currentNamespace)\n currentNamespace = null\n continue\n }\n\n // --- Class block start: `class ClassName {` or `class ClassName:::style {` ---\n const classBlockMatch = line.match(/^class\\s+(\\S+?)(?:\\s*~(\\w+)~)?\\s*\\{$/)\n if (classBlockMatch) {\n const id = declareClass(classBlockMatch[1]!, classBlockMatch[2])\n currentClass = classMap.get(id) ?? null\n braceDepth = 1\n openClassLine = stmt.line\n continue\n }\n\n // --- Standalone class declaration (no body): `class ClassName` / `class ClassName:::style` ---\n const classOnlyMatch = line.match(/^class\\s+(\\S+?)(?:\\s*~(\\w+)~)?\\s*$/)\n if (classOnlyMatch) {\n declareClass(classOnlyMatch[1]!, classOnlyMatch[2])\n continue\n }\n\n // --- Single-line body: `class ClassName { <<interface>> }` / `class ClassName:::style { -int size }` ---\n // One annotation or one member; `;`-separated members on a single line\n // are already split into separate statements upstream, so a multi-member\n // body can't reach here intact.\n const inlineBodyMatch = line.match(/^class\\s+(\\S+?)\\s*\\{\\s*(.*?)\\s*\\}$/)\n if (inlineBodyMatch) {\n const id = declareClass(inlineBodyMatch[1]!, undefined)\n const cls = classMap.get(id)\n const body = inlineBodyMatch[2]!\n const annotMatch = body.match(/^<<(\\w+)>>$/)\n if (cls && annotMatch) {\n cls.annotation = annotMatch[1]!\n } else if (cls && body.startsWith('<<')) {\n // Same broken-annotation-attempt case as the multi-line body\n // branch above, just on the single-line form (#761).\n throw new Error(\n `Line ${stmt.line}: Malformed class annotation \"${body}\" — expected \"<<name>>\" (e.g. \"<<interface>>\").`,\n )\n } else if (cls && body) {\n addMember(cls, body, stmt.line)\n }\n continue\n }\n\n // --- Inline attribute: `ClassName : +String name` ---\n const inlineAttrMatch = line.match(/^(\\S+?)\\s*:\\s*(.+)$/)\n if (inlineAttrMatch) {\n // Make sure this isn't a relationship line (those have arrows)\n const rest = inlineAttrMatch[2]!\n if (!rest.match(/<\\|--|--|\\*--|o--|-->|\\.\\.>|\\.\\.\\|>/)) {\n addMember(ensureClass(classMap, inlineAttrMatch[1]!), rest, stmt.line)\n continue\n }\n }\n\n // --- Relationship ---\n // Pattern: [FROM] [\"card\"] ARROW [\"card\"] [TO] [: label]\n // Arrows: <|--, *--, o--, -->, ..|>, ..>\n // Can also be reversed: --o, --*, --|>\n const rel = parseRelationship(line)\n if (rel) {\n // Ensure both classes exist. Either end may carry the `:::style`\n // shorthand (Mermaid's docs warn against combining it with a relation\n // statement, but it's better to strip it than to mint a class whose id\n // is the literal `Animal:::someclass`).\n rel.from = declareClass(rel.from, undefined, false)\n rel.to = declareClass(rel.to, undefined, false)\n diagram.relationships.push(rel)\n continue\n }\n\n // A line containing a relationship-arrow token that still didn't parse\n // above is a malformed/dangling relationship attempt — e.g. \"Animal\n // <|--\" with no target, or an unsupported arrow shape — rather than an\n // unrelated line that merely falls through. Scoped to lines that\n // already look like an attempt (same \"attempt, then validate\" pattern\n // #722 used for ER/xychart-beta) so genuinely unrecognized syntax with\n // no arrow at all keeps falling through silently (#761).\n if (RELATIONSHIP_ARROW_MARKER.test(line)) {\n throw new Error(\n `Line ${stmt.line}: Malformed class-diagram relationship \"${line}\". Expected \"FROM ARROW TO\" (optionally with cardinalities and a \": label\"), e.g. \"Animal <|-- Dog\" or 'A \"1\" --> \"*\" B : label'. ARROW must be one of <|--, <|.., *--, o--, -->, --*, --o, --|>, ..>, ..|>, <--, <.., or --.`,\n )\n }\n }\n\n if (currentClass !== null) {\n throw new Error(\n `Line ${openClassLine}: Unclosed class body for \"${currentClass.id}\" — expected a closing \"}\" before the diagram ends.`,\n )\n }\n\n diagram.classes = [...classMap.values()]\n return diagram\n\n /**\n * Register a class from a declaration token, splitting off any `:::style`\n * shorthand into `diagram.classAssignments`. Returns the bare id.\n *\n * @param generic - `~T~` generic parameter captured by the declaration regex\n * @param inNamespace - whether to record the class in the open namespace\n * (declarations do; relationship endpoints don't)\n */\n function declareClass(\n token: string,\n generic: string | undefined,\n inNamespace: boolean = true,\n ): string {\n const { id: rawId, className } = splitClassShorthand(token)\n let id = rawId\n let resolvedGeneric = generic\n if (!resolvedGeneric) {\n // The declaration regexes only split a `~T~` generic off the bare id\n // when nothing else trails it. When the `:::style` shorthand trails\n // the generic instead (`Foo~T~:::hot`), the generic gets swallowed\n // into the shorthand-stripped id here (`splitClassShorthand` returns\n // `Foo~T~`) — strip it off now so the class is declared as `Foo`\n // rather than the literal `Foo~T~` (#506).\n const genericMatch = rawId.match(/^(.+?)~(\\w+)~$/)\n if (genericMatch) {\n id = genericMatch[1]!\n resolvedGeneric = genericMatch[2]!\n }\n }\n const cls = ensureClass(classMap, id)\n if (resolvedGeneric) {\n cls.label = `${id}<${resolvedGeneric}>`\n }\n if (className) {\n diagram.classAssignments.set(id, className)\n }\n if (inNamespace && currentNamespace) {\n currentNamespace.classIds.push(id)\n }\n return id\n }\n}\n\n/** Parse one member line and file it under the class's attributes or methods. */\nfunction addMember(cls: ClassNode, line: string, lineNumber: number): void {\n const member = parseMember(line, lineNumber)\n if (!member) return\n if (member.isMethod) {\n cls.methods.push(member.member)\n } else {\n cls.attributes.push(member.member)\n }\n}\n\n/** Ensure a class exists in the map, creating a default if needed */\nfunction ensureClass(classMap: Map<string, ClassNode>, id: string): ClassNode {\n let cls = classMap.get(id)\n if (!cls) {\n cls = { id, label: id, attributes: [], methods: [] }\n classMap.set(id, cls)\n }\n return cls\n}\n\n/** Count occurrences of `needle` in `input`. */\nfunction countOccurrence(input: string, needle: string): number {\n return Math.max(0, input.split(needle).length - 1)\n}\n\n/**\n * Convert one comma-free (or already re-joined) segment's `~`-delimited\n * generics to angle brackets, pairing the outermost `~`s first so nested\n * generics like `List~List~T~~` become `List<List<T>>`. A segment with a\n * single `~` is left alone — there's nothing to pair it with — and an odd\n * count with a leading `~` keeps that leading one as-is (mermaid treats it\n * as literal text, not a delimiter). Mirrors mermaid's `processSet`.\n */\nfunction convertGenericSegment(input: string): string {\n const tildeCount = countOccurrence(input, '~')\n if (tildeCount <= 1) return input\n\n let text = input\n let keepLeadingTilde = false\n if (tildeCount % 2 !== 0 && text.startsWith('~')) {\n text = text.slice(1)\n keepLeadingTilde = true\n }\n\n const chars = [...text]\n let open = chars.indexOf('~')\n let close = chars.lastIndexOf('~')\n while (open !== -1 && close !== -1 && open !== close) {\n chars[open] = '<'\n chars[close] = '>'\n open = chars.indexOf('~')\n close = chars.lastIndexOf('~')\n }\n if (keepLeadingTilde) chars.unshift('~')\n return chars.join('')\n}\n\n/**\n * Convert mermaid's `~T~` generic syntax to the `<T>` form mermaid itself\n * renders — `List~Observer~` → `List<Observer>`, `Map~K,V~` → `Map<K,V>`,\n * `List~List~T~~` → `List<List<T>>`.\n *\n * Port of mermaid's `parseGenericTypes` (packages/mermaid/src/diagrams/\n * common/common.ts): the text is split on commas so a comma *inside* a\n * generic (`Map~K,V~`) can be re-joined with its neighbors — two adjacent\n * comma-separated pieces that each carry exactly one `~` are the two halves\n * of one generic — before each piece's `~` pairs are converted outermost-first.\n */\nexport function parseGenericTypes(input: string): string {\n const pieces = input.split(/(,)/)\n const output: string[] = []\n for (let i = 0; i < pieces.length; i++) {\n let piece = pieces[i]!\n if (piece === ',' && i > 0 && i + 1 < pieces.length) {\n const previous = pieces[i - 1]!\n const next = pieces[i + 1]!\n if (\n countOccurrence(previous, '~') === 1 &&\n countOccurrence(next, '~') === 1\n ) {\n piece = `${previous},${next}`\n i++\n output.pop()\n }\n }\n output.push(convertGenericSegment(piece))\n }\n return output.join('')\n}\n\n/** Parse a class member line (attribute or method) */\nfunction parseMember(\n line: string,\n lineNumber: number,\n): { member: ClassMember; isMethod: boolean } | null {\n const trimmed = line.trim().replace(/;$/, '')\n if (!trimmed) return null\n\n // Extract visibility prefix\n let visibility: ClassMember['visibility'] = ''\n let rest = trimmed\n const visibilityChar = rest[0]\n if (\n visibilityChar === '+' ||\n visibilityChar === '-' ||\n visibilityChar === '#' ||\n visibilityChar === '~'\n ) {\n visibility = visibilityChar\n rest = rest.slice(1).trim()\n }\n\n // An attempted method signature with an opening \"(\" but no closing \")\"\n // at all can't be a genuine attribute either (an attribute name/type\n // never legitimately contains a bare, unclosed paren) — surface it\n // instead of silently parsing \"eat(void\" as a member literally named\n // that (#761).\n if (rest.includes('(') && !rest.includes(')')) {\n throw new Error(\n `Line ${lineNumber}: Malformed class member \"${trimmed}\" — unclosed \"(\" in a method signature. Expected e.g. \"+eat() void\" or \"+eat(Food f) void\".`,\n )\n }\n\n // Mermaid renders `List~Observer~` as `List<Observer>` (its own\n // `parseGenericTypes`). It's applied per field below — name, params and\n // return type separately, the way mermaid's ClassMember does — rather\n // than to the whole line, since the comma re-join inside\n // parseGenericTypes would otherwise pair a `~` in the params with one in\n // the return type. Doing it after the visibility prefix is stripped also\n // keeps a package-visibility `~` from pairing with a generic's.\n\n // Check if it's a method (has parentheses)\n const methodMatch = rest.match(/^(.+?)\\(([^)]*)\\)(?:\\s*(.+))?$/)\n if (methodMatch) {\n const name = parseGenericTypes(methodMatch[1]!.trim())\n const rawParams = methodMatch[2]?.trim()\n const params = rawParams ? parseGenericTypes(rawParams) : undefined // Store the parameter string\n const rawType = methodMatch[3]?.trim()\n const type = rawType ? parseGenericTypes(rawType) : undefined\n // Check for static ($) or abstract (*) markers\n const isStatic = name.endsWith('$') || rest.includes('$')\n const isAbstract = name.endsWith('*') || rest.includes('*')\n return {\n member: {\n visibility,\n name: name.replace(/[$*]$/, ''),\n type: type || undefined,\n isStatic,\n isAbstract,\n isMethod: true,\n params,\n },\n isMethod: true,\n }\n }\n\n // It's an attribute. Mermaid renders attribute text verbatim (its\n // ClassMember keeps everything after the visibility marker as one id and\n // only splits methods into name/params/return type), so `String name`,\n // `name Type` and `name: Type` all display as written. Splitting two\n // tokens into Type + name guessed the order and flipped `-data Map` into\n // `- Map: data` (#1265).\n const name = parseGenericTypes(rest)\n const type: string | undefined = undefined\n\n const isStatic = name.endsWith('$')\n const isAbstract = name.endsWith('*')\n\n return {\n member: {\n visibility,\n name: name.replace(/[$*]$/, ''),\n type: type || undefined,\n isStatic,\n isAbstract,\n isMethod: false,\n },\n isMethod: false,\n }\n}\n\n/** Parse a relationship line into a ClassRelationship */\nfunction parseRelationship(line: string): ClassRelationship | null {\n // Relationship regex — handles all arrow types with optional cardinality and labels\n // Pattern: FROM [\"card\"] ARROW [\"card\"] TO[:::style] [: label]\n // The `:::style` shorthand on TO is matched as its own group so the `:`\n // label separator that follows can't swallow it as a label of `::style`;\n // it's re-joined onto the id here and split off again by the caller.\n const match = line.match(\n new RegExp(\n `^(\\\\S+?)\\\\s+(?:\"([^\"]*?)\"\\\\s+)?(${RELATIONSHIP_ARROW_SOURCE})\\\\s+(?:\"([^\"]*?)\"\\\\s+)?(\\\\S+?)(?::::([\\\\w][\\\\w-]*))?(?:\\\\s*:\\\\s*(.+))?$`,\n ),\n )\n if (!match) return null\n\n const from = match[1]!\n const rawFromCardinality = match[2]\n const fromCardinality = rawFromCardinality\n ? normalizeBrTags(rawFromCardinality)\n : undefined\n const arrow = match[3]!.trim()\n const rawToCardinality = match[4]\n const toCardinality = rawToCardinality\n ? normalizeBrTags(rawToCardinality)\n : undefined\n const to = match[6] ? `${match[5]!}:::${match[6]}` : match[5]!\n const rawLabel = match[7]?.trim()\n const label = rawLabel ? normalizeBrTags(rawLabel) : undefined\n\n const parsed = parseArrow(arrow)\n if (!parsed) return null\n\n return {\n from,\n to,\n type: parsed.type,\n markerAt: parsed.markerAt,\n label,\n fromCardinality,\n toCardinality,\n }\n}\n\n/**\n * Map arrow syntax to relationship type and marker placement side.\n * Prefix markers (`<|--`, `*--`, `o--`) place the UML shape at the 'from' end.\n * Suffix markers (`..|>`, `-->`, `..>`, `--*`, `--o`) place it at the 'to' end.\n */\nfunction parseArrow(\n arrow: string,\n): { type: RelationshipType; markerAt: 'from' | 'to' } | null {\n // Trim whitespace that might be captured by the regex\n const a = arrow.trim()\n switch (a) {\n case '<|--':\n return { type: 'inheritance', markerAt: 'from' }\n case '--|>':\n return { type: 'inheritance', markerAt: 'to' }\n case '<|..':\n return { type: 'realization', markerAt: 'from' }\n case '..|>':\n return { type: 'realization', markerAt: 'to' }\n case '*--':\n return { type: 'composition', markerAt: 'from' }\n case '--*':\n return { type: 'composition', markerAt: 'to' }\n case 'o--':\n return { type: 'aggregation', markerAt: 'from' }\n case '--o':\n return { type: 'aggregation', markerAt: 'to' }\n case '-->':\n return { type: 'association', markerAt: 'to' }\n case '<--':\n return { type: 'association', markerAt: 'from' }\n case '..>':\n return { type: 'dependency', markerAt: 'to' }\n case '<..':\n return { type: 'dependency', markerAt: 'from' }\n case '--':\n return { type: 'association', markerAt: 'to' }\n default:\n return null\n }\n}\n","// ============================================================================\n// Class member formatting — shared between the SVG (layout.ts) and ASCII\n// (ascii/class-diagram.ts) renderers so method parentheses/params can't drift\n// between the two again. See issue #290.\n// ============================================================================\n\nimport type { ClassMember } from './types.ts'\n\n/** Format a class member as a display string: visibility + name(+params for methods) + optional type */\nexport function formatClassMember(m: ClassMember): string {\n const vis = m.visibility ? `${m.visibility} ` : ''\n const name = m.isMethod ? `${m.name}(${m.params || ''})` : m.name\n const type = m.type ? `: ${m.type}` : ''\n return `${vis}${name}${type}`\n}\n","import type {\n ErDiagram,\n ErEntity,\n ErAttribute,\n ErRelationship,\n Cardinality,\n} from './types.ts'\nimport {\n normalizeBrTags,\n toDirection,\n type Statement,\n} from '@zombie-mermaid/core'\n\n// ============================================================================\n// ER diagram parser\n//\n// Parses Mermaid erDiagram syntax into an ErDiagram structure.\n//\n// Supported syntax:\n// CUSTOMER ||--o{ ORDER : places\n// CUSTOMER {\n// string name PK\n// int age\n// string email UK \"user email\"\n// }\n//\n// Cardinality notation:\n// || || exactly one\n// |o o| zero or one\n// }| |{ one or more\n// }o o{ zero or more\n//\n// Line style:\n// -- identifying (solid line)\n// .. non-identifying (dashed line)\n// ============================================================================\n\n/**\n * Parse a Mermaid ER diagram.\n * Expects the first line to be \"erDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// either a bounds-checked loop-index array access (`lines[i]!` inside a\n// `for (let i = 1; i < lines.length; ...)` loop) or a regex-mandatory-\n// capture-group access after `.match()` (a group that isn't wrapped in an\n// optional `(?:...)?`, so it always participates when the overall match\n// succeeds). Both are the same idioms already accepted as justified\n// elsewhere in this codebase (see src/parser.ts, PR #158, and this\n// subsystem's layout.ts/renderer.ts audit, PR #148, which fixed the\n// genuinely risky assertions there but didn't reach this file — PR #146\n// separately reviewed this file's `as` casts, which are a different\n// concern from these `!`s) — `noUncheckedIndexedAccess` can't see either\n// guarantee, but removing the `!` would only replace a proven-safe\n// assertion with an unreachable guard. Left as-is; no behavior change.\nexport function parseErDiagram(lines: Statement[]): ErDiagram {\n const diagram: ErDiagram = {\n entities: [],\n relationships: [],\n }\n\n // Track entities by ID for deduplication\n const entityMap = new Map<string, ErEntity>()\n // Track entity body parsing\n let currentEntity: ErEntity | null = null\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- Inside entity body ---\n if (currentEntity) {\n if (line === '}') {\n currentEntity = null\n continue\n }\n\n // Attribute line: type name [PK|FK|UK] [\"comment\"]\n const attr = parseAttribute(line)\n if (attr) {\n currentEntity.attributes.push(attr)\n }\n continue\n }\n\n // --- direction directive: `direction TB` / `direction LR` / etc. ---\n // ER diagrams have no subgraph nesting, so a single top-level direction\n // applies to the whole diagram (unlike flowcharts, where `direction` can\n // also appear per-subgraph).\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch) {\n diagram.direction = toDirection(dirMatch[1]!)\n continue\n }\n\n // --- Entity block start: `ENTITY_NAME {`, `id[Alias Label] {`, or\n // `id[\"Quoted Alias\"] {` — optionally with the body (and even the\n // closing `}`) inline on the same line, e.g. `p[Person] { string name }`.\n const entityBlockMatch = line.match(/^(\\S+?)(?:\\[(.+)\\])?\\s*\\{(.*)$/)\n if (entityBlockMatch) {\n const id = entityBlockMatch[1]!\n const aliasRaw = entityBlockMatch[2]\n const alias =\n aliasRaw !== undefined\n ? normalizeBrTags(aliasRaw.trim().replace(/^[\"']|[\"']$/g, ''))\n : undefined\n const entity = ensureEntity(entityMap, id, alias)\n\n const trailing = entityBlockMatch[3]!.trim()\n const closesInline = trailing.endsWith('}')\n const body = closesInline ? trailing.slice(0, -1).trim() : trailing\n for (const part of body\n .split(';')\n .map((s) => s.trim())\n .filter(Boolean)) {\n const attr = parseAttribute(part)\n if (attr) entity.attributes.push(attr)\n }\n\n // Only keep the block open for subsequent attribute lines when this\n // line didn't already close it.\n if (!closesInline) {\n currentEntity = entity\n }\n continue\n }\n\n // --- Relationship: `ENTITY1 cardinality1--cardinality2 ENTITY2 : label` ---\n const rel = parseRelationshipLine(line, stmt.line)\n if (rel) {\n // Ensure both entities exist\n ensureEntity(entityMap, rel.entity1)\n ensureEntity(entityMap, rel.entity2)\n diagram.relationships.push(rel)\n continue\n }\n }\n\n diagram.entities = [...entityMap.values()]\n return diagram\n}\n\n/**\n * Ensure an entity exists in the map. When `alias` is provided (from an\n * `id[Alias]` entity-block header), it becomes the display label while the\n * map key — and every relationship reference — stays keyed off the raw `id`.\n */\nfunction ensureEntity(\n entityMap: Map<string, ErEntity>,\n id: string,\n alias?: string,\n): ErEntity {\n let entity = entityMap.get(id)\n if (!entity) {\n entity = { id, label: alias ?? id, attributes: [] }\n entityMap.set(id, entity)\n } else if (alias !== undefined) {\n entity.label = alias\n }\n return entity\n}\n\n/** Parse an attribute line inside an entity block */\nfunction parseAttribute(line: string): ErAttribute | null {\n // Format: type name [PK|FK|UK [...]] [\"comment\"]\n const match = line.match(/^(\\S+)\\s+(\\S+)(?:\\s+(.+))?$/)\n if (!match) return null\n\n const type = match[1]!\n const name = match[2]!\n const rest = match[3]?.trim() ?? ''\n\n // Extract key constraints (PK, FK, UK) and optional comment\n const keys: ErAttribute['keys'] = []\n let comment: string | undefined\n\n // Extract quoted comment first (supports <br> tags)\n const commentMatch = rest.match(/\"([^\"]*)\"/)\n if (commentMatch) {\n comment = normalizeBrTags(commentMatch[1]!)\n }\n\n // Extract key constraints\n const restWithoutComment = rest.replace(/\"[^\"]*\"/, '').trim()\n for (const part of restWithoutComment.split(/\\s+/)) {\n const upper = part.toUpperCase()\n if (upper === 'PK' || upper === 'FK' || upper === 'UK') {\n keys.push(upper)\n }\n }\n\n return { type, name, keys, comment }\n}\n\n/**\n * Parse a relationship line.\n *\n * Cardinality symbols on each side of the line style:\n * Left side (entity1): || |o }| }o\n * Line: -- (identifying) or .. (non-identifying)\n * Right side (entity2): || o| |{ o{\n *\n * Full pattern example: CUSTOMER ||--o{ ORDER : places\n */\nfunction parseRelationshipLine(\n line: string,\n lineNumber: number,\n): ErRelationship | null {\n // Loosely match a line shaped like a relationship attempt: two bare\n // tokens flanking a `--`/`..`-based marker, with an optional `: label`.\n // A line with no such marker at all doesn't look like a relationship\n // attempt and falls through silently (returns null), same as before —\n // that part of the parser's leniency is unchanged.\n //\n // Everything that *does* match this shape is validated below and either\n // returns a fully-parsed relationship or throws an actionable error.\n // Before this pass, an invalid cardinality token or a missing/empty\n // label silently dropped the *entire* line (both entities included)\n // with zero indication anything was wrong — see issue #541 (parser\n // error-message quality audit — this parser had zero throw sites before\n // this pass).\n const attempt = line.match(\n /^(\\S+)\\s+(\\S*(?:--|\\.\\.)\\S*)\\s+(\\S+)\\s*(?::\\s*(.*))?$/,\n )\n if (!attempt) return null\n\n const entity1 = attempt[1]!\n const cardinalityStr = attempt[2]!\n const entity2 = attempt[3]!\n const hasLabel = attempt[4] !== undefined\n const rawLabel = attempt[4]?.trim() ?? ''\n\n // Cardinality symbols on each side of the line style:\n // Left side (entity1): || |o }| }o\n // Line: -- (identifying) or .. (non-identifying)\n // Right side (entity2): || o| |{ o{\n // `shapeMatch` only confirms the token is built from crow's-foot\n // characters around a line-style marker — `parseLeftCardinality`/\n // `parseRightCardinality` still reject a shape match that isn't one of\n // the four valid two-character combinations per side (e.g. a lone \"|\").\n const shapeMatch = cardinalityStr.match(/^([|o}{]*)(--|\\.\\.?)([|o}{]*)$/)\n const cardinality1 = shapeMatch ? parseLeftCardinality(shapeMatch[1]!) : null\n const cardinality2 = shapeMatch ? parseRightCardinality(shapeMatch[3]!) : null\n\n if (!cardinality1 || !cardinality2) {\n throw new Error(\n `Line ${lineNumber}: Invalid ER relationship cardinality \"${cardinalityStr}\" in \"${line}\". ` +\n 'Left side must be one of ||, |o, }|, }o; right side must be one of ||, o|, |{, o{ (e.g. \"||--o{\").',\n )\n }\n\n if (!hasLabel || rawLabel.length === 0) {\n throw new Error(\n `Line ${lineNumber}: ER relationship \"${entity1} ${cardinalityStr} ${entity2}\" is missing a \": label\" — expected e.g. \"${entity1} ${cardinalityStr} ${entity2} : label\".`,\n )\n }\n\n // Strip surrounding quotes if present, then normalize br tags.\n const label = normalizeBrTags(rawLabel.replace(/^[\"']|[\"']$/g, ''))\n const identifying = shapeMatch![2] === '--'\n\n return { entity1, entity2, cardinality1, cardinality2, label, identifying }\n}\n\n/**\n * Parse a left-side (entity1) cardinality notation string.\n * The crow's-foot character sits nearer the entity, so left- and\n * right-side notations are mirror images of each other and must be\n * matched exactly rather than order-normalized (sorting `}o` and `o{`\n * to the same key conflates \"zero or more\" with malformed input).\n */\nfunction parseLeftCardinality(str: string): Cardinality | null {\n if (str === '||') return 'one'\n if (str === '|o') return 'zero-one'\n if (str === '}|') return 'many'\n if (str === '}o') return 'zero-many'\n return null\n}\n\n/** Parse a right-side (entity2) cardinality notation string. */\nfunction parseRightCardinality(str: string): Cardinality | null {\n if (str === '||') return 'one'\n if (str === 'o|') return 'zero-one'\n if (str === '|{') return 'many'\n if (str === 'o{') return 'zero-many'\n return null\n}\n","// ============================================================================\n// `box <color?> <label?>` header parsing\n//\n// Mermaid's sequenceDb.parseBoxData takes the first word of the header (or a\n// leading `rgb(...)`/`hsl(...)` call), asks the browser whether it is a valid\n// CSS colour, and — if it isn't — treats the whole header as the label. There\n// is no browser here, so \"is a colour\" is decided by a fixed list of CSS\n// named colours plus the hex / rgb() / hsl() function forms. That list is\n// also what keeps a diagram-supplied string safe to emit inside an SVG\n// attribute: anything not matching it never reaches the renderer as a colour.\n// ============================================================================\n\n/** The CSS Color Module Level 4 named colours (plus `transparent`, handled separately). */\nconst CSS_NAMED_COLORS = new Set(\n (\n 'aliceblue antiquewhite aqua aquamarine azure beige bisque black ' +\n 'blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse ' +\n 'chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan ' +\n 'darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta ' +\n 'darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen ' +\n 'darkslateblue darkslategray darkslategrey darkturquoise darkviolet ' +\n 'deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite ' +\n 'forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green ' +\n 'greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender ' +\n 'lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan ' +\n 'lightgoldenrodyellow lightgray lightgreen lightgrey lightpink ' +\n 'lightsalmon lightseagreen lightskyblue lightslategray lightslategrey ' +\n 'lightsteelblue lightyellow lime limegreen linen magenta maroon ' +\n 'mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen ' +\n 'mediumslateblue mediumspringgreen mediumturquoise mediumvioletred ' +\n 'midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive ' +\n 'olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise ' +\n 'palevioletred papayawhip peachpuff peru pink plum powderblue purple ' +\n 'rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown ' +\n 'seagreen seashell sienna silver skyblue slateblue slategray slategrey ' +\n 'snow springgreen steelblue tan teal thistle tomato turquoise violet ' +\n 'wheat white whitesmoke yellow yellowgreen'\n ).split(' '),\n)\n\n// `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`. Mermaid itself can't accept hex\n// here (upstream's lexer treats `#` as a comment); this parser has no such\n// comment syntax, so hex is accepted as a strict superset.\nconst HEX_COLOR_RE = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i\n// `rgb(33,66,99)`, `rgba(33,66,99,0.5)`, `hsl(10, 40%, 90%)`, `hsla(...)`,\n// including the modern space-separated / slash-alpha forms. The argument\n// character class is the safety boundary: digits, `.`, `%`, `,`, `/`, and\n// whitespace only.\nconst FUNCTION_COLOR_RE =\n /^(?:rgba?|hsla?)\\(\\s*[\\d.%]+(?:[\\s,/]+[\\d.%]+){2,3}\\s*\\)$/i\n\n/** Whether `value` is a colour this renderer will emit as-is (case-insensitive). */\nexport function isCssColor(value: string): boolean {\n return (\n CSS_NAMED_COLORS.has(value.toLowerCase()) ||\n HEX_COLOR_RE.test(value) ||\n FUNCTION_COLOR_RE.test(value)\n )\n}\n\n/**\n * Split the text after the `box` keyword into a colour and a label, per\n * Mermaid's `parseBoxData`: the leading word (or function call) is the\n * colour if it is one, otherwise the whole header is the label. An explicit\n * `transparent` is consumed as \"no colour\" so `box transparent Aqua` yields\n * a colourless box labelled `Aqua`, exactly as upstream documents.\n */\nexport function parseBoxHeader(header: string): {\n color?: string\n label: string\n} {\n const trimmed = header.trim()\n const match = trimmed.match(\n /^((?:rgba?|hsla?)\\s*\\([^)]*\\)|#[0-9a-fA-F]+|\\S+)?(.*)$/,\n )\n const candidate = match?.[1] ?? ''\n const rest = match?.[2]?.trim() ?? ''\n if (candidate.toLowerCase() === 'transparent') {\n return { label: rest }\n }\n if (candidate !== '' && isCssColor(candidate)) {\n return { color: candidate, label: rest }\n }\n return { label: trimmed }\n}\n","import type { SequenceDiagram, Message, Block, Actor } from './types.ts'\nimport { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport { parseBoxHeader } from './box-color.ts'\n\n/**\n * Which `box … end` group (index into `diagram.boxes`) is currently open,\n * and which box each participant already belongs to. Threaded through the\n * actor-creating helpers so a participant first mentioned inside a box —\n * by a declaration, a `create`, or (leniently) a message — joins it, the\n * way Mermaid's `addActor` assigns `currentBox`.\n */\ninterface BoxContext {\n open: number | undefined\n membership: Map<string, number>\n}\n\n/**\n * Narrow a regex-captured block keyword to `Block['type']`. The capturing\n * regex at the call site uses the same `(loop|alt|opt|par|critical|break|\n * rect)` alternation, so the input is always one of these seven values in\n * practice — but the match itself is typed as `string`.\n *\n * Exported for direct unit testing (see\n * src/__tests__/sequence-parser.test.ts) — not otherwise part of this\n * module's public parsing API.\n */\nexport function isBlockType(value: string): value is Block['type'] {\n return (\n value === 'loop' ||\n value === 'alt' ||\n value === 'opt' ||\n value === 'par' ||\n value === 'critical' ||\n value === 'break' ||\n value === 'rect'\n )\n}\n\n/**\n * Narrow a regex-captured block keyword to `Block['type']`, throwing if it\n * somehow isn't one of the seven recognized keywords (see `isBlockType`\n * above — unreachable via the guarding regex in practice, but this keeps\n * the failure explicit rather than silently mistyping the value).\n *\n * Exported for direct unit testing (see\n * src/__tests__/sequence-parser.test.ts) — the throw branch is unreachable\n * through the public `parseSequenceDiagram` API (the regex that captures\n * the value already restricts it to the seven valid keywords), so it can\n * only be exercised by calling this function directly.\n */\nexport function toBlockType(value: string): Block['type'] {\n if (!isBlockType(value)) {\n throw new Error(`Invalid block type: \"${value}\"`)\n }\n return value\n}\n\n// ============================================================================\n// Sequence diagram parser\n//\n// Parses Mermaid sequenceDiagram syntax into a SequenceDiagram structure.\n//\n// Supported syntax:\n// participant A as Alice\n// actor B as Bob\n// A->>B: Solid arrow\n// A-->>B: Dashed arrow\n// A-)B: Open arrow\n// A--)B: Dashed open arrow\n// A->>+B: Activate target\n// A-->>-B: Deactivate source\n// activate A / deactivate A (standalone form of the +/- shorthand)\n// create participant C [as Label] / create actor C [as Label]\n// (on the line before C's first message)\n// destroy C (on the line before C's last message)\n// box <color?> <label?> ... end (participant grouping; see box-color.ts)\n// A<<->>B: Bidirectional solid arrow\n// A<<-->>B: Bidirectional dashed arrow\n// autonumber / autonumber <start> <step> / autonumber off\n// loop Label ... end\n// alt Label ... else Label ... end\n// opt Label ... end\n// par Label ... and Label ... end\n// Note left of A: Text\n// Note right of A: Text\n// Note over A,B: Text\n// ============================================================================\n\n// Message-line arrow regexes, shared by the two-pass match in the \"Message\"\n// branch of the parsing loop below (see the comment there for why there are\n// two, and issue #341 for the mis-split bug this two-pass approach fixes).\n// `MESSAGE_LONG_ARROW_RE` only recognizes the \"long\" arrow forms — anything\n// ending in `>` (`->`, `-->`, `->>`, `-->>`) plus the bidirectional tokens\n// (`<<->>`, `<<-->>`) — which essentially never occur by accident inside an\n// unquoted actor name. `MESSAGE_ANY_ARROW_RE` is the original, full\n// alternation (also matching the short open/cross forms `-)`, `--)`, `-x`,\n// `--x`), used as a fallback when a line has no long arrow at all.\nconst MESSAGE_LONG_ARROW_RE =\n /^(.+?)\\s*(<<->>|<<-->>|--?>?>)\\s*([+-]?)(.+?)\\s*:\\s*(.+)$/\nconst MESSAGE_ANY_ARROW_RE =\n /^(.+?)\\s*(<<->>|<<-->>|--?>?>|--?[)x]|--?>>|--?>)\\s*([+-]?)(.+?)\\s*:\\s*(.+)$/\n\n/**\n * Parse a Mermaid sequence diagram.\n * Expects the first line to be \"sequenceDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// one of three idioms already accepted as justified elsewhere in this\n// codebase — (1) a bounds-checked loop-index array access (`lines[i]!`\n// inside a `for (let i = 1; i < lines.length; ...)` loop), (2) a\n// regex-mandatory-capture-group access after `.match()` (a group not\n// wrapped in an optional `(?:...)?`, so it always participates when the\n// overall match succeeds), or (3) `blockStack[blockStack.length - 1]!` /\n// `blockStack.pop()!`, both guarded immediately above by an explicit\n// `blockStack.length > 0` check. See src/parser.ts (PR #158) and this\n// subsystem's layout.ts audit (PR #149), which fixed the one genuinely\n// risky assertion in the subsystem (a Map get/set race) but didn't reach\n// this file — PR #146 separately reviewed this file's `as` casts, a\n// different concern from these `!`s. `noUncheckedIndexedAccess` can't see\n// any of these guarantees, but removing the `!` would only replace a\n// proven-safe assertion with an unreachable guard. Left as-is; no\n// behavior change.\nexport function parseSequenceDiagram(lines: Statement[]): SequenceDiagram {\n const diagram: SequenceDiagram = {\n actors: [],\n messages: [],\n blocks: [],\n notes: [],\n activations: [],\n boxes: [],\n }\n\n const boxCtx: BoxContext = { open: undefined, membership: new Map() }\n // Block-stack depth at the moment the open box began. Mermaid's grammar\n // only allows participant declarations inside a box, so `end` there can\n // only mean the box — but this parser is lenient about other statements,\n // and a `loop` opened inside a box must still own the next `end`.\n let boxOpenedAtDepth = 0\n // Line the currently-open box started on, for the unclosed-box error at\n // EOF below (issue #762) — `boxCtx.open` alone doesn't carry position.\n let boxOpenLine: number | undefined\n\n // Track actor IDs to auto-create actors referenced in messages\n const actorIds = new Set<string>()\n // Track block nesting with a stack\n const blockStack: Array<{\n type: Block['type']\n label: string\n startIndex: number\n dividers: Block['dividers']\n line: number\n }> = []\n\n // `autonumber` state — a bare `autonumber` turns numbering on starting at 1\n // (step 1); `autonumber <start> <step>` sets both explicitly; `autonumber\n // off` turns it back off. Only messages consume a number — notes and\n // block/divider lines don't advance the counter.\n const autonumber = { enabled: false, next: 1, step: 1 }\n\n // `create participant X` / `destroy X` bind to the very next message —\n // Mermaid's sequenceDb enforces that the next message's recipient is the\n // created participant, and that the destroyed one is its sender or\n // recipient, throwing otherwise. Mirrored here (same error text) rather\n // than guessing which later message was meant. A directive with no\n // message after it at all is left as a plain declaration.\n let pendingCreate: string | undefined\n let pendingDestroy: string | undefined\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- box <color?> <label?> ---\n // Opens a participant group; closed by `end`. Boxes cannot nest\n // (Mermaid's grammar rejects it), so a second `box` while one is open\n // is an error rather than an implicit close.\n const boxMatch = line.match(/^box(?:\\s+(.*))?$/)\n if (boxMatch) {\n if (boxCtx.open !== undefined) {\n throw new Error(\n `Line ${stmt.line}: Sequence diagram: a box cannot be nested inside another box — close the open box with \"end\" first`,\n )\n }\n const { color, label } = parseBoxHeader(boxMatch[1] ?? '')\n const box: SequenceDiagram['boxes'][number] = {\n label: normalizeBrTags(label),\n actorIds: [],\n }\n if (color !== undefined) box.color = color\n diagram.boxes.push(box)\n boxCtx.open = diagram.boxes.length - 1\n boxOpenedAtDepth = blockStack.length\n boxOpenLine = stmt.line\n continue\n }\n\n // --- create participant / create actor ---\n // \"create participant C\" / \"create actor C as Label\" — the line before\n // C's first message. Same shape as a plain declaration, so the alias and\n // the participant/actor kind are kept, not dropped with the keyword.\n const createMatch = line.match(\n /^create\\s+(participant|actor)\\s+(\\S+?)(?:\\s+as\\s+(.+))?$/,\n )\n if (createMatch) {\n const type: 'participant' | 'actor' =\n createMatch[1] === 'actor' ? 'actor' : 'participant'\n const id = createMatch[2]!\n if (actorIds.has(id)) {\n // Mermaid's own wording (sequenceDb `createParticipant`).\n throw new Error(\n `Line ${stmt.line}: It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior`,\n )\n }\n actorIds.add(id)\n diagram.actors.push({\n id,\n label: normalizeBrTags(createMatch[3]?.trim() ?? id),\n type,\n })\n joinOpenBox(diagram, boxCtx, id, stmt.line)\n pendingCreate = id\n continue\n }\n\n // --- destroy ---\n // \"destroy C\" — the line before C's last message.\n const destroyMatch = line.match(/^destroy\\s+(.+)$/)\n if (destroyMatch) {\n const id = destroyMatch[1]!.trim()\n ensureActor(diagram, actorIds, boxCtx, id, stmt.line)\n pendingDestroy = id\n continue\n }\n\n // --- autonumber directive ---\n const autonumberMatch = line.match(\n /^autonumber(?:\\s+(off|\\d+(?:\\.\\d{1,2})?)(?:\\s+(\\d+(?:\\.\\d{1,2})?))?)?$/,\n )\n if (autonumberMatch) {\n const start = autonumberMatch[1]\n const step = autonumberMatch[2]\n if (start === 'off') {\n autonumber.enabled = false\n } else {\n autonumber.enabled = true\n autonumber.next = start !== undefined ? Number(start) : 1\n autonumber.step = step !== undefined ? Number(step) : 1\n }\n continue\n }\n\n // --- Participant / Actor declaration ---\n // \"participant A as Alice\" or \"participant Alice\"\n // \"actor B as Bob\" or \"actor Bob\"\n const actorMatch = line.match(\n /^(participant|actor)\\s+(\\S+?)(?:\\s+as\\s+(.+))?$/,\n )\n if (actorMatch) {\n // Group 1 is constrained by the regex alternation to 'participant' | 'actor'\n const type: 'participant' | 'actor' =\n actorMatch[1] === 'actor' ? 'actor' : 'participant'\n const id = actorMatch[2]!\n const rawLabel = actorMatch[3]?.trim() ?? id\n const label = normalizeBrTags(rawLabel)\n if (!actorIds.has(id)) {\n actorIds.add(id)\n diagram.actors.push({ id, label, type })\n }\n // A re-declaration inside a box joins it (or errors if it already\n // belongs to a different one) — Mermaid's `addActor` rule.\n joinOpenBox(diagram, boxCtx, id, stmt.line)\n continue\n }\n\n // --- Note ---\n // \"Note left of A: text\" / \"Note right of A: text\" / \"Note over A,B: text\"\n const noteMatch = line.match(\n /^Note\\s+(left of|right of|over)\\s+([^:]+):\\s*(.+)$/i,\n )\n if (noteMatch) {\n const posStr = noteMatch[1]!.toLowerCase()\n const actorsStr = noteMatch[2]!.trim()\n const text = normalizeBrTags(noteMatch[3]!.trim())\n const noteActorIds = actorsStr.split(',').map((s) => s.trim())\n\n // Ensure actors exist\n for (const aid of noteActorIds) {\n ensureActor(diagram, actorIds, boxCtx, aid, stmt.line)\n }\n\n let position: 'left' | 'right' | 'over' = 'over'\n if (posStr === 'left of') position = 'left'\n else if (posStr === 'right of') position = 'right'\n\n diagram.notes.push({\n actorIds: noteActorIds,\n text,\n position,\n afterIndex: diagram.messages.length - 1,\n })\n continue\n }\n\n // --- Block start: loop, alt, opt, par, critical, break, rect ---\n const blockMatch = line.match(\n /^(loop|alt|opt|par|critical|break|rect)\\s*(.*)$/,\n )\n if (blockMatch) {\n const blockType = toBlockType(blockMatch[1]!)\n const rawBlockLabel = blockMatch[2]?.trim() ?? ''\n const label = normalizeBrTags(rawBlockLabel)\n blockStack.push({\n type: blockType,\n label,\n startIndex: diagram.messages.length,\n dividers: [],\n line: stmt.line,\n })\n continue\n }\n\n // --- Block divider: else, and ---\n const dividerMatch = line.match(/^(else|and)\\s*(.*)$/)\n if (dividerMatch && blockStack.length > 0) {\n const rawDividerLabel = dividerMatch[2]?.trim() ?? ''\n const label = normalizeBrTags(rawDividerLabel)\n blockStack[blockStack.length - 1]!.dividers.push({\n index: diagram.messages.length,\n label,\n })\n continue\n }\n\n // --- Box end ---\n // `end` closes the open box unless a block was opened *inside* it,\n // in which case the block (innermost) owns this `end`.\n if (\n line === 'end' &&\n boxCtx.open !== undefined &&\n blockStack.length === boxOpenedAtDepth\n ) {\n boxCtx.open = undefined\n boxOpenLine = undefined\n continue\n }\n\n // --- Block end ---\n if (line === 'end' && blockStack.length > 0) {\n const completed = blockStack.pop()!\n diagram.blocks.push({\n type: completed.type,\n label: completed.label,\n startIndex: completed.startIndex,\n endIndex: Math.max(diagram.messages.length - 1, completed.startIndex),\n dividers: completed.dividers,\n })\n continue\n }\n\n // --- Unmatched end ---\n // Neither branch above claimed this \"end\" — no box and no block is\n // currently open, so there's nothing for it to close. Previously\n // silently ignored (#762); mirrors the \"box cannot be nested\" throw\n // above in surfacing a structural mistake instead of dropping it.\n if (line === 'end') {\n throw new Error(\n `Line ${stmt.line}: Sequence diagram: \"end\" does not match any open block (\"loop\"/\"alt\"/\"opt\"/\"par\"/\"critical\"/\"break\"/\"rect\") or \"box\" — nothing is currently open to close.`,\n )\n }\n\n // --- Standalone activate / deactivate ---\n // \"activate A\" / \"deactivate A\". Mermaid's own grammar expands the `+`/`-`\n // arrow shorthand into exactly this (message, then an activeStart for the\n // recipient / activeEnd for the sender), so record the same event the\n // shorthand implies and let layout.ts feed both through one activation\n // stack. Checked before the message regex: neither keyword contains an\n // arrow token, but an actor literally named `activate` used as a message\n // *source* (`activate->>B: x`) still reaches the message branch, since\n // the `\\s+` here requires whitespace after the keyword.\n const activationMatch = line.match(/^(activate|deactivate)\\s+(.+)$/)\n if (activationMatch) {\n const actorId = activationMatch[2]!.trim()\n ensureActor(diagram, actorIds, boxCtx, actorId, stmt.line)\n diagram.activations.push({\n actorId,\n kind: activationMatch[1] === 'activate' ? 'start' : 'end',\n afterIndex: diagram.messages.length - 1,\n })\n continue\n }\n\n // --- Message ---\n // Patterns: A->>B, A-->>B, A-)B, A--)B, A<<->>B, A<<-->>B, with optional\n // +/- activation. Format: FROM ARROW TO: LABEL\n //\n // FROM/TO are matched with a lazy `.+?` (not `\\S+?`) so an undeclared\n // actor name can contain spaces or internal hyphens — e.g. `cron\n // job->>customer-notifier: hi` — mirroring real Mermaid's own sequence\n // grammar, whose unquoted ACTOR token excludes only the characters that\n // start an arrow/label (`/ \\ + ( ) < - > :`) rather than all whitespace.\n // Because the quantifier is lazy and anchored by the arrow/colon tokens\n // that follow, this still resolves to the same minimal split as before\n // for plain single-word names.\n //\n // Two-pass match (issue #341): the lazy FROM capture stops as soon as\n // *any* position looks like a valid arrow+TO+`:`+LABEL tail, which is a\n // false positive when an unquoted actor name happens to contain a short\n // open/cross arrow substring (`-)`, `--)`, `-x`, `--x` — matched by the\n // `--?[)x]` alternative) ahead of the *real* arrow later in the line —\n // e.g. `foo-x-bar->>baz: hi` mis-split at the embedded `-x` instead of\n // the real `->>`. Those two-char forms are rare and highly ambiguous\n // inside a bare identifier, whereas the \"long\" forms (anything ending\n // in `>`, plus the bidirectional tokens) essentially never occur by\n // accident, since they require a literal `>` character in an unquoted\n // name. So: try the long forms only first, and only fall back to the\n // full alternation (including the short forms) if the line has no long\n // arrow at all — which is what keeps a genuinely short-arrow message\n // like `A-)B: msg` working. This doesn't attempt to disambiguate every\n // theoretically possible collision (e.g. a literal `->` substring\n // embedded before a real `->>`) — see the issue's own \"Scope\" section,\n // which limits the fix to the `-x`/`-)`/`--x`/`--)` substrings.\n const msgMatch =\n line.match(MESSAGE_LONG_ARROW_RE) ?? line.match(MESSAGE_ANY_ARROW_RE)\n if (msgMatch) {\n // Mermaid's own unquoted actor-name grammar excludes `>` and `)` from\n // ever starting an identifier (see the FROM/TO comment above) — so a\n // TO capture starting with either one means the real arrow in the\n // source is longer than what the alternation above actually matched\n // (e.g. \"->>>\" only matches as \"->>\", leaving a stray \">\" to be\n // absorbed into TO as part of a garbled actor name) rather than a\n // genuinely valid, if unusual, message. Reject it with an actionable\n // error instead of silently minting that garbled actor — see the\n // `Alice->>>Bob: Hello` example in issue #762.\n const arrowToken = msgMatch[2]!\n const toFirstChar = msgMatch[4]![0]\n if (toFirstChar === '>' || toFirstChar === ')') {\n throw new Error(\n `Line ${stmt.line}: Malformed sequence-diagram arrow in \"${line}\" — \"${arrowToken}${toFirstChar}\" is not a recognized arrow. Expected one of: ->, -->, ->>, -->>, -x, --x, -), --), <<->>, <<-->>.`,\n )\n }\n pushMessage(\n diagram,\n actorIds,\n boxCtx,\n autonumber,\n msgMatch[1]!,\n msgMatch[2]!,\n msgMatch[3],\n msgMatch[4]!,\n msgMatch[5]!,\n stmt.line,\n )\n const msgIndex = diagram.messages.length - 1\n const msg = diagram.messages[msgIndex]!\n if (pendingCreate !== undefined) {\n if (msg.to !== pendingCreate) {\n throw new Error(\n `Line ${stmt.line}: The created participant ${pendingCreate} does not have an associated creating message after its declaration. Please check the sequence diagram.`,\n )\n }\n findActor(diagram, pendingCreate).createdAt = msgIndex\n pendingCreate = undefined\n }\n if (pendingDestroy !== undefined) {\n if (msg.from !== pendingDestroy && msg.to !== pendingDestroy) {\n throw new Error(\n `Line ${stmt.line}: The destroyed participant ${pendingDestroy} does not have an associated destroying message after its declaration. Please check the sequence diagram.`,\n )\n }\n findActor(diagram, pendingDestroy).destroyedAt = msgIndex\n pendingDestroy = undefined\n }\n continue\n }\n }\n\n // A block/box left open at EOF is a structural mistake, not something to\n // silently accept as \"closed by end of input\" — mirrors the class-diagram\n // parser's unclosed-body check (#761) for the same reason: previously\n // this was silently ignored (#762). Checked innermost-first (the block\n // stack) since an unclosed block is the more specific, more actionable\n // thing to report when both are open.\n if (blockStack.length > 0) {\n const unclosed = blockStack[blockStack.length - 1]!\n throw new Error(\n `Line ${unclosed.line}: Sequence diagram: unclosed \"${unclosed.type}\" block — expected a matching \"end\" before the diagram ends.`,\n )\n }\n if (boxCtx.open !== undefined) {\n const openBox = diagram.boxes[boxCtx.open]!\n throw new Error(\n `Line ${boxOpenLine}: Sequence diagram: unclosed \"box${openBox.label ? ` ${openBox.label}` : ''}\" — expected a matching \"end\" before the diagram ends.`,\n )\n }\n\n return diagram\n}\n\n/**\n * Look up an actor the parser itself already registered (every caller\n * passes an id that went through `ensureActor` or the create branch first,\n * so the miss branch is a parser-invariant violation, not user error).\n */\nfunction findActor(diagram: SequenceDiagram, id: string): Actor {\n const actor = diagram.actors.find((a) => a.id === id)\n if (actor === undefined) {\n /* v8 ignore next */\n throw new Error(`Sequence diagram: unknown actor \"${id}\"`)\n }\n return actor\n}\n\n/** Ensure an actor exists, creating a default participant if not */\nfunction ensureActor(\n diagram: SequenceDiagram,\n actorIds: Set<string>,\n boxCtx: BoxContext,\n id: string,\n lineNumber: number,\n): void {\n if (!actorIds.has(id)) {\n actorIds.add(id)\n diagram.actors.push({ id, label: id, type: 'participant' })\n }\n joinOpenBox(diagram, boxCtx, id, lineNumber)\n}\n\n/**\n * Put `id` into the currently open `box`, if any. A participant already in\n * a *different* box is an error (Mermaid's own wording); one already in\n * this box, or no box open, is a no-op.\n */\nfunction joinOpenBox(\n diagram: SequenceDiagram,\n boxCtx: BoxContext,\n id: string,\n lineNumber: number,\n): void {\n const open = boxCtx.open\n if (open === undefined) return\n const existing = boxCtx.membership.get(id)\n if (existing === open) return\n if (existing !== undefined) {\n const from = diagram.boxes[existing]!.label\n const to = diagram.boxes[open]!.label\n throw new Error(\n `Line ${lineNumber}: A same participant should only be defined in one Box: ${id} can't be in '${from}' and in '${to}' at the same time.`,\n )\n }\n boxCtx.membership.set(id, open)\n diagram.boxes[open]!.actorIds.push(id)\n}\n\n/**\n * Build a `Message` from a matched arrow-message line and push it onto the\n * diagram. Shared by both message regexes in `parseSequenceDiagram` above so\n * the arrow → line-style/arrow-head/bidirectional mapping and `autonumber`\n * bookkeeping can't drift out of sync between them.\n */\nfunction pushMessage(\n diagram: SequenceDiagram,\n actorIds: Set<string>,\n boxCtx: BoxContext,\n autonumber: { enabled: boolean; next: number; step: number },\n from: string,\n arrow: string,\n activationMark: string | undefined,\n to: string,\n rawLabel: string,\n lineNumber: number,\n): void {\n ensureActor(diagram, actorIds, boxCtx, from, lineNumber)\n ensureActor(diagram, actorIds, boxCtx, to, lineNumber)\n\n const bidirectional = arrow === '<<->>' || arrow === '<<-->>'\n const lineStyle = bidirectional\n ? arrow === '<<-->>'\n ? 'dashed'\n : 'solid'\n : arrow.startsWith('--')\n ? 'dashed'\n : 'solid'\n // \">>\" = filled arrow, \")\" or \">\" alone = open arrow, \"x\" = cross (treat as\n // filled). Both bidirectional tokens end in \">>\", so they fall out as filled.\n const arrowHead =\n arrow.includes('>>') || arrow.includes('x') ? 'filled' : 'open'\n // \"x\"/\"--x\" is Mermaid's \"lost message\" terminator — a cross, not a plain\n // filled arrowhead. `arrow.includes('x')` is unambiguous here: the only\n // arrow tokens containing \"x\" are \"-x\"/\"--x\" (see the message regex's\n // `--?[)x]` alternative above), never \"->>\"/\"-->>\" or the bidirectional\n // forms.\n const isLost = arrow.includes('x')\n\n const msg: Message = {\n from,\n to,\n label: normalizeBrTags(rawLabel.trim()),\n lineStyle,\n arrowHead,\n }\n if (isLost) msg.isLost = true\n if (bidirectional) msg.bidirectional = true\n if (activationMark === '+') msg.activate = true\n if (activationMark === '-') msg.deactivate = true\n\n if (autonumber.enabled) {\n msg.seqNumber = autonumber.next\n autonumber.next =\n Math.round((autonumber.next + autonumber.step) * 100) / 100\n }\n\n diagram.messages.push(msg)\n}\n","// ============================================================================\n// Sequence diagram — activation/deactivation balance check\n//\n// A mechanical, deterministic semantic check: every `activate X` (or the\n// `+` arrow shorthand) must be closed by a matching `deactivate X` (or the\n// `-` shorthand) before the diagram ends. This doesn't need an LLM judge —\n// it's the same activation-stack bookkeeping\n// packages/svg-renderer/src/sequence/layout.ts already runs to *draw*\n// activation bars (see its `activationStacks` map), reused here to *report*\n// imbalance instead of silently rendering around it.\n//\n// Motivation (issue #539, split from #536): research on LLM-generated\n// Mermaid sequence diagrams found they fail mostly on activation handling\n// and error/status tracking, not basic syntax — which existing validators\n// (syntax checkers, and agentic-mermaid's structural/geometric/lint\n// `verify` tool: dangling edges, label overflow, duplicate/unreachable\n// nodes) already cover well. Activation balance is a concrete, narrow,\n// mechanically-checkable instance of that gap.\n// ============================================================================\n\nimport type { SequenceDiagram, Message } from './types.ts'\n\nexport type ActivationIssueCode =\n 'DANGLING_ACTIVATION' | 'UNMATCHED_DEACTIVATION'\n\nexport interface ActivationIssue {\n code: ActivationIssueCode\n /** Actor whose activation is unbalanced. */\n actorId: string\n /** Human-readable explanation, including a source-position hint. */\n message: string\n}\n\nexport interface ActivationCheckResult {\n /** true when every activation is balanced (no issues found). */\n ok: boolean\n issues: ActivationIssue[]\n}\n\n/** One `activate`/`deactivate` event in source order, whichever form wrote it. */\ninterface Event {\n actorId: string\n kind: 'start' | 'end'\n afterIndex: number\n}\n\n/**\n * Describe where an event occurred, for a human-readable issue message.\n * Mirrors the `afterIndex` semantics of `SequenceDiagram.activations` (see\n * types.ts): -1 means \"before the first message\", otherwise the index of\n * the message this event follows.\n */\nfunction describePosition(\n diagram: SequenceDiagram,\n afterIndex: number,\n): string {\n if (afterIndex < 0) return 'before the first message'\n const msg: Message | undefined = diagram.messages[afterIndex]\n if (!msg) return 'at an unresolved position'\n const label = msg.label ? `: ${msg.label}` : ''\n return `after message ${afterIndex + 1} (\"${msg.from} -> ${msg.to}${label}\")`\n}\n\n/**\n * Check a parsed sequence diagram for activation/deactivation imbalance.\n *\n * Merges the inline `+`/`-` arrow shorthand (`Message.activate` /\n * `Message.deactivate`) with standalone `activate X` / `deactivate X`\n * statements (`SequenceDiagram.activations`) into one chronological event\n * stream per actor — the same merge `layout.ts` performs to position\n * activation bars — then walks a stack per actor:\n *\n * - A `deactivate` with nothing open on that actor's stack is reported as\n * `UNMATCHED_DEACTIVATION` (the renderer silently ignores this case —\n * see `endActivation` in layout.ts — this check surfaces it instead).\n * - Anything left on a stack once every message has been processed is\n * reported as `DANGLING_ACTIVATION` (the renderer draws these extending\n * to the bottom of the diagram, which usually isn't what the author\n * intended).\n */\nexport function checkActivationBalance(\n diagram: SequenceDiagram,\n): ActivationCheckResult {\n // Standalone activations, grouped by the message index they follow — same\n // grouping layout.ts builds for its `activationEventsByAfterIndex`.\n const standaloneByAfterIndex = new Map<number, typeof diagram.activations>()\n for (const event of diagram.activations) {\n const list = standaloneByAfterIndex.get(event.afterIndex) ?? []\n list.push(event)\n standaloneByAfterIndex.set(event.afterIndex, list)\n }\n\n const events: Event[] = []\n function pushStandalone(afterIndex: number): void {\n for (const event of standaloneByAfterIndex.get(afterIndex) ?? []) {\n events.push({ actorId: event.actorId, kind: event.kind, afterIndex })\n }\n }\n\n // Events before the first message open first, same as layout.ts.\n pushStandalone(-1)\n for (let i = 0; i < diagram.messages.length; i++) {\n const msg = diagram.messages[i]!\n if (msg.activate) {\n events.push({ actorId: msg.to, kind: 'start', afterIndex: i })\n }\n if (msg.deactivate) {\n events.push({ actorId: msg.from, kind: 'end', afterIndex: i })\n }\n pushStandalone(i)\n }\n\n // actorId -> stack of afterIndex values where an activation was opened.\n const stacks = new Map<string, number[]>()\n const issues: ActivationIssue[] = []\n\n for (const event of events) {\n const stack = stacks.get(event.actorId) ?? []\n stacks.set(event.actorId, stack)\n if (event.kind === 'start') {\n stack.push(event.afterIndex)\n continue\n }\n const openedAt = stack.pop()\n if (openedAt === undefined) {\n issues.push({\n code: 'UNMATCHED_DEACTIVATION',\n actorId: event.actorId,\n message:\n `\"deactivate ${event.actorId}\" ${describePosition(diagram, event.afterIndex)} ` +\n `has no matching \"activate ${event.actorId}\" open at that point.`,\n })\n }\n }\n\n for (const [actorId, stack] of stacks) {\n for (const openedAt of stack) {\n issues.push({\n code: 'DANGLING_ACTIVATION',\n actorId,\n message:\n `\"activate ${actorId}\" opened ${describePosition(diagram, openedAt)} ` +\n `is never closed with a matching \"deactivate ${actorId}\".`,\n })\n }\n }\n\n return { ok: issues.length === 0, issues }\n}\n","// ============================================================================\n// Sequence diagram — activation/deactivation auto-fix\n//\n// Extends activation-check.ts (issue #539) with the \"optionally proposes a\n// fix\" half of that issue's scope. Deliberately narrow: only\n// `DANGLING_ACTIVATION` (an `activate X` never closed) is auto-fixable —\n// the mechanical, unambiguous repair is to append a matching `deactivate X`\n// after the diagram's last statement, which is always syntactically valid\n// regardless of block nesting (Mermaid's `deactivate` isn't block-scoped).\n//\n// `UNMATCHED_DEACTIVATION` (a `deactivate X` with nothing open) is\n// deliberately NOT auto-fixed: fixing it means either deleting that\n// specific statement or inserting a preceding `activate X`, and this\n// package doesn't track source line/position per activation statement (see\n// SequenceDiagram.activations — only a message-index `afterIndex`, no raw\n// line offset), so a text-level edit could not target the right occurrence\n// with confidence when the same statement text repeats. Rather than guess\n// and risk mangling unrelated source, this case is reported back as a\n// remaining (unfixable) issue for a human — or a fuzzier LLM-based layer,\n// per the issue's own caveat about unvalidated frontier-model behavior — to\n// resolve.\n// ============================================================================\n\nimport { detectDiagramType, splitStatements } from '@zombie-mermaid/core'\nimport { parseSequenceDiagram } from './parser.ts'\nimport {\n checkActivationBalance,\n type ActivationIssue,\n} from './activation-check.ts'\n\nexport interface ActivationFixResult {\n /** true when the returned diagram has no remaining activation issues. */\n ok: boolean\n /** Original diagram, unchanged if there was nothing fixable. */\n originalDiagram: string\n /**\n * Diagram with a \"deactivate X\" statement appended for every\n * DANGLING_ACTIVATION issue found. Identical to `originalDiagram` when\n * there was nothing to fix.\n */\n fixedDiagram: string\n /** Human-readable description of each fix actually applied. */\n fixesApplied: string[]\n /**\n * Issues that could not be auto-fixed (currently always\n * UNMATCHED_DEACTIVATION — see module header). Empty when `ok` is true.\n */\n remainingIssues: ActivationIssue[]\n}\n\n/**\n * Check a Mermaid sequence diagram for activation/deactivation imbalance\n * and, where mechanically safe, return a corrected version.\n *\n * Throws the same way `parseSequenceDiagram`/`detectDiagramType` do on\n * invalid or non-sequence input — callers (e.g. the MCP tool handler) are\n * expected to catch and translate, matching `check-sequence-activations.ts`'s\n * own error handling.\n */\nexport function fixActivationBalance(\n sourceDiagram: string,\n): ActivationFixResult {\n const diagramType = detectDiagramType(sourceDiagram)\n if (diagramType !== 'sequence') {\n throw new Error(\n 'fixActivationBalance only supports sequence diagrams (source must ' +\n `start with \"sequenceDiagram\"); detected diagram type: \"${diagramType}\".`,\n )\n }\n\n const lines = splitStatements(sourceDiagram)\n const diagram = parseSequenceDiagram(lines)\n const result = checkActivationBalance(diagram)\n\n if (result.ok) {\n return {\n ok: true,\n originalDiagram: sourceDiagram,\n fixedDiagram: sourceDiagram,\n fixesApplied: [],\n remainingIssues: [],\n }\n }\n\n const dangling = result.issues.filter(\n (issue) => issue.code === 'DANGLING_ACTIVATION',\n )\n const remainingIssues = result.issues.filter(\n (issue) => issue.code !== 'DANGLING_ACTIVATION',\n )\n\n const appended = dangling\n .map((issue) => ` deactivate ${issue.actorId}`)\n .join('\\n')\n const fixedDiagram =\n dangling.length === 0\n ? sourceDiagram\n : `${sourceDiagram.replace(/\\s+$/, '')}\\n${appended}`\n\n const fixesApplied = dangling.map(\n (issue) =>\n `Appended \"deactivate ${issue.actorId}\" to close: ${issue.message}`,\n )\n\n return {\n ok: remainingIssues.length === 0,\n originalDiagram: sourceDiagram,\n fixedDiagram,\n fixesApplied,\n remainingIssues,\n }\n}\n","import type { XYChart, XYAxis, XYChartSeries } from './types.ts'\nimport type { Statement } from '@zombie-mermaid/core'\n\n// ============================================================================\n// XY Chart parser\n//\n// Parses Mermaid xychart-beta syntax into a typed XYChart structure.\n//\n// Supported directives:\n// xychart-beta [horizontal]\n// title \"Chart Title\"\n// x-axis [label1, label2, ...] — categorical\n// x-axis min --> max — numeric range\n// x-axis \"Axis Title\" [label1, ...] — with title\n// x-axis \"Axis Title\" min --> max — with title\n// y-axis (same patterns)\n// bar [val1, val2, ...]\n// line [val1, val2, ...]\n// ============================================================================\n\n/**\n * Parse a Mermaid xychart-beta diagram from preprocessed lines.\n * Lines should already be trimmed and comment-stripped.\n */\nexport function parseXYChart(lines: Statement[]): XYChart {\n const xAxis: XYAxis = {}\n const yAxis: XYAxis = {}\n const series: XYChartSeries[] = []\n let title: string | undefined\n let horizontal = false\n\n for (const stmt of lines) {\n const line = stmt.text\n // Header line — detect horizontal\n if (/^xychart(-beta)?\\b/i.test(line)) {\n if (/\\bhorizontal\\b/i.test(line)) horizontal = true\n continue\n }\n\n // Title\n const titleMatch = line.match(/^title\\s+\"([^\"]+)\"/)\n if (titleMatch) {\n title = titleMatch[1]\n continue\n }\n\n // x-axis with categories: x-axis \"Title\" [a, b, c] or x-axis [a, b, c]\n const xCatMatch = line.match(/^x-axis\\s+(?:\"([^\"]*)\"\\s*)?\\[([^\\]]+)\\]/)\n if (xCatMatch) {\n if (xCatMatch[1]) xAxis.title = xCatMatch[1]\n xAxis.categories = splitCategoryList(xCatMatch[2]!).map((s) =>\n unquote(s.trim()),\n )\n continue\n }\n\n // x-axis with range: x-axis \"Title\" min --> max or x-axis min --> max\n const xRangeMatch = line.match(\n /^x-axis\\s+(?:\"([^\"]*)\"\\s+)?(-?\\d+(?:\\.\\d+)?)\\s*-->\\s*(-?\\d+(?:\\.\\d+)?)/,\n )\n if (xRangeMatch) {\n if (xRangeMatch[1]) xAxis.title = xRangeMatch[1]\n xAxis.range = {\n min: parseFloat(xRangeMatch[2]!),\n max: parseFloat(xRangeMatch[3]!),\n }\n continue\n }\n\n // y-axis with range: y-axis \"Title\" min --> max or y-axis min --> max\n const yRangeMatch = line.match(\n /^y-axis\\s+(?:\"([^\"]*)\"\\s+)?(-?\\d+(?:\\.\\d+)?)\\s*-->\\s*(-?\\d+(?:\\.\\d+)?)/,\n )\n if (yRangeMatch) {\n if (yRangeMatch[1]) yAxis.title = yRangeMatch[1]\n yAxis.range = {\n min: parseFloat(yRangeMatch[2]!),\n max: parseFloat(yRangeMatch[3]!),\n }\n continue\n }\n\n // y-axis with just title (no range)\n const yTitleOnly = line.match(/^y-axis\\s+\"([^\"]+)\"\\s*$/)\n if (yTitleOnly) {\n yAxis.title = yTitleOnly[1]\n continue\n }\n\n // bar [...]\n const barMatch = line.match(/^bar\\s+\\[([^\\]]+)\\]/)\n if (barMatch) {\n series.push({\n type: 'bar',\n data: parseNumericArray(barMatch[1]!, 'bar', line, stmt.line),\n })\n continue\n }\n\n // line [...]\n const lineMatch = line.match(/^line\\s+\\[([^\\]]+)\\]/)\n if (lineMatch) {\n series.push({\n type: 'line',\n data: parseNumericArray(lineMatch[1]!, 'line', line, stmt.line),\n })\n continue\n }\n\n // None of the recognized directive shapes matched. A line that doesn't\n // start with any known xychart-beta keyword at all falls through\n // silently below, same as before — but a line that *does* start with\n // one of the five keyworded directives (x-axis/y-axis/bar/line/title)\n // and still failed every pattern above is almost certainly an attempt\n // at that directive with broken syntax (an unclosed bracket, a missing\n // quote, a malformed range). Previously this was silently dropped —\n // the diagram would render with that axis/series just missing and no\n // indication why. Surface an actionable error instead. See issue #541\n // (parser error-message quality audit — this parser had zero throw\n // sites before this pass).\n const keywordMatch = line.match(/^(x-axis|y-axis|bar|line|title)\\b/i)\n if (keywordMatch) {\n throw malformedDirectiveError(\n keywordMatch[1]!.toLowerCase(),\n line,\n stmt.line,\n )\n }\n }\n\n // Auto-derive y-axis range from data if not specified\n if (!yAxis.range && series.length > 0) {\n const allValues = series.flatMap((s) => s.data)\n let min = Math.min(...allValues)\n let max = Math.max(...allValues)\n const span = max - min || 1\n // Add 10% padding\n min = min - span * 0.1\n max = max + span * 0.1\n // Floor to 0 if all values are positive and min is close to 0\n if (min > 0 && min < span * 0.5) min = 0\n yAxis.range = { min, max }\n }\n\n // Fallback y-axis range\n if (!yAxis.range) {\n yAxis.range = { min: 0, max: 100 }\n }\n\n return { title, horizontal, xAxis, yAxis, series }\n}\n\n/**\n * Split a categorical x-axis bracket's comma-separated contents into raw\n * item strings, treating a comma *inside* a double-quoted item as literal\n * text rather than a delimiter. A naive `.split(',')` would break\n * `[\"Jan, Feb\", \"Mar\"]` into three pieces instead of two — quoting a value\n * that itself contains a comma is exactly the case quoting exists for. Each\n * returned raw item is still trimmed and unquoted by the caller.\n */\nfunction splitCategoryList(str: string): string[] {\n const items: string[] = []\n let current = ''\n let inQuotes = false\n for (const char of str) {\n if (char === '\"') {\n inQuotes = !inQuotes\n current += char\n } else if (char === ',' && !inQuotes) {\n items.push(current)\n current = ''\n } else {\n current += char\n }\n }\n items.push(current)\n return items\n}\n\n/**\n * Strip one matching pair of leading/trailing double quotes from a category\n * item, if present — mirroring how an axis *title* capture group already\n * unquotes via its regex. `x-axis [A, B, C]` items are only split and\n * trimmed, so a quoted item like `\"CLI output / logs\"` previously kept its\n * literal quote characters in the rendered label. See issue #1087.\n */\nfunction unquote(value: string): string {\n if (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"')) {\n return value.slice(1, -1)\n }\n return value\n}\n\n/**\n * Parse a `bar`/`line` series' comma-separated bracket contents into\n * numbers, throwing on any element that isn't a valid number instead of\n * silently coercing it to `NaN` (which serializes as `null` and can\n * propagate into layout math with no indication anything was wrong — see\n * issue #541's audit finding on this exact parser).\n */\nfunction parseNumericArray(\n str: string,\n seriesType: 'bar' | 'line',\n line: string,\n lineNumber: number,\n): number[] {\n return str.split(',').map((raw, index) => {\n const trimmed = raw.trim()\n const value = parseFloat(trimmed)\n if (trimmed.length === 0 || Number.isNaN(value)) {\n throw new Error(\n `Line ${lineNumber}: Invalid numeric value ${JSON.stringify(trimmed)} at position ${\n index + 1\n } in \"${line}\". Every value in a ${seriesType} [...] list must be a number.`,\n )\n }\n return value\n })\n}\n\n/** Expected-syntax hint per xychart-beta keyword, for `malformedDirectiveError`. */\nconst XYCHART_DIRECTIVE_HELP: Record<string, string> = {\n 'x-axis':\n 'x-axis [A, B, C] (categories) or x-axis 0 --> 100 (numeric range), either optionally preceded by a quoted title',\n 'y-axis':\n 'y-axis 0 --> 100 (numeric range) or y-axis \"Title\" (title only), optionally preceded by a quoted title before a range',\n bar: 'bar [10, 20, 30] — a comma-separated numeric array in square brackets',\n line: 'line [10, 20, 30] — a comma-separated numeric array in square brackets',\n title: 'title \"Chart Title\" — a double-quoted string',\n}\n\n/**\n * Build the error for a line that starts with a recognized xychart-beta\n * keyword (x-axis/y-axis/bar/line/title) but doesn't match that keyword's\n * expected syntax in any of the forms this parser supports.\n */\nfunction malformedDirectiveError(\n keyword: string,\n line: string,\n lineNumber: number,\n): Error {\n const help = XYCHART_DIRECTIVE_HELP[keyword] ?? keyword\n return new Error(\n `Line ${lineNumber}: Malformed xychart-beta \"${keyword}\" directive: \"${line}\". Expected: ${help}.`,\n )\n}\n","// ============================================================================\n// XY Chart — shared color palette\n//\n// Generates monochromatic shades from the theme accent color.\n// Series 0 = accent (or blue fallback). Series 1+ are darker/lighter\n// shades of the same hue with subtle hue drift to stay in the same\n// color family (like navy ↔ cyan from blue). Hue drifts toward navy for\n// darker shades and toward cyan for lighter ones, so neighbouring series\n// do not collapse into two near-identical blues (#1244).\n//\n// Used by both the SVG and ASCII renderers.\n// ============================================================================\n\n/** Default accent for charts when the theme doesn't provide one. */\nexport const CHART_ACCENT_FALLBACK = '#3b82f6' // blue-500\n\n// ---------------------------------------------------------------------------\n// HSL ↔ Hex conversion\n// ---------------------------------------------------------------------------\n\nfunction hexToHsl(hex: string): [number, number, number] {\n const h = hex.replace('#', '')\n const ri = parseInt(h.substring(0, 2), 16) / 255\n const gi = parseInt(h.substring(2, 4), 16) / 255\n const bi = parseInt(h.substring(4, 6), 16) / 255\n\n const max = Math.max(ri, gi, bi)\n const min = Math.min(ri, gi, bi)\n const l = (max + min) / 2\n\n if (max === min) return [0, 0, l * 100]\n\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n\n let hue: number\n if (max === ri) hue = ((gi - bi) / d + (gi < bi ? 6 : 0)) / 6\n else if (max === gi) hue = ((bi - ri) / d + 2) / 6\n else hue = ((ri - gi) / d + 4) / 6\n\n return [hue * 360, s * 100, l * 100]\n}\n\nfunction hslToHex(h: number, s: number, l: number): string {\n const si = s / 100\n const li = l / 100\n\n const c = (1 - Math.abs(2 * li - 1)) * si\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n const m = li - c / 2\n\n let r: number, g: number, b: number\n if (h < 60) {\n r = c\n g = x\n b = 0\n } else if (h < 120) {\n r = x\n g = c\n b = 0\n } else if (h < 180) {\n r = 0\n g = c\n b = x\n } else if (h < 240) {\n r = 0\n g = x\n b = c\n } else if (h < 300) {\n r = x\n g = 0\n b = c\n } else {\n r = c\n g = 0\n b = x\n }\n\n const toHex = (v: number) =>\n Math.round((v + m) * 255)\n .toString(16)\n .padStart(2, '0')\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Check whether a string is a valid 6-digit hex color (e.g. \"#3b82f6\"). */\nexport function isValidHex(color: string): boolean {\n return /^#[0-9a-fA-F]{6}$/.test(color)\n}\n\n/**\n * Detect whether a background color is dark (lightness < 50%).\n */\nexport function isDarkBackground(bgHex: string): boolean {\n return hexToHsl(bgHex)[2] < 50\n}\n\n/**\n * Get the hex color for a series index.\n * Index 0 returns the accent color as-is.\n * Index 1+ alternate between darker and lighter shades of the same hue\n * with subtle hue drift (-8° per tier for darker, -12° for lighter) to stay in the same family.\n *\n * When `bgColor` is provided, shade direction adapts to the background:\n * - Light bg: odd = darker, even = lighter (default)\n * - Dark bg: odd = lighter, even = darker (so shades stay visible)\n */\nexport function getSeriesColor(\n index: number,\n accentColor: string,\n bgColor?: string,\n): string {\n if (index === 0) return accentColor\n // Fall back to defaults when inputs aren't valid hex (e.g. CSS variable refs like \"var(--accent)\")\n const safeAccent = isValidHex(accentColor)\n ? accentColor\n : CHART_ACCENT_FALLBACK\n const safeBg = bgColor && isValidHex(bgColor) ? bgColor : undefined\n const [h, s] = hexToHsl(safeAccent)\n const chartS = Math.max(55, Math.min(85, s))\n\n const tier = Math.ceil(index / 2)\n const oddIndex = index % 2 === 1\n\n // On dark backgrounds, flip: odd = lighter, even = darker\n const dark = safeBg && isDarkBackground(safeBg) ? !oddIndex : oddIndex\n const l = dark ? Math.max(25, 48 - tier * 13) : Math.min(78, 55 + tier * 11)\n\n // Subtle hue drift: darker shades shift slightly negative (toward navy),\n // lighter shades shift negative too (toward sky/cyan). The lighter shade\n // used to drift positive (toward indigo), which landed series 2 within a\n // few ΔE of series 0 (#1244); drifting the other way keeps the three first\n // series distinct in hue as well as lightness.\n const hShift = (dark ? -8 : -12) * tier\n const newH = (((h + hShift) % 360) + 360) % 360\n\n return hslToHex(newH, chartS, l)\n}\n","import type { PieChart, PieSlice } from './types.ts'\n\n// ============================================================================\n// Pie chart parser\n//\n// Parses Mermaid `pie` syntax into a typed PieChart, matching what Mermaid\n// itself accepts and rejects. Mermaid parses pie charts with a Langium\n// grammar (mermaid-js/mermaid packages/parser/src/language/pie/pie.langium\n// plus common/common.langium) and then validates slices in pieDb.ts. This\n// file is a small hand-written lexer + recursive-descent parser over the same\n// token patterns, so the accepted language is the same:\n//\n// pie [showData] [title <text> | accTitle: <text> | accDescr …]\n// title <text> (own line; rest of line, unquoted)\n// accTitle: <text>\n// accDescr: <text>\n// accDescr { <multi-line text> }\n// \"Label\" : 42.5 (or 'Label'; label MUST be quoted)\n//\n// Mermaid's rules, each mirrored below:\n// - `showData` is only valid directly after `pie`; a `showData` line on\n// its own is a syntax error.\n// - Numbers are `-?\\d+\\.\\d+` or `-?(0|[1-9]\\d*)` — no `+`, no exponent,\n// no leading `.`, no trailing `.`.\n// - Everything after a complete statement on the same line (other than a\n// `%%` comment) is a syntax error.\n// - A negative value is an error (pieDb.addSection), reported only once\n// the whole chart has parsed, and before the duplicate-label check.\n// - A zero value is accepted.\n// - A repeated label keeps its first value; later ones are ignored.\n// - A chart with no slices (a bare `pie`) is valid.\n// - `title`/`accTitle`/`accDescr` may repeat; the last one wins, and an\n// empty one counts as unset.\n// - Keywords are case-sensitive (`Pie`, `showdata`, `Title` are errors).\n//\n// Why raw text and not `Statement[]` like the other mermaid-parser parsers:\n// `splitStatements` splits on `;` and treats `'` as a quote when looking for\n// `%%`, neither of which Mermaid's pie grammar does — `title A; B` is one\n// title there, and a quoted label may even span lines. Lexing the source\n// directly keeps those cases identical to Mermaid. The SVG registry's\n// `parse(lines, text)` already hands every parser the raw text too.\n//\n// Earlier ports: lukilabs/beautiful-mermaid#151 (birenroy) — the starting\n// point for the model and tests — and #150 (Daniele-rolli), whose parser\n// rejected unknown lines instead of dropping them.\n// ============================================================================\n\ntype TokenKind =\n | 'NEWLINE'\n | 'PIE'\n | 'SHOW_DATA'\n | 'COLON'\n | 'NUMBER'\n | 'STRING'\n | 'TITLE'\n | 'ACC_TITLE'\n | 'ACC_DESCR'\n\ninterface Token {\n kind: TokenKind\n text: string\n /** 1-based source line the token starts on. */\n line: number\n}\n\n/**\n * Token patterns in Langium's lexing order: whitespace first, then\n * keywords (longest first), then terminals. `null` kinds are hidden (skipped).\n * Each is sticky so it only matches at the current position.\n *\n * `pie`/`showData` carry the `(?:(?=%%)|(?!\\S))` suffix Mermaid's\n * `AbstractMermaidTokenBuilder` adds: the keyword must be followed by\n * whitespace, a comment, or the end of the text.\n */\nconst TOKEN_PATTERNS: ReadonlyArray<readonly [TokenKind | null, RegExp]> = [\n [null, /[\\t ]+/y],\n ['SHOW_DATA', /showData(?:(?=%%)|(?!\\S))/y],\n ['PIE', /pie(?:(?=%%)|(?!\\S))/y],\n ['COLON', /:/y],\n ['NUMBER', /-?[0-9]+\\.[0-9]+(?!\\.)|-?(?:0|[1-9][0-9]*)(?!\\.)/y],\n [\n 'ACC_DESCR',\n /[\\t ]*accDescr(?:[\\t ]*:(?:[^\\n\\r]*?(?=%%)|[^\\n\\r]*)|\\s*\\{[^}]*\\})/y,\n ],\n ['ACC_TITLE', /[\\t ]*accTitle[\\t ]*:(?:[^\\n\\r]*?(?=%%)|[^\\n\\r]*)/y],\n ['TITLE', /[\\t ]*title(?:[\\t ][^\\n\\r]*?(?=%%)|[\\t ][^\\n\\r]*|)/y],\n ['STRING', /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'/y],\n ['NEWLINE', /\\n/y],\n [null, /[\\t ]*%%[^\\n\\r]*/y],\n]\n\n/** Mermaid's `directiveRegex` (diagram-api/regexes.ts), verbatim. */\nconst DIRECTIVE_REGEX =\n /%{2}{\\s*(?:(\\w+)\\s*:|(\\w+))\\s*(?:(\\w+)|((?:(?!}%{2}).|\\r?\\n)*))?\\s*(?:}%{2})?/gi\n\n/** Value extractors from Mermaid's common/matcher.ts. */\nconst ACC_DESCR_VALUE = /accDescr(?:[\\t ]*:([^\\n\\r]*)|\\s*\\{([^}]*)\\})/\nconst ACC_TITLE_VALUE = /accTitle[\\t ]*:([^\\n\\r]*)/\nconst TITLE_VALUE = /title([\\t ][^\\n\\r]*|)/\n\nconst SYNTAX_HELP =\n 'A pie chart is `pie [showData] [title <text>]` followed by lines of `\"Label\" : value` (label quoted with \" or \\'), `title <text>`, `accTitle: <text>`, or `accDescr: <text>`.'\n\n/**\n * Mirror Mermaid's text preprocessing (preprocess.ts `cleanupText` and\n * `removeDirectives`) that runs before the grammar sees the source:\n * - CRLF / lone CR become LF;\n * - double-quoted attributes inside HTML-like tags become single-quoted;\n * - `%%{ … }%%` directives are removed. Unlike Mermaid, the newlines a\n * multi-line directive spans are kept, so line numbers in errors still\n * point at the caller's source.\n */\nfunction preprocess(text: string): string {\n return text\n .replace(/\\r\\n?/g, '\\n')\n .replace(\n /<(\\w+)([^>]*)>/g,\n (_match, tag: string, attributes: string) =>\n '<' + tag + attributes.replace(/=\"([^\"]*)\"/g, \"='$1'\") + '>',\n )\n .replace(DIRECTIVE_REGEX, (directive) => directive.replace(/[^\\n]/g, ''))\n}\n\nfunction countNewlines(text: string): number {\n let count = 0\n for (const ch of text) if (ch === '\\n') count++\n return count\n}\n\n/** Build a `Line N: …` error quoting the offending source line. */\nfunction lineError(\n lines: readonly string[],\n line: number,\n message: string,\n): Error {\n /* v8 ignore next -- every caller passes a line inside `lines` */\n const source = (lines[line - 1] ?? '').trim()\n return new Error(`Line ${line}: ${message} in \"${source}\". ${SYNTAX_HELP}`)\n}\n\n/** What the lexer expected at a position it couldn't tokenize. */\nfunction lexHint(source: string, pos: number, previous?: Token): string {\n const ch = source[pos]\n if (ch === '\"' || ch === \"'\") {\n return `Unterminated quoted label (missing closing ${ch})`\n }\n if (previous?.kind === 'COLON') {\n return 'Invalid slice value — expected a number like 42 or 42.5 after \":\"'\n }\n const atLineStart =\n previous === undefined ||\n previous.kind === 'NEWLINE' ||\n previous.kind === 'PIE' ||\n previous.kind === 'SHOW_DATA'\n if (atLineStart) {\n return 'Unrecognized statement — slice labels must be quoted, e.g. \"Dogs\" : 42'\n }\n return `Unexpected text ${JSON.stringify(source.slice(pos).split('\\n')[0])}`\n}\n\nfunction tokenize(source: string, lines: readonly string[]): Token[] {\n const tokens: Token[] = []\n let pos = 0\n let line = 1\n\n while (pos < source.length) {\n let matched = false\n for (const [kind, pattern] of TOKEN_PATTERNS) {\n pattern.lastIndex = pos\n const match = pattern.exec(source)\n if (match === null || match[0].length === 0) continue\n const text = match[0]\n if (kind !== null) tokens.push({ kind, text, line })\n line += countNewlines(text)\n pos += text.length\n matched = true\n break\n }\n if (!matched) {\n throw lineError(lines, line, lexHint(source, pos, tokens.at(-1)))\n }\n }\n\n return tokens\n}\n\n/**\n * Langium's default `STRING` value conversion (ValueConverter.convertString):\n * drop the surrounding quotes and resolve backslash escapes. Mermaid's pie\n * value converter doesn't override it for slice labels.\n */\nfunction convertString(input: string): string {\n let result = ''\n for (let i = 1; i < input.length - 1; i++) {\n const ch = input.charAt(i)\n if (ch !== '\\\\') {\n result += ch\n continue\n }\n const escaped = input.charAt(++i)\n switch (escaped) {\n case 'b':\n result += '\\b'\n break\n case 'f':\n result += '\\f'\n break\n case 'n':\n result += '\\n'\n break\n case 'r':\n result += '\\r'\n break\n case 't':\n result += '\\t'\n break\n case 'v':\n result += '\\v'\n break\n case '0':\n result += '\\0'\n break\n default:\n result += escaped\n }\n }\n return result\n}\n\n/**\n * Mermaid's `AbstractMermaidValueConverter.runCommonConverter` for the\n * TITLE / ACC_TITLE / ACC_DESCR tokens.\n */\nfunction convertTitleLike(kind: TokenKind, text: string): string {\n const regex =\n kind === 'ACC_DESCR'\n ? ACC_DESCR_VALUE\n : kind === 'ACC_TITLE'\n ? ACC_TITLE_VALUE\n : TITLE_VALUE\n const match = regex.exec(text)\n if (match?.[1] !== undefined) {\n return match[1].trim().replace(/[\\t ]{2,}/gm, ' ')\n }\n // Only the `accDescr { … }` form leaves group 1 unset, and it always sets\n // group 2: every TITLE / ACC_TITLE / ACC_DESCR token matches its value\n // regex. (Mermaid's converter returns undefined if neither group is set.)\n /* v8 ignore next */\n const braced = match?.[2] ?? ''\n return braced\n .replace(/^\\s*/gm, '')\n .replace(/\\s+$/gm, '')\n .replace(/[\\t ]{2,}/gm, ' ')\n .replace(/[\\n\\r]{2,}/gm, '\\n')\n}\n\nconst DESCRIBE: Record<TokenKind, string> = {\n NEWLINE: 'end of line',\n PIE: '\"pie\"',\n SHOW_DATA: '\"showData\"',\n COLON: '\":\"',\n NUMBER: 'number',\n STRING: 'quoted label',\n TITLE: 'title',\n ACC_TITLE: 'accTitle',\n ACC_DESCR: 'accDescr',\n}\n\ninterface RawSlice extends PieSlice {\n line: number\n}\n\n/**\n * Parse Mermaid pie chart source text.\n *\n * Throws `Line N: …` errors (the shape the MCP diagnostics expect) for\n * anything Mermaid's own parser would reject.\n */\nexport function parsePieChart(text: string): PieChart {\n const source = preprocess(text)\n const lines = source.split('\\n')\n const tokens = tokenize(source, lines)\n let i = 0\n\n const unexpected = (token: Token | undefined, expected: string): Error => {\n if (token === undefined) {\n return lineError(\n lines,\n lines.length,\n `Unexpected end of input — expected ${expected}`,\n )\n }\n let message = `Unexpected ${DESCRIBE[token.kind]} — expected ${expected}`\n if (token.kind === 'SHOW_DATA') {\n message +=\n '; showData is only allowed directly after \"pie\" on the header line'\n } else if (token.kind === 'PIE') {\n message += '; the \"pie\" header may only appear once'\n }\n return lineError(lines, token.line, message)\n }\n\n /** Statements end at a newline or the end of the text (Mermaid's `EOL`). */\n const expectEndOfStatement = (what: string): void => {\n const next = tokens[i]\n if (next !== undefined && next.kind !== 'NEWLINE') {\n throw unexpected(next, `end of line after ${what}`)\n }\n }\n\n while (tokens[i]?.kind === 'NEWLINE') i++\n if (tokens[i]?.kind !== 'PIE') {\n throw unexpected(tokens[i], 'the \"pie\" header')\n }\n i++\n\n let showData = false\n if (tokens[i]?.kind === 'SHOW_DATA') {\n showData = true\n i++\n }\n\n let title = ''\n let accTitle = ''\n let accDescr = ''\n const rawSlices: RawSlice[] = []\n\n while (i < tokens.length) {\n const token = tokens[i]!\n switch (token.kind) {\n case 'NEWLINE':\n i++\n break\n case 'TITLE':\n case 'ACC_TITLE':\n case 'ACC_DESCR': {\n const value = convertTitleLike(token.kind, token.text)\n if (token.kind === 'TITLE') title = value\n else if (token.kind === 'ACC_TITLE') accTitle = value\n else accDescr = value\n i++\n expectEndOfStatement(DESCRIBE[token.kind])\n break\n }\n case 'STRING': {\n i++\n if (tokens[i]?.kind !== 'COLON') {\n throw unexpected(tokens[i], '\":\" after the slice label')\n }\n i++\n const valueToken = tokens[i]\n if (valueToken?.kind !== 'NUMBER') {\n throw unexpected(valueToken, 'a number after \":\"')\n }\n i++\n expectEndOfStatement('the slice value')\n rawSlices.push({\n label: convertString(token.text),\n value: Number(valueToken.text),\n line: token.line,\n })\n break\n }\n default:\n throw unexpected(\n token,\n 'a quoted slice label, title, accTitle, or accDescr',\n )\n }\n }\n\n // Mermaid's pieDb.addSection, applied in source order once the whole\n // chart has parsed: negative check first, then first-label-wins.\n const slices: PieSlice[] = []\n const seen = new Set<string>()\n for (const { label, value, line } of rawSlices) {\n if (value < 0) {\n throw new Error(\n `Line ${line}: \"${label}\" has invalid value: ${value}. Negative values are not allowed in pie charts. All slice values must be >= 0.`,\n )\n }\n if (seen.has(label)) continue\n seen.add(label)\n // `-0` passes Mermaid's `< 0` check; store it as plain 0.\n slices.push({ label, value: value === 0 ? 0 : value })\n }\n\n const chart: PieChart = { showData, slices }\n if (title) chart.title = title\n if (accTitle) chart.accTitle = accTitle\n if (accDescr) chart.accDescr = accDescr\n return chart\n}\n","// ============================================================================\n// zombie-mermaid — expanded node syntax A@{ shape: ..., label: ... }\n//\n// Mermaid v11.3.0 added a metadata form for node definitions:\n//\n// A@{ shape: rounded, label: \"Start here\" }\n// B@{ shape: doc }\n// C@{ icon: \"fa:bell\", form: \"circle\", label: \"Alert\" }\n// D@{ img: \"https://example.com/a.png\", label: \"Diagram\", w: 120, h: 80 }\n//\n// It exposes ~30 semantic shape names (many with several aliases) that the\n// classic bracket syntax cannot express. This module owns both halves of\n// supporting it: parsing the metadata block, and resolving a semantic name to\n// the geometry this renderer draws.\n// ============================================================================\n\nimport type { NodeShape } from '@zombie-mermaid/core'\n\n/**\n * Every documented Mermaid shape name (and alias) mapped to the geometry this\n * renderer draws for it.\n *\n * Mermaid's list is deliberately semantic — `database`, `manual-input`,\n * `paper-tape` — while a renderer only has so many distinct outlines. Names\n * that share an outline map to the same `NodeShape`; that is a rendering\n * choice, not a parse failure, and it is documented in docs/diagrams.md so\n * the collapse is discoverable rather than surprising.\n *\n * Keys are lowercase; lookup lowercases its input.\n */\nconst SHAPE_ALIASES: Record<string, NodeShape> = {\n // --- Rectangle family ---\n rect: 'rectangle',\n rectangle: 'rectangle',\n proc: 'rectangle',\n process: 'rectangle',\n 'normal-rect': 'rectangle',\n\n // --- Rounded ---\n rounded: 'rounded',\n event: 'rounded',\n 'rounded-rect': 'rounded',\n\n // --- Stadium / terminal ---\n stadium: 'stadium',\n pill: 'stadium',\n terminal: 'stadium',\n\n // --- Subroutine / framed rectangle ---\n subproc: 'subroutine',\n subprocess: 'subroutine',\n subroutine: 'subroutine',\n 'framed-rectangle': 'subroutine',\n 'fr-rect': 'subroutine',\n\n // --- Cylinder / database ---\n cyl: 'cylinder',\n cylinder: 'cylinder',\n db: 'cylinder',\n database: 'cylinder',\n 'h-cyl': 'cylinder',\n das: 'cylinder',\n 'horizontal-cylinder': 'cylinder',\n 'lin-cyl': 'cylinder',\n 'lined-cylinder': 'cylinder',\n disk: 'cylinder',\n\n // --- Circle ---\n circ: 'circle',\n circle: 'circle',\n start: 'circle',\n\n // --- Double circle ---\n 'dbl-circ': 'doublecircle',\n 'double-circle': 'doublecircle',\n stop: 'doublecircle',\n\n // --- Filled / crossed circles ---\n 'f-circ': 'filled-circle',\n 'filled-circle': 'filled-circle',\n junction: 'filled-circle',\n 'cross-circ': 'crossed-circle',\n 'crossed-circle': 'crossed-circle',\n summary: 'crossed-circle',\n\n // --- Diamond / decision ---\n diam: 'diamond',\n diamond: 'diamond',\n decision: 'diamond',\n question: 'diamond',\n\n // --- Hexagon / prepare ---\n hex: 'hexagon',\n hexagon: 'hexagon',\n prepare: 'hexagon',\n\n // --- Asymmetric / odd ---\n odd: 'asymmetric',\n 'rect-left-inv-arrow': 'asymmetric',\n\n // --- Parallelograms ---\n 'lean-r': 'parallelogram',\n 'lean-right': 'parallelogram',\n 'in-out': 'parallelogram',\n 'lean-l': 'parallelogram-alt',\n 'lean-left': 'parallelogram-alt',\n 'out-in': 'parallelogram-alt',\n\n // --- Trapezoids ---\n 'trap-b': 'trapezoid',\n 'trapezoid-bottom': 'trapezoid',\n priority: 'trapezoid',\n 'trap-t': 'trapezoid-alt',\n 'trapezoid-top': 'trapezoid-alt',\n manual: 'trapezoid-alt',\n 'curv-trap': 'trapezoid-alt',\n 'curved-trapezoid': 'trapezoid-alt',\n display: 'trapezoid-alt',\n\n // --- Document family ---\n doc: 'document',\n document: 'document',\n 'lin-doc': 'document',\n 'lined-document': 'document',\n 'tag-doc': 'document',\n 'tagged-document': 'document',\n docs: 'stacked-document',\n documents: 'stacked-document',\n 'st-doc': 'stacked-document',\n 'stacked-document': 'stacked-document',\n\n // --- Card / notched rectangle ---\n 'notch-rect': 'card',\n card: 'card',\n 'notched-rectangle': 'card',\n\n // --- Lined / divided / tagged rectangles ---\n 'lin-rect': 'lined-process',\n 'lined-rectangle': 'lined-process',\n 'lin-proc': 'lined-process',\n 'shaded-process': 'lined-process',\n 'div-rect': 'divided-process',\n 'divided-rectangle': 'divided-process',\n 'div-proc': 'divided-process',\n 'tag-rect': 'rectangle',\n 'tagged-rectangle': 'rectangle',\n 'tag-proc': 'rectangle',\n procs: 'stacked-process',\n processes: 'stacked-process',\n 'st-rect': 'stacked-process',\n 'stacked-rectangle': 'stacked-process',\n\n // --- Triangles ---\n tri: 'triangle',\n triangle: 'triangle',\n extract: 'triangle',\n 'flip-tri': 'flipped-triangle',\n 'flipped-triangle': 'flipped-triangle',\n 'manual-file': 'flipped-triangle',\n\n // --- Window pane / internal storage ---\n 'win-pane': 'window-pane',\n 'window-pane': 'window-pane',\n 'internal-storage': 'window-pane',\n\n // --- Fork / join ---\n fork: 'fork-join',\n join: 'fork-join',\n 'long-rect': 'fork-join',\n\n // --- Notched pentagon / loop limit ---\n 'notch-pent': 'notched-pentagon',\n 'loop-limit': 'notched-pentagon',\n 'notched-pentagon': 'notched-pentagon',\n\n // --- Sloped rectangle / manual input ---\n 'sl-rect': 'sloped-rectangle',\n 'sloped-rectangle': 'sloped-rectangle',\n 'manual-input': 'sloped-rectangle',\n\n // --- Flag / paper tape ---\n flag: 'flag',\n 'paper-tape': 'flag',\n\n // --- Bow-tie rectangle / stored data ---\n 'bow-rect': 'bow-tie-rectangle',\n 'bow-tie-rectangle': 'bow-tie-rectangle',\n 'stored-data': 'bow-tie-rectangle',\n\n // --- Delay / half-rounded rectangle ---\n delay: 'half-rounded-rectangle',\n 'half-rounded-rectangle': 'half-rounded-rectangle',\n\n // --- Braces / comment ---\n brace: 'brace',\n 'brace-l': 'brace',\n comment: 'brace',\n 'brace-r': 'brace-right',\n braces: 'braces',\n\n // --- Lightning bolt / communication link ---\n bolt: 'bolt',\n 'com-link': 'bolt',\n 'lightning-bolt': 'bolt',\n\n // --- Bare text, no outline ---\n text: 'text',\n\n // --- Anchor / hidden ---\n anchor: 'anchor',\n}\n\n/**\n * Resolve a Mermaid shape name to the geometry this renderer draws.\n * Returns `undefined` for an unrecognized name so the caller can decide\n * whether to fall back or report it.\n */\nexport function resolveShapeName(name: string): NodeShape | undefined {\n return SHAPE_ALIASES[name.trim().toLowerCase()]\n}\n\n/** Every shape name this renderer accepts, for documentation and tests. */\nexport function knownShapeNames(): string[] {\n return Object.keys(SHAPE_ALIASES).sort()\n}\n\n/** The metadata a `@{ ... }` block can carry. */\nexport interface ExpandedNodeMeta {\n shape?: string\n label?: string\n icon?: string\n img?: string\n /** Outline drawn around an icon or image: `square`, `circle`, `rounded`. */\n form?: string\n /** Explicit width/height for an image node. */\n w?: string\n h?: string\n /** Image fit mode Mermaid accepts alongside `img`. */\n constraint?: string\n /** Any other key seen, preserved rather than dropped. */\n [key: string]: string | undefined\n}\n\n/**\n * Parse the body of a `@{ ... }` block into key/value pairs.\n *\n * The body is a comma-separated list of `key: value`. Values may be quoted\n * with `\"` or `'`, and a quoted value may contain commas, colons, and braces\n * — which is why this is a scanner rather than a `split(',')`.\n *\n * Mermaid also accepts a bare value with no key as shorthand for the shape\n * (`A@{ rounded }`); that is handled by the caller, which sees an entry with\n * an empty key.\n */\nexport function parseExpandedMeta(body: string): ExpandedNodeMeta {\n const meta: ExpandedNodeMeta = {}\n\n for (const entry of splitTopLevel(body, ',')) {\n const trimmed = entry.trim()\n if (trimmed.length === 0) continue\n\n const colon = indexOfTopLevel(trimmed, ':')\n if (colon === -1) {\n // Bare value — Mermaid's shorthand for `shape: <value>`.\n meta.shape ??= stripQuotes(trimmed)\n continue\n }\n\n const key = trimmed.slice(0, colon).trim().toLowerCase()\n const value = stripQuotes(trimmed.slice(colon + 1).trim())\n if (key.length > 0) meta[key] = value\n }\n\n return meta\n}\n\n/** Split on `separator`, ignoring separators inside quotes. */\nfunction splitTopLevel(text: string, separator: string): string[] {\n const parts: string[] = []\n let current = ''\n let quote: string | null = null\n\n for (const ch of text) {\n if (quote !== null) {\n current += ch\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n current += ch\n continue\n }\n if (ch === separator) {\n parts.push(current)\n current = ''\n continue\n }\n current += ch\n }\n\n parts.push(current)\n return parts\n}\n\n/** Index of the first `needle` outside quotes, or -1. */\nfunction indexOfTopLevel(text: string, needle: string): number {\n let quote: string | null = null\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === needle) return i\n }\n return -1\n}\n\n/** Remove one layer of matching wrapping quotes. */\nfunction stripQuotes(value: string): string {\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n return value.slice(1, -1)\n }\n return value\n}\n\n/**\n * Find the `@{ ... }` block at the start of `text`, returning its body and\n * total length.\n *\n * Brace matching is depth-aware and quote-aware, so a label containing `}`\n * (`A@{ label: \"a } b\" }`) does not terminate the block early. Returns\n * `undefined` if `text` does not open with `@{` or the block is unterminated.\n */\nexport function matchExpandedBlock(\n text: string,\n): { body: string; length: number } | undefined {\n if (!text.startsWith('@{')) return undefined\n\n let depth = 0\n let quote: string | null = null\n\n for (let i = 1; i < text.length; i++) {\n const ch = text[i]!\n\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === '{') {\n depth++\n continue\n }\n if (ch === '}') {\n depth--\n if (depth === 0) {\n return { body: text.slice(2, i), length: i + 1 }\n }\n }\n }\n\n return undefined\n}\n","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 './expanded-shapes.ts'\nimport type { ExpandedNodeMeta } from './expanded-shapes.ts'\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\", \"pie\"'\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 *other* headers\n * (sequenceDiagram/classDiagram/erDiagram/xychart-beta/stateDiagram-v2/pie);\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 // Mermaid's `pie` keyword is case-sensitive, so `PieChart` lands here.\n if (/^pie/.test(lower)) return 'pie'\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 =\n /^(<|o|x)?(-{2,}|={2,}|-\\.+-|~{3,})(>|o|x)?(?:\\s*\\|([^|]*)\\|)?/\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 — register it if the node doesn't exist yet.\n // If it already exists, it stays in the subgraph that claimed it first\n // (nodes belong to the subgraph where they're first defined). A node that\n // no subgraph has claimed yet (declared earlier at the top level, e.g.\n // `X --> A` before `subgraph SA; A; end`) is adopted by the current one,\n // as Mermaid does; otherwise the frame would come out empty (#1289).\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 } else if (!isClaimedBySubgraph(graph, subgraphStack, id)) {\n trackInSubgraph(subgraphStack, id)\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/** True when any finished or still-open subgraph already lists `nodeId`. */\nfunction isClaimedBySubgraph(\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n nodeId: string,\n): boolean {\n const claims = (sg: MermaidSubgraph): boolean =>\n sg.nodeIds.includes(nodeId) || sg.children.some(claims)\n return graph.subgraphs.some(claims) || subgraphStack.some(claims)\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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"}
|