TemplateCommentsParser   F
last analyzed

Complexity

Total Complexity 74

Size/Duplication

Total Lines 383
Duplicated Lines 0 %

Importance

Changes 6
Bugs 3 Features 0
Metric Value
wmc 74
eloc 167
c 6
b 3
f 0
dl 0
loc 383
rs 2.48

28 Methods

Rating   Name   Duplication   Size   Complexity  
C subparse() 0 76 16
A setParent() 0 11 3
A getStream() 0 3 1
A getExpressionParser() 0 3 1
B parse() 0 57 9
A pushLocalScope() 0 3 1
A peekBlockStack() 0 3 1
A getCurrentToken() 0 3 1
A getImportedSymbol() 0 4 1
A getVarName() 0 3 1
A hasBlock() 0 5 1
C filterBodyNodes() 0 55 17
A popLocalScope() 0 3 1
A getBlockStack() 0 5 1
A popBlockStack() 0 3 1
A hasTraits() 0 5 1
A getBlock() 0 5 1
A embedTemplate() 0 5 1
A hasMacro() 0 5 1
A __construct() 0 25 4
A getParent() 0 5 1
A isMainScope() 0 3 1
A setBlock() 0 7 2
A addImportedSymbol() 0 3 1
A pushBlockStack() 0 3 1
A setMacro() 0 3 1
A addTrait() 0 3 1
A hasInheritance() 0 3 2

How to fix   Complexity   

Complex Class

Complex classes like TemplateCommentsParser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TemplateCommentsParser, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Template Comments plugin for Craft CMS
4
 *
5
 * Adds a HTML comment to demarcate each Twig template that is included or extended.
6
 *
7
 * @link      https://nystudio107.com/
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
8
 * @copyright Copyright (c)  nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
9
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
10
11
/**
12
 * This is a wholesale copy pasta of the Twig Parser class; with one modification so that it
13
 * does not throw a "A block definition cannot be nested under non-capturing nodes" SyntaxError exception
14
 *
15
 * This exception is explained in detail here: https://github.com/twigphp/Twig/issues/3926
16
 *
17
 * So that Template Comments can add HTML comments that indicate any {% include %} or {% extends %} templates
18
 * with execution timing, it uses its own CommentTemplateLoader which wraps the loaded template
19
 * with {% comments 'templateName' %}<loaded-template>{% endcomments %}
20
 *
21
 * There is a corresponding CommentsTokenParser that takes care of parsing the {% comments %} tags
22
 *
23
 * This worked in Twig 1.x and 2.x, but they added a check for block definitions nested under non-capturing
24
 * blocks in Twig 3.x, which causes it to throw an exception in that case. So if you end up with something like:
25
 *
26
 * {% comments 'index' %}
27
 *     {% block conent %}
28
 *     {% endblock %}
29
 * {% encomments %}
30
 *
31
 * ...the SyntaxError exception will be thrown.
32
 *
33
 * I tried adding implements NodeCaptureInterface to the CommentsNode but that resulted in the Parser returning
34
 * the node, causing duplicate rendering for every {% include %} or {% extends %} that was wrapped in a
35
 * {% comments %} tag
36
 *
37
 * We can't just subclass the Parser class, because the properties and methods are private
38
 *
39
 * So here we are.
40
 *
41
 * Don't judge me
42
 */
43
44
namespace nystudio107\templatecomments\web\twig;
45
46
use nystudio107\templatecomments\helpers\Reflection as ReflectionHelper;
47
use ReflectionException;
48
use Twig\Environment;
49
use Twig\Error\SyntaxError;
50
use Twig\ExpressionParser;
0 ignored issues
show
Bug introduced by
The type Twig\ExpressionParser was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
51
use Twig\Node\BlockNode;
52
use Twig\Node\BlockReferenceNode;
53
use Twig\Node\BodyNode;
54
use Twig\Node\Expression\AbstractExpression;
55
use Twig\Node\MacroNode;
56
use Twig\Node\ModuleNode;
57
use Twig\Node\Node;
58
use Twig\Node\NodeCaptureInterface;
59
use Twig\Node\NodeOutputInterface;
60
use Twig\Node\PrintNode;
61
use Twig\Node\TextNode;
62
use Twig\NodeTraverser;
63
use Twig\Parser;
64
use Twig\Token;
65
use Twig\TokenParser\TokenParserInterface;
66
use Twig\TokenStream;
67
use Twig\TwigTest;
68
use Twig\Util\ReflectionCallable;
69
use function chr;
70
use function count;
71
use function get_class;
72
use function is_array;
73
use function sprintf;
74
75
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
76
 * @author Fabien Potencier <[email protected]>
77
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
78
class TemplateCommentsParser extends Parser
79
{
80
    private $stack = [];
0 ignored issues
show
Coding Style introduced by
Private member variable "stack" must be prefixed with an underscore
Loading history...
81
    private $stream;
0 ignored issues
show
Coding Style introduced by
Private member variable "stream" must be prefixed with an underscore
Loading history...
82
    private $parent;
0 ignored issues
show
Coding Style introduced by
Private member variable "parent" must be prefixed with an underscore
Loading history...
83
    private $visitors;
0 ignored issues
show
Coding Style introduced by
Private member variable "visitors" must be prefixed with an underscore
Loading history...
84
    private $expressionParser;
0 ignored issues
show
Coding Style introduced by
Private member variable "expressionParser" must be prefixed with an underscore
Loading history...
85
    private $blocks;
0 ignored issues
show
Coding Style introduced by
Private member variable "blocks" must be prefixed with an underscore
Loading history...
86
    private $blockStack;
0 ignored issues
show
Coding Style introduced by
Private member variable "blockStack" must be prefixed with an underscore
Loading history...
87
    private $macros;
0 ignored issues
show
Coding Style introduced by
Private member variable "macros" must be prefixed with an underscore
Loading history...
88
    private $env;
0 ignored issues
show
Coding Style introduced by
Private member variable "env" must be prefixed with an underscore
Loading history...
89
    private $importedSymbols;
0 ignored issues
show
Coding Style introduced by
Private member variable "importedSymbols" must be prefixed with an underscore
Loading history...
90
    private $traits;
0 ignored issues
show
Coding Style introduced by
Private member variable "traits" must be prefixed with an underscore
Loading history...
91
    private $embeddedTemplates = [];
0 ignored issues
show
Coding Style introduced by
Private member variable "embeddedTemplates" must be prefixed with an underscore
Loading history...
92
    private $varNameSalt = 0;
0 ignored issues
show
Coding Style introduced by
Private member variable "varNameSalt" must be prefixed with an underscore
Loading history...
93
    private $expressionParserClass;
0 ignored issues
show
Coding Style introduced by
Private member variable "expressionParserClass" must be prefixed with an underscore
Loading history...
94
95
    public function __construct(Environment $env)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
96
    {
97
        $this->env = $env;
98
        $this->expressionParserClass = ExpressionParser::class;
99
        // Get the existing parser object used by the Twig $env
100
        try {
101
            $parserReflection = ReflectionHelper::getReflectionProperty($env, 'parser');
102
        } catch (ReflectionException $e) {
103
            return;
104
        }
105
        $parserReflection->setAccessible(true);
106
        $parser = $parserReflection->getValue($env);
107
        if ($parser === null) {
108
            return;
109
        }
110
        // Get the expression parser used by the current parser
111
        try {
112
            $expressionParserReflection = ReflectionHelper::getReflectionProperty($parser, 'expressionParser');
113
        } catch (ReflectionException $e) {
114
            return;
115
        }
116
        // Preserve the existing expression parser and use it
117
        $expressionParserReflection->setAccessible(true);
118
        $expressionParser = $expressionParserReflection->getValue($parser);
119
        $this->expressionParserClass = get_class($expressionParser);
120
    }
121
122
    public function getVarName(): string
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getVarName()
Loading history...
123
    {
124
        return sprintf('__internal_parse_%d', $this->varNameSalt++);
125
    }
126
127
    public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function parse()
Loading history...
128
    {
129
        $vars = get_object_vars($this);
130
        unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['varNameSalt']);
131
        $this->stack[] = $vars;
132
133
        // node visitors
134
        if (null === $this->visitors) {
135
            $this->visitors = $this->env->getNodeVisitors();
136
        }
137
138
        if (null === $this->expressionParser) {
139
            $this->expressionParser = new $this->expressionParserClass($this, $this->env);
140
        }
141
142
        $this->stream = $stream;
143
        $this->parent = null;
144
        $this->blocks = [];
145
        $this->macros = [];
146
        $this->traits = [];
147
        $this->blockStack = [];
148
        $this->importedSymbols = [[]];
149
        $this->embeddedTemplates = [];
150
151
        try {
152
            $body = $this->subparse($test, $dropNeedle);
153
154
            if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) {
155
                $body = new Node();
156
            }
157
        } catch (SyntaxError $e) {
158
            if (!$e->getSourceContext()) {
159
                $e->setSourceContext($this->stream->getSourceContext());
160
            }
161
162
            if (!$e->getTemplateLine()) {
163
                $e->setTemplateLine($this->getCurrentToken()->getLine());
164
            }
165
166
            throw $e;
167
        }
168
169
        $node = new ModuleNode(new BodyNode([$body]), $this->parent, new Node($this->blocks), new Node($this->macros), new Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext());
170
171
        $traverser = new NodeTraverser($this->env, $this->visitors);
172
173
        /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
174
         * @var ModuleNode $node
175
         */
176
        $node = $traverser->traverse($node);
177
178
        // restore previous stack so previous parse() call can resume working
179
        foreach (array_pop($this->stack) as $key => $val) {
180
            $this->$key = $val;
181
        }
182
183
        return $node;
184
    }
185
186
    public function subparse($test, bool $dropNeedle = false): Node
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function subparse()
Loading history...
187
    {
188
        $lineno = $this->getCurrentToken()->getLine();
189
        $rv = [];
190
        while (!$this->stream->isEOF()) {
191
            switch ($this->getCurrentToken()->getType()) {
192
                case Token::TEXT_TYPE:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
193
                    $token = $this->stream->next();
194
                    $rv[] = new TextNode($token->getValue(), $token->getLine());
195
                    break;
196
197
                case Token::VAR_START_TYPE:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
198
                    $token = $this->stream->next();
199
                    $expr = $this->expressionParser->parseExpression();
200
                    $this->stream->expect(Token::VAR_END_TYPE);
201
                    $rv[] = new PrintNode($expr, $token->getLine());
202
                    break;
203
204
                case Token::BLOCK_START_TYPE:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
205
                    $this->stream->next();
206
                    $token = $this->getCurrentToken();
207
208
                    if (Token::NAME_TYPE !== $token->getType()) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
209
                        throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext());
210
                    }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
211
212
                    if (null !== $test && $test($token)) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
213
                        if ($dropNeedle) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
214
                            $this->stream->next();
215
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
216
217
                        if (1 === count($rv)) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
218
                            return $rv[0];
219
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
220
221
                        return new Node($rv, [], $lineno);
222
                    }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
223
224
                    if (!$subparser = $this->env->getTokenParser($token->getValue())) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
225
                        if (null !== $test) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
226
                            $e = new SyntaxError(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());
227
228
                            $callable = (new ReflectionCallable(new TwigTest('decision', $test)))->getCallable();
229
                            if (is_array($callable) && $callable[0] instanceof TokenParserInterface) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
230
                                $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $callable[0]->getTag(), $lineno));
231
                            }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
232
                        } else {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
233
                            $e = new SyntaxError(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());
234
                            $e->addSuggestions($token->getValue(), array_keys($this->env->getTokenParsers()));
235
                        }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
236
237
                        throw $e;
238
                    }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
239
240
                    $this->stream->next();
241
242
                    $subparser->setParser($this);
243
                    $node = $subparser->parse($token);
244
                    if (!$node) {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
245
                        trigger_deprecation('twig/twig', '3.12', 'Returning "null" from "%s" is deprecated and forbidden by "TokenParserInterface".', $subparser::class);
246
                    } else {
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
247
                        $node->setNodeTag($subparser->getTag());
248
                        $rv[] = $node;
249
                    }
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
250
                    break;
251
252
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
253
                    throw new SyntaxError('The lexer or the parser ended up in an unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext());
254
            }
255
        }
256
257
        if (1 === count($rv)) {
258
            return $rv[0];
259
        }
260
261
        return new Node($rv, [], $lineno);
262
    }
263
264
    public function getBlockStack(): array
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getBlockStack()
Loading history...
265
    {
266
        trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__);
267
268
        return $this->blockStack;
269
    }
270
271
    public function peekBlockStack()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function peekBlockStack()
Loading history...
272
    {
273
        return $this->blockStack[count($this->blockStack) - 1] ?? null;
274
    }
275
276
    public function popBlockStack(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function popBlockStack()
Loading history...
277
    {
278
        array_pop($this->blockStack);
279
    }
280
281
    public function pushBlockStack($name): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function pushBlockStack()
Loading history...
282
    {
283
        $this->blockStack[] = $name;
284
    }
285
286
    public function hasBlock(string $name): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function hasBlock()
Loading history...
287
    {
288
        trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__);
289
290
        return isset($this->blocks[$name]);
291
    }
292
293
    public function getBlock(string $name): Node
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getBlock()
Loading history...
294
    {
295
        trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__);
296
297
        return $this->blocks[$name];
298
    }
299
300
    public function setBlock(string $name, BlockNode $value): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function setBlock()
Loading history...
301
    {
302
        if (isset($this->blocks[$name])) {
303
            throw new SyntaxError(sprintf("The block '%s' has already been defined line %d.", $name, $this->blocks[$name]->getTemplateLine()), $this->getCurrentToken()->getLine(), $this->blocks[$name]->getSourceContext());
304
        }
305
306
        $this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine());
307
    }
308
309
    public function hasMacro(string $name): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function hasMacro()
Loading history...
310
    {
311
        trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__);
312
313
        return isset($this->macros[$name]);
314
    }
315
316
    public function setMacro(string $name, MacroNode $node): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function setMacro()
Loading history...
317
    {
318
        $this->macros[$name] = $node;
319
    }
320
321
    public function addTrait($trait): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function addTrait()
Loading history...
322
    {
323
        $this->traits[] = $trait;
324
    }
325
326
    public function hasTraits(): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function hasTraits()
Loading history...
327
    {
328
        trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__);
329
330
        return count($this->traits) > 0;
331
    }
332
333
    public function embedTemplate(ModuleNode $template)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function embedTemplate()
Loading history...
334
    {
335
        $template->setIndex(mt_rand());
336
337
        $this->embeddedTemplates[] = $template;
338
    }
339
340
    public function addImportedSymbol(string $type, string $alias, ?string $name = null, ?AbstractExpression $node = null): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function addImportedSymbol()
Loading history...
341
    {
342
        $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node];
343
    }
344
345
    public function getImportedSymbol(string $type, string $alias)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getImportedSymbol()
Loading history...
346
    {
347
        // if the symbol does not exist in the current scope (0), try in the main/global scope (last index)
348
        return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[count($this->importedSymbols) - 1][$type][$alias] ?? null);
349
    }
350
351
    public function isMainScope(): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function isMainScope()
Loading history...
352
    {
353
        return 1 === count($this->importedSymbols);
354
    }
355
356
    public function pushLocalScope(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function pushLocalScope()
Loading history...
357
    {
358
        array_unshift($this->importedSymbols, []);
359
    }
360
361
    public function popLocalScope(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function popLocalScope()
Loading history...
362
    {
363
        array_shift($this->importedSymbols);
364
    }
365
366
    public function getExpressionParser(): ExpressionParser
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getExpressionParser()
Loading history...
367
    {
368
        return $this->expressionParser;
369
    }
370
371
    public function getParent(): ?Node
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getParent()
Loading history...
372
    {
373
        trigger_deprecation('twig/twig', '3.12', 'Method "%s()" is deprecated.', __METHOD__);
374
375
        return $this->parent;
376
    }
377
378
    public function setParent(?Node $parent): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function setParent()
Loading history...
379
    {
380
        if (null === $parent) {
381
            trigger_deprecation('twig/twig', '3.12', 'Passing "null" to "%s()" is deprecated.', __METHOD__);
382
        }
383
384
        if (null !== $this->parent) {
385
            throw new SyntaxError('Multiple extends tags are forbidden.', $parent->getTemplateLine(), $parent->getSourceContext());
0 ignored issues
show
Bug introduced by
The method getTemplateLine() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

385
            throw new SyntaxError('Multiple extends tags are forbidden.', $parent->/** @scrutinizer ignore-call */ getTemplateLine(), $parent->getSourceContext());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
386
        }
387
388
        $this->parent = $parent;
389
    }
390
391
    public function hasInheritance()
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function hasInheritance()
Loading history...
392
    {
393
        return $this->parent || 0 < count($this->traits);
394
    }
395
396
    public function getStream(): TokenStream
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getStream()
Loading history...
397
    {
398
        return $this->stream;
399
    }
400
401
    public function getCurrentToken(): Token
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getCurrentToken()
Loading history...
402
    {
403
        return $this->stream->getCurrent();
404
    }
405
406
    private function filterBodyNodes(Node $node, bool $nested = false): ?Node
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function filterBodyNodes()
Loading history...
Coding Style introduced by
Private method name "TemplateCommentsParser::filterBodyNodes" must be prefixed with an underscore
Loading history...
407
    {
408
        // check that the body does not contain non-empty output nodes
409
        if (
0 ignored issues
show
Coding Style introduced by
First condition of a multi-line IF statement must directly follow the opening parenthesis
Loading history...
410
            ($node instanceof TextNode && !ctype_space($node->getAttribute('data')))
0 ignored issues
show
Coding Style introduced by
Each line in a multi-line IF statement must begin with a boolean operator
Loading history...
411
            || (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface)
412
        ) {
413
            if (str_contains((string)$node, chr(0xEF) . chr(0xBB) . chr(0xBF))) {
414
                $t = substr($node->getAttribute('data'), 3);
415
                if ('' === $t || ctype_space($t)) {
416
                    // bypass empty nodes starting with a BOM
417
                    return null;
418
                }
419
            }
420
421
            throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext());
422
        }
423
424
        // bypass nodes that "capture" the output
425
        if ($node instanceof NodeCaptureInterface) {
426
            // a "block" tag in such a node will serve as a block definition AND be displayed in place as well
427
            return $node;
428
        }
429
430
        /**
431
         * We intentionally skip this check to avoid throwing an exception, so our {% comments %} tag can
432
         * render correctly
433
         * // "block" tags that are not captured (see above) are only used for defining
434
         * // the content of the block. In such a case, nesting it does not work as
435
         * // expected as the definition is not part of the default template code flow.
436
         * if ($nested && $node instanceof BlockReferenceNode) {
437
         * throw new SyntaxError('A block definition cannot be nested under non-capturing nodes.', $node->getTemplateLine(), $this->stream->getSourceContext());
438
         * }
439
         */
440
        if ($node instanceof NodeOutputInterface) {
441
            return null;
442
        }
443
444
        // "block" tags that are not captured (see above) are only used for defining
445
        // the content of the block. In such a case, nesting it does not work as
446
        // expected as the definition is not part of the default template code flow.
447
        if ($nested && $node instanceof BlockReferenceNode) {
448
            throw new SyntaxError('A block definition cannot be nested under non-capturing nodes.', $node->getTemplateLine(), $this->stream->getSourceContext());
449
        }
450
451
        // here, $nested means "being at the root level of a child template"
452
        // we need to discard the wrapping "Node" for the "body" node
453
        $nested = $nested || Node::class !== get_class($node);
454
        foreach ($node as $k => $n) {
455
            if (null !== $n && null === $this->filterBodyNodes($n, $nested)) {
456
                $node->removeNode($k);
457
            }
458
        }
459
460
        return $node;
461
    }
462
}
463