chore: Extract node parsing from renameNode functionality (no-changelog) (#14822)

This commit is contained in:
Charlie Kolb
2025-04-23 13:22:08 +02:00
committed by GitHub
parent 6197b0cb6d
commit 88bce7fd8b
3 changed files with 207 additions and 53 deletions

View File

@@ -0,0 +1,71 @@
export function hasDotNotationBannedChar(nodeName: string) {
const DOT_NOTATION_BANNED_CHARS = /^(\d)|[\\ `!@#$%^&*()_+\-=[\]{};':"\\|,.<>?~]/g;
return DOT_NOTATION_BANNED_CHARS.test(nodeName);
}
export function backslashEscape(nodeName: string) {
const BACKSLASH_ESCAPABLE_CHARS = /[.*+?^${}()|[\]\\]/g;
return nodeName.replace(BACKSLASH_ESCAPABLE_CHARS, (char) => `\\${char}`);
}
export function dollarEscape(nodeName: string) {
return nodeName.replace(new RegExp('\\$', 'g'), '$$$$');
}
type AccessPattern = {
checkPattern: string;
replacePattern: (name: string) => string;
customCallback?: (expression: string, newName: string, escapedNewName: string) => string;
};
const ACCESS_PATTERNS: AccessPattern[] = [
{
checkPattern: '$(',
replacePattern: (s) => String.raw`(\$\(['"])${s}(['"]\))`,
},
{
checkPattern: '$node[',
replacePattern: (s) => String.raw`(\$node\[['"])${s}(['"]\])`,
},
{
checkPattern: '$node.',
replacePattern: (s) => String.raw`(\$node\.)${s}(\.?)`,
customCallback: (expression: string, newName: string, escapedNewName: string) => {
if (hasDotNotationBannedChar(newName)) {
const regex = new RegExp(`.${backslashEscape(newName)}( |\\.)`, 'g');
return expression.replace(regex, `["${escapedNewName}"]$1`);
}
return expression;
},
},
{
checkPattern: '$items(',
replacePattern: (s) => String.raw`(\$items\(['"])${s}(['"],|['"]\))`,
},
];
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;
// 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
for (const pattern of ACCESS_PATTERNS) {
if (expression.includes(pattern.checkPattern)) {
expression = expression.replace(
new RegExp(pattern.replacePattern(escapedOldName), 'g'),
`$1${escapedNewName}$2`,
);
if (pattern.customCallback) {
expression = pattern.customCallback(expression, newName, escapedNewName);
}
}
}
return expression;
}

View File

@@ -31,6 +31,7 @@ import type {
} from './Interfaces';
import { NodeConnectionTypes } from './Interfaces';
import * as NodeHelpers from './NodeHelpers';
import { applyAccessPatterns } from './NodeReferenceParserUtils';
import * as ObservableObject from './ObservableObject';
function dedupe<T>(arr: T[]): T[] {
@@ -333,43 +334,7 @@ export class Workflow {
typeof parameterValue === 'string' &&
(parameterValue.charAt(0) === '=' || hasRenamableContent)
) {
// Is expression so has to be rewritten
// 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 (parameterValue.includes(currentName)) {
// Really contains node-name (even though we do not know yet if really as $node-expression)
const escapedOldName = backslashEscape(currentName); // for match
const escapedNewName = dollarEscape(newName); // for replacement
const setNewName = (expression: string, oldPattern: string) =>
expression.replace(new RegExp(oldPattern, 'g'), `$1${escapedNewName}$2`);
if (parameterValue.includes('$(')) {
const oldPattern = String.raw`(\$\(['"])${escapedOldName}(['"]\))`;
parameterValue = setNewName(parameterValue, oldPattern);
}
if (parameterValue.includes('$node[')) {
const oldPattern = String.raw`(\$node\[['"])${escapedOldName}(['"]\])`;
parameterValue = setNewName(parameterValue, oldPattern);
}
if (parameterValue.includes('$node.')) {
const oldPattern = String.raw`(\$node\.)${escapedOldName}(\.?)`;
parameterValue = setNewName(parameterValue, oldPattern);
if (hasDotNotationBannedChar(newName)) {
const regex = new RegExp(`.${backslashEscape(newName)}( |\\.)`, 'g');
parameterValue = parameterValue.replace(regex, `["${escapedNewName}"]$1`);
}
}
if (parameterValue.includes('$items(')) {
const oldPattern = String.raw`(\$items\(['"])${escapedOldName}(['"],|['"]\))`;
parameterValue = setNewName(parameterValue, oldPattern);
}
}
parameterValue = applyAccessPatterns(parameterValue, currentName, newName);
}
return parameterValue;
@@ -963,19 +928,3 @@ export class Workflow {
return this.__getStartNode(Object.keys(this.nodes));
}
}
function hasDotNotationBannedChar(nodeName: string) {
const DOT_NOTATION_BANNED_CHARS = /^(\d)|[\\ `!@#$%^&*()_+\-=[\]{};':"\\|,.<>?~]/g;
return DOT_NOTATION_BANNED_CHARS.test(nodeName);
}
function backslashEscape(nodeName: string) {
const BACKSLASH_ESCAPABLE_CHARS = /[.*+?^${}()|[\]\\]/g;
return nodeName.replace(BACKSLASH_ESCAPABLE_CHARS, (char) => `\\${char}`);
}
function dollarEscape(nodeName: string) {
return nodeName.replace(new RegExp('\\$', 'g'), '$$$$');
}