Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/twig.expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ module.exports = function (Twig) {
Twig.expression.type.slice
]);

// The closing bracket expected for each token that opens a group. Used to
// detect an unclosed group left on the stack after an expression is compiled.
Twig.expression.closingBracket = {
[Twig.expression.type.object.start]: '}',
[Twig.expression.type.array.start]: ']',
[Twig.expression.type.parameter.start]: ')',
[Twig.expression.type.subexpression.start]: ')'
};

// Some commonly used compile and parse functions.
Twig.expression.fn = {
compile: {
Expand Down Expand Up @@ -1323,8 +1332,17 @@ module.exports = function (Twig) {
Twig.log.trace('Twig.expression.compile: ', 'Output is', output);
}

// A well formed expression closes every group it opens, so once the
// input is consumed the stack should only hold operators. An opening
// bracket left here is an unclosed group and must be reported rather
// than silently flushed into the output.
while (stack.length > 0) {
output.push(stack.pop());
token = stack.pop();
if (Object.prototype.hasOwnProperty.call(Twig.expression.closingBracket, token.type)) {
throw new Twig.Error('Expected closing \'' + Twig.expression.closingBracket[token.type] + '\' in expression \'' + rawToken.value + '\'');
}

output.push(token);
}

Twig.log.trace('Twig.expression.compile: ', 'Final output is', output);
Expand Down
18 changes: 18 additions & 0 deletions test/test.regression.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,22 @@ describe('Twig.js Regression Tests ->', function () {
const testTemplate = twig({data: str});
testTemplate.render().should.equal(expected);
});

it('#896 should report a syntax error for an unclosed brace instead of rendering [object Object]', function () {
// '{{{ ... }}' opens an object with the extra '{' that is never closed.
// It used to render as '[object Object]' rather than failing.
(function () {
twig({rethrow: true, data: 'Hey {{{data.variables.aa | default(\'there\') }}'}).render({data: {variables: {}}});
}).should.throw(/Expected closing '}'/);

(function () {
twig({rethrow: true, data: 'Hey {{{data.variables.aa | default(\'there\') }}}'}).render({data: {variables: {}}});
}).should.throw(/Expected closing '}'/);
});

it('#896 should still render a triple-open brace whose object is closed', function () {
twig({data: '{% for val in arr %}{{{(val.value):null}|json_encode}}{% endfor %}'})
.render({arr: [{value: 'one'}, {value: 'two'}]})
.should.equal('{"one":null}{"two":null}');
});
});