mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-22 04:21:32 +02:00
fix(core): Keep parentheses around && and || operands of ?? in expressions (#40962)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e5d3f0f9f4
commit
dbcfecf5b9
|
|
@ -431,6 +431,23 @@ export const extendTransform = (expression: string): { code: string } | undefine
|
|||
},
|
||||
});
|
||||
|
||||
// recast adds parentheses by operator precedence only, but JavaScript
|
||||
// rejects `&&` or `||` as an operand of `??` without parentheses.
|
||||
// The ast-types definitions do not declare `extra`, which recast reads.
|
||||
visit(ast, {
|
||||
visitLogicalExpression(path) {
|
||||
this.traverse(path);
|
||||
|
||||
if (path.node.operator !== '??') return;
|
||||
|
||||
for (const operand of [path.node.left, path.node.right]) {
|
||||
if (operand.type === 'LogicalExpression' && operand.operator !== '??') {
|
||||
Object.assign(operand, { extra: { parenthesized: true } });
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return print(ast);
|
||||
} catch (e) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -189,6 +189,29 @@ describe('Expression Parser', () => {
|
|||
test('Optional chaining with undefined argument', () => {
|
||||
expect(evaluate('={{ [1, 2, 3, 4]?.sum((undefined)?.test) }}')).toBe(10);
|
||||
});
|
||||
|
||||
describe('Nullish coalescing with an && or || operand next to an optional chain', () => {
|
||||
test.each([
|
||||
['$json.a?.[0] ?? ($json.a.last() && 1)', { a: [5, 6] }, 5],
|
||||
['$json.a?.[0] ?? ($json.a.last() && 1)', { a: [null, 6] }, 1],
|
||||
['$json.a?.[0] ?? ($json.a.last() || 1)', { a: [null, 0] }, 1],
|
||||
['($json.a.last() && 1) ?? $json.a?.[0]', { a: [5, 6] }, 1],
|
||||
['($json.a?.[0] || $json.b) ?? $json.a.last()', { a: [0, 6], b: null }, 6],
|
||||
['$json.a?.[0] ?? ($json.b && ($json.c || $json.a.last()))', { a: [null, 6], b: 1 }, 6],
|
||||
])('should keep the parentheses around the operand of "??" in %s', (code, data, result) => {
|
||||
expect(extendTransform(code)?.code).toBeDefined();
|
||||
expect(evaluate(`={{ ${code} }}`, [data])).toBe(result);
|
||||
});
|
||||
|
||||
test('should print the parentheses once', () => {
|
||||
expect(extendTransform('$json.a?.[0] ?? ($json.a.last() && 1)')?.code).toBe(
|
||||
'(window.chainCancelToken1 = ((window.chainValue1 = $json.a) ?? undefined) === undefined, window.chainCancelToken1 === true ? undefined : window.chainValue1[0]) ?? (extend($json.a, "last", []) && 1)',
|
||||
);
|
||||
expect(extendTransform('1 ?? ($json.a.last() && 2)')?.code).toBe(
|
||||
'1 ?? (extend($json.a, "last", []) && 2)',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Non dot extensions', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user