diff --git a/src/mermaidSequenceDiff.ts b/src/mermaidSequenceDiff.ts index d24ff04..e3f3ed7 100644 --- a/src/mermaidSequenceDiff.ts +++ b/src/mermaidSequenceDiff.ts @@ -1,4 +1,63 @@ -// stub, replaced in Tasks 4-5 +/** + * mermaidSequenceDiff — F7.1 (#22). Pure sequence-diagram parser + diff/emit. + * Mermaid sequence diagrams have NO per-message color directive; the only + * per-message hook is the `rect rgb(...) … end` background block. So the emitter + * rebuilds the message stream (ghosted-removed messages re-inserted at their + * baseline position) wrapping each added/changed/removed message in a one-message + * tinted rect, and re-declares removed participants (INV-29/31). Deterministic; + * no vscode, no DOM. + */ +import { diffArrays } from "diff"; +import { CW_COLORS } from "./mermaidDiff"; + +export interface SeqDiagram { + header: string; + participants: string[]; + /** Message/other statement lines, verbatim & trimmed, in order. */ + statements: string[]; +} + +// `A->>B: text`, plus ->, -->, -->>, -x, --x, -), --) variants. Captures from/to. +const MSG = /^([A-Za-z0-9_]+)\s*(-{1,2}(?:>>?|x|\)))\s*([A-Za-z0-9_]+)\s*:/; +const PARTICIPANT = /^(?:participant|actor)\s+([A-Za-z0-9_]+)/; + +export function parseSequence(source: string): SeqDiagram { + const lines = source.split(/\r?\n/); + let header = ""; + const declared: string[] = []; + const seen = new Set(); + const statements: string[] = []; + const addP = (p: string) => { + if (!seen.has(p)) { + seen.add(p); + declared.push(p); + } + }; + for (const rawLine of lines) { + const line = rawLine.trim(); + if (line === "" || line.startsWith("%%")) continue; + if (!header && /^sequenceDiagram\b/.test(line)) { + header = line; + continue; + } + const pm = line.match(PARTICIPANT); + if (pm) { + addP(pm[1]); + continue; + } + const mm = line.match(MSG); + if (mm) { + addP(mm[1]); + addP(mm[3]); + } + statements.push(line); + } + return { header: header || "sequenceDiagram", participants: declared, statements }; +} + +// Replaced in Task 5 with the real diff/emit. export function diffSequence(_beforeSrc: string, currentSrc: string): string { + void diffArrays; + void CW_COLORS; return currentSrc; } diff --git a/test/mermaidSequenceDiff.test.ts b/test/mermaidSequenceDiff.test.ts new file mode 100644 index 0000000..accfc26 --- /dev/null +++ b/test/mermaidSequenceDiff.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from "vitest"; +import { parseSequence } from "../src/mermaidSequenceDiff"; + +describe("parseSequence", () => { + it("captures explicit participants in order", () => { + const d = parseSequence("sequenceDiagram\n participant A\n participant B\n A->>B: hi"); + expect(d.participants).toEqual(["A", "B"]); + }); + + it("captures message statements verbatim (trimmed)", () => { + const d = parseSequence("sequenceDiagram\n A->>B: hi\n B-->>A: bye"); + expect(d.statements).toEqual(["A->>B: hi", "B-->>A: bye"]); + }); + + it("infers participants from messages when not declared", () => { + const d = parseSequence("sequenceDiagram\n A->>B: hi\n B->>C: on"); + expect(d.participants).toEqual(["A", "B", "C"]); + }); + + it("ignores comments and blank lines", () => { + const d = parseSequence("sequenceDiagram\n%% note\n\n A->>B: hi"); + expect(d.statements).toEqual(["A->>B: hi"]); + }); +});