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; }