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
67 changes: 64 additions & 3 deletions src/Parse/Lexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,21 @@ protected function lexBlock(): void
throw SyntaxException::unexpectedEndOfTemplate();
}

// The first identifier names the tag and determines whether its body
// must later be treated as opaque raw data.
if ($tag === null && $lastToken->type === TokenType::Identifier) {
// Inside a liquid tag a line starting with `comment` opens a comment
// block, which must not reach the expression lexer any more than a
// `{% comment %}` block does.
if ($tag !== null
&& $tag->data === 'liquid'
&& $lastToken->type === TokenType::Identifier
&& $lastToken->data === 'comment'
&& $this->startsLiquidTagLine($tag, $lastToken)
) {
array_pop($this->tokens);
$this->skipLiquidComment($lastToken->lineNumber);
$this->skipWhitespace();
} elseif ($tag === null && $lastToken->type === TokenType::Identifier) {
// The first identifier names the tag and determines whether its
// body must later be treated as opaque raw data.
$tag = $lastToken;
}
}
Expand Down Expand Up @@ -338,6 +350,55 @@ protected function lexComment(): void
}
}

/**
* A liquid tag is line based: only the first word of a line names a tag.
*/
protected function startsLiquidTagLine(Token $tag, Token $token): bool
{
$previous = $this->tokens[count($this->tokens) - 2] ?? null;

return $previous === null
|| $previous === $tag
|| $previous->lineNumber < $token->lineNumber;
}

/**
* Skip a `comment`/`endcomment` block inside a liquid tag.
*
* Same contract as lexComment(): the body emits no tokens and is never
* lexed, so it may hold anything (apostrophes, stray delimiters), and the
* first `endcomment` closes the block without tracking nesting.
*/
protected function skipLiquidComment(int $lineNumber): void
{
// The liquid tag ends at its own `%}`, so an `endcomment` past that point
// belongs to something else and the comment is never closed.
$blockEnd = strpos($this->source, self::BLOCK_END, $this->cursor);
$offset = $this->cursor;

while (true) {
if ($blockEnd === false || $offset > $blockEnd) {
// Report the `comment` line, like an unclosed `{% comment %}` does.
$exception = SyntaxException::tagBlockNeverClosed('comment');
$exception->lineNumber = $lineNumber;

throw $exception;
}

$offset += strspn($this->source, " \t", $offset);

if ($this->comesNext('endcomment', $offset)
&& strspn($this->source, self::WORD, $offset + strlen('endcomment'), 1) === 0
) {
$this->skip($offset + strlen('endcomment') - $this->cursor);

return;
}

$offset += strcspn($this->source, "\n", $offset) + 1;
}
}

protected function lexInlineComment(): void
{
$offset = $this->cursor;
Expand Down
89 changes: 89 additions & 0 deletions tests/Integration/Tags/LiquidTagTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,95 @@
);
});

test('comment tag inside liquid tag', function () {
assertTemplateResult('center', <<<'LIQUID'
{%- liquid
comment
Intended for blocks and sections that provide values for all the referenced settings.

Accepts:
settings: {block.settings || section.settings}
endcomment

assign horizontal_alignment = settings.horizontal_alignment
echo horizontal_alignment
-%}
LIQUID, staticData: ['settings' => ['horizontal_alignment' => 'center']]);

// The body is never lexed, so it may contain anything.
assertTemplateResult('ttt', <<<'LIQUID'
{%- liquid
comment
it's a comment {{ with {% delimiters
endcomment
echo 'ttt'
-%}
LIQUID
);

assertTemplateResult('', <<<'LIQUID'
{%- liquid
comment
endcomment
-%}
LIQUID
);

assertTemplateResult('12', <<<'LIQUID'
{%- liquid
for value in (1..2)
comment
skipped
endcomment
echo value
endfor
-%}
LIQUID
);

// Whitespace control of the liquid tag itself is not affected.
assertTemplateResult('Hello!World!', <<<'LIQUID'
Hello!
{%- liquid
comment
this is inside a liquid tag
endcomment
-%}
World!
LIQUID
);

// `comment` only opens a comment when it starts a line.
assertTemplateResult('ok', <<<'LIQUID'
{%- liquid
assign comment = 'ok'
echo comment
-%}
LIQUID
);
});

test('comment tag inside liquid tag errors', function () {
assertMatchSyntaxError("Liquid syntax error (line 2): 'comment' tag was never closed", <<<'LIQUID'
{%- liquid
comment
forgot to close the comment
echo 'a'
-%}
LIQUID
);

assertMatchSyntaxError("Liquid syntax error (line 5): Unknown tag 'error'", <<<'LIQUID'
{%- liquid
comment
a comment
endcomment
error no such tag
-%}
LIQUID
);
});

test('liquid tag in raw', function () {
assertTemplateResult("{% liquid echo 'test' %}", <<<'LIQUID'
{% raw %}{% liquid echo 'test' %}{% endraw %}
Expand Down