From e148c416a51c97e74f5436e0ea9160e6a87c88c7 Mon Sep 17 00:00:00 2001 From: James Prior Date: Sat, 10 Feb 2024 10:47:56 +0000 Subject: [PATCH] fix: ignore `else` expressions and superfluous blocks --- src/builtin/tags/if.ts | 37 +++++++++++++++++----- src/builtin/tags/unless.ts | 44 ++++++++++++++++++++++----- tests/golden/golden_liquid.json | 54 +++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 16 deletions(-) diff --git a/src/builtin/tags/if.ts b/src/builtin/tags/if.ts index 5b79b295..086e0077 100644 --- a/src/builtin/tags/if.ts +++ b/src/builtin/tags/if.ts @@ -32,7 +32,7 @@ export class IfTag implements Tag { TOKEN_EOF, ]); protected static END_ELSEIF_BLOCK = new Set([TAG_ENDIF, TAG_ELSIF, TAG_ELSE]); - protected static END_ELSE_BLOCK = new Set([TAG_ENDIF]); + protected static END_ELSE_BLOCK = new Set([TAG_ENDIF, TAG_ELSIF, TAG_ELSE]); readonly block = true; readonly name: string = TAG_IF; @@ -71,24 +71,45 @@ export class IfTag implements Tag { }); } + let alternative: BlockNode | undefined = undefined; + if ( stream.current.kind === TOKEN_TAG && stream.current.value === TAG_ELSE ) { - return new this.nodeClass( - token, - condition, - consequence, - conditionalAlternatives, - parser.parseBlock(stream, IfTag.END_ELSE_BLOCK, stream.next()), - ); + const tok = stream.next(); + // @ts-expect-error: stream.current has changed, so `kind` will have changed too. + if (stream.current.kind === TOKEN_EXPRESSION) { + // Superfluous expressions inside an `else` tag are ignored. + stream.next(); + } + + alternative = parser.parseBlock(stream, IfTag.END_ELSE_BLOCK, tok); } + // Extraneous `else` and `elsif` blocks are ignored. + if ( + !(stream.current.kind === TOKEN_TAG && stream.current.value === TAG_ENDIF) + ) { + while (stream.current.kind !== TOKEN_EOF) { + if ( + stream.current.kind === TOKEN_TAG && + stream.current.value === TAG_ENDIF + ) { + break; + } + stream.next(); + } + } + + stream.expectTag(TAG_ENDIF); + return new this.nodeClass( token, condition, consequence, conditionalAlternatives, + alternative, ); } } diff --git a/src/builtin/tags/unless.ts b/src/builtin/tags/unless.ts index f0adac85..1ba0ac4e 100644 --- a/src/builtin/tags/unless.ts +++ b/src/builtin/tags/unless.ts @@ -31,12 +31,18 @@ export class UnlessTag implements Tag { TAG_ELSE, TOKEN_EOF, ]); + protected static END_ELSEIF_BLOCK = new Set([ TAG_ENDUNLESS, TAG_ELSIF, TAG_ELSE, ]); - protected static END_ELSE_BLOCK = new Set([TAG_ENDUNLESS]); + + protected static END_ELSE_BLOCK = new Set([ + TAG_ENDUNLESS, + TAG_ELSIF, + TAG_ELSE, + ]); readonly block = true; readonly name: string = TAG_UNLESS; @@ -79,17 +85,38 @@ export class UnlessTag implements Tag { }); } + let alternative: BlockNode | undefined = undefined; + if ( stream.current.kind === TOKEN_TAG && stream.current.value === TAG_ELSE ) { - return new this.nodeClass( - token, - condition, - consequence, - conditionalAlternatives, - parser.parseBlock(stream, UnlessTag.END_ELSE_BLOCK, stream.next()), - ); + const tok = stream.next(); + // @ts-expect-error: stream.current has changed, so `kind` will have changed too. + if (stream.current.kind === TOKEN_EXPRESSION) { + // Superfluous expressions inside an `else` tag are ignored. + stream.next(); + } + + alternative = parser.parseBlock(stream, UnlessTag.END_ELSE_BLOCK, tok); + } + + // Extraneous `else` and `elsif` blocks are ignored. + if ( + !( + stream.current.kind === TOKEN_TAG && + stream.current.value === TAG_ENDUNLESS + ) + ) { + while (stream.current.kind !== TOKEN_EOF) { + if ( + stream.current.kind === TOKEN_TAG && + stream.current.value === TAG_ENDUNLESS + ) { + break; + } + stream.next(); + } } return new this.nodeClass( @@ -97,6 +124,7 @@ export class UnlessTag implements Tag { condition, consequence, conditionalAlternatives, + alternative, ); } } diff --git a/tests/golden/golden_liquid.json b/tests/golden/golden_liquid.json index 6ad69b36..fa22c0c5 100644 --- a/tests/golden/golden_liquid.json +++ b/tests/golden/golden_liquid.json @@ -3865,6 +3865,33 @@ "error": false, "strict": false }, + { + "name": "else tag expressions are ignored", + "template": "{% if false %}1{% else nonsense %}2{% endif %}", + "want": "2", + "context": {}, + "partials": {}, + "error": false, + "strict": true + }, + { + "name": "extra else blocks are ignored", + "template": "{% if false %}1{% else %}2{% else %}3{% endif %}", + "want": "2", + "context": {}, + "partials": {}, + "error": false, + "strict": true + }, + { + "name": "extra elsif blocks are ignored", + "template": "{% if false %}1{% else %}2{% elsif true %}3{% endif %}", + "want": "2", + "context": {}, + "partials": {}, + "error": false, + "strict": true + }, { "name": "int does not equal string", "template": "{% if 1 == '1' %}true{% else %}false{% endif %}", @@ -9335,6 +9362,33 @@ "error": false, "strict": false }, + { + "name": "else tag expressions are ignored", + "template": "{% unless true %}1{% else nonsense %}2{% endunless %}", + "want": "2", + "context": {}, + "partials": {}, + "error": false, + "strict": true + }, + { + "name": "extra else blocks are ignored", + "template": "{% unless true %}1{% else %}2{% else %}3{% endunless %}", + "want": "2", + "context": {}, + "partials": {}, + "error": false, + "strict": true + }, + { + "name": "extra elsif blocks are ignored", + "template": "{% unless true %}1{% else %}2{% elsif true %}3{% endunless %}", + "want": "2", + "context": {}, + "partials": {}, + "error": false, + "strict": true + }, { "name": "literal false condition", "template": "{% unless false %}foo{% endunless %}",