From 10868cfc539a5b20527a4017f3f759458bf7904e Mon Sep 17 00:00:00 2001 From: Roman Davydchuk Date: Fri, 20 Mar 2026 15:42:14 +0000 Subject: [PATCH 1/3] fix: Handle quotes --- .../src/node-reference-parser-utils.ts | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/workflow/src/node-reference-parser-utils.ts b/packages/workflow/src/node-reference-parser-utils.ts index 28b9895ffbbc..aaf5520080b0 100644 --- a/packages/workflow/src/node-reference-parser-utils.ts +++ b/packages/workflow/src/node-reference-parser-utils.ts @@ -101,24 +101,51 @@ const ACCESS_PATTERNS: AccessPattern[] = [ }, ]; +export function prepareOldNodeName(nodeName: string) { + // if node name contains literal \ -> replace with \\ + // since that's how it'll be written in a JS expression + const doubleSlashes = nodeName.replace('\\', '\\\\'); + // escape special characters for regex + const escaped = backslashEscape(doubleSlashes); + // quotes may or may not be escaped in the JS expression + // so we replace literal quotes with regexes handle that + return escaped.replace(/"/g, '(?:\\\\?")').replace(/'/g, "(?:\\\\?')"); +} + +export function prepareNewNodeName(nodeName: string) { + // escape $ for replacement regex + const dollarEscaped = dollarEscape(nodeName); + // escape literal \ ' " characters + return dollarEscaped.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll("'", "\\'"); +} + export function applyAccessPatterns(expression: string, previousName: string, newName: string) { // To not run the "expensive" regex stuff when it is not needed - // make a simple check first if it really contains the node-name - if (!expression.includes(previousName)) return expression; + // make a simple check first if it contains any of the access patterns + let noMatch = true; + for (const pattern of ACCESS_PATTERNS) { + if (expression.includes(pattern.checkPattern)) { + noMatch = false; + break; + } + } + + if (noMatch) { + return expression; + } - // Really contains node-name (even though we do not know yet if really as $node-expression) - const escapedOldName = backslashEscape(previousName); // for match - const escapedNewName = dollarEscape(newName); // for replacement + const preparedOldName = prepareOldNodeName(previousName); + const preparedNewName = prepareNewNodeName(newName); for (const pattern of ACCESS_PATTERNS) { if (expression.includes(pattern.checkPattern)) { expression = expression.replace( - new RegExp(pattern.replacePattern(escapedOldName), 'g'), - `$1${escapedNewName}$2`, + new RegExp(pattern.replacePattern(preparedOldName), 'g'), + `$1${preparedNewName}$2`, ); if (pattern.customCallback) { - expression = pattern.customCallback(expression, newName, escapedNewName); + expression = pattern.customCallback(expression, newName, preparedNewName); } } } From 45620570a0d42a9bfd561870d33dbccd8c9b1f58 Mon Sep 17 00:00:00 2001 From: Roman Davydchuk Date: Fri, 20 Mar 2026 21:06:26 +0000 Subject: [PATCH 2/3] fix: Backslash escaping --- packages/workflow/src/node-reference-parser-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workflow/src/node-reference-parser-utils.ts b/packages/workflow/src/node-reference-parser-utils.ts index aaf5520080b0..0e0c90f44094 100644 --- a/packages/workflow/src/node-reference-parser-utils.ts +++ b/packages/workflow/src/node-reference-parser-utils.ts @@ -104,7 +104,7 @@ const ACCESS_PATTERNS: AccessPattern[] = [ export function prepareOldNodeName(nodeName: string) { // if node name contains literal \ -> replace with \\ // since that's how it'll be written in a JS expression - const doubleSlashes = nodeName.replace('\\', '\\\\'); + const doubleSlashes = nodeName.replaceAll('\\', '\\\\'); // escape special characters for regex const escaped = backslashEscape(doubleSlashes); // quotes may or may not be escaped in the JS expression From 9b510290202ef2eaa86739062ca153b083ecb208 Mon Sep 17 00:00:00 2001 From: Roman Davydchuk Date: Fri, 20 Mar 2026 21:15:12 +0000 Subject: [PATCH 3/3] test: Add test cases --- .../src/node-reference-parser-utils.ts | 4 +- .../test/node-reference-parser-utils.test.ts | 46 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/packages/workflow/src/node-reference-parser-utils.ts b/packages/workflow/src/node-reference-parser-utils.ts index 0e0c90f44094..fb29c837b18e 100644 --- a/packages/workflow/src/node-reference-parser-utils.ts +++ b/packages/workflow/src/node-reference-parser-utils.ts @@ -101,7 +101,7 @@ const ACCESS_PATTERNS: AccessPattern[] = [ }, ]; -export function prepareOldNodeName(nodeName: string) { +function prepareOldNodeName(nodeName: string) { // if node name contains literal \ -> replace with \\ // since that's how it'll be written in a JS expression const doubleSlashes = nodeName.replaceAll('\\', '\\\\'); @@ -112,7 +112,7 @@ export function prepareOldNodeName(nodeName: string) { return escaped.replace(/"/g, '(?:\\\\?")').replace(/'/g, "(?:\\\\?')"); } -export function prepareNewNodeName(nodeName: string) { +function prepareNewNodeName(nodeName: string) { // escape $ for replacement regex const dollarEscaped = dollarEscape(nodeName); // escape literal \ ' " characters diff --git a/packages/workflow/test/node-reference-parser-utils.test.ts b/packages/workflow/test/node-reference-parser-utils.test.ts index 537abefb34f3..cee6b8e64565 100644 --- a/packages/workflow/test/node-reference-parser-utils.test.ts +++ b/packages/workflow/test/node-reference-parser-utils.test.ts @@ -126,10 +126,50 @@ describe('NodeReferenceParserUtils', () => { expected: 'someRandomExpression("oldName")', }, { - expression: '$("old\\"Name")', - previousName: 'old\\"Name', + expression: '$("someone\'s node")', + previousName: "someone's node", + newName: 'other node', + expected: '$("other node")', + }, + { + expression: '$(\'some "node"\')', + previousName: 'some "node"', + newName: 'other node', + expected: "$('other node')", + }, + { + expression: '$("test\\\\some")', + previousName: 'test\\some', + newName: 'other node', + expected: '$("other node")', + }, + { + expression: '$("old name")', + previousName: 'old name', + newName: 'new "name"', + expected: '$("new \\"name\\"")', + }, + { + expression: '$("old name")', + previousName: 'old name', + newName: "new 'name'", + expected: '$("new \\\'name\\\'")', + }, + { + expression: '$("test some")', + previousName: 'test some', + newName: 'other\\node', + expected: '$("other\\\\node")', + }, + { + // $("old\"Na\\me\'") -> old"Na\me' + expression: '$("old\\"Na\\\\me\\\'")', + // old"Na\me' + previousName: 'old"Na\\me\'', + // n\'ew\"Name newName: 'n\\\'ew\\"Name', - expected: '$("n\\\'ew\\"Name")', + // $("n\\\'ew\\\"Name") -> n\'ew\"Name + expected: '$("n\\\\\\\'ew\\\\\\"Name")', }, ])( 'should correctly transform expression "$expression" with previousName "$previousName" and newName "$newName"',