diff --git a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts index 7f3cac9447f36..18b4061330003 100644 --- a/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts +++ b/packages/editor-ui/src/utils/__tests__/nodeTypesUtils.spec.ts @@ -89,7 +89,7 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: expected: ['Edit Fields 2'], }, { - caseName: 'Should handle expressions with single and double quotes', + caseName: 'Should handle expressions with single quotes, double quotes and backticks', node: { parameters: { authentication: 'oAuth2', @@ -105,6 +105,11 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: value: '={{ $("Edit Fields 2").item.json.sheet }}', mode: 'id', }, + rowName: { + __rl: true, + value: '={{ $(`Edit Fields 3`).item.json.row }}', + mode: 'id', + }, filtersUI: {}, combineFilters: 'AND', options: {}, @@ -121,7 +126,7 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: }, }, }, - expected: ['Edit Fields', 'Edit Fields 2'], + expected: ['Edit Fields', 'Edit Fields 2', 'Edit Fields 3'], }, { caseName: 'Should only add one reference for each referenced node', @@ -182,7 +187,7 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: expected: ['Edit Fields', 'Edit Fields 2'], }, { - caseName: 'Should ignore spaces around node references', + caseName: 'Should respect whitespace around node references', node: { parameters: { curlImport: '', @@ -202,15 +207,15 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', name: 'HTTP Request', }, - expected: ['Edit Fields'], + expected: [' Edit Fields '], }, { - caseName: 'Should ignore non-existing nodes', + caseName: 'Should ignore whitespace inside expressions', node: { parameters: { curlImport: '', method: 'GET', - url: "={{ $(' Edit Fields ').item.json.one }}", + url: "={{ $( 'Edit Fields' ).item.json.one }}", authentication: 'none', provideSslCertificates: false, sendQuery: false, @@ -251,12 +256,13 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: expected: [], }, { - caseName: 'Should handle node names with quotes', + caseName: 'Should correctly detect node names that contain single quotes', node: { parameters: { curlImport: '', method: 'GET', - url: "={{ $('Edit 'Fields' 2').item.json.name }}", + // In order to carry over backslashes to test function, the string needs to be double escaped + url: "={{ $('Edit \\'Fields\\' 2').item.json.name }}", authentication: 'none', provideSslCertificates: false, sendQuery: false, @@ -273,6 +279,101 @@ const referencedNodesTestCases: Array<{ caseName: string; node: INode; expected: }, expected: ["Edit 'Fields' 2"], }, + { + caseName: 'Should correctly detect node names with inner backticks', + node: { + parameters: { + curlImport: '', + method: 'GET', + url: "={{ $('Edit `Fields` 2').item.json.name }}", + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ['Edit `Fields` 2'], + }, + { + caseName: 'Should correctly detect node names with inner escaped backticks', + node: { + parameters: { + curlImport: '', + method: 'GET', + // eslint-disable-next-line prettier/prettier + url: "={{ $(`Edit \\`Fields\\` 2`).item.json.name }}", + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ['Edit `Fields` 2'], + }, + { + caseName: 'Should correctly detect node names with inner escaped double quotes', + node: { + parameters: { + curlImport: '', + method: 'GET', + // In order to carry over backslashes to test function, the string needs to be double escaped + url: '={{ $("Edit \\"Fields\\" 2").item.json.name }}', + authentication: 'none', + provideSslCertificates: false, + sendQuery: false, + sendHeaders: false, + sendBody: false, + options: {}, + infoMessage: '', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: ['Edit "Fields" 2'], + }, + { + caseName: 'Should not detect invalid expressions', + node: { + parameters: { + curlImport: '', + method: 'GET', + // String not closed properly + url: "={{ $('Edit ' fields').item.json.document }", + // Mixed quotes + url2: '{{ $("Edit \'Fields" 2").item.json.name }}', + url3: '{{ $("Edit `Fields" 2").item.json.name }}', + // Quotes not escaped + url4: '{{ $("Edit "Fields" 2").item.json.name }}', + url5: "{{ $('Edit 'Fields' 2').item.json.name }}", + url6: '{{ $(`Edit `Fields` 2`).item.json.name }}', + }, + type: 'n8n-nodes-base.httpRequest', + typeVersion: 4.2, + position: [220, 220], + id: 'edc36001-aee7-4052-b66e-cf127f4b6ea5', + name: 'HTTP Request', + }, + expected: [], + }, ]; describe.each(referencedNodesTestCases)('getReferencedNodes', (testCase) => { diff --git a/packages/editor-ui/src/utils/nodeTypesUtils.ts b/packages/editor-ui/src/utils/nodeTypesUtils.ts index b0a8cffc87035..03f930c740bea 100644 --- a/packages/editor-ui/src/utils/nodeTypesUtils.ts +++ b/packages/editor-ui/src/utils/nodeTypesUtils.ts @@ -505,10 +505,9 @@ export const getNodeIconColor = ( /** Regular expression to extract the node names from the expressions in the template. - Example: $('expression') => expression - Or: $("expression") => expression + Supports single quotes, double quotes, and backticks. */ -const entityRegex = /\$\((\\?['"])(.*?)\1\)/g; +const entityRegex = /\$\(\s*(\\?["'`])((?:\\.|(?!\1)[^\\])*)\1\s*\)/g; /** * Extract the node names from the expressions in the template. @@ -522,6 +521,13 @@ function extractNodeNames(template: string): string[] { return nodeNames; } +/** + * Unescape quotes in the string. Supports single quotes, double quotes, and backticks. + */ +export function unescapeQuotes(str: string): string { + return str.replace(/\\(['"`])/g, '$1'); +} + /** * Extract the node names from the expressions in the node parameters. */ @@ -533,15 +539,21 @@ export function getReferencedNodes(node: INode): string[] { // Go through all parameters and check if they contain expressions on any level for (const key in node.parameters) { let names: string[] = []; - if (node.parameters[key] && typeof node.parameters[key] === 'object') { + if ( + node.parameters[key] && + typeof node.parameters[key] === 'object' && + Object.keys(node.parameters[key]).length + ) { names = extractNodeNames(JSON.stringify(node.parameters[key])); - } else if (typeof node.parameters[key] === 'string') { + } else if (typeof node.parameters[key] === 'string' && node.parameters[key]) { names = extractNodeNames(node.parameters[key]); } if (names.length) { - names.forEach((name) => { - referencedNodes.add(name.trim()); - }); + names + .map((name) => unescapeQuotes(name)) + .forEach((name) => { + referencedNodes.add(name); + }); } } return referencedNodes.size ? Array.from(referencedNodes) : [];