Skip to content

Commit 4f893b4

Browse files
committed
Fix Stempler parsing of @ inside a string that is not a directive. (#1197)
1 parent dffd6d4 commit 4f893b4

File tree

5 files changed

+36
-6
lines changed

5 files changed

+36
-6
lines changed

src/Lexer/Buffer.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ final class Buffer implements \IteratorAggregate
2020
public function __construct(
2121
/** @internal */
2222
private readonly \Generator $generator,
23-
private int $offset = 0
23+
private int $offset = 0,
2424
) {
2525
}
2626

@@ -136,4 +136,9 @@ public function replay(int $offset): void
136136
}
137137
}
138138
}
139+
140+
public function cleanReplay(): void
141+
{
142+
$this->replay = [];
143+
}
139144
}

src/Lexer/Grammar/Dynamic/DirectiveGrammar.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ private function finalize(): bool
229229
{
230230
$tokens = $this->tokens;
231231

232+
// A directive must have at least one keyword
233+
// Without it, it's just a char
234+
if (\count($tokens) === 1 && $tokens[0]->content === self::DIRECTIVE_CHAR) {
235+
return false;
236+
}
237+
232238
foreach (\array_reverse($tokens, true) as $i => $t) {
233239
if ($t->type !== DynamicGrammar::TYPE_WHITESPACE) {
234240
break;

src/Lexer/Grammar/DynamicGrammar.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,20 @@ final class DynamicGrammar implements GrammarInterface
4545
private readonly BracesGrammar $raw;
4646

4747
public function __construct(
48-
private readonly ?DirectiveRendererInterface $directiveRenderer = null
48+
private readonly ?DirectiveRendererInterface $directiveRenderer = null,
4949
) {
5050
$this->echo = new BracesGrammar(
5151
'{{',
5252
'}}',
5353
self::TYPE_OPEN_TAG,
54-
self::TYPE_CLOSE_TAG
54+
self::TYPE_CLOSE_TAG,
5555
);
5656

5757
$this->raw = new BracesGrammar(
5858
'{!!',
5959
'!!}',
6060
self::TYPE_OPEN_RAW_TAG,
61-
self::TYPE_CLOSE_RAW_TAG
61+
self::TYPE_CLOSE_RAW_TAG,
6262
);
6363
}
6464

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

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

112116
$src->replay($n->offset);

tests/Directive/DirectiveTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
declare(strict_types=1);
44

5-
namespace Directive;
5+
namespace Spiral\Tests\Stempler\Directive;
66

77
use Spiral\Tests\Stempler\fixtures\ImageDirective;
8-
use Spiral\Tests\Stempler\Directive\BaseTestCase;
98

109
final class DirectiveTest extends BaseTestCase
1110
{

tests/Transform/DynamicToPHPTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
namespace Spiral\Tests\Stempler\Transform;
66

7+
use PHPUnit\Framework\Attributes\DataProvider;
78
use Spiral\Stempler\Directive\LoopDirective;
89
use Spiral\Stempler\Node\PHP;
10+
use Spiral\Stempler\Node\Raw;
911
use Spiral\Stempler\Transform\Finalizer\DynamicToPHP;
1012

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

22+
public static function provideStringWithoutDirective(): iterable
23+
{
24+
yield ['https://unpkg.com/tailwindcss@^1.6/dist/tailwind.min.css'];
25+
}
26+
27+
#[DataProvider('provideStringWithoutDirective')]
28+
public function testLinkWithReservedSymbol(string $string): void
29+
{
30+
$doc = $this->parse($string);
31+
32+
self::assertInstanceOf(Raw::class, $doc->nodes[0]);
33+
self::assertSame($string, $doc->nodes[0]->content);
34+
}
35+
2036
public function testDirective(): void
2137
{
2238
$doc = $this->parse('@foreach($users as $u) @endforeach');

0 commit comments

Comments
 (0)