From b5563f4bb1bab337d1e175d44b3a1c6327a33ee4 Mon Sep 17 00:00:00 2001 From: Antoine Veldhoven Date: Mon, 17 Nov 2025 15:31:02 +0100 Subject: [PATCH] Handle ternary operator in arrays and objects. --- src/twig.expression.js | 6 ++++-- src/twig.expression.operator.js | 37 ++++++++++++++++++++------------- test/test.expressions.js | 13 ++++++++++++ 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/twig.expression.js b/src/twig.expression.js index 50f85d67..927de5cb 100644 --- a/src/twig.expression.js +++ b/src/twig.expression.js @@ -260,6 +260,8 @@ module.exports = function (Twig) { // Check if this is a ternary or object key being set if (stack[stack.length - 1] && stack[stack.length - 1].value === '?') { // Continue as normal for a ternary + // Mark the ? operator as having a colon (full ternary, not shorthand) + stack[stack.length - 1].hasColon = true; } else { // This is not a ternary so we push the token to the output where it can be handled // when the assocated object is closed. @@ -304,7 +306,7 @@ module.exports = function (Twig) { } }); } else { - Twig.expression.operator.parse(token.value, stack); + Twig.expression.operator.parse(token.value, stack, token); } } }, @@ -339,7 +341,7 @@ module.exports = function (Twig) { stack.push(operator); }, parse(token, stack) { - Twig.expression.operator.parse(token.value, stack); + Twig.expression.operator.parse(token.value, stack, token); } }, { diff --git a/src/twig.expression.operator.js b/src/twig.expression.operator.js index 027382e1..68e329e4 100644 --- a/src/twig.expression.operator.js +++ b/src/twig.expression.operator.js @@ -157,19 +157,34 @@ module.exports = function (Twig) { * * Returns the updated stack. */ - Twig.expression.operator.parse = function (operator, stack) { + Twig.expression.operator.parse = function (operator, stack, token) { Twig.log.trace('Twig.expression.operator.parse: ', 'Handling ', operator); let a; let b; let c; - if (operator === '?') { - c = stack.pop(); - } + // For shorthand ternary (a ? b), we only need 2 operands + // Check if this is a shorthand ternary by seeing if the token has a hasColon flag + const isShorthandTernary = (operator === '?' && token && !token.hasColon); - b = stack.pop(); - if (operator !== 'not') { - a = stack.pop(); + if (operator === '?') { + if (isShorthandTernary) { + // Shorthand ternary: only pop 2 operands + b = stack.pop(); + a = stack.pop(); + c = ''; + } else { + // Full ternary: pop 3 operands + c = stack.pop(); + b = stack.pop(); + a = stack.pop(); + } + } else { + // Other operators + b = stack.pop(); + if (operator !== 'not') { + a = stack.pop(); + } } if (operator !== 'in' && operator !== 'not in' && operator !== '??') { @@ -219,13 +234,7 @@ module.exports = function (Twig) { break; case '?': - if (a === undefined) { - // An extended ternary. - a = b; - b = c; - c = undefined; - } - + // a, b, c are already set correctly based on isShorthandTernary if (Twig.lib.boolval(a)) { stack.push(b); } else { diff --git a/test/test.expressions.js b/test/test.expressions.js index 1552ab51..a990ee7b 100644 --- a/test/test.expressions.js +++ b/test/test.expressions.js @@ -372,6 +372,19 @@ describe('Twig.js Expressions ->', function () { testTemplate.render({test: null}).should.equal('false'); testTemplate.render({test: undefined}).should.equal('false'); }); + it('should support the ternary operator inside objects', function () { + const testTemplate = twig({data: '{% set classes = { "a-button": true, "modifier": modifier ? modifier, "false":false}|json_encode %}{{ classes }}'}); + const output = testTemplate.render({}); + + output.should.equal('{"a-button":true,"modifier":"","false":false}'); + }); + + it('should support the ternary operator inside arrays', function () { + const testTemplate = twig({data: '{% set classes = [ "a-button", modifier ? modifier, false]|json_encode %}{{ classes }}'}); + const output = testTemplate.render({}); + + output.should.equal('["a-button","",false]'); + }); it('should support in/containment functionality for arrays', function () { let testTemplate;