mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-19 15:30:03 +02:00
fix: ANTLR parser interaction parameter passing
- Fixed callback argument parsing for empty parentheses: callback() now correctly passes undefined instead of empty string - Fixed tooltip parsing for call patterns: click A call callback() "tooltip" now correctly extracts tooltip - All interaction patterns now work correctly: * click nodeId call functionName(args) - with arguments * click nodeId call functionName() - without arguments * click nodeId call functionName() "tooltip" - with tooltip * click nodeId href "url" - direct links * click nodeId href "url" "tooltip" - links with tooltip - Improved lexer grammar to handle callback arguments as single tokens - All 13 interaction tests now passing (100% success rate)
This commit is contained in:
59
debug-callback-args.js
Normal file
59
debug-callback-args.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { execSync } from 'child_process';
|
||||||
|
|
||||||
|
console.log('=== DEBUGGING CALLBACK ARGUMENTS ===');
|
||||||
|
|
||||||
|
// Test the specific failing case
|
||||||
|
const testInput = 'graph TD\nA-->B\nclick A call callback("test0", test1, test2)';
|
||||||
|
console.log('Test input:', testInput);
|
||||||
|
|
||||||
|
// Create a temporary test file to debug the ANTLR parser
|
||||||
|
import fs from 'fs';
|
||||||
|
const testFile = `
|
||||||
|
// Debug callback arguments parsing
|
||||||
|
process.env.USE_ANTLR_PARSER = 'true';
|
||||||
|
|
||||||
|
const flow = require('./packages/mermaid/src/diagrams/flowchart/flowDb.ts');
|
||||||
|
const parser = require('./packages/mermaid/src/diagrams/flowchart/parser/antlr/antlr-parser.ts');
|
||||||
|
|
||||||
|
console.log('Testing callback arguments parsing...');
|
||||||
|
|
||||||
|
// Mock the setClickEvent to see what parameters it receives
|
||||||
|
const originalSetClickEvent = flow.default.setClickEvent;
|
||||||
|
flow.default.setClickEvent = function(...args) {
|
||||||
|
console.log('DEBUG setClickEvent called with args:', args);
|
||||||
|
console.log(' - nodeId:', args[0]);
|
||||||
|
console.log(' - functionName:', args[1]);
|
||||||
|
console.log(' - functionArgs:', args[2]);
|
||||||
|
console.log(' - args.length:', args.length);
|
||||||
|
return originalSetClickEvent.apply(this, args);
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = parser.parse('${testInput}');
|
||||||
|
console.log('Parse completed successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Parse error:', error.message);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
fs.writeFileSync('debug-callback-test.js', testFile);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = execSync('node debug-callback-test.js', {
|
||||||
|
cwd: '/Users/ashishjain/projects/mermaid',
|
||||||
|
encoding: 'utf8',
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
console.log('Result:', result);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error:', error.message);
|
||||||
|
if (error.stdout) console.log('Stdout:', error.stdout);
|
||||||
|
if (error.stderr) console.log('Stderr:', error.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
try {
|
||||||
|
fs.unlinkSync('debug-callback-test.js');
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore cleanup errors
|
||||||
|
}
|
@@ -3,7 +3,7 @@ lexer grammar FlowLexer;
|
|||||||
// Virtual tokens for parser
|
// Virtual tokens for parser
|
||||||
tokens {
|
tokens {
|
||||||
NODIR, DIR, PIPE, PE, SQE, DIAMOND_STOP, STADIUMEND, SUBROUTINEEND, CYLINDEREND, DOUBLECIRCLEEND,
|
NODIR, DIR, PIPE, PE, SQE, DIAMOND_STOP, STADIUMEND, SUBROUTINEEND, CYLINDEREND, DOUBLECIRCLEEND,
|
||||||
ELLIPSE_END_TOKEN, TRAPEND, INVTRAPEND, PS, SQS, TEXT, CIRCLEEND, STR
|
ELLIPSE_END_TOKEN, TRAPEND, INVTRAPEND, PS, SQS, TEXT, CIRCLEEND, STR, CALLBACKARGS
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lexer modes to match Jison's state-based lexing
|
// Lexer modes to match Jison's state-based lexing
|
||||||
@@ -157,13 +157,10 @@ SHAPE_DATA_STRING_END: '"' -> popMode;
|
|||||||
SHAPE_DATA_STRING_CONTENT: (~["]+);
|
SHAPE_DATA_STRING_CONTENT: (~["]+);
|
||||||
|
|
||||||
mode CALLBACKNAME_MODE;
|
mode CALLBACKNAME_MODE;
|
||||||
|
// Simplified approach: match the entire callback with arguments as one token
|
||||||
|
CALLBACKNAME_WITH_ARGS: [A-Za-z0-9_]+ '(' (~[)])* ')' -> popMode, type(CALLBACKARGS);
|
||||||
CALLBACKNAME_PAREN_EMPTY: '(' WS* ')' -> popMode, type(CALLBACKARGS);
|
CALLBACKNAME_PAREN_EMPTY: '(' WS* ')' -> popMode, type(CALLBACKARGS);
|
||||||
CALLBACKNAME_PAREN_START: '(' -> popMode, pushMode(CALLBACKARGS_MODE);
|
CALLBACKNAME: [A-Za-z0-9_]+;
|
||||||
CALLBACKNAME: (~[(])*;
|
|
||||||
|
|
||||||
mode CALLBACKARGS_MODE;
|
|
||||||
CALLBACKARGS_END: ')' -> popMode;
|
|
||||||
CALLBACKARGS: (~[)])*;
|
|
||||||
|
|
||||||
mode CLICK_MODE;
|
mode CLICK_MODE;
|
||||||
CLICK_NEWLINE: ('\r'? '\n')+ -> popMode, type(NEWLINE);
|
CLICK_NEWLINE: ('\r'? '\n')+ -> popMode, type(NEWLINE);
|
||||||
|
@@ -1143,7 +1143,12 @@ class FlowchartListener implements ParseTreeListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.db.setLink(nodeId, url, target);
|
// Only pass target parameter if it's defined (matches Jison behavior)
|
||||||
|
if (target !== undefined) {
|
||||||
|
this.db.setLink(nodeId, url, target);
|
||||||
|
} else {
|
||||||
|
this.db.setLink(nodeId, url);
|
||||||
|
}
|
||||||
if (tooltip) {
|
if (tooltip) {
|
||||||
this.db.setTooltip(nodeId, tooltip);
|
this.db.setTooltip(nodeId, tooltip);
|
||||||
}
|
}
|
||||||
@@ -1151,24 +1156,53 @@ class FlowchartListener implements ParseTreeListener {
|
|||||||
} else if (secondToken && secondToken.trim() === 'call') {
|
} else if (secondToken && secondToken.trim() === 'call') {
|
||||||
// CALL patterns: click nodeId call functionName[(args)] [tooltip]
|
// CALL patterns: click nodeId call functionName[(args)] [tooltip]
|
||||||
if (children.length >= 3) {
|
if (children.length >= 3) {
|
||||||
const functionName = children[2].getText();
|
const callbackToken = children[2].getText();
|
||||||
|
|
||||||
|
let functionName = callbackToken;
|
||||||
let functionArgs = undefined;
|
let functionArgs = undefined;
|
||||||
let tooltip = undefined;
|
let tooltip = undefined;
|
||||||
|
|
||||||
// Check if function has arguments (CALLBACKARGS token)
|
// Check if the callback token contains arguments: functionName(args)
|
||||||
if (children.length >= 4) {
|
const callbackMatch = /^([A-Za-z0-9_]+)\(([^)]*)\)$/.exec(callbackToken);
|
||||||
const argsToken = children[3].getText();
|
if (callbackMatch) {
|
||||||
// Only set functionArgs if it's not empty parentheses
|
functionName = callbackMatch[1];
|
||||||
if (argsToken && argsToken.trim() !== '' && argsToken.trim() !== '()') {
|
functionArgs = callbackMatch[2];
|
||||||
functionArgs = argsToken;
|
// If arguments are empty, set to undefined to match Jison behavior
|
||||||
|
if (functionArgs.trim() === '') {
|
||||||
|
functionArgs = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check if function has arguments in a separate token (CALLBACKARGS token)
|
||||||
|
if (children.length >= 4) {
|
||||||
|
const argsToken = children[3].getText();
|
||||||
|
|
||||||
|
// Handle different argument formats
|
||||||
|
if (argsToken && argsToken.trim() !== '' && argsToken.trim() !== '()') {
|
||||||
|
// If it's just parentheses with content, extract the content
|
||||||
|
if (argsToken.startsWith('(') && argsToken.endsWith(')')) {
|
||||||
|
functionArgs = argsToken.slice(1, -1); // Remove outer parentheses
|
||||||
|
} else {
|
||||||
|
functionArgs = argsToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for tooltip
|
// Check for tooltip
|
||||||
if (children.length >= 5) {
|
// For call patterns, tooltip can be in different positions:
|
||||||
const lastToken = children[children.length - 1].getText();
|
// - If callback has args in same token: click A call callback(args) "tooltip" -> tooltip at index 3
|
||||||
if (lastToken.startsWith('"')) {
|
// - If callback has no args: click A call callback() "tooltip" -> tooltip at index 3
|
||||||
tooltip = this.extractStringContent(lastToken);
|
// - If callback has separate args token: click A call callback (args) "tooltip" -> tooltip at index 4
|
||||||
|
if (children.length >= 4) {
|
||||||
|
const tooltipToken = children[3].getText();
|
||||||
|
if (tooltipToken && tooltipToken.startsWith('"') && tooltipToken.endsWith('"')) {
|
||||||
|
tooltip = this.extractStringContent(tooltipToken);
|
||||||
|
} else if (children.length >= 5) {
|
||||||
|
// Check index 4 for separate args case
|
||||||
|
const tooltipToken4 = children[4].getText();
|
||||||
|
if (tooltipToken4 && tooltipToken4.startsWith('"') && tooltipToken4.endsWith('"')) {
|
||||||
|
tooltip = this.extractStringContent(tooltipToken4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user