From 574d76c674bb387a9013eec7b299b80acba84e9a Mon Sep 17 00:00:00 2001 From: Ashish Jain Date: Thu, 18 Sep 2025 12:58:43 +0200 Subject: [PATCH] fixed broken test in sequence migration to antlr new strucuture --- demos/sequence-antlr-test.html | 312 ++++++++++++++++++ .../sequence/parser/antlr/SequenceListener.ts | 2 + .../parser/antlr/SequenceParserCore.ts | 136 ++++++-- .../sequence/parser/antlr/SequenceVisitor.ts | 3 +- 4 files changed, 428 insertions(+), 25 deletions(-) create mode 100644 demos/sequence-antlr-test.html diff --git a/demos/sequence-antlr-test.html b/demos/sequence-antlr-test.html new file mode 100644 index 000000000..d1562a44f --- /dev/null +++ b/demos/sequence-antlr-test.html @@ -0,0 +1,312 @@ + + + + + + + Mermaid Sequence ANTLR Parser Test Page + + + + + +

🎯 Mermaid Sequence ANTLR Parser Test Page

+ +
+

🔧 Parser Information

+

Environment Variable: Loading...

+

Expected: USE_ANTLR_PARSER=true

+

Status: Checking...

+
+ +
+

Test 1: Basic Sequence Diagram

+

Simple sequence diagram to test basic ANTLR parser functionality:

+
+sequenceDiagram
+  Alice->>Bob: Hello Bob, how are you?
+  Bob-->>Alice: Great!
+    
+
+ +
+

Test 2: Participants and Actors

+

Testing participant and actor declarations:

+
+sequenceDiagram
+  participant A as Alice
+  participant B as Bob
+  actor C as Charlie
+  
+  A->>B: Hello Bob
+  B->>C: Hi Charlie
+  C-->>A: Hello Alice
+    
+
+ +
+

Test 3: Different Arrow Types

+

Testing various arrow types and message formats:

+
+sequenceDiagram
+  Alice->>Bob: Solid arrow
+  Bob-->>Alice: Dotted arrow
+  Alice-xBob: Cross ending
+  Bob--xAlice: Dotted cross
+  Alice-)Bob: Open arrow
+  Bob--)Alice: Dotted open
+    
+
+ +
+

Test 4: Activation Boxes

+

Testing activation boxes and lifelines:

+
+sequenceDiagram
+  Alice->>+John: Hello John, how are you?
+  Alice->>+John: John, can you hear me?
+  John-->>-Alice: Hi Alice, I can hear you!
+  John-->>-Alice: I feel great!
+    
+
+ +
+

Test 5: Notes and Comments

+

Testing notes over participants:

+
+sequenceDiagram
+  participant Alice
+  participant Bob
+  
+  Alice->>Bob: Hello Bob
+  Note over Alice,Bob: This is a note over both
+  Note right of Bob: This note is on right of Bob
+  Note left of Alice: This note is on left of Alice
+  Bob-->>Alice: Hi Alice
+    
+
+ +
+

Test 6: Loops and Alt

+

Testing control flow structures:

+
+sequenceDiagram
+  Alice->>Bob: Hello Bob
+  
+  loop Every minute
+    Bob-->>Alice: Great!
+  end
+  
+  alt is sick
+    Bob->>Alice: Not so good :(
+  else is well
+    Bob->>Alice: Feeling fresh like a daisy
+  end
+    
+
+ + + + + diff --git a/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceListener.ts b/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceListener.ts index 3606d18dd..4c5eac10d 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceListener.ts +++ b/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceListener.ts @@ -37,6 +37,8 @@ export class SequenceListener extends SequenceParserCore implements ParseTreeLis // Optional: Handle error nodes // eslint-disable-next-line no-console console.log('❌ SequenceListener: Error node encountered'); + // Throw error to match Jison parser behavior for syntax errors + throw new Error('Syntax error in sequence diagram'); }; // Loop block handlers diff --git a/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceParserCore.ts b/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceParserCore.ts index 648c5fd56..9fb8913d6 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceParserCore.ts +++ b/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceParserCore.ts @@ -273,14 +273,16 @@ export class SequenceParserCore { try { const raw = ctx.restOfLine?.()?.getText?.() as string | undefined; const line = raw ? (raw.startsWith(':') ? raw.slice(1) : raw).trim() : ''; - const data = this.db.parseBoxData(line); - this.db.addBox(data); + // RECT should generate RECT_START signal with parsed message, matching Jison behavior + const parsedMessage = this.db.parseMessage(line); + this.db.addSignal(undefined, undefined, parsedMessage, this.db.LINETYPE.RECT_START); } catch {} } protected processRectBlockExit(): void { try { - this.db.boxEnd(); + // RECT should generate RECT_END signal, not box end + this.db.addSignal(undefined, undefined, undefined, this.db.LINETYPE.RECT_END); } catch {} } @@ -341,6 +343,14 @@ export class SequenceParserCore { } catch {} } + // Helper method to ensure actor exists (matching Jison behavior) + protected ensureActorExists(actorId: string): void { + if (!this.db.getActors().has(actorId)) { + // Create actor implicitly with default participant type + this.db.addActor(actorId, actorId, { text: actorId, type: 'participant' }, 'participant'); + } + } + // Signal statement processing protected processSignalStatement(ctx: any): void { try { @@ -355,19 +365,45 @@ export class SequenceParserCore { return; } + // Create actors implicitly if they don't exist (matching Jison behavior) + this.ensureActorExists(from); + this.ensureActorExists(to); + const signalType = ctx.signaltype?.()?.getText?.() as string | undefined; if (!signalType) { return; } - const msgText = ctx.text2?.()?.getText?.() as string | undefined; + const rawText = ctx.text2?.()?.getText?.() as string | undefined; + // Strip leading colon from TXT token (TXT includes ':' prefix) + const msgText = + rawText && rawText.startsWith(':') ? rawText.slice(1).trim() : rawText?.trim(); const msg = msgText ? this.db.parseMessage(msgText) : undefined; const lineType = this.mapSignalType(signalType); + // Check for activation/deactivation symbols (matching original ANTLR logic) + const hasPlus = !!ctx.PLUS?.(); + const hasMinus = !!ctx.MINUS?.(); + if (lineType !== undefined) { - this.db.addSignal(from, to, msg, lineType); + // Main signal; pass 'activate' flag if there is a plus before the target actor + this.db.addSignal(from, to, msg, lineType, hasPlus); + + // One-line activation/deactivation side-effects (matching original ANTLR logic) + if (hasPlus && to) { + this.db.addSignal(to, undefined, undefined, this.db.LINETYPE.ACTIVE_START); + } + if (hasMinus && from) { + this.db.addSignal(from, undefined, undefined, this.db.LINETYPE.ACTIVE_END); + } } - } catch {} + } catch (error) { + // Re-throw validation errors (like activation errors) so tests can catch them + if (error instanceof Error && error.message.includes('inactivate an inactive participant')) { + throw error; + } + // Silently ignore other parsing errors + } } // Note statement processing @@ -378,13 +414,46 @@ export class SequenceParserCore { const actor1 = actors?.[0]?.getText?.() as string | undefined; const actor2 = actors?.[1]?.getText?.() as string | undefined; - const msgText = ctx.text2?.()?.getText?.() as string | undefined; - const msg = msgText ? this.db.parseMessage(msgText) : undefined; + // Ensure actors exist + if (actor1) { + this.ensureActorExists(actor1); + } + if (actor2) { + this.ensureActorExists(actor2); + } + const rawText = ctx.text2?.()?.getText?.() as string | undefined; + // Strip leading colon from TXT token (TXT includes ':' prefix) + const msgText = + rawText && rawText.startsWith(':') ? rawText.slice(1).trim() : rawText?.trim(); + const msg = msgText ? this.db.parseMessage(msgText) : { text: msgText || '' }; + + // Use the same pattern as Jison parser: create addNote object and let db.apply() handle it if (placement === 'over' && actor2) { - this.db.addSignal(actor1, actor2, msg, this.db.LINETYPE.NOTE); + // Note over two actors: Alice,Bob (pass array of actor strings) + const payload = { + type: 'addNote' as const, + placement: this.db.PLACEMENT.OVER, + actor: [actor1, actor2], + text: msg, + }; + this.db.apply(payload); } else if (actor1) { - this.db.addSignal(actor1, undefined, msg, this.db.LINETYPE.NOTE, placement); + // Note over single actor or left/right of actor (pass actor string) + const placementValue = + placement === 'over' + ? this.db.PLACEMENT.OVER + : placement === 'leftOf' + ? this.db.PLACEMENT.LEFTOF + : this.db.PLACEMENT.RIGHTOF; + + const payload = { + type: 'addNote' as const, + placement: placementValue, + actor: actor1, + text: msg, + }; + this.db.apply(payload); } } catch {} } @@ -396,7 +465,10 @@ export class SequenceParserCore { if (!actor) { return; } - const msgText = ctx.text2?.()?.getText?.() as string | undefined; + const rawText = ctx.text2?.()?.getText?.() as string | undefined; + // Strip leading colon from TXT token (TXT includes ':' prefix) + const msgText = + rawText && rawText.startsWith(':') ? rawText.slice(1).trim() : rawText?.trim(); const msg = msgText ? this.db.parseMessage(msgText) : undefined; this.db.addLinks(actor, msg); } catch {} @@ -409,9 +481,13 @@ export class SequenceParserCore { if (!actor) { return; } - const msgText = ctx.text2?.()?.getText?.() as string | undefined; + const rawText = ctx.text2?.()?.getText?.() as string | undefined; + // Strip leading colon from TXT token (TXT includes ':' prefix) + const msgText = + rawText && rawText.startsWith(':') ? rawText.slice(1).trim() : rawText?.trim(); const msg = msgText ? this.db.parseMessage(msgText) : undefined; - this.db.addLink(actor, msg); + // Use addALink for single link format (not addLink) + this.db.addALink(actor, msg); } catch {} } @@ -422,7 +498,10 @@ export class SequenceParserCore { if (!actor) { return; } - const msgText = ctx.text2?.()?.getText?.() as string | undefined; + const rawText = ctx.text2?.()?.getText?.() as string | undefined; + // Strip leading colon from TXT token (TXT includes ':' prefix) + const msgText = + rawText && rawText.startsWith(':') ? rawText.slice(1).trim() : rawText?.trim(); const msg = msgText ? this.db.parseMessage(msgText) : undefined; this.db.addProperties(actor, msg); } catch {} @@ -435,7 +514,10 @@ export class SequenceParserCore { if (!actor) { return; } - const msgText = ctx.text2?.()?.getText?.() as string | undefined; + const rawText = ctx.text2?.()?.getText?.() as string | undefined; + // Strip leading colon from TXT token (TXT includes ':' prefix) + const msgText = + rawText && rawText.startsWith(':') ? rawText.slice(1).trim() : rawText?.trim(); const msg = msgText ? this.db.parseMessage(msgText) : undefined; this.db.addDetails(actor, msg); } catch {} @@ -457,29 +539,36 @@ export class SequenceParserCore { } else if (isDeactivate) { this.db.addSignal(actor, undefined, undefined, this.db.LINETYPE.ACTIVE_END); } - } catch {} + } catch (error) { + // Re-throw validation errors (like activation errors) so tests can catch them + if (error instanceof Error && error.message.includes('inactivate an inactive participant')) { + throw error; + } + // Silently ignore other parsing errors + } } // Autonumber statement processing protected processAutonumberStatement(ctx: any): void { try { const isOff = !!ctx.OFF?.(); - const numTok = ctx.NUM?.(); - const nums = Array.isArray(numTok) ? numTok : numTok ? [numTok] : []; - const numTexts = nums.map((n) => n.getText?.() as string).filter(Boolean); + // The grammar uses ACTOR tokens for numbers, not NUM tokens + const actorTok = ctx.ACTOR?.(); + const actors = Array.isArray(actorTok) ? actorTok : actorTok ? [actorTok] : []; + const actorTexts = actors.map((n) => n.getText?.() as string).filter(Boolean); let start: number | undefined; let step: number | undefined; - if (numTexts.length >= 1) { - const v = Number.parseInt(numTexts[0], 10); + if (actorTexts.length >= 1) { + const v = Number.parseInt(actorTexts[0], 10); if (!Number.isNaN(v)) { start = v; } } - if (numTexts.length >= 2) { - const v = Number.parseInt(numTexts[1], 10); + if (actorTexts.length >= 2) { + const v = Number.parseInt(actorTexts[1], 10); if (!Number.isNaN(v)) { step = v; } @@ -499,7 +588,6 @@ export class SequenceParserCore { sequenceVisible: visible, signalType: this.db.LINETYPE.AUTONUMBER, }; - this.db.apply(payload); } catch {} } diff --git a/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceVisitor.ts b/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceVisitor.ts index 25a7efa82..52ee16a59 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceVisitor.ts +++ b/packages/mermaid/src/diagrams/sequence/parser/antlr/SequenceVisitor.ts @@ -70,7 +70,8 @@ export class SequenceVisitor extends SequenceParserCore implements SequenceParse visitErrorNode(_node: any): any { // eslint-disable-next-line no-console console.log('❌ SequenceVisitor: Error node encountered'); - return null; + // Throw error to match Jison parser behavior for syntax errors + throw new Error('Syntax error in sequence diagram'); } // Loop block visitors