Skip to content

Commit

Permalink
Fix Stempler parsing of @ inside a string that is not a directive. …
Browse files Browse the repository at this point in the history
…(#1197)
  • Loading branch information
spiralbot committed Jan 21, 2025
1 parent dffd6d4 commit 4f893b4
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 6 deletions.
7 changes: 6 additions & 1 deletion src/Lexer/Buffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class Buffer implements \IteratorAggregate
public function __construct(
/** @internal */
private readonly \Generator $generator,
private int $offset = 0
private int $offset = 0,
) {
}

Expand Down Expand Up @@ -136,4 +136,9 @@ public function replay(int $offset): void
}
}
}

public function cleanReplay(): void
{
$this->replay = [];
}
}
6 changes: 6 additions & 0 deletions src/Lexer/Grammar/Dynamic/DirectiveGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ private function finalize(): bool
{
$tokens = $this->tokens;

// A directive must have at least one keyword
// Without it, it's just a char
if (\count($tokens) === 1 && $tokens[0]->content === self::DIRECTIVE_CHAR) {
return false;
}

foreach (\array_reverse($tokens, true) as $i => $t) {
if ($t->type !== DynamicGrammar::TYPE_WHITESPACE) {
break;
Expand Down
10 changes: 7 additions & 3 deletions src/Lexer/Grammar/DynamicGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,20 @@ final class DynamicGrammar implements GrammarInterface
private readonly BracesGrammar $raw;

public function __construct(
private readonly ?DirectiveRendererInterface $directiveRenderer = null
private readonly ?DirectiveRendererInterface $directiveRenderer = null,
) {
$this->echo = new BracesGrammar(
'{{',
'}}',
self::TYPE_OPEN_TAG,
self::TYPE_CLOSE_TAG
self::TYPE_CLOSE_TAG,
);

$this->raw = new BracesGrammar(
'{!!',
'!!}',
self::TYPE_OPEN_RAW_TAG,
self::TYPE_CLOSE_RAW_TAG
self::TYPE_CLOSE_RAW_TAG,
);
}

Expand Down Expand Up @@ -107,6 +107,10 @@ public function parse(Buffer $src): \Generator

$src->replay($directive->getLastOffset());
continue;
} else {
// When we found directive char but it's not a directive, we need to clean the replay buffer
// because it may contain extra tokens that we don't need to return back to the stream
$src->cleanReplay();
}

$src->replay($n->offset);
Expand Down
3 changes: 1 addition & 2 deletions tests/Directive/DirectiveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

declare(strict_types=1);

namespace Directive;
namespace Spiral\Tests\Stempler\Directive;

use Spiral\Tests\Stempler\fixtures\ImageDirective;
use Spiral\Tests\Stempler\Directive\BaseTestCase;

final class DirectiveTest extends BaseTestCase
{
Expand Down
16 changes: 16 additions & 0 deletions tests/Transform/DynamicToPHPTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Spiral\Tests\Stempler\Transform;

use PHPUnit\Framework\Attributes\DataProvider;
use Spiral\Stempler\Directive\LoopDirective;
use Spiral\Stempler\Node\PHP;
use Spiral\Stempler\Node\Raw;
use Spiral\Stempler\Transform\Finalizer\DynamicToPHP;

class DynamicToPHPTest extends BaseTestCase
Expand All @@ -17,6 +19,20 @@ public function testOutput(): void
self::assertInstanceOf(PHP::class, $doc->nodes[0]);
}

public static function provideStringWithoutDirective(): iterable
{
yield ['https://unpkg.com/tailwindcss@^1.6/dist/tailwind.min.css'];
}

#[DataProvider('provideStringWithoutDirective')]
public function testLinkWithReservedSymbol(string $string): void
{
$doc = $this->parse($string);

self::assertInstanceOf(Raw::class, $doc->nodes[0]);
self::assertSame($string, $doc->nodes[0]->content);
}

public function testDirective(): void
{
$doc = $this->parse('@foreach($users as $u) @endforeach');
Expand Down

0 comments on commit 4f893b4

Please sign in to comment.