Skip to content

Commit

Permalink
[5.x] Antlers: Adds support for disabling Antlers entirely inside tag…
Browse files Browse the repository at this point in the history
… parameter content (#8887)
  • Loading branch information
JohnathonKoster authored Feb 29, 2024
1 parent 6263657 commit 4f244a8
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/View/Antlers/Language/Nodes/Parameters/ParameterNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class ParameterNode extends AbstractNode

public $isVariableReference = false;

public $containsEscapedContent = false;

public $originalName = '';

public $name = '';

public $value = '';
Expand Down
9 changes: 8 additions & 1 deletion src/View/Antlers/Language/Parser/AntlersNodeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ private function getParameters(AntlersNode $node)

if ($hasFoundName == false && $current == DocumentParser::Punctuation_Equals) {
if (! empty($currentChars)) {
if ((ctype_alpha($currentChars[0]) || ctype_digit($currentChars[0]) || $currentChars[0] == DocumentParser::Punctuation_Colon || $currentChars[0] == DocumentParser::AtChar) == false) {
if (! (ctype_alpha($currentChars[0]) || ctype_digit($currentChars[0]) || $currentChars[0] == DocumentParser::Punctuation_Colon || $currentChars[0] == DocumentParser::AtChar)) {
$currentChars = [];

continue;
Expand Down Expand Up @@ -660,6 +660,8 @@ private function getParameters(AntlersNode $node)

$parameterNode = new ParameterNode();

$parameterNode->originalName = $name;

if (Str::startsWith($name, DocumentParser::Punctuation_Colon)) {
$parameterNode->isVariableReference = true;
$name = StringUtilities::substr($name, 1);
Expand All @@ -670,6 +672,11 @@ private function getParameters(AntlersNode $node)
}
}

if (Str::startsWith($name, DocumentParser::AtChar)) {
$parameterNode->containsEscapedContent = true;
$name = mb_substr($name, 1);
}

$parameterNode->name = $name;
$parameterNode->value = $content;
$parameterNode->startPosition = $node->relativePositionFromOffset($startAt, $nameStart);
Expand Down
53 changes: 51 additions & 2 deletions src/View/Antlers/Language/Parser/DocumentParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ class DocumentParser
private $threeCharCollisionCount = -1;
private $threeCharCollisions = [];
private $isVirtual = false;
private $mayBeStartingEscapedContent = false;
private $isParsingEscapedContent = false;
private $escapedContentEndSymbol = null;
private $escapedContentSymbolEncountered = 0;

/**
* A list of node visitors.
Expand Down Expand Up @@ -991,8 +995,18 @@ private function getRightBrace()
return str_split(self::getRightBraceEscape());
}

private function resetEscapedContentState()
{
$this->mayBeStartingEscapedContent = false;
$this->isParsingEscapedContent = false;
$this->escapedContentEndSymbol = null;
$this->escapedContentSymbolEncountered = 0;
}

private function scanToEndOfAntlersRegion()
{
$this->resetEscapedContentState();

for ($this->currentIndex; $this->currentIndex < $this->inputLen; $this->currentIndex += 1) {
$this->checkCurrentOffsets();

Expand All @@ -1017,7 +1031,42 @@ private function scanToEndOfAntlersRegion()
continue;
}

if ($this->cur == self::LeftBrace) {
if ($this->isParsingEscapedContent && $this->cur == $this->escapedContentEndSymbol && $this->prev != self::String_EscapeCharacter) {
$this->escapedContentSymbolEncountered++;

if ($this->escapedContentSymbolEncountered >= 2) {
$this->resetEscapedContentState();
}
}

if ($this->mayBeStartingEscapedContent) {
if ($this->cur != null && ctype_space($this->cur) || $this->next == null) {
$this->resetEscapedContentState();
} else {
if ($this->cur == self::Punctuation_Equals && ($this->next == self::String_Terminator_SingleQuote || $this->next == self::String_Terminator_DoubleQuote)) {
$this->mayBeStartingEscapedContent = false;
$this->isParsingEscapedContent = true;
$this->escapedContentEndSymbol = $this->next;

// We'll use this counter to track the number of
// times we've seen the end symbol. We will do
// it this way to avoid modifying the logic
// below, which is already a bit complex.
$this->escapedContentSymbolEncountered = 0;
}
}
}

if ($this->cur == self::AtChar && ($this->prev != null && ctype_space($this->prev))) {
if ($this->next != null && (ctype_alpha($this->next) || $this->next == self::Punctuation_Underscore || $this->next == self::AtChar)) {
// It is possible that we might be starting some escaped content.
// We will need more information to determine this, but let's
// flag that it is currently a possibility and handle it.
$this->mayBeStartingEscapedContent = true;
}
}

if (! $this->isParsingEscapedContent && $this->cur == self::LeftBrace) {
$results = $this->scanToEndOfInterpolatedRegion();
GlobalRuntimeState::$interpolatedVariables[] = $results[2];

Expand All @@ -1027,7 +1076,7 @@ private function scanToEndOfAntlersRegion()
continue;
}

if ($this->cur == self::RightBrace && $this->next != null && $this->next == self::RightBrace) {
if (! $this->isParsingEscapedContent && $this->cur == self::RightBrace && $this->next != null && $this->next == self::RightBrace) {
$node = $this->makeAntlersTagNode($this->currentIndex, false);

if ($node->name != null && $node->name->name == 'noparse') {
Expand Down
42 changes: 40 additions & 2 deletions tests/Antlers/Parser/NodeParametersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ protected function assertParameterNameValue($parameter, $name, $value)
public function test_at_params_can_be_supplied()
{
$template = <<<'EOT'
{{ form:create test="test-value" @submit.prevent="sendForm()" }}
{{ form:create test="test-value" attr:@submit.prevent="sendForm()" }}
EOT;

$nodes = $this->parseNodes($template);
Expand All @@ -42,7 +42,7 @@ public function test_at_params_can_be_supplied()
$this->assertSame('test', $param1->name);
$this->assertSame('test-value', $param1->value);

$this->assertSame('@submit.prevent', $param2->name);
$this->assertSame('attr:@submit.prevent', $param2->name);
$this->assertSame('sendForm()', $param2->value);
}

Expand Down Expand Up @@ -328,4 +328,42 @@ public function test_it_parses_shorthand_parameters_and_regular_parameters()
$this->assertTrue($nodes[0]->parameters[3]->isVariableReference);
$this->assertFalse($nodes[0]->parameters[4]->isVariableReference);
}

public function test_curly_braces_inside_a_parameter_can_be_ignored_entirely()
{
$template = <<<'EOT'
{{ form @x-data="{ open: false }" @attr:x-bind="..." @x-init="() => { open = true }" x-show="open" }}
EOT;

$nodes = $this->parseNodes($template);

$this->assertCount(1, $nodes);
$this->assertInstanceOf(AntlersNode::class, $nodes[0]);

/** @var AntlersNode $antlersNode */
$antlersNode = $nodes[0];

$this->assertTrue($antlersNode->hasParameters);
$this->assertCount(4, $antlersNode->parameters);

$pXData = $antlersNode->parameters[0];
$this->assertSame('x-data', $pXData->name);
$this->assertSame('@x-data', $pXData->originalName);
$this->assertSame('{ open: false }', $pXData->value);

$pXBind = $antlersNode->parameters[1];
$this->assertSame('attr:x-bind', $pXBind->name);
$this->assertSame('@attr:x-bind', $pXBind->originalName);
$this->assertSame('...', $pXBind->value);

$pXInit = $antlersNode->parameters[2];
$this->assertSame('x-init', $pXInit->name);
$this->assertSame('@x-init', $pXInit->originalName);
$this->assertSame('() => { open = true }', $pXInit->value);

$pXShow = $antlersNode->parameters[3];
$this->assertSame('x-show', $pXShow->name);
$this->assertSame('x-show', $pXShow->originalName);
$this->assertSame('open', $pXShow->value);
}
}

0 comments on commit 4f244a8

Please sign in to comment.