Skip to content

Commit 102b496

Browse files
committed
added doc tag
1 parent 399173f commit 102b496

File tree

6 files changed

+217
-1
lines changed

6 files changed

+217
-1
lines changed

src/Extensions/StandardExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public function getTags(): array
1717
Tags\ContinueTag::class,
1818
Tags\CycleTag::class,
1919
Tags\DecrementTag::class,
20+
Tags\DocTag::class,
2021
Tags\EchoTag::class,
2122
Tags\ForTag::class,
2223
Tags\IfChanged::class,

src/Tags/DocTag.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Keepsuit\Liquid\Tags;
4+
5+
use Keepsuit\Liquid\Exceptions\SyntaxException;
6+
use Keepsuit\Liquid\Nodes\BodyNode;
7+
use Keepsuit\Liquid\Nodes\Raw;
8+
use Keepsuit\Liquid\Parse\TagParseContext;
9+
use Keepsuit\Liquid\Render\RenderContext;
10+
use Keepsuit\Liquid\TagBlock;
11+
12+
class DocTag extends TagBlock
13+
{
14+
protected Raw $body;
15+
16+
public static function tagName(): string
17+
{
18+
return 'doc';
19+
}
20+
21+
public static function hasRawBody(): bool
22+
{
23+
return true;
24+
}
25+
26+
public function parse(TagParseContext $context): static
27+
{
28+
$context->params->assertEnd();
29+
30+
assert($context->body instanceof BodyNode);
31+
32+
$body = $context->body->children()[0] ?? null;
33+
$this->body = match (true) {
34+
$body instanceof Raw => $body,
35+
default => throw new SyntaxException('doc tag must have a single raw body'),
36+
};
37+
38+
$this->ensureNoNestedDocTags();
39+
40+
return $this;
41+
}
42+
43+
public function render(RenderContext $context): string
44+
{
45+
return '';
46+
}
47+
48+
/**
49+
* @throws SyntaxException
50+
*/
51+
protected function ensureNoNestedDocTags(): void
52+
{
53+
if (preg_match('/{%-?\s*doc\s*-?%}/', $this->body->value) === 1) {
54+
throw new SyntaxException('Nested doc tags are not allowed');
55+
}
56+
}
57+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
test('doc tag', function () {
4+
$template = <<<'LIQUID'
5+
{% doc %}
6+
Renders loading-spinner.
7+
@param {string} foo - some foo
8+
@param {string} [bar] - optional bar
9+
@example
10+
{% render 'loading-spinner', foo: 'foo' %}
11+
{% render 'loading-spinner', foo: 'foo', bar: 'bar' %}
12+
{% enddoc %}
13+
LIQUID;
14+
15+
assertTemplateResult('', $template);
16+
});
17+
18+
test('doc tag does not support extra arguments', function () {
19+
$template = <<<'LIQUID'
20+
{% doc extra %}
21+
{% enddoc %}
22+
LIQUID;
23+
24+
assertMatchSyntaxError('Liquid syntax error (line 2): Unexpected token Identifier: "extra"', $template);
25+
});
26+
27+
test('doc tag must support valid tags', function () {
28+
assertMatchSyntaxError("Liquid syntax error (line 1): 'doc' tag was never closed", '{% doc %} foo');
29+
assertMatchSyntaxError('Liquid syntax error (line 1): Unexpected character }', '{% doc } foo {% enddoc %}');
30+
assertMatchSyntaxError('Liquid syntax error (line 1): Unexpected character }', '{% doc } foo %}{% enddoc %}');
31+
});
32+
33+
test('doc tag ignores liquid nodes', function () {
34+
$template = <<<'LIQUID'
35+
{% doc %}
36+
{% if true %}
37+
{% if ... %}
38+
{%- for ? -%}
39+
{% while true %}
40+
{%
41+
unless if
42+
%}
43+
{% endcase %}
44+
{% enddoc %}
45+
LIQUID;
46+
47+
assertTemplateResult('', $template);
48+
});
49+
50+
test('doc tag ignores unclosed liquid tags', function () {
51+
$template = <<<'LIQUID'
52+
{% doc %}
53+
{% if true %}
54+
{% enddoc %}
55+
LIQUID;
56+
57+
assertTemplateResult('', $template);
58+
});
59+
60+
test('doc tag does not allow nested docs', function () {
61+
$template = <<<'LIQUID'
62+
{% doc %}
63+
{% doc %}
64+
{% doc %}
65+
{% enddoc %}
66+
LIQUID;
67+
68+
assertMatchSyntaxError('Liquid syntax error (line 4): Nested doc tags are not allowed', $template);
69+
});
70+
71+
test('doc tag ignores nested raw tags', function () {
72+
$template = <<<'LIQUID'
73+
{% doc %}
74+
{% raw %}
75+
{% enddoc %}
76+
LIQUID;
77+
78+
assertTemplateResult('', $template);
79+
});
80+
81+
test('doc tag ignores unclosed assign', function () {
82+
$template = <<<'LIQUID'
83+
{% doc %}
84+
{% assign foo = "1"
85+
{% enddoc %}
86+
LIQUID;
87+
88+
assertTemplateResult('', $template);
89+
});
90+
91+
test('doc tag ignores malformed syntax', function () {
92+
$template = <<<'LIQUID'
93+
{% doc %}
94+
{% {{
95+
{%- enddoc %}
96+
LIQUID;
97+
98+
assertTemplateResult('', $template);
99+
});
100+
101+
test('doc tag preserves error line numbers', function () {
102+
$template = <<<'LIQUID'
103+
{% doc %}
104+
{% if true %}
105+
{% enddoc %}
106+
{{ errors.standard_error }}
107+
LIQUID;
108+
109+
$expected = <<<'TEXT'
110+
111+
Liquid error (line 4): Standard error
112+
TEXT;
113+
114+
assertTemplateResult(
115+
$expected,
116+
$template,
117+
['errors' => new \Keepsuit\Liquid\Tests\Stubs\ErrorDrop],
118+
renderErrors: true
119+
);
120+
});
121+
122+
test('doc tag whitespace control', function () {
123+
assertTemplateResult('Hello!', ' {%- doc -%}123{%- enddoc -%}Hello!');
124+
assertTemplateResult('Hello!', '{%- doc -%}123{%- enddoc -%} Hello!');
125+
assertTemplateResult('Hello!', ' {%- doc -%}123{%- enddoc -%} Hello!');
126+
assertTemplateResult('Hello!', <<<'LIQUID'
127+
{%- doc %}Whitespace control!{% enddoc -%}
128+
Hello!
129+
LIQUID);
130+
});
131+
132+
test('doc tag delimiter handling', function () {
133+
assertTemplateResult('', <<<'LIQUID'
134+
{% if true -%}
135+
{% doc %}
136+
{% docEXTRA %}wut{% enddocEXTRA %}xyz
137+
{% enddoc %}
138+
{%- endif %}
139+
LIQUID);
140+
assertMatchSyntaxError("Liquid syntax error (line 1): 'doc' tag was never closed", '{% doc %}123{% enddoc xyz %}');
141+
assertTemplateResult('', "{% doc %}123{% enddoc\n xyz %}{% enddoc %}");
142+
});

tests/Unit/BlockTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use Keepsuit\Liquid\Nodes\Text;
44
use Keepsuit\Liquid\Nodes\Variable;
5+
use Keepsuit\Liquid\Tags\DocTag;
56
use Keepsuit\Liquid\Tags\IfTag;
67

78
test('blankspace', function () {
@@ -66,3 +67,13 @@
6667
->{1}->children()->{0}->children()->{0}->toBeInstanceOf(Text::class)
6768
->{2}->toBeInstanceOf(Text::class);
6869
});
70+
71+
test('doc tag with block', function () {
72+
$template = parseTemplate(" {% doc %} {% enddoc %} ");
73+
74+
expect($template->root->body->children())
75+
->toHaveCount(3)
76+
->{0}->toBeInstanceOf(Text::class)
77+
->{1}->toBeInstanceOf(DocTag::class)
78+
->{2}->toBeInstanceOf(Text::class);
79+
});

tests/Unit/EnvironmentTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515

1616
$tags = $env->tagRegistry->all();
1717

18-
expect($tags)->toHaveCount(16)
18+
expect($tags)->toHaveCount(17)
1919
->toHaveKey('assign')
2020
->toHaveKey('break')
2121
->toHaveKey('capture')
2222
->toHaveKey('case')
2323
->toHaveKey('cycle')
2424
->toHaveKey('decrement')
25+
->toHaveKey('doc')
2526
->toHaveKey('echo')
2627
->toHaveKey('for')
2728
->toHaveKey('ifchanged')

tests/Unit/ParseTreeVisitorTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@
152152
]);
153153
});
154154

155+
test('doc', function () {
156+
expect(visit('{% doc %}{{ test }}{% enddoc %}'))->toBe([]);
157+
});
158+
155159
function traversal(string $source): ParseTreeVisitor
156160
{
157161
$environment = EnvironmentFactory::new()

0 commit comments

Comments
 (0)