From 6be8803ad489782bc4c8e7dc55d3207797eb82d1 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Mon, 11 Aug 2025 08:42:49 +0200 Subject: [PATCH] 0 failing --- .../diagrams/flowchart/parser/flowParser.ts | 147 ++++++++++++++++-- 1 file changed, 138 insertions(+), 9 deletions(-) diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flowParser.ts b/packages/mermaid/src/diagrams/flowchart/parser/flowParser.ts index 1d147857b..cfa3261cd 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flowParser.ts +++ b/packages/mermaid/src/diagrams/flowchart/parser/flowParser.ts @@ -185,6 +185,67 @@ class LezerFlowParser { while (i < tokens.length) { const token = tokens[i]; + // Normalize reserved tokens when used as identifiers or inside identifiers + if (token.type === 'DEFAULT') { + const prev = processedTokens[processedTokens.length - 1]; + // Keep DEFAULT only when used in linkStyle (immediately after LINKSTYLE) + if (!(prev && prev.type === 'LINKSTYLE')) { + processedTokens.push({ ...token, type: 'NODE_STRING', value: 'default' }); + i++; + continue; + } + } + if (token.type === 'END') { + const prev = processedTokens[processedTokens.length - 1]; + const next = i + 1 < tokens.length ? tokens[i + 1] : undefined; + let merged = false; + // Case 1: Merge prev + 'end' when adjacent (no whitespace) + if ( + prev && + (prev.type === 'Identifier' || prev.type === 'NODE_STRING') && + prev.to === token.from + ) { + let newVal = prev.value + 'end'; + let newTo = token.to; + // Also merge next if adjacent and identifier-like + if ( + next && + (next.type === 'Identifier' || next.type === 'NODE_STRING') && + token.to === next.from + ) { + newVal += next.value; + newTo = next.to; + i += 1; // additionally skip next + } + // Replace prev with merged NODE_STRING + processedTokens[processedTokens.length - 1] = { + type: 'NODE_STRING', + value: newVal, + from: prev.from, + to: newTo, + } as any; + i++; + merged = true; + continue; + } + // Case 2: Merge 'end' + next when adjacent (start of identifier) + if ( + next && + (next.type === 'Identifier' || next.type === 'NODE_STRING') && + token.to === next.from + ) { + processedTokens.push({ + type: 'NODE_STRING', + value: 'end' + next.value, + from: token.from, + to: next.to, + } as any); + i += 2; + continue; + } + // Otherwise, keep END as a standalone token (e.g., subgraph end) + } + // Skip non-statement tokens if (token.type === 'GRAPH' || token.type === 'DIR' || token.type === 'SEMI') { processedTokens.push(token); @@ -1130,6 +1191,19 @@ class LezerFlowParser { log.debug(`UIO Processing token ${i}: ${token.type} = "${token.value}"`); log.debug(`UIO Processing token ${i}: ${token.type} = "${token.value}"`); + // Handle accessibility statements early to avoid misinterpreting them as nodes + if (typeof token.value === 'string') { + const v = token.value.trim(); + if (v.startsWith('accTitle')) { + i = this.parseAccTitleStatement(tokens, i); + continue; + } + if (v.startsWith('accDescr')) { + i = this.parseAccDescrStatement(tokens, i); + continue; + } + } + switch (token.type) { case 'GRAPH': case 'Flowchart': @@ -1166,7 +1240,23 @@ class LezerFlowParser { break; } - // Priority 2: Orphaned shape token for the last referenced node (e.g., A-->B>text]) + // Disambiguate TagEnd usage: odd-shape start vs continuation arrow head + if (token.type === 'TagEnd' && token.value === '>') { + const oddAhead = this.isOddShapeAhead(tokens, i); + if (oddAhead && this.lastReferencedNodeId) { + console.log( + `UIO DEBUG: TagEnd '>' detected odd-shape ahead; applying orphaned shape to ${this.lastReferencedNodeId}` + ); + i = this.parseOrphanedShapeStatement(tokens, i); + break; + } + if (this.lastTargetNodes.length > 0) { + i = this.parseContinuationEdgeStatement(tokens, i); + break; + } + } + + // Orphaned shape token for the last referenced node (e.g., A-->B>text]) if (this.isShapeStart(token) && this.lastReferencedNodeId) { console.log( `UIO DEBUG: Detected orphaned shape token '${token.type}:${token.value}' for lastReferencedNodeId=${this.lastReferencedNodeId}` @@ -1175,12 +1265,6 @@ class LezerFlowParser { break; } - // Priority 3: Continuation edge head (e.g., A-->B-->C) - if (token.type === 'TagEnd' && token.value === '>' && this.lastTargetNodes.length > 0) { - i = this.parseContinuationEdgeStatement(tokens, i); - break; - } - // Fallback: Delegate to parseStatement i = this.parseStatement(tokens, i); break; @@ -1265,6 +1349,7 @@ class LezerFlowParser { i++; } else if (token.type === 'TagEnd' && token.value === '>') { // Handle '>' as LR direction + direction = 'LR'; i++; } else if (token.value === '<' || token.value === '^' || token.value === 'v') { @@ -1470,6 +1555,18 @@ class LezerFlowParser { return this.parseNodeStatement(tokens, i); } + // Standalone class application before an edge: ':::className' applies to last created node + if ( + lookahead.length >= 1 && + lookahead[0].type === 'NODE_STRING' && + lookahead[0].value.startsWith(':::') + ) { + console.log( + `UIO DEBUG: Taking node statement path (standalone inline class application detected)` + ); + return this.parseNodeStatement(tokens, i); + } + // Edge property block: edgeId@{...} if ( lookahead.length >= 3 && @@ -1909,6 +2006,38 @@ class LezerFlowParser { return false; } + /** + * Heuristic: determine if a TagEnd '>' at index starts an odd-shape (>text]) + * rather than acting as a split arrow head in chaining (e.g., B-->C --> D). + * Looks ahead a short distance: if we see a SquareEnd ']' without encountering + * another LINK/Arrow or semicolon, treat it as an odd-shape start. + */ + private isOddShapeAhead( + tokens: { type: string; value: string; from: number; to: number }[], + index: number + ): boolean { + // Expect current token to be TagEnd '>' + let sawText = false; + for (let j = index + 1; j < Math.min(tokens.length, index + 12); j++) { + const t = tokens[j]; + if (t.type === 'SEMI') { + return false; + } // statement end before shape close + if (t.type === 'LINK' || t.type === 'Arrow') { + return false; + } // likely part of an edge + if (t.type === 'SquareEnd') { + return sawText; + } // found closing ']' after some text + if (t.type === 'NODE_STRING' || t.type === 'STR' || t.type === '⚠') { + // Consider punctuation/text as content + sawText = true; + continue; + } + } + return false; + } + /** * Parse a shaped node for a specific target ID (used for embedded arrow patterns) * @param tokens - Array of tokens @@ -5029,9 +5158,9 @@ class LezerFlowParser { // Title-only case: pass same object for id and title so FlowDB infers generated id if (titleOnly) { const same = { text: titleText, type: titleType } as const; - this.yy.addSubGraph(same as any, subgraphDocument, same as any); + this.yy.addSubGraph(same as any, subgraphDocument as any, same as any); } else { - this.yy.addSubGraph({ text: idText }, subgraphDocument, { + this.yy.addSubGraph({ text: idText }, subgraphDocument as any, { text: titleText, type: titleType, });