Passed
Push — 3.x ( cff945...7979b9 )
by Kyle
05:48 queued 13s
created

AbstractPHPParser::isClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 17
c 0
b 0
f 0
dl 0
loc 19
ccs 18
cts 18
cp 1
rs 9.7
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * This file is part of PDepend.
5
 *
6
 * PHP Version 5
7
 *
8
 * Copyright (c) 2008-2017 Manuel Pichler <[email protected]>.
9
 * All rights reserved.
10
 *
11
 * Redistribution and use in source and binary forms, with or without
12
 * modification, are permitted provided that the following conditions
13
 * are met:
14
 *
15
 *   * Redistributions of source code must retain the above copyright
16
 *     notice, this list of conditions and the following disclaimer.
17
 *
18
 *   * Redistributions in binary form must reproduce the above copyright
19
 *     notice, this list of conditions and the following disclaimer in
20
 *     the documentation and/or other materials provided with the
21
 *     distribution.
22
 *
23
 *   * Neither the name of Manuel Pichler nor the names of his
24
 *     contributors may be used to endorse or promote products derived
25
 *     from this software without specific prior written permission.
26
 *
27
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
30
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
31
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
32
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
33
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
37
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38
 * POSSIBILITY OF SUCH DAMAGE.
39
 *
40
 * @copyright 2008-2017 Manuel Pichler. All rights reserved.
41
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
42
 */
43
44
namespace PDepend\Source\Language\PHP;
45
46
use InvalidArgumentException;
47
use PDepend\Source\AST\AbstractASTCallable;
48
use PDepend\Source\AST\AbstractASTClassOrInterface;
49
use PDepend\Source\AST\AbstractASTNode;
50
use PDepend\Source\AST\ASTAllocationExpression;
51
use PDepend\Source\AST\ASTArguments;
52
use PDepend\Source\AST\ASTArray;
53
use PDepend\Source\AST\ASTArrayElement;
54
use PDepend\Source\AST\ASTAssignmentExpression;
55
use PDepend\Source\AST\ASTBooleanAndExpression;
56
use PDepend\Source\AST\ASTBooleanOrExpression;
57
use PDepend\Source\AST\ASTBreakStatement;
58
use PDepend\Source\AST\ASTCallable;
59
use PDepend\Source\AST\ASTCastExpression;
60
use PDepend\Source\AST\ASTCatchStatement;
61
use PDepend\Source\AST\ASTClass;
62
use PDepend\Source\AST\ASTClassFqnPostfix;
63
use PDepend\Source\AST\ASTClassOrInterfaceReference;
64
use PDepend\Source\AST\ASTCloneExpression;
65
use PDepend\Source\AST\ASTClosure;
66
use PDepend\Source\AST\ASTComment;
67
use PDepend\Source\AST\ASTCompilationUnit;
68
use PDepend\Source\AST\ASTCompoundExpression;
69
use PDepend\Source\AST\ASTCompoundVariable;
70
use PDepend\Source\AST\ASTConditionalExpression;
71
use PDepend\Source\AST\ASTConstant;
72
use PDepend\Source\AST\ASTConstantDeclarator;
73
use PDepend\Source\AST\ASTConstantDefinition;
74
use PDepend\Source\AST\ASTContinueStatement;
75
use PDepend\Source\AST\ASTDeclareStatement;
76
use PDepend\Source\AST\ASTDoWhileStatement;
77
use PDepend\Source\AST\ASTEchoStatement;
78
use PDepend\Source\AST\ASTElseIfStatement;
79
use PDepend\Source\AST\ASTEnum;
80
use PDepend\Source\AST\ASTEnumCase;
81
use PDepend\Source\AST\ASTEvalExpression;
82
use PDepend\Source\AST\ASTExitExpression;
83
use PDepend\Source\AST\ASTExpression;
84
use PDepend\Source\AST\ASTFieldDeclaration;
85
use PDepend\Source\AST\ASTFinallyStatement;
86
use PDepend\Source\AST\ASTForeachStatement;
87
use PDepend\Source\AST\ASTForInit;
88
use PDepend\Source\AST\ASTFormalParameter;
89
use PDepend\Source\AST\ASTFormalParameters;
90
use PDepend\Source\AST\ASTForStatement;
91
use PDepend\Source\AST\ASTForUpdate;
92
use PDepend\Source\AST\ASTFunction;
93
use PDepend\Source\AST\ASTFunctionPostfix;
94
use PDepend\Source\AST\ASTGlobalStatement;
95
use PDepend\Source\AST\ASTGotoStatement;
96
use PDepend\Source\AST\ASTHeredoc;
97
use PDepend\Source\AST\ASTIdentifier;
98
use PDepend\Source\AST\ASTIfStatement;
99
use PDepend\Source\AST\ASTIncludeExpression;
100
use PDepend\Source\AST\ASTIndexExpression;
101
use PDepend\Source\AST\ASTInstanceOfExpression;
102
use PDepend\Source\AST\ASTInterface;
103
use PDepend\Source\AST\ASTIntersectionType;
104
use PDepend\Source\AST\ASTIssetExpression;
105
use PDepend\Source\AST\ASTLabelStatement;
106
use PDepend\Source\AST\ASTListExpression;
107
use PDepend\Source\AST\ASTLiteral;
108
use PDepend\Source\AST\ASTLogicalAndExpression;
109
use PDepend\Source\AST\ASTLogicalOrExpression;
110
use PDepend\Source\AST\ASTLogicalXorExpression;
111
use PDepend\Source\AST\ASTMatchEntry;
112
use PDepend\Source\AST\ASTMemberPrimaryPrefix;
113
use PDepend\Source\AST\ASTMethod;
114
use PDepend\Source\AST\ASTNamespace;
115
use PDepend\Source\AST\ASTNode;
116
use PDepend\Source\AST\ASTParentReference;
117
use PDepend\Source\AST\ASTPostDecrementExpression;
0 ignored issues
show
Bug introduced by
The type PDepend\Source\AST\ASTPostDecrementExpression 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...
118
use PDepend\Source\AST\ASTPostfixExpression;
119
use PDepend\Source\AST\ASTPostIncrementExpression;
0 ignored issues
show
Bug introduced by
The type PDepend\Source\AST\ASTPostIncrementExpression 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...
120
use PDepend\Source\AST\ASTPreDecrementExpression;
121
use PDepend\Source\AST\ASTPreIncrementExpression;
122
use PDepend\Source\AST\ASTPropertyPostfix;
123
use PDepend\Source\AST\ASTRequireExpression;
124
use PDepend\Source\AST\ASTReturnStatement;
125
use PDepend\Source\AST\ASTScalarType;
126
use PDepend\Source\AST\ASTScope;
127
use PDepend\Source\AST\ASTScopeStatement;
128
use PDepend\Source\AST\ASTSelfReference;
129
use PDepend\Source\AST\ASTShiftLeftExpression;
130
use PDepend\Source\AST\ASTShiftRightExpression;
131
use PDepend\Source\AST\ASTStatement;
132
use PDepend\Source\AST\ASTStaticReference;
133
use PDepend\Source\AST\ASTStaticVariableDeclaration;
134
use PDepend\Source\AST\ASTString;
135
use PDepend\Source\AST\ASTSwitchLabel;
136
use PDepend\Source\AST\ASTSwitchStatement;
137
use PDepend\Source\AST\ASTThrowStatement;
138
use PDepend\Source\AST\ASTTrait;
139
use PDepend\Source\AST\ASTTraitAdaptation;
140
use PDepend\Source\AST\ASTTraitAdaptationAlias;
141
use PDepend\Source\AST\ASTTraitAdaptationPrecedence;
142
use PDepend\Source\AST\ASTTraitReference;
143
use PDepend\Source\AST\ASTTraitUseStatement;
144
use PDepend\Source\AST\ASTTryStatement;
145
use PDepend\Source\AST\ASTType;
146
use PDepend\Source\AST\ASTTypeArray;
147
use PDepend\Source\AST\ASTUnaryExpression;
148
use PDepend\Source\AST\ASTUnionType;
149
use PDepend\Source\AST\ASTUnsetStatement;
150
use PDepend\Source\AST\ASTValue;
151
use PDepend\Source\AST\ASTVariable;
152
use PDepend\Source\AST\ASTVariableDeclarator;
153
use PDepend\Source\AST\ASTVariableVariable;
154
use PDepend\Source\AST\ASTWhileStatement;
155
use PDepend\Source\AST\ASTYieldStatement;
156
use PDepend\Source\AST\State;
157
use PDepend\Source\Builder\Builder;
158
use PDepend\Source\Parser\InvalidStateException;
159
use PDepend\Source\Parser\MissingValueException;
160
use PDepend\Source\Parser\NoActiveScopeException;
161
use PDepend\Source\Parser\ParserException;
162
use PDepend\Source\Parser\SymbolTable;
163
use PDepend\Source\Parser\TokenException;
164
use PDepend\Source\Parser\TokenStack;
165
use PDepend\Source\Parser\TokenStreamEndException;
166
use PDepend\Source\Parser\UnexpectedTokenException;
167
use PDepend\Source\Tokenizer\FullTokenizer;
168
use PDepend\Source\Tokenizer\Token;
169
use PDepend\Source\Tokenizer\Tokenizer;
170
use PDepend\Source\Tokenizer\Tokens;
171
use PDepend\Util\Cache\CacheDriver;
172
use PDepend\Util\IdBuilder;
173
use PDepend\Util\Log;
174
use PDepend\Util\Type;
175
176
/**
177
 * The php source parser implementation that supports features up to PHP version 8.1.
178
 *
179
 * With the default settings the parser includes annotations, better known as
180
 * doc comment tags, in the generated result. This means it extracts the type
181
 * information of @var tags for properties, and types in @return + @throws tags
182
 * of functions and methods. The current implementation tries to ignore all
183
 * scalar types from <b>boolean</b> to <b>void</b>. You should disable this
184
 * feature for project that have more or less invalid doc comments, because it
185
 * could produce invalid results.
186
 *
187
 * <code>
188
 *   $parser->setIgnoreAnnotations();
189
 * </code>
190
 *
191
 * <b>Note</b>: Due to the fact that it is possible to use the same name for
192
 * multiple classes and interfaces, and there is no way to determine to which
193
 * package it belongs, while the parser handles class, interface or method
194
 * signatures, the parser could/will create a code tree that doesn't reflect the
195
 * real source structure.
196
 *
197
 * @copyright 2008-2017 Manuel Pichler. All rights reserved.
198
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
199
 */
200
abstract class AbstractPHPParser
201
{
202
    /** Tell if readonly is allowed as class modifier in the current PHP level. */
203
    protected const READONLY_CLASS_ALLOWED = false;
204
205
    /**
206
     * Regular expression for inline type definitions in regular comments. This
207
     * kind of type is supported by IDEs like Netbeans or eclipse.
208
     */
209
    private const REGEXP_INLINE_TYPE = '(^\s*/\*\s*
210
                                 @var\s+
211
                                   \$[a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff]*\s+
212
                                   (.*?)
213
                                \s*\*/\s*$)ix';
214
215
    /**
216
     * Regular expression for types defined in <b>throws</b> annotations of
217
     * method or function doc comments.
218
     */
219
    private const REGEXP_THROWS_TYPE = '(\*\s*
220
                             @throws\s+
221
                               ([a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff\\\\]*)
222
                            )ix';
223
224
    /**
225
     * Regular expression for types defined in annotations like <b>return</b> or
226
     * <b>var</b> in doc comments of functions and methods.
227
     */
228
    private const REGEXP_RETURN_TYPE = '(\*\s*
229
                     @return\s+
230
                      (array\(\s*
231
                        (\w+\s*=>\s*)?
232
                        ([a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff\|\\\\]*)\s*
233
                      \)
234
                      |
235
                      ([a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff\|\\\\]*))\s+
236
                      |
237
                       ([a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff\|\\\\]*)\[\]
238
                    )ix';
239
240
    /**
241
     * Regular expression for types defined in annotations like <b>return</b> or
242
     * <b>var</b> in doc comments of functions and methods.
243
     */
244
    private const REGEXP_VAR_TYPE = '(\*\s*
245
                      @var\s+
246
                       (array\(\s*
247
                         (\w+\s*=>\s*)?
248
                         ([a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff\|\\\\]*)\s*
249
                       \)
250
                       |
251
                       ([a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff\|\\\\]*))\s+
252
                       |
253
                       ([a-zA-Z_\x7f-\xff\\\\][a-zA-Z0-9_\x7f-\xff\|\\\\]*)\[\]\s+
254
                       |
255
                       (array)\(\s*\)\s+
256
                     )ix';
257
258
    /**
259
     * Regular expression for integer numbers representation.
260
     *
261
     * @see https://php.net/manual/en/language.types.integer.php
262
     * @see https://github.com/php/doc-en/blob/d494ffa4d9f83b60fe66972ec2c0cf0301513b4a/language/types/integer.xml#L77-L89
263
     */
264
    private const REGEXP_INTEGER = '(
265
                       0
266
                       |
267
                       [1-9][0-9]*(?:_[0-9]+)*
268
                       |
269
                       0[xX][0-9a-fA-F]+(?:_[0-9a-fA-F]+)*
270
                       |
271
                       0[oO]?[0-7]+(?:_[0-7]+)*
272
                       |
273
                       0[bB][01]+(?:_[01]+)*
274
                     )x';
275
276
    /**
277
     * The used data structure builder.
278
     *
279
     * @var PHPBuilder<mixed>
280
     */
281
    protected PHPBuilder $builder;
282
283
    /** Stack with all active token scopes. */
284
    protected TokenStack $tokenStack;
285
286
    /** The used code tokenizer. */
287
    protected Tokenizer $tokenizer;
288
289
    /** @var array<int, int> */
290
    protected array $possiblePropertyTypes = [
291
        Tokens::T_STRING,
292
        Tokens::T_ARRAY,
293
        Tokens::T_QUESTION_MARK,
294
        Tokens::T_BACKSLASH,
295
        Tokens::T_CALLABLE,
296
        Tokens::T_SELF,
297
        Tokens::T_NULL,
298
        Tokens::T_FALSE,
299
    ];
300
301
    /**
302
     * Internal state flag, that will be set to <b>true</b> when the parser has
303
     * prefixed a qualified name with the actual namespace.
304
     */
305
    private bool $namespacePrefixReplaced = false;
306
307
    /** The currently parsed file instance. */
308
    private ASTCompilationUnit $compilationUnit;
309
310
    /** The symbol table used to handle PHP use statements. */
311
    private SymbolTable $useSymbolTable;
312
313
    /** The actually parsed class or interface instance. */
314
    private ?AbstractASTClassOrInterface $classOrInterface = null;
315
316
    /** @since 0.10.0 */
317
    private CacheDriver $cache;
318
319
    /** The name of the last detected namespace. */
320
    private ?string $namespaceName = null;
321
322
    /** Last parsed package tag. */
323
    private ?string $packageName = Builder::DEFAULT_NAMESPACE;
324
325
    /** The package defined in the file level comment. */
326
    private ?string $globalPackageName = Builder::DEFAULT_NAMESPACE;
327
328
    /** The last parsed doc comment or <b>null</b>. */
329
    private ?string $docComment = null;
330
331
    /** Bitfield of last parsed modifiers. */
332
    private int $modifiers = 0;
333
334
    /**
335
     * If this property is set to <b>true</b> the parser will ignore all doc
336
     * comment annotations.
337
     */
338
    private bool $ignoreAnnotations = false;
339
340
    /**
341
     * Used identifier builder instance.
342
     *
343
     * @since 0.9.12
344
     */
345
    private readonly IdBuilder $idBuilder;
346
347
    /**
348
     * The maximum valid nesting level allowed.
349
     *
350
     * @since 0.9.12
351
     */
352
    private int $maxNestingLevel = 1024;
353
354
    /** True if current statement is echoing (such as after <?=) */
355
    private bool $echoing = false;
356
357
    /**
358
     * Constructs a new source parser.
359
     *
360
     * @param PHPBuilder<mixed> $builder
361
     */
362 1447
    public function __construct(Tokenizer $tokenizer, Builder $builder, CacheDriver $cache)
363
    {
364 1447
        $this->tokenizer = $tokenizer;
365 1447
        $this->builder = $builder;
0 ignored issues
show
Documentation Bug introduced by
$builder is of type PDepend\Source\Builder\Builder, but the property $builder was declared to be of type PDepend\Source\Language\PHP\PHPBuilder. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
366 1447
        $this->cache = $cache;
367
368 1447
        $this->idBuilder = new IdBuilder();
0 ignored issues
show
Bug introduced by
The property idBuilder is declared read-only in PDepend\Source\Language\PHP\AbstractPHPParser.
Loading history...
369 1447
        $this->tokenStack = new TokenStack();
370 1447
        $this->useSymbolTable = new SymbolTable();
371
372 1447
        $this->builder->setCache($this->cache);
373
    }
374
375
    /**
376
     * Sets the ignore annotations flag. This means that the parser will ignore
377
     * doc comment annotations.
378
     */
379 4
    public function setIgnoreAnnotations(): void
380
    {
381 4
        $this->ignoreAnnotations = true;
382
    }
383
384
    /**
385
     * Configures the maximum allowed nesting level.
386
     *
387
     * @param int $maxNestingLevel The maximum allowed nesting level.
388
     * @since 0.9.12
389
     */
390 13
    public function setMaxNestingLevel($maxNestingLevel): void
391
    {
392 13
        $this->maxNestingLevel = $maxNestingLevel;
393
    }
394
395
    /**
396
     * Returns the maximum allowed nesting/recursion level.
397
     *
398
     * @return int
399
     * @since 0.9.12
400
     */
401 1441
    private function getMaxNestingLevel()
402
    {
403 1441
        return $this->maxNestingLevel;
404
    }
405
406
    /**
407
     * @return ASTType
408
     */
409 40
    private function parseReturnTypeHint()
410
    {
411 40
        $this->consumeComments();
412 40
        $this->consumeQuestionMark();
413 40
        $this->consumeComments();
414
415 40
        return $this->parseEndReturnTypeHint();
416
    }
417
418
    /**
419
     * Parses a scalar type hint or a callable type hint.
420
     *
421
     * @param string $image
422
     * @return ASTType
423
     * @throws ParserException
424
     */
425 46
    private function parseScalarOrCallableTypeHint($image)
426
    {
427 46
        return match (strtolower($image)) {
428 46
            'int',
429 46
            'bool',
430 46
            'float',
431 46
            'string',
432 46
            'void',
433 46
            'never' => $this->builder->buildAstScalarType($image),
434 46
            'callable' => $this->builder->buildAstTypeCallable(),
435 46
            'iterable' => $this->builder->buildAstTypeIterable(),
436 46
            default => throw new ParserException('Unsupported typehint'),
437 46
        };
438
    }
439
440 447
    private function consumeQuestionMark(): void
441
    {
442 447
        if ($this->tokenizer->peek() === Tokens::T_QUESTION_MARK) {
443 16
            $this->consumeToken(Tokens::T_QUESTION_MARK);
444
        }
445
    }
446
447
    /**
448
     * @param int $tokenType
449
     * @param int $modifiers
450
     *
451
     * @return int
452
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
453 7
    private function getModifiersForConstantDefinition($tokenType, $modifiers)
454
    {
455 7
        $allowed = State::IS_PUBLIC | State::IS_PROTECTED | State::IS_PRIVATE;
456 7
        $modifiers &= $allowed;
457
458 7
        if ($this->classOrInterface instanceof ASTInterface && ($modifiers & (State::IS_PROTECTED | State::IS_PRIVATE)) !== 0) {
459 2
            throw new InvalidStateException(
460 2
                $this->requireNextToken()->startLine,
461 2
                (string) $this->compilationUnit,
462 2
                sprintf(
463 2
                    'Constant can\'t be declared private or protected in interface "%s".',
464 2
                    $this->classOrInterface->getImage()
0 ignored issues
show
Bug introduced by
The method getImage() 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

464
                    $this->classOrInterface->/** @scrutinizer ignore-call */ 
465
                                             getImage()

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...
465 2
                )
466 2
            );
467
        }
468
469 5
        return $modifiers;
470
    }
471
472
    /**
473
     * @return ASTType
474
     */
475 40
    private function parseEndReturnTypeHint()
476
    {
477 40
        return $this->parseTypeHint();
478
    }
479
480
    /**
481
     * Attempts to the next sequence of tokens as an anonymous class and adds it to the allocation expression
482
     *
483
     * @template T of ASTAllocationExpression
484
     *
485
     * @param T $allocation
0 ignored issues
show
Bug introduced by
The type PDepend\Source\Language\PHP\T 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...
486
     *
487
     * @return T|null
488
     */
489 108
    private function parseAnonymousClassDeclaration(ASTAllocationExpression $allocation): ?ASTAllocationExpression
490
    {
491 108
        $this->consumeComments();
492
493 108
        if (Tokens::T_CLASS !== $this->tokenizer->peek()) {
494 105
            return null;
495
        }
496
497 4
        $classOrInterface = $this->classOrInterface;
498
499 4
        $this->tokenStack->push();
500
501 4
        $this->consumeToken(Tokens::T_CLASS);
502 4
        $this->consumeComments();
503
504 4
        $class = $this->builder->buildAnonymousClass();
505 4
        $class->setName(
506 4
            sprintf(
507 4
                'class@anonymous%s0x%s',
508 4
                $this->compilationUnit->getFileName(),
509 4
                uniqid('')
510 4
            )
511 4
        );
512 4
        $class->setCompilationUnit($this->compilationUnit);
513 4
        $class->setUserDefined();
514
515 4
        if ($this->isNextTokenArguments()) {
516 4
            $class->addChild($this->parseArguments());
517
        }
518
519 4
        $this->consumeComments();
520 4
        $tokenType = $this->tokenizer->peek();
521
522 4
        if ($tokenType === Tokens::T_EXTENDS) {
523
            $class = $this->parseClassExtends($class);
524
525
            $this->consumeComments();
526
            $tokenType = $this->tokenizer->peek();
527
        }
528
529 4
        if ($tokenType === Tokens::T_IMPLEMENTS) {
530
            $this->consumeToken(Tokens::T_IMPLEMENTS);
531
            $this->parseInterfaceList($class);
532
        }
533
534 4
        $tokens = [];
535 4
        $allocation->addChild(
536 4
            $this->setNodePositionsAndReturn(
537 4
                $this->parseTypeBody($class),
538 4
                $tokens
539 4
            )
540 4
        );
541 4
        $class->setTokens($tokens);
542
543 4
        $this->classOrInterface = $classOrInterface;
544
545 4
        return $allocation;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $allocation returns the type PDepend\Source\AST\ASTAllocationExpression which is incompatible with the documented return type PDepend\Source\Language\PHP\T|null.
Loading history...
546
    }
547
548
    /**
549
     * @param array<string> $fragments
550
     */
551 2
    private function parseUseDeclarationVersion70(array $fragments): void
552
    {
553 2
        $namespacePrefixReplaced = $this->namespacePrefixReplaced;
554
555 2
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
556 2
        $this->consumeComments();
557
558 2
        while (true) {
559 2
            $nextToken = $this->tokenizer->peek();
560
561
            switch ($nextToken) {
562
                case Tokens::T_CONST:
563
                case Tokens::T_FUNCTION:
564 1
                    $this->consumeToken($nextToken);
565
            }
566
567 2
            if (Tokens::T_CURLY_BRACE_CLOSE === $this->tokenizer->peek()) {
568 1
                break;
569
            }
570
571 2
            $subFragments = $this->parseQualifiedNameRaw();
572 2
            $this->consumeComments();
573
574 2
            $image = $this->parseNamespaceImage($subFragments);
575
576 2
            if (Tokens::T_COMMA !== $this->tokenizer->peek()) {
577 1
                break;
578
            }
579
580 2
            $this->consumeToken(Tokens::T_COMMA);
581 2
            $this->consumeComments();
582
583
            // Add mapping between image and qualified name to symbol table
584 2
            if ($image !== false) {
585 2
                $this->useSymbolTable->add($image, implode('', [...$fragments, ...$subFragments]));
586
            }
587
        }
588
589 2
        if (isset($image, $subFragments) && $image !== false) {
590 2
            $this->useSymbolTable->add($image, implode('', [...$fragments, ...$subFragments]));
591
        }
592
593 2
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
594 2
        $this->consumeComments();
595
596 2
        $this->namespacePrefixReplaced = $namespacePrefixReplaced;
597
    }
598
599
    /**
600
     * @return AbstractASTNode
601
     */
602 24
    private function parseConstantArgument(ASTConstant $constant, ASTArguments $arguments)
603
    {
604 24
        if ($this->tokenizer->peek() === Tokens::T_COLON) {
605 6
            $token = $this->tokenizer->next();
606
            assert($token instanceof Token);
607 6
            $this->tokenStack->add($token);
608
609 6
            $expression = $this->parseOptionalExpression();
610 6
            if ($expression) {
611 6
                return $this->builder->buildAstNamedArgument($constant->getImage(), $expression);
612
            }
613
        }
614
615 18
        return $constant;
616
    }
617
618
    /**
619
     * @return ASTNode|null
620
     */
621 207
    private function parseArgumentExpression()
622
    {
623 207
        if ($this->tokenizer->peekNext() === Tokens::T_COLON) {
624 6
            $token = $this->tokenizer->currentToken();
625 6
            if ($token) {
626 6
                $image = $token->image;
627
628
                // Variable RegExp from https://www.php.net/manual/en/language.variables.basics.php
629 6
                if (preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $image)) {
630 6
                    $this->consumeToken($token->type);
631
632 6
                    return $this->builder->buildAstConstant($image);
633
                }
634
            }
635
        }
636
637 206
        return $this->parseOptionalExpression();
638
    }
639
640
    /**
641
     * Determines if the following expression can be stored as a static value.
642
     *
643
     * @return bool
644
     */
645 72
    private function isFollowedByStaticValueOrStaticArray()
646
    {
647
        // If we can't anticipate, we should assume it can be a dynamic value
648 72
        if (!($this->tokenizer instanceof FullTokenizer)) {
649
            return false;
650
        }
651
652 72
        for ($i = 0; $type = $this->tokenizer->peekAt($i); $i++) {
0 ignored issues
show
Bug introduced by
The method peekAt() does not exist on PDepend\Source\Tokenizer\Tokenizer. Did you maybe mean peek()? ( Ignorable by Annotation )

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

652
        for ($i = 0; $type = $this->tokenizer->/** @scrutinizer ignore-call */ peekAt($i); $i++) {

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...
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
653
            switch ($type) {
654
                case Tokens::T_COMMENT:
655
                case Tokens::T_DOC_COMMENT:
656
                case Tokens::T_ARRAY:
657
                case Tokens::T_SQUARED_BRACKET_OPEN:
658
                case Tokens::T_SQUARED_BRACKET_CLOSE:
659
                case Tokens::T_PARENTHESIS_OPEN:
660
                case Tokens::T_PARENTHESIS_CLOSE:
661
                case Tokens::T_COMMA:
662
                case Tokens::T_DOUBLE_ARROW:
663
                case Tokens::T_NULL:
664
                case Tokens::T_TRUE:
665
                case Tokens::T_FALSE:
666
                case Tokens::T_LNUMBER:
667
                case Tokens::T_DNUMBER:
668
                case Tokens::T_STRING:
669
                case Tokens::T_EQUAL:
670
                case Tokens::T_START_HEREDOC:
671
                case Tokens::T_END_HEREDOC:
672
                case Tokens::T_ENCAPSED_AND_WHITESPACE:
673 63
                    break;
674
675
                case Tokens::T_SEMICOLON:
676
                case Tokenizer::T_EOF:
677 63
                    return true;
678
679
                default:
680 13
                    return false;
681
            }
682
        }
683
684
        return false;
685
    }
686
687
    /**
688
     * Parses the contents of the tokenizer and generates a node tree based on
689
     * the found tokens.
690
     *
691
     * @throws ParserException
692
     */
693 1445
    public function parse(): void
694
    {
695 1445
        $compilationUnit = $this->tokenizer->getSourceFile();
696 1445
        if (!$compilationUnit) {
697
            return;
698
        }
699 1445
        $this->compilationUnit = $compilationUnit;
700
701 1445
        $compilationUnit
702 1445
            ->setCache($this->cache)
703 1445
            ->setId($this->idBuilder->forFile($compilationUnit));
704
705 1445
        $filename = $compilationUnit->getFileName();
706 1445
        $hash = ($filename === 'php://stdin' || !$filename)
707
            ? md5((string) $compilationUnit->getSource())
708 1445
            : md5_file($filename);
709
710 1445
        $id = $compilationUnit->getId();
711 1445
        if ($hash && $id && $this->cache->restore($id, $hash)) {
712 5
            return;
713
        }
714
715 1441
        if ($id) {
716 1441
            $this->cache->remove($id);
717
        }
718
719 1441
        $this->setUpEnvironment();
720
721 1441
        $this->tokenStack->push();
722
723 1441
        Log::debug('Processing file ' . $this->compilationUnit);
724
725 1441
        $tokenType = $this->tokenizer->peek();
726
727 1441
        while ($tokenType !== Tokenizer::T_EOF) {
728
            switch ($tokenType) {
729
                case Tokens::T_COMMENT:
730 7
                    $this->consumeToken(Tokens::T_COMMENT);
731
732 7
                    break;
733
734
                case Tokens::T_DOC_COMMENT:
735 87
                    $comment = $this->consumeToken(Tokens::T_DOC_COMMENT)->image;
736
737 87
                    $this->packageName = $this->parsePackageAnnotation($comment);
738 87
                    $this->docComment = $comment;
739
740 87
                    break;
741
742
                case Tokens::T_USE:
743
                    // Parse a use statement. This method has no return value but it
744
                    // creates a new entry in the symbol map.
745 18
                    $this->parseUseDeclarations();
746
747 17
                    break;
748
749
                case Tokens::T_NAMESPACE:
750 84
                    $this->parseNamespaceDeclaration();
751
752 82
                    break;
753
754
                case Tokens::T_NO_PHP:
755
                case Tokens::T_OPEN_TAG:
756
                case Tokens::T_OPEN_TAG_WITH_ECHO:
757 1441
                    $this->consumeToken($tokenType);
758 1441
                    $this->reset(0, $tokenType === Tokens::T_OPEN_TAG_WITH_ECHO);
759
760 1441
                    break;
761
762
                case Tokens::T_CLOSE_TAG:
763 206
                    $this->parseNonePhpCode();
764 206
                    $this->reset();
765
766 206
                    break;
767
768
                default:
769 1436
                    if (null === $this->parseOptionalStatement()) {
770
                        // Consume whatever token
771 31
                        $this->consumeToken($tokenType);
772
                    }
773
774 1370
                    break;
775
            }
776
777 1441
            $tokenType = $this->tokenizer->peek();
778
        }
779
780 1370
        $this->compilationUnit->setTokens($this->tokenStack->pop());
781 1370
        $id = $this->compilationUnit->getId();
782 1370
        if ($id) {
783 1370
            $this->cache->store($id, $this->compilationUnit, $hash ?: null);
784
        }
785
786 1370
        $this->tearDownEnvironment();
787
    }
788
789
    /**
790
     * Initializes the parser environment.
791
     *
792
     * @since 0.9.12
793
     */
794 1441
    private function setUpEnvironment(): void
795
    {
796 1441
        ini_set('xdebug.max_nesting_level', (string) $this->getMaxNestingLevel());
797
798 1441
        $this->useSymbolTable->createScope();
799
800 1441
        $this->reset();
801
    }
802
803
    /**
804
     * Restores the parser environment back.
805
     *
806
     * @throws NoActiveScopeException
807
     * @since 0.9.12
808
     */
809 1370
    private function tearDownEnvironment(): void
810
    {
811 1370
        ini_restore('xdebug.max_nesting_level');
812
813 1370
        $this->useSymbolTable->destroyScope();
814
    }
815
816
    /**
817
     * Resets some object properties.
818
     *
819
     * @param int $modifiers Optional default modifiers.
820
     * @param bool $echoing True if current statement is echoing (such as after <?=).
821
     */
822 1441
    private function reset($modifiers = 0, $echoing = false): void
823
    {
824 1441
        $this->packageName = Builder::DEFAULT_NAMESPACE;
825 1441
        $this->docComment = null;
826 1441
        $this->modifiers = $modifiers;
827 1441
        $this->echoing = $echoing;
828
    }
829
830
    /**
831
     * Tests if the given token type is a reserved keyword in the supported PHP
832
     * version.
833
     *
834
     * @param int $tokenType
835
     * @return bool
836
     * @since 1.1.1
837
     */
838 19
    protected function isKeyword($tokenType)
839
    {
840 19
        return match ($tokenType) {
841 19
            Tokens::T_CLASS,
842 19
            Tokens::T_TRAIT,
843 19
            Tokens::T_CALLABLE,
844 19
            Tokens::T_INSTEADOF,
845 19
            Tokens::T_INTERFACE => true,
846 19
            default => false,
847 19
        };
848
    }
849
850
    /**
851
     * Parses a valid class or interface name and returns the image of the parsed
852
     * token.
853
     *
854
     * @return string
855
     * @throws TokenStreamEndException
856
     * @throws UnexpectedTokenException
857
     */
858 974
    private function parseClassName()
859
    {
860 974
        $type = $this->tokenizer->peek();
861
862 974
        if ($this->isClassName($type)) {
863 973
            return $this->consumeToken($type)->image;
864
        }
865
866 2
        throw $this->getUnexpectedNextTokenException();
867
    }
868
869
    /**
870
     * Will return <b>true</b> if the given <b>$tokenType</b> is a valid class
871
     * name part.
872
     *
873
     * @param int $tokenType The type of a parsed token.
874
     * @return bool
875
     * @since 0.10.6
876
     */
877 977
    private function isClassName($tokenType)
878
    {
879 977
        return match ($tokenType) {
880 977
            Tokens::T_DIR,
881 977
            Tokens::T_USE,
882 977
            Tokens::T_GOTO,
883 977
            Tokens::T_NULL,
884 977
            Tokens::T_NS_C,
885 977
            Tokens::T_TRUE,
886 977
            Tokens::T_CLONE,
887 977
            Tokens::T_FALSE,
888 977
            Tokens::T_TRAIT,
889 977
            Tokens::T_STRING,
890 977
            Tokens::T_TRAIT_C,
891 977
            Tokens::T_CALLABLE,
892 977
            Tokens::T_INSTEADOF,
893 977
            Tokens::T_NAMESPACE,
894 977
            Tokens::T_READONLY => true,
895 977
            default => false,
896 977
        };
897
    }
898
899
    /**
900
     * Parses a function name from the given tokenizer and returns the string
901
     * literal representing the function name. If no valid token exists in the
902
     * token stream, this method will throw an exception.
903
     *
904
     * @return string
905
     * @throws UnexpectedTokenException
906
     * @throws TokenStreamEndException
907
     * @since 0.10.0
908
     */
909 843
    private function parseFunctionName()
910
    {
911 843
        $tokenType = $this->tokenizer->peek();
912
913 843
        if ($this->isFunctionName($tokenType)) {
914 839
            return $this->consumeToken($tokenType)->image;
915
        }
916
917 4
        throw $this->getUnexpectedNextTokenException();
918
    }
919
920
    /**
921
     * @param int $tokenType
922
     * @return bool
923
     */
924 527
    protected function isConstantName($tokenType)
925
    {
926 527
        return match ($tokenType) {
927 527
            Tokens::T_CALLABLE,
928 527
            Tokens::T_TRAIT,
929 527
            Tokens::T_EXTENDS,
930 527
            Tokens::T_IMPLEMENTS,
931 527
            Tokens::T_STATIC,
932 527
            Tokens::T_ABSTRACT,
933 527
            Tokens::T_FINAL,
934 527
            Tokens::T_PUBLIC,
935 527
            Tokens::T_PROTECTED,
936 527
            Tokens::T_PRIVATE,
937 527
            Tokens::T_CONST,
938 527
            Tokens::T_ENDDECLARE,
939 527
            Tokens::T_ENDFOR,
940 527
            Tokens::T_ENDFOREACH,
941 527
            Tokens::T_ENDIF,
942 527
            Tokens::T_ENDWHILE,
943 527
            Tokens::T_EMPTY,
944 527
            Tokens::T_EVAL,
945 527
            Tokens::T_LOGICAL_AND,
946 527
            Tokens::T_GLOBAL,
947 527
            Tokens::T_GOTO,
948 527
            Tokens::T_INSTANCEOF,
949 527
            Tokens::T_INSTEADOF,
950 527
            Tokens::T_INTERFACE,
951 527
            Tokens::T_ISSET,
952 527
            Tokens::T_NAMESPACE,
953 527
            Tokens::T_NEW,
954 527
            Tokens::T_LOGICAL_OR,
955 527
            Tokens::T_LOGICAL_XOR,
956 527
            Tokens::T_TRY,
957 527
            Tokens::T_USE,
958 527
            Tokens::T_VAR,
959 527
            Tokens::T_EXIT,
960 527
            Tokens::T_LIST,
961 527
            Tokens::T_CLONE,
962 527
            Tokens::T_INCLUDE,
963 527
            Tokens::T_INCLUDE_ONCE,
964 527
            Tokens::T_THROW,
965 527
            Tokens::T_ARRAY,
966 527
            Tokens::T_PRINT,
967 527
            Tokens::T_ECHO,
968 527
            Tokens::T_REQUIRE,
969 527
            Tokens::T_REQUIRE_ONCE,
970 527
            Tokens::T_RETURN,
971 527
            Tokens::T_ELSE,
972 527
            Tokens::T_ELSEIF,
973 527
            Tokens::T_DEFAULT,
974 527
            Tokens::T_BREAK,
975 527
            Tokens::T_CONTINUE,
976 527
            Tokens::T_SWITCH,
977 527
            Tokens::T_YIELD,
978 527
            Tokens::T_FUNCTION,
979 527
            Tokens::T_IF,
980 527
            Tokens::T_ENDSWITCH,
981 527
            Tokens::T_FINALLY,
982 527
            Tokens::T_FOR,
983 527
            Tokens::T_FOREACH,
984 527
            Tokens::T_DECLARE,
985 527
            Tokens::T_CASE,
986 527
            Tokens::T_DO,
987 527
            Tokens::T_WHILE,
988 527
            Tokens::T_AS,
989 527
            Tokens::T_CATCH,
990
            // Tokens::T_DIE,
991 527
            Tokens::T_SELF,
992 527
            Tokens::T_PARENT,
993 527
            Tokens::T_UNSET => true,
994 527
            default => $this->isFunctionName($tokenType),
995 527
        };
996
    }
997
998
    /**
999
     * Tests if the give token is a valid function name in the supported PHP
1000
     * version.
1001
     *
1002
     * @param int $tokenType
1003
     * @return bool
1004
     * @since 2.3
1005
     */
1006 140
    protected function isFunctionName($tokenType)
1007
    {
1008 140
        return match ($tokenType) {
1009 140
            Tokens::T_STRING,
1010 140
            Tokens::T_NULL,
1011 140
            Tokens::T_SELF,
1012 140
            Tokens::T_TRUE,
1013 140
            Tokens::T_FALSE,
1014 140
            Tokens::T_PARENT => true,
1015 140
            default => false,
1016 140
        };
1017
    }
1018
1019
    /**
1020
     * @return string
1021
     * @throws UnexpectedTokenException
1022
     * @throws TokenStreamEndException
1023
     */
1024 340
    private function parseMethodName()
1025
    {
1026 340
        $tokenType = $this->tokenizer->peek();
1027
1028 340
        if ($this->isMethodName($tokenType)) {
1029 340
            return $this->consumeToken($tokenType)->image;
1030
        }
1031
1032
        throw $this->getUnexpectedNextTokenException();
1033
    }
1034
1035
    /**
1036
     * @param int $tokenType
1037
     * @return bool
1038
     */
1039 445
    private function isMethodName($tokenType)
1040
    {
1041 445
        return match ($tokenType) {
1042 445
            Tokens::T_CLASS => true,
1043 445
            default => $this->isConstantName($tokenType),
1044 445
        };
1045
    }
1046
1047
    /**
1048
     * Parses a trait declaration.
1049
     *
1050
     * @return ASTTrait
1051
     * @since 1.0.0
1052
     */
1053 59
    private function parseTraitDeclaration()
1054
    {
1055 59
        $this->tokenStack->push();
1056
1057 59
        $trait = $this->parseTraitSignature();
1058 58
        $trait = $this->parseTypeBody($trait);
1059 58
        $trait->setTokens($this->tokenStack->pop());
1060
1061 58
        $this->reset();
1062
1063 58
        return $trait;
1064
    }
1065
1066
    /**
1067
     * Parses the signature of a trait.
1068
     *
1069
     * @return ASTTrait
1070
     */
1071 59
    private function parseTraitSignature()
1072
    {
1073 59
        $this->consumeToken(Tokens::T_TRAIT);
1074 59
        $this->consumeComments();
1075
1076 59
        $qualifiedName = $this->createQualifiedTypeName($this->parseClassName());
1077
1078 58
        $trait = $this->builder->buildTrait($qualifiedName);
1079 58
        $trait->setCompilationUnit($this->compilationUnit);
1080 58
        $trait->setComment($this->docComment);
1081 58
        $trait->setId($this->idBuilder->forClassOrInterface($trait));
1082 58
        $trait->setUserDefined();
1083
1084 58
        return $trait;
1085
    }
1086
1087
    /**
1088
     * Parses the dependencies in a interface signature.
1089
     *
1090
     * @return ASTInterface
1091
     */
1092 100
    private function parseInterfaceDeclaration()
1093
    {
1094 100
        $this->tokenStack->push();
1095
1096 100
        $interface = $this->parseInterfaceSignature();
1097 98
        $interface = $this->parseTypeBody($interface);
1098 92
        $interface->setTokens($this->tokenStack->pop());
1099
1100 92
        $this->reset();
1101
1102 92
        return $interface;
1103
    }
1104
1105
    /**
1106
     * Parses the signature of an interface and finally returns a configured
1107
     * interface instance.
1108
     *
1109
     * @return ASTInterface
1110
     * @since 0.10.2
1111
     */
1112 100
    private function parseInterfaceSignature()
1113
    {
1114 100
        $this->consumeToken(Tokens::T_INTERFACE);
1115 100
        $this->consumeComments();
1116
1117 100
        $qualifiedName = $this->createQualifiedTypeName($this->parseClassName());
1118
1119 100
        $interface = $this->builder->buildInterface($qualifiedName);
1120 100
        $interface->setCompilationUnit($this->compilationUnit);
1121 100
        $interface->setComment($this->docComment);
1122 100
        $interface->setId($this->idBuilder->forClassOrInterface($interface));
1123 100
        $interface->setUserDefined();
1124
1125 100
        return $this->parseOptionalExtendsList($interface);
1126
    }
1127
1128
    /**
1129
     * Parses an optional interface list of an interface declaration.
1130
     *
1131
     * @template T of ASTInterface
1132
     * @param T $interface
1133
     * @return T
1134
     * @since 0.10.2
1135
     */
1136 100
    private function parseOptionalExtendsList(ASTInterface $interface): ASTInterface
1137
    {
1138 100
        $this->consumeComments();
1139 100
        $tokenType = $this->tokenizer->peek();
1140
1141 100
        if ($tokenType === Tokens::T_EXTENDS) {
1142 39
            $this->consumeToken(Tokens::T_EXTENDS);
1143 39
            $this->parseInterfaceList($interface);
1144
        }
1145
1146 98
        return $interface;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $interface returns the type PDepend\Source\AST\ASTInterface which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
1147
    }
1148
1149
    /**
1150
     * Parses the dependencies in a class signature.
1151
     *
1152
     * @return ASTClass
1153
     */
1154 523
    private function parseClassDeclaration()
1155
    {
1156 523
        $startToken = $this->tokenizer->currentToken();
1157 523
        $this->tokenStack->push();
1158
1159 523
        $class = $this->parseClassSignature();
1160 517
        $class = $this->parseTypeBody($class);
1161 500
        $class->setTokens($this->tokenStack->pop(), $startToken);
1162
1163 500
        $this->reset();
1164
1165 500
        return $class;
1166
    }
1167
1168
    /**
1169
     * Parses the signature of a class.
1170
     *
1171
     * The signature of a class consists of optional class modifiers like, final
1172
     * or abstract, the T_CLASS token, the class name, an optional parent class
1173
     * and an optional list of implemented interfaces.
1174
     *
1175
     * @return ASTClass
1176
     * @since 1.0.0
1177
     */
1178 523
    private function parseClassSignature()
1179
    {
1180 523
        $this->parseClassModifiers();
1181 521
        $this->consumeToken(Tokens::T_CLASS);
1182 521
        $this->consumeComments();
1183
1184 521
        $qualifiedName = $this->createQualifiedTypeName($this->parseClassName());
1185
1186 521
        $class = $this->builder->buildClass($qualifiedName);
1187 521
        $class->setCompilationUnit($this->compilationUnit);
1188 521
        $class->setModifiers($this->modifiers);
1189 521
        $class->setComment($this->docComment);
1190 521
        $class->setId($this->idBuilder->forClassOrInterface($class));
1191 521
        $class->setUserDefined();
1192
1193 521
        $this->consumeComments();
1194 521
        $tokenType = $this->tokenizer->peek();
1195
1196 521
        if ($tokenType === Tokens::T_EXTENDS) {
1197 101
            $class = $this->parseClassExtends($class);
1198
1199 100
            $this->consumeComments();
1200 100
            $tokenType = $this->tokenizer->peek();
1201
        }
1202
1203 520
        if ($tokenType === Tokens::T_IMPLEMENTS) {
1204 44
            $this->consumeToken(Tokens::T_IMPLEMENTS);
1205 44
            $this->parseInterfaceList($class);
1206
        }
1207
1208 517
        return $class;
1209
    }
1210
1211
    /**
1212
     * This method parses an optional class modifier. Valid class modifiers are
1213
     * <b>final</b> or <b>abstract</b>.
1214
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
1215 523
    private function parseClassModifiers(): void
1216
    {
1217 523
        $this->consumeComments();
1218 523
        $tokenType = $this->tokenizer->peek();
1219
1220 523
        $validModifiers = [
1221 523
            Tokens::T_ABSTRACT,
1222 523
            Tokens::T_FINAL,
1223 523
            Tokens::T_READONLY,
1224 523
        ];
1225
1226 523
        $finalAllowed = true;
1227 523
        $abstractAllowed = true;
1228
1229 523
        while (in_array($tokenType, $validModifiers, true)) {
1230 39
            if ($tokenType === Tokens::T_ABSTRACT) {
1231 31
                if (!$abstractAllowed) {
1232
                    throw $this->getUnexpectedNextTokenException();
1233
                }
1234 31
                $finalAllowed = false;
1235 31
                $this->consumeToken(Tokens::T_ABSTRACT);
1236 31
                $this->modifiers |= State::IS_EXPLICIT_ABSTRACT;
1237 11
            } elseif ($tokenType === Tokens::T_FINAL) {
1238 7
                if (!$finalAllowed) {
1239 1
                    throw $this->getUnexpectedNextTokenException();
1240
                }
1241 6
                $abstractAllowed = false;
1242 6
                $this->consumeToken(Tokens::T_FINAL);
1243 6
                $this->modifiers |= State::IS_FINAL;
1244 6
            } elseif ($tokenType === Tokens::T_READONLY) {
1245 6
                if (!static::READONLY_CLASS_ALLOWED) {
1246 1
                    throw $this->getUnexpectedNextTokenException();
1247
                }
1248
1249 5
                $this->consumeToken(Tokens::T_READONLY);
1250 5
                $this->modifiers |= State::IS_READONLY;
1251
            }
1252
1253 38
            $tokenType = $this->tokenizer->peek();
1254
        }
1255
1256 521
        $this->consumeComments();
1257
    }
1258
1259
    /**
1260
     * Parses a parent class declaration for the given <b>$class</b>.
1261
     *
1262
     * @template T of ASTClass
1263
     *
1264
     * @param T $class
1265
     * @return T
1266
     * @since 1.0.0
1267
     */
1268 101
    private function parseClassExtends(ASTClass $class): ASTClass
1269
    {
1270 101
        $this->consumeToken(Tokens::T_EXTENDS);
1271 101
        $this->tokenStack->push();
1272
1273 101
        $class->setParentClassReference(
1274 101
            $this->setNodePositionsAndReturn(
1275 101
                $this->builder->buildAstClassReference(
1276 101
                    $this->parseQualifiedName(),
1277 101
                ),
1278 101
            ),
1279 101
        );
1280
1281 100
        return $class;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $class returns the type PDepend\Source\AST\ASTClass which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
1282
    }
1283
1284
    /**
1285
     * This method parses a list of interface names as used in the <b>extends</b>
1286
     * part of a interface declaration or in the <b>implements</b> part of a
1287
     * class declaration.
1288
     */
1289 78
    private function parseInterfaceList(AbstractASTClassOrInterface $abstractType): void
1290
    {
1291 78
        while (true) {
1292 78
            $this->tokenStack->push();
1293
1294 78
            $abstractType->addInterfaceReference(
1295 78
                $this->setNodePositionsAndReturn(
1296 78
                    $this->builder->buildAstClassOrInterfaceReference(
1297 78
                        $this->parseQualifiedName(),
1298 78
                    ),
1299 78
                ),
1300 78
            );
1301
1302 77
            $this->consumeComments();
1303 77
            $tokenType = $this->tokenizer->peek();
1304
1305 77
            if ($tokenType === Tokens::T_CURLY_BRACE_OPEN) {
1306 73
                break;
1307
            }
1308 25
            $this->consumeToken(Tokens::T_COMMA);
1309
        }
1310
    }
1311
1312
    /**
1313
     * Parses a class/interface/trait body.
1314
     *
1315
     * @template T of AbstractASTClassOrInterface
1316
     *
1317
     * @param T $classOrInterface
1318
     * @return T
1319
     * @throws UnexpectedTokenException
1320
     * @throws TokenStreamEndException
1321
     */
1322 602
    private function parseTypeBody(AbstractASTClassOrInterface $classOrInterface): AbstractASTClassOrInterface
1323
    {
1324 602
        $this->classOrInterface = $classOrInterface;
1325
1326
        // Consume comments and read opening curly brace
1327 602
        $this->consumeComments();
1328 602
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
1329
1330 600
        $defaultModifier = State::IS_PUBLIC;
1331 600
        if ($classOrInterface instanceof ASTInterface) {
1332 97
            $defaultModifier |= State::IS_ABSTRACT;
1333
        }
1334 600
        $this->reset();
1335
1336 600
        $tokenType = $this->tokenizer->peek();
1337
1338 600
        while ($tokenType !== Tokenizer::T_EOF) {
1339
            switch ($tokenType) {
1340
                case Tokens::T_ABSTRACT:
1341
                case Tokens::T_PUBLIC:
1342
                case Tokens::T_PRIVATE:
1343
                case Tokens::T_PROTECTED:
1344
                case Tokens::T_STATIC:
1345
                case Tokens::T_FINAL:
1346
                case Tokens::T_READONLY:
1347
                case Tokens::T_FUNCTION:
1348
                case Tokens::T_VARIABLE:
1349
                case Tokens::T_VAR:
1350 388
                    $methodOrProperty = $this->parseMethodOrFieldDeclaration(
1351 388
                        $defaultModifier,
1352 388
                    );
1353
1354 373
                    if ($methodOrProperty instanceof ASTNode) {
1355 373
                        $classOrInterface->addChild($methodOrProperty);
1356
                    }
1357
1358 373
                    $this->reset();
1359
1360 373
                    break;
1361
1362
                case Tokens::T_CONST:
1363 65
                    $classOrInterface->addChild($this->parseConstantDefinition());
1364 62
                    $this->reset();
1365
1366 62
                    break;
1367
1368
                case Tokens::T_CURLY_BRACE_CLOSE:
1369 579
                    $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
1370
1371 579
                    $this->reset();
1372
1373
                    // Reset context class or interface instance
1374 579
                    $this->classOrInterface = null;
1375
1376
                    // Stop processing
1377 579
                    return $classOrInterface;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $classOrInterface returns the type PDepend\Source\AST\ASTEn...ractASTClassOrInterface which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
1378
1379
                case Tokens::T_COMMENT:
1380 18
                    $token = $this->consumeToken(Tokens::T_COMMENT);
1381
1382 18
                    $comment = $this->builder->buildAstComment($token->image);
1383 18
                    $comment->configureLinesAndColumns(
1384 18
                        $token->startLine,
1385 18
                        $token->endLine,
1386 18
                        $token->startColumn,
1387 18
                        $token->endColumn,
1388 18
                    );
1389
1390 18
                    $classOrInterface->addChild($comment);
1391
1392 18
                    break;
1393
1394
                case Tokens::T_DOC_COMMENT:
1395 66
                    $token = $this->consumeToken(Tokens::T_DOC_COMMENT);
1396
1397 66
                    $comment = $this->builder->buildAstComment($token->image);
1398 66
                    $comment->configureLinesAndColumns(
1399 66
                        $token->startLine,
1400 66
                        $token->endLine,
1401 66
                        $token->startColumn,
1402 66
                        $token->endColumn,
1403 66
                    );
1404
1405 66
                    $classOrInterface->addChild($comment);
1406
1407 66
                    $this->docComment = $token->image;
1408
1409 66
                    break;
1410
1411
                case Tokens::T_USE:
1412 71
                    $classOrInterface->addChild($this->parseTraitUseStatement());
1413
1414 70
                    break;
1415
1416
                case Tokens::T_CASE:
1417 1
                    if (!($classOrInterface instanceof ASTEnum)) {
1418
                        throw new TokenException(
1419
                            'Enum case should be located only inside enum classes',
1420
                        );
1421
                    }
1422
1423 1
                    $case = $this->parseEnumCase();
1424 1
                    $case->setEnum($classOrInterface);
1425 1
                    $classOrInterface->addChild($case);
1426
1427 1
                    break;
1428
1429
                default:
1430 2
                    throw $this->getUnexpectedNextTokenException();
1431
            }
1432
1433 442
            $tokenType = $this->tokenizer->peek();
1434
        }
1435
1436 1
        throw new TokenStreamEndException($this->tokenizer);
1437
    }
1438
1439
    /**
1440
     * This method will parse a list of modifiers and a following property or
1441
     * method.
1442
     *
1443
     * @param int $modifiers
1444
     * @return ASTNode
1445
     * @since 0.9.6
1446
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
1447 388
    private function parseMethodOrFieldDeclaration($modifiers = 0)
1448
    {
1449 388
        $this->tokenStack->push();
1450 388
        $tokenType = $this->tokenizer->peek();
1451
1452 388
        while ($tokenType !== Tokenizer::T_EOF) {
1453
            switch ($tokenType) {
1454
                case Tokens::T_PRIVATE:
1455 64
                    $modifiers |= State::IS_PRIVATE;
1456 64
                    $modifiers &= ~State::IS_PUBLIC;
1457
1458 64
                    break;
1459
1460
                case Tokens::T_PROTECTED:
1461 54
                    $modifiers |= State::IS_PROTECTED;
1462 54
                    $modifiers &= ~State::IS_PUBLIC;
1463
1464 54
                    break;
1465
1466
                case Tokens::T_VAR:
1467
                case Tokens::T_PUBLIC:
1468 245
                    $modifiers |= State::IS_PUBLIC;
1469
1470 245
                    break;
1471
1472
                case Tokens::T_STATIC:
1473 34
                    $modifiers |= State::IS_STATIC;
1474
1475 34
                    break;
1476
1477
                case Tokens::T_ABSTRACT:
1478 22
                    $modifiers |= State::IS_ABSTRACT;
1479
1480 22
                    break;
1481
1482
                case Tokens::T_FINAL:
1483 16
                    $modifiers |= State::IS_FINAL;
1484
1485 16
                    break;
1486
1487
                case Tokens::T_READONLY:
1488 2
                    $modifiers |= State::IS_READONLY;
1489
1490 2
                    break;
1491
1492
                case Tokens::T_FUNCTION:
1493 324
                    $method = $this->parseMethodDeclaration();
1494 313
                    $method->setModifiers($modifiers);
1495 313
                    $method->setCompilationUnit($this->compilationUnit);
1496 313
                    $method->setId($this->idBuilder->forMethod($method));
1497 313
                    $method->setTokens($this->tokenStack->pop());
1498
1499 313
                    return $method;
1500
1501
                case Tokens::T_VARIABLE:
1502 80
                    $declaration = $this->parseFieldDeclaration();
1503 80
                    $declaration->setModifiers($modifiers);
1504
1505 80
                    return $declaration;
1506
1507
                default:
1508 16
                    return $this->parseUnknownDeclaration($tokenType, $modifiers);
1509
            }
1510
1511 304
            $this->consumeToken($tokenType);
1512 304
            $this->consumeComments();
1513
1514 304
            $tokenType = $this->tokenizer->peek();
1515
        }
1516
1517
        throw $this->getUnexpectedNextTokenException();
1518
    }
1519
1520
    /**
1521
     * Override this in later PHPParserVersions as necessary
1522
     *
1523
     * @param int $tokenType
1524
     * @param int $modifiers
1525
     * @return AbstractASTNode
1526
     * @throws UnexpectedTokenException
1527
     */
1528 16
    private function parseUnknownDeclaration($tokenType, $modifiers)
1529
    {
1530
        /**
1531
         * Typed properties
1532
         * https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.core.typed-properties
1533
         */
1534 16
        if (in_array($tokenType, $this->possiblePropertyTypes, true)) {
1535 8
            $type = $this->parseTypeHint();
1536 8
            $declaration = $this->parseFieldDeclaration();
1537 7
            $declaration->prependChild($type);
1538 7
            $declaration->setModifiers($modifiers);
1539
1540 7
            return $declaration;
1541
        }
1542
1543 8
        if ($tokenType !== Tokens::T_CONST) {
1544 1
            throw $this->getUnexpectedNextTokenException();
1545
        }
1546
1547 7
        $definition = $this->parseConstantDefinition();
1548 7
        $constantModifiers = $this->getModifiersForConstantDefinition($tokenType, $modifiers);
1549 5
        $definition->setModifiers($constantModifiers);
1550
1551 5
        return $definition;
1552
    }
1553
1554
    /**
1555
     * This method will parse a class field declaration with all it's variables.
1556
     *
1557
     * <code>
1558
     * // Simple field declaration
1559
     * class Foo {
1560
     *     protected $foo;
1561
     * }
1562
     *
1563
     * // Field declaration with multiple properties
1564
     * class Foo {
1565
     *     protected $foo = 23
1566
     *               $bar = 42,
1567
     *               $baz = null;
1568
     * }
1569
     * </code>
1570
     *
1571
     * @return ASTFieldDeclaration
1572
     * @since 0.9.6
1573
     */
1574 87
    private function parseFieldDeclaration()
1575
    {
1576 87
        $declaration = $this->builder->buildAstFieldDeclaration();
1577 87
        $declaration->setComment($this->docComment);
1578
1579 87
        $type = $this->parseFieldDeclarationType();
1580
1581 87
        if ($type !== null) {
1582 22
            $declaration->addChild($type);
1583
        }
1584
1585 87
        $this->consumeComments();
1586 87
        $tokenType = $this->tokenizer->peek();
1587
1588 87
        while ($tokenType !== Tokenizer::T_EOF) {
1589 87
            $declaration->addChild($this->parseVariableDeclarator());
1590
1591 86
            $this->consumeComments();
1592 86
            $tokenType = $this->tokenizer->peek();
1593
1594 86
            if ($tokenType !== Tokens::T_COMMA) {
1595 86
                break;
1596
            }
1597
1598 11
            $this->consumeToken(Tokens::T_COMMA);
1599
1600 11
            $this->consumeComments();
1601 11
            $tokenType = $this->tokenizer->peek();
1602
        }
1603
1604 86
        $this->setNodePositionsAndReturn($declaration);
1605
1606 86
        $this->consumeToken(Tokens::T_SEMICOLON);
1607
1608 86
        return $declaration;
1609
    }
1610
1611
    /**
1612
     * This method parses a simple function or a PHP 5.3 lambda function or
1613
     * closure.
1614
     *
1615
     * @return ASTCallable
1616
     * @since 0.9.5
1617
     */
1618 844
    private function parseFunctionOrClosureDeclaration()
1619
    {
1620 844
        $this->tokenStack->push();
1621
1622 844
        $this->consumeToken(Tokens::T_FUNCTION);
1623 844
        $this->consumeComments();
1624
1625 844
        $returnReference = $this->parseOptionalByReference();
1626
1627 844
        if ($this->isNextTokenFormalParameterList()) {
1628 2
            return $this->setNodePositionsAndReturn(
1629 2
                $this->parseClosureDeclaration(),
1630 2
            );
1631
        }
1632
1633 843
        $docComment = $this->docComment;
1634 843
        $callable = $this->parseFunctionDeclaration();
1635 808
        $this->compilationUnit->addChild($callable);
1636
1637 808
        $callable->setComment($docComment);
1638 808
        $callable->setTokens($this->tokenStack->pop());
1639 808
        $this->prepareCallable($callable);
1640
1641 808
        if ($returnReference) {
1642 4
            $callable->setReturnsReference();
1643
        }
1644
1645 808
        $this->reset();
1646
1647 808
        return $callable;
1648
    }
1649
1650
    /**
1651
     * Parses an optional by reference token. The return value will be
1652
     * <b>true</b> when a reference token was found, otherwise this method will
1653
     * return <b>false</b>.
1654
     *
1655
     * @return bool
1656
     * @since 0.9.8
1657
     */
1658 1151
    private function parseOptionalByReference()
1659
    {
1660 1151
        return $this->isNextTokenByReference() && $this->parseByReference();
1661
    }
1662
1663
    /**
1664
     * Tests that the next available token is the returns by reference token.
1665
     *
1666
     * @return bool
1667
     * @since 0.9.8
1668
     */
1669 1151
    private function isNextTokenByReference()
1670
    {
1671 1151
        return ($this->tokenizer->peek() === Tokens::T_BITWISE_AND);
1672
    }
1673
1674
    /**
1675
     * This method parses a returns by reference token and returns <b>true</b>.
1676
     *
1677
     * @return bool
1678
     */
1679 23
    private function parseByReference()
1680
    {
1681 23
        $this->consumeToken(Tokens::T_BITWISE_AND);
1682 23
        $this->consumeComments();
1683
1684 23
        return true;
1685
    }
1686
1687
    /**
1688
     * Tests that the next available token is an opening parenthesis.
1689
     *
1690
     * @return bool
1691
     * @since 0.9.10
1692
     */
1693 844
    private function isNextTokenFormalParameterList()
1694
    {
1695 844
        $this->consumeComments();
1696
1697 844
        return ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN);
1698
    }
1699
1700
    /**
1701
     * This method parses a function declaration.
1702
     *
1703
     * @return ASTFunction
1704
     * @since 0.9.5
1705
     */
1706 843
    private function parseFunctionDeclaration()
1707
    {
1708 843
        $this->consumeComments();
1709
1710
        // Next token must be the function identifier
1711 843
        $functionName = $this->parseFunctionName();
1712
1713 839
        $function = $this->builder->buildFunction($functionName);
1714 839
        $function->setCompilationUnit($this->compilationUnit);
1715 839
        $function->setId($this->idBuilder->forFunction($function));
1716
1717 839
        $this->parseCallableDeclaration($function);
1718
1719
        // First check for an existing namespace
1720 808
        if (isset($this->namespaceName)) {
1721 29
            $namespaceName = $this->namespaceName;
1722 779
        } elseif ($this->packageName !== Builder::DEFAULT_NAMESPACE) {
1723 18
            $namespaceName = $this->packageName;
1724
        } else {
1725 776
            $namespaceName = $this->globalPackageName;
1726
        }
1727
1728 808
        $namespace = $this->builder->buildNamespace((string) $namespaceName);
1729 808
        $namespace->setPackageAnnotation(null === $this->namespaceName);
1730 808
        $namespace->addFunction($function);
1731
1732
        // Store function in source file, because we need them during the file's
1733
        // __wakeup() phase for function declarations within another function or
1734
        // method declaration.
1735 808
        $this->compilationUnit->addChild($function);
1736
1737 808
        return $function;
1738
    }
1739
1740
    /**
1741
     * This method parses a method declaration.
1742
     *
1743
     * @return ASTMethod
1744
     * @since 0.9.5
1745
     */
1746 324
    private function parseMethodDeclaration()
1747
    {
1748
        // Read function keyword
1749 324
        $this->consumeToken(Tokens::T_FUNCTION);
1750 324
        $this->consumeComments();
1751
1752 324
        $returnsReference = $this->parseOptionalByReference();
1753
1754 324
        $methodName = $this->parseMethodName();
1755
1756 324
        $method = $this->builder->buildMethod($methodName);
1757 324
        $method->setComment($this->docComment);
1758 324
        $method->setCompilationUnit($this->compilationUnit);
1759
1760 324
        $this->classOrInterface?->addMethod($method);
1761
1762 324
        $this->parseCallableDeclaration($method);
1763 313
        $this->prepareCallable($method);
1764
1765 313
        if ($returnsReference) {
1766 5
            $method->setReturnsReference();
1767
        }
1768
1769 313
        return $method;
1770
    }
1771
1772
    /**
1773
     * This method parses a PHP 5.3 closure or lambda function.
1774
     *
1775
     * @return ASTClosure
1776
     * @since 0.9.5
1777
     */
1778 29
    private function parseClosureDeclaration()
1779
    {
1780 29
        $this->tokenStack->push();
1781
1782 29
        if (Tokens::T_FUNCTION === $this->tokenizer->peek()) {
1783 27
            $this->consumeToken(Tokens::T_FUNCTION);
1784
        }
1785
1786 29
        $closure = $this->builder->buildAstClosure();
1787 29
        $closure->setReturnsByReference($this->parseOptionalByReference());
1788 29
        $closure->addChild($this->parseFormalParameters($closure));
1789 29
        $closure = $this->parseOptionalBoundVariables($closure);
1790 28
        $this->parseCallableDeclarationAddition($closure);
1791 28
        $closure->addChild($this->parseScope());
1792
1793 28
        return $this->setNodePositionsAndReturn($closure);
1794
    }
1795
1796
    /**
1797
     * Parses a function or a method and adds it to the parent context node.
1798
     */
1799 1141
    private function parseCallableDeclaration(AbstractASTCallable $callable): void
1800
    {
1801 1141
        $callable->addChild($this->parseFormalParameters($callable));
1802 1127
        $this->parseCallableDeclarationAddition($callable);
1803
1804 1127
        $this->consumeComments();
1805
1806 1127
        if ($this->tokenizer->peek() === Tokens::T_CURLY_BRACE_OPEN) {
1807 1109
            $callable->addChild($this->parseScope());
1808
1809 1081
            return;
1810
        }
1811
1812 38
        $this->consumeToken(Tokens::T_SEMICOLON);
1813
    }
1814
1815
    /**
1816
     * Extension for version specific additions.
1817
     *
1818
     * @template T of AbstractASTCallable|ASTClosure
1819
     *
1820
     * @param T $callable
1821
     * @return T
1822
     */
1823 1127
    private function parseCallableDeclarationAddition($callable): AbstractASTCallable|ASTClosure
1824
    {
1825 1127
        $this->consumeComments();
1826 1127
        if (Tokens::T_COLON !== $this->tokenizer->peek()) {
1827 1098
            return $callable;
1828
        }
1829
1830 40
        $this->consumeToken(Tokens::T_COLON);
1831
1832 40
        $type = $this->parseReturnTypeHint();
1833 40
        $callable->addChild($type);
1834
1835 40
        return $callable;
1836
    }
1837
1838
    /**
1839
     * Parses a trait use statement.
1840
     *
1841
     * @return ASTTraitUseStatement
1842
     * @since 1.0.0
1843
     */
1844 71
    private function parseTraitUseStatement()
1845
    {
1846 71
        $this->tokenStack->push();
1847 71
        $this->consumeToken(Tokens::T_USE);
1848
1849 71
        $useStatement = $this->builder->buildAstTraitUseStatement();
1850 71
        $useStatement->addChild($this->parseTraitReference());
1851
1852 71
        $this->consumeComments();
1853
1854 71
        while (Tokens::T_COMMA === $this->tokenizer->peek()) {
1855 20
            $this->consumeToken(Tokens::T_COMMA);
1856 20
            $useStatement->addChild($this->parseTraitReference());
1857
        }
1858
1859 71
        return $this->setNodePositionsAndReturn(
1860 71
            $this->parseOptionalTraitAdaptation($useStatement),
1861 71
        );
1862
    }
1863
1864
    /**
1865
     * Parses a trait reference instance.
1866
     *
1867
     * @return ASTTraitReference
1868
     * @since 1.0.0
1869
     */
1870 71
    private function parseTraitReference()
1871
    {
1872 71
        $this->consumeComments();
1873 71
        $this->tokenStack->push();
1874
1875 71
        return $this->setNodePositionsAndReturn(
1876 71
            $this->builder->buildAstTraitReference(
1877 71
                $this->parseQualifiedName(),
1878 71
            ),
1879 71
        );
1880
    }
1881
1882
    /**
1883
     * Parses the adaptation list of the given use statement or simply reads
1884
     * the terminating semicolon, when no adaptation list exists.
1885
     *
1886
     * @template T of ASTTraitUseStatement
1887
     * @param T $useStatement
1888
     * @return T
1889
     * @since 1.0.0
1890
     */
1891 71
    private function parseOptionalTraitAdaptation(ASTTraitUseStatement $useStatement): ASTTraitUseStatement
1892
    {
1893 71
        $this->consumeComments();
1894
1895 71
        if (Tokens::T_CURLY_BRACE_OPEN === $this->tokenizer->peek()) {
1896 55
            $useStatement->addChild($this->parseTraitAdaptation());
1897
        } else {
1898 20
            $this->consumeToken(Tokens::T_SEMICOLON);
1899
        }
1900
1901 70
        return $useStatement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $useStatement returns the type PDepend\Source\AST\ASTTraitUseStatement which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
1902
    }
1903
1904
    /**
1905
     * Parses the adaptation expression of a trait use statement.
1906
     *
1907
     * @return ASTTraitAdaptation
1908
     * @since 1.0.0
1909
     */
1910 55
    private function parseTraitAdaptation()
1911
    {
1912 55
        $this->tokenStack->push();
1913
1914 55
        $adaptation = $this->builder->buildAstTraitAdaptation();
1915
1916 55
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
1917
1918
        do {
1919 55
            $this->tokenStack->push();
1920
1921 55
            $reference = $this->parseTraitMethodReference();
1922 55
            $this->consumeComments();
1923
1924 55
            $stmt = Tokens::T_AS === $this->tokenizer->peek()
1925 35
                ? $this->parseTraitAdaptationAliasStatement($reference)
1926 20
                : $this->parseTraitAdaptationPrecedenceStatement($reference);
1927
1928 54
            $this->consumeComments();
1929 54
            $this->consumeToken(Tokens::T_SEMICOLON);
1930
1931 54
            $adaptation->addChild($this->setNodePositionsAndReturn($stmt));
1932
1933 54
            $this->consumeComments();
1934 54
        } while (Tokens::T_CURLY_BRACE_CLOSE !== $this->tokenizer->peek());
1935
1936 54
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
1937
1938 54
        return $this->setNodePositionsAndReturn($adaptation);
1939
    }
1940
1941
    /**
1942
     * Parses a trait method reference and returns the found reference as an
1943
     * <b>array</b>.
1944
     *
1945
     * The returned array with contain only one element, when the referenced
1946
     * method is specified by the method's name, without the declaring trait.
1947
     * When the method reference contains the declaring trait the returned
1948
     * <b>array</b> will contain two elements. The first element is the plain
1949
     * method name and the second element is an instance of the
1950
     * {@link ASTTraitReference} class that represents the
1951
     * declaring trait.
1952
     *
1953
     * @return array{string, ?ASTNode}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{string, ?ASTNode} at position 2 could not be parsed: Expected ':' at position 2, but found 'string'.
Loading history...
1954
     * @since 1.0.0
1955
     */
1956 55
    private function parseTraitMethodReference()
1957
    {
1958 55
        $this->tokenStack->push();
1959
1960 55
        $qualifiedName = $this->parseQualifiedName();
1961
1962 55
        $this->consumeComments();
1963
1964 55
        if (Tokens::T_DOUBLE_COLON === $this->tokenizer->peek()) {
1965 25
            $traitReference = $this->setNodePositionsAndReturn(
1966 25
                $this->builder->buildAstTraitReference($qualifiedName),
1967 25
            );
1968
1969 25
            $this->consumeToken(Tokens::T_DOUBLE_COLON);
1970 25
            $this->consumeComments();
1971
1972 25
            return [$this->parseMethodName(), $traitReference];
1973
        }
1974
1975 31
        $this->tokenStack->pop();
1976
1977 31
        return [$qualifiedName, null];
1978
    }
1979
1980
    /**
1981
     * Parses a trait adaptation alias statement.
1982
     *
1983
     * @param array{string, ?ASTNode} $reference Parsed method reference array.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{string, ?ASTNode} at position 2 could not be parsed: Expected ':' at position 2, but found 'string'.
Loading history...
1984
     * @return ASTTraitAdaptationAlias
1985
     * @since 1.0.0
1986
     */
1987 35
    private function parseTraitAdaptationAliasStatement(array $reference)
1988
    {
1989 35
        $stmt = $this->builder->buildAstTraitAdaptationAlias($reference[0]);
1990
1991 35
        if (isset($reference[1])) {
1992 6
            $stmt->addChild($reference[1]);
1993
        }
1994
1995 35
        $this->consumeToken(Tokens::T_AS);
1996 35
        $this->consumeComments();
1997
1998 35
        switch ($this->tokenizer->peek()) {
1999
            case Tokens::T_PUBLIC:
2000 7
                $stmt->setNewModifier(State::IS_PUBLIC);
2001 7
                $this->consumeToken(Tokens::T_PUBLIC);
2002 7
                $this->consumeComments();
2003
2004 7
                break;
2005
2006
            case Tokens::T_PROTECTED:
2007 7
                $stmt->setNewModifier(State::IS_PROTECTED);
2008 7
                $this->consumeToken(Tokens::T_PROTECTED);
2009 7
                $this->consumeComments();
2010
2011 7
                break;
2012
2013
            case Tokens::T_PRIVATE:
2014 6
                $stmt->setNewModifier(State::IS_PRIVATE);
2015 6
                $this->consumeToken(Tokens::T_PRIVATE);
2016 6
                $this->consumeComments();
2017
2018 6
                break;
2019
        }
2020
2021 35
        if (Tokens::T_SEMICOLON !== $this->tokenizer->peek()) {
2022 16
            $stmt->setNewName($this->parseMethodName());
2023
        }
2024
2025 35
        return $stmt;
2026
    }
2027
2028
    /**
2029
     * Parses a trait adaptation precedence statement.
2030
     *
2031
     * @param array{string, ?ASTNode} $reference Parsed method reference array.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{string, ?ASTNode} at position 2 could not be parsed: Expected ':' at position 2, but found 'string'.
Loading history...
2032
     * @return ASTTraitAdaptationPrecedence
2033
     * @throws InvalidStateException
2034
     * @since 1.0.0
2035
     */
2036 20
    private function parseTraitAdaptationPrecedenceStatement(array $reference)
2037
    {
2038 20
        if (!isset($reference[1])) {
2039 1
            throw new InvalidStateException(
2040 1
                $this->requireNextToken()->startLine,
2041 1
                $this->compilationUnit->getFileName() ?? 'unknown',
2042 1
                'Expecting full qualified trait method name.',
2043 1
            );
2044
        }
2045
2046 19
        $stmt = $this->builder->buildAstTraitAdaptationPrecedence($reference[0]);
2047 19
        $stmt->addChild($reference[1]);
2048
2049 19
        $this->consumeToken(Tokens::T_INSTEADOF);
2050 19
        $this->consumeComments();
2051
2052 19
        $stmt->addChild($this->parseTraitReference());
2053
2054 19
        $this->consumeComments();
2055 19
        while (Tokens::T_COMMA === $this->tokenizer->peek()) {
2056 3
            $this->consumeToken(Tokens::T_COMMA);
2057 3
            $stmt->addChild($this->parseTraitReference());
2058 3
            $this->consumeComments();
2059
        }
2060
2061 19
        return $stmt;
2062
    }
2063
2064
    /**
2065
     * Parses an allocation expression.
2066
     *
2067
     * <code>
2068
     * function foo()
2069
     * {
2070
     * //  -------------
2071
     *     new bar\Baz();
2072
     * //  -------------
2073
     *
2074
     * //  ---------
2075
     *     new Foo();
2076
     * //  ---------
2077
     * }
2078
     * </code>
2079
     *
2080
     * @return ASTAllocationExpression
2081
     * @throws ParserException
2082
     * @since 0.9.6
2083
     */
2084 108
    private function parseAllocationExpression()
2085
    {
2086 108
        $this->tokenStack->push();
2087
2088 108
        $allocation = $this->parseAllocationExpressionValue();
2089
2090 104
        if ($this->isNextTokenArguments()) {
2091 73
            $allocation->addChild($this->parseArguments());
2092
        }
2093
2094 104
        return $this->setNodePositionsAndReturn($allocation);
2095
    }
2096
2097
    /**
2098
     * Parse the type reference used in an allocation expression.
2099
     *
2100
     * @template T of ASTAllocationExpression
2101
     *
2102
     * @param T $allocation
2103
     * @return T
2104
     * @throws ParserException
2105
     * @since 2.3
2106
     */
2107 108
    private function parseAllocationExpressionTypeReference(ASTAllocationExpression $allocation): ASTAllocationExpression
2108
    {
2109 108
        return $this->parseAnonymousClassDeclaration($allocation)
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parseAnony...ence($allocation, true) returns the type PDepend\Source\AST\ASTAllocationExpression which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
2110 108
            ?: $this->parseExpressionTypeReference($allocation, true);
2111
    }
2112
2113
    /**
2114
     * Parse the instanciation for new keyword without arguments.
2115
     *
2116
     * @return ASTAllocationExpression
2117
     * @throws ParserException
2118
     */
2119 108
    private function parseAllocationExpressionValue()
2120
    {
2121 108
        $token = $this->consumeToken(Tokens::T_NEW);
2122 108
        $this->consumeComments();
2123 108
        $inParentheses = ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN);
2124
2125 108
        if ($inParentheses) {
2126 1
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
2127
        }
2128
2129 108
        $allocation = $this->builder->buildAstAllocationExpression($token->image);
2130
2131 108
        if (!$inParentheses) {
2132 108
            return $this->parseAllocationExpressionTypeReference($allocation);
2133
        }
2134
2135 1
        $expression = $this->parseOptionalExpression();
2136
2137 1
        if (!$expression) {
2138
            throw $this->getUnexpectedNextTokenException();
2139
        }
2140
2141 1
        $allocation->addChild($expression);
2142 1
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
2143
2144 1
        return $allocation;
2145
    }
2146
2147
    /**
2148
     * Parses a eval-expression node.
2149
     *
2150
     * @return ASTEvalExpression
2151
     * @since 0.9.12
2152
     */
2153 4
    private function parseEvalExpression()
2154
    {
2155 4
        $this->tokenStack->push();
2156 4
        $token = $this->consumeToken(Tokens::T_EVAL);
2157
2158 4
        $expr = $this->builder->buildAstEvalExpression($token->image);
2159 4
        $expr->addChild($this->parseParenthesisExpression());
2160
2161 4
        return $this->setNodePositionsAndReturn($expr);
2162
    }
2163
2164
    /**
2165
     * This method parses an exit-expression.
2166
     *
2167
     * @return ASTExitExpression
2168
     * @since 0.9.12
2169
     */
2170 6
    private function parseExitExpression()
2171
    {
2172 6
        $this->tokenStack->push();
2173 6
        $token = $this->consumeToken(Tokens::T_EXIT);
2174
2175 6
        $expr = $this->builder->buildAstExitExpression($token->image);
2176
2177 6
        $this->consumeComments();
2178 6
        if ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN) {
2179 2
            $expr->addChild($this->parseParenthesisExpression());
2180
        }
2181
2182 6
        return $this->setNodePositionsAndReturn($expr);
2183
    }
2184
2185
    /**
2186
     * Parses a clone-expression node.
2187
     *
2188
     * @return ASTCloneExpression
2189
     * @since 0.9.12
2190
     */
2191 4
    private function parseCloneExpression()
2192
    {
2193 4
        $this->tokenStack->push();
2194 4
        $token = $this->consumeToken(Tokens::T_CLONE);
2195
2196 4
        $expr = $this->builder->buildAstCloneExpression($token->image);
2197 4
        $expr->addChild($this->parseExpression());
2198
2199 4
        return $this->setNodePositionsAndReturn($expr);
2200
    }
2201
2202
    /**
2203
     * Throws an exception if the given token is not a valid list unpacking opening token for current PHP level.
2204
     *
2205
     * @param int $tokenType
2206
     * @param Token $unexpectedToken
2207
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
2208 24
    private function ensureTokenIsListUnpackingOpening($tokenType, $unexpectedToken = null): void
2209
    {
2210 24
        if (!$this->isListUnpacking($tokenType)) {
2211
            throw $this->getUnexpectedTokenException($unexpectedToken ?: $this->tokenizer->prevToken());
2212
        }
2213
    }
2214
2215
    /**
2216
     * This method parses a single list-statement node.
2217
     *
2218
     * @return ASTListExpression
2219
     * @since 0.9.12
2220
     */
2221 19
    private function parseListExpression()
2222
    {
2223 19
        $this->tokenStack->push();
2224
2225 19
        $tokenType = $this->tokenizer->peek();
2226 19
        $this->ensureTokenIsListUnpackingOpening($tokenType);
2227 19
        $shortSyntax = ($tokenType !== Tokens::T_LIST);
2228
2229 19
        if ($shortSyntax) {
2230 3
            $token = $this->consumeToken(Tokens::T_SQUARED_BRACKET_OPEN);
2231 3
            $list = $this->builder->buildAstListExpression($token->image);
2232
        } else {
2233 16
            $token = $this->consumeToken(Tokens::T_LIST);
2234 16
            $this->consumeComments();
2235
2236 16
            $list = $this->builder->buildAstListExpression($token->image);
2237
2238 16
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
2239
        }
2240
2241 19
        $this->consumeComments();
2242
2243 19
        while (($tokenType = $this->tokenizer->peek()) !== Tokenizer::T_EOF) {
2244
            // The variable is optional:
2245
            //   list(, , , , $something) = ...;
2246
            // is valid.
2247
            switch ($tokenType) {
2248
                case Tokens::T_COMMA:
2249 16
                    $this->consumeToken(Tokens::T_COMMA);
2250 16
                    $this->consumeComments();
2251
2252 16
                    break;
2253
2254
                case Tokens::T_SQUARED_BRACKET_CLOSE:
2255
                case Tokens::T_PARENTHESIS_CLOSE:
2256 19
                    break 2;
2257
2258
                case Tokens::T_LIST:
2259
                case Tokens::T_SQUARED_BRACKET_OPEN:
2260 2
                    $list->addChild($this->parseListExpression());
2261 2
                    $this->consumeComments();
2262
2263 2
                    break;
2264
2265
                default:
2266 18
                    $list->addChild($this->parseListSlotExpression());
2267 18
                    $this->consumeComments();
2268
2269 18
                    break;
2270
            }
2271
        }
2272
2273 19
        $closeToken = $shortSyntax ? Tokens::T_SQUARED_BRACKET_CLOSE : Tokens::T_PARENTHESIS_CLOSE;
2274 19
        $this->consumeToken($closeToken);
2275
2276 19
        return $this->setNodePositionsAndReturn($list);
2277
    }
2278
2279
    /**
2280
     * Parse individual slot of a list() expression.
2281
     * @return ASTNode
2282
     */
2283 18
    private function parseListSlotExpression()
2284
    {
2285 18
        $startToken = $this->tokenizer->currentToken();
0 ignored issues
show
Unused Code introduced by
The assignment to $startToken is dead and can be removed.
Loading history...
2286 18
        $node = $this->parseOptionalExpression();
2287
2288 18
        if ($node && $this->tokenizer->peek() === Tokens::T_DOUBLE_ARROW) {
2289 3
            $this->consumeComments();
2290 3
            $this->consumeToken(Tokens::T_DOUBLE_ARROW);
2291 3
            $this->consumeComments();
2292
2293 3
            return in_array($this->tokenizer->peek(), [Tokens::T_LIST, Tokens::T_SQUARED_BRACKET_OPEN], true)
2294 1
                ? $this->parseListExpression()
2295 3
                : $this->parseVariableOrConstantOrPrimaryPrefix();
2296
        }
2297
2298 16
        return $node ?: $this->parseVariableOrConstantOrPrimaryPrefix();
2299
    }
2300
2301
    /**
2302
     * Parses a include-expression node.
2303
     *
2304
     * @return ASTIncludeExpression
2305
     * @since 0.9.12
2306
     */
2307 8
    private function parseIncludeExpression()
2308
    {
2309 8
        $expr = $this->builder->buildAstIncludeExpression();
2310
2311 8
        return $this->parseRequireOrIncludeExpression($expr, Tokens::T_INCLUDE);
2312
    }
2313
2314
    /**
2315
     * Parses a include_once-expression node.
2316
     *
2317
     * @return ASTIncludeExpression
2318
     * @since 0.9.12
2319
     */
2320 1
    private function parseIncludeOnceExpression()
2321
    {
2322 1
        $expr = $this->builder->buildAstIncludeExpression();
2323 1
        $expr->setOnce();
2324
2325 1
        return $this->parseRequireOrIncludeExpression($expr, Tokens::T_INCLUDE_ONCE);
2326
    }
2327
2328
    /**
2329
     * Parses a require-expression node.
2330
     *
2331
     * @return ASTRequireExpression
2332
     * @since 0.9.12
2333
     */
2334 2
    private function parseRequireExpression()
2335
    {
2336 2
        $expr = $this->builder->buildAstRequireExpression();
2337
2338 2
        return $this->parseRequireOrIncludeExpression($expr, Tokens::T_REQUIRE);
2339
    }
2340
2341
    /**
2342
     * Parses a require_once-expression node.
2343
     *
2344
     * @return ASTRequireExpression
2345
     * @since 0.9.12
2346
     */
2347 1
    private function parseRequireOnceExpression()
2348
    {
2349 1
        $expr = $this->builder->buildAstRequireExpression();
2350 1
        $expr->setOnce();
2351
2352 1
        return $this->parseRequireOrIncludeExpression($expr, Tokens::T_REQUIRE_ONCE);
2353
    }
2354
2355
    /**
2356
     * Parses a <b>require_once</b>-, <b>require</b>-, <b>include_once</b>- or
2357
     * <b>include</b>-expression node.
2358
     *
2359
     * @template T of ASTExpression
2360
     *
2361
     * @param T $expr
2362
     * @param int $type
2363
     * @return T
2364
     * @since 0.9.12
2365
     */
2366 12
    private function parseRequireOrIncludeExpression(ASTExpression $expr, $type): ASTExpression
2367
    {
2368 12
        $this->tokenStack->push();
2369
2370 12
        $this->consumeToken($type);
2371 12
        $this->consumeComments();
2372
2373 12
        if ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN) {
2374 5
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
2375 5
            $child = $this->parseOptionalExpression();
2376 5
            if ($child) {
2377 5
                $expr->addChild($child);
2378
            }
2379 5
            $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
2380 7
        } elseif ($child = $this->parseOptionalExpression()) {
2381 7
            $expr->addChild($child);
2382
        }
2383
2384 12
        return $this->setNodePositionsAndReturn($expr);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->setNodePositionsAndReturn($expr) returns the type PDepend\Source\AST\ASTExpression which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
2385
    }
2386
2387
    /**
2388
     * Parses a cast-expression node.
2389
     *
2390
     * @return ASTCastExpression
2391
     * @since 0.10.0
2392
     */
2393 20
    private function parseCastExpression()
2394
    {
2395 20
        $token = $this->consumeToken($this->tokenizer->peek());
2396
2397 20
        $expr = $this->builder->buildAstCastExpression($token->image);
2398 20
        $expr->configureLinesAndColumns(
2399 20
            $token->startLine,
2400 20
            $token->endLine,
2401 20
            $token->startColumn,
2402 20
            $token->endColumn,
2403 20
        );
2404
2405 20
        return $expr;
2406
    }
2407
2408
    /**
2409
     * This method will parse an increment-expression. Depending on the previous node
2410
     * this can be a {@link ASTPostIncrementExpression} or {@link ASTPostfixExpression}.
2411
     *
2412
     * @param list<ASTNode> $expressions List of previous parsed expression nodes.
0 ignored issues
show
Bug introduced by
The type PDepend\Source\Language\PHP\list 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...
2413
     * @return list<ASTNode>
2414
     * @since 0.10.0
2415
     */
2416 69
    private function parseIncrementExpression(array $expressions)
2417
    {
2418 69
        $expression = end($expressions);
2419 69
        if ($expression && $this->isReadWriteVariable($expression)) {
2420 14
            array_pop($expressions);
2421
2422 14
            $expressions[] = $this->parsePostIncrementExpression($expression);
2423
2424 14
            return $expressions;
2425
        }
2426
2427 59
        $expressions[] = $this->parsePreIncrementExpression();
2428
2429 59
        return $expressions;
2430
    }
2431
2432
    /**
2433
     * Parses a post increment-expression and adds the given child to that node.
2434
     *
2435
     * @param ASTNode $child The child expression node.
2436
     * @return ASTPostfixExpression
2437
     * @since 0.10.0
2438
     */
2439 14
    private function parsePostIncrementExpression(ASTNode $child)
2440
    {
2441 14
        $token = $this->consumeToken(Tokens::T_INC);
2442
2443 14
        $expr = $this->builder->buildAstPostfixExpression($token->image);
2444 14
        $expr->addChild($child);
2445 14
        $expr->configureLinesAndColumns(
2446 14
            $child->getStartLine(),
2447 14
            $token->endLine,
2448 14
            $child->getStartColumn(),
2449 14
            $token->endColumn,
2450 14
        );
2451
2452 14
        return $expr;
2453
    }
2454
2455
    /**
2456
     * Parses a pre increment-expression and adds the given child to that node.
2457
     *
2458
     * @return ASTPreIncrementExpression
2459
     * @since 0.10.0
2460
     */
2461 59
    private function parsePreIncrementExpression()
2462
    {
2463 59
        $token = $this->consumeToken(Tokens::T_INC);
2464
2465 59
        $expr = $this->builder->buildAstPreIncrementExpression();
2466 59
        $expr->configureLinesAndColumns(
2467 59
            $token->startLine,
2468 59
            $token->endLine,
2469 59
            $token->startColumn,
2470 59
            $token->endColumn,
2471 59
        );
2472
2473 59
        return $expr;
2474
    }
2475
2476
    /**
2477
     * This method will parse an decrement-expression. Depending on the previous node
2478
     * this can be a {@link ASTPostDecrementExpression} or {@link ASTPostfixExpression}.
2479
     *
2480
     * @param list<ASTNode> $expressions List of previous parsed expression nodes.
2481
     * @return list<ASTNode>
2482
     * @since 0.10.0
2483
     */
2484 33
    private function parseDecrementExpression(array $expressions)
2485
    {
2486 33
        $expression = end($expressions);
2487 33
        if ($expression && $this->isReadWriteVariable($expression)) {
2488 5
            array_pop($expressions);
2489
2490 5
            $expressions[] = $this->parsePostDecrementExpression($expression);
2491
2492 5
            return $expressions;
2493
        }
2494
2495 28
        $expressions[] = $this->parsePreDecrementExpression();
2496
2497 28
        return $expressions;
2498
    }
2499
2500
    /**
2501
     * Parses a post decrement-expression and adds the given child to that node.
2502
     *
2503
     * @param ASTNode $child The child expression node.
2504
     * @return ASTPostfixExpression
2505
     * @since 0.10.0
2506
     */
2507 5
    private function parsePostDecrementExpression(ASTNode $child)
2508
    {
2509 5
        $token = $this->consumeToken(Tokens::T_DEC);
2510
2511 5
        $expr = $this->builder->buildAstPostfixExpression($token->image);
2512 5
        $expr->addChild($child);
2513 5
        $expr->configureLinesAndColumns(
2514 5
            $child->getStartLine(),
2515 5
            $token->endLine,
2516 5
            $child->getStartColumn(),
2517 5
            $token->endColumn,
2518 5
        );
2519
2520 5
        return $expr;
2521
    }
2522
2523
    /**
2524
     * Parses a pre decrement-expression and adds the given child to that node.
2525
     *
2526
     * @return ASTPreDecrementExpression
2527
     * @since 0.10.0
2528
     */
2529 28
    private function parsePreDecrementExpression()
2530
    {
2531 28
        $token = $this->consumeToken(Tokens::T_DEC);
2532
2533 28
        $expr = $this->builder->buildAstPreDecrementExpression();
2534 28
        $expr->configureLinesAndColumns(
2535 28
            $token->startLine,
2536 28
            $token->endLine,
2537 28
            $token->startColumn,
2538 28
            $token->endColumn,
2539 28
        );
2540
2541 28
        return $expr;
2542
    }
2543
2544
    /**
2545
     * Parses one or more optional php <b>array</b> or <b>string</b> expressions.
2546
     *
2547
     * <code>
2548
     * ---------
2549
     * $array[0];
2550
     * ---------
2551
     *
2552
     * ----------------
2553
     * $array[1]['foo'];
2554
     * ----------------
2555
     *
2556
     * ----------------
2557
     * $string{1}[0]{0};
2558
     * ----------------
2559
     * </code>
2560
     *
2561
     * @param AbstractASTNode $node The parent/context node instance.
2562
     * @since 0.9.12
2563
     */
2564 719
    private function parseOptionalIndexExpression(AbstractASTNode $node): AbstractASTNode
2565
    {
2566 719
        $this->consumeComments();
2567
2568 719
        return match ($this->tokenizer->peek()) {
2569 719
            Tokens::T_CURLY_BRACE_OPEN => $this->parseStringIndexExpression($node),
2570 719
            Tokens::T_SQUARED_BRACKET_OPEN => $this->parseArrayIndexExpression($node),
2571 719
            default => $node,
2572 719
        };
2573
    }
2574
2575
    /**
2576
     * Parses an index expression as it is valid to access elements in a php
2577
     * string or array.
2578
     *
2579
     * @param ASTNode $node The context source node.
2580
     * @param ASTExpression $expr The concrete index expression.
2581
     * @param int $open The open token type.
2582
     * @param int $close The close token type.
2583
     * @since 0.9.12
2584
     */
2585 65
    private function parseIndexExpression(
2586
        ASTNode $node,
2587
        ASTExpression $expr,
2588
        $open,
2589
        $close,
2590
    ): AbstractASTNode {
2591 65
        $this->consumeToken($open);
2592
2593 65
        if (($child = $this->parseOptionalExpression()) !== null) {
2594 62
            $expr->addChild($child);
2595
        }
2596
2597 65
        $token = $this->consumeToken($close);
2598
2599 65
        $expr->configureLinesAndColumns(
2600 65
            $node->getStartLine(),
2601 65
            $token->endLine,
2602 65
            $node->getStartColumn(),
2603 65
            $token->endColumn,
2604 65
        );
2605
2606 65
        return $this->parseOptionalIndexExpression($expr);
2607
    }
2608
2609
    /**
2610
     * Parses a mandatory array index expression.
2611
     *
2612
     * <code>
2613
     * //    ---
2614
     * $array[0];
2615
     * //    ---
2616
     * </code>
2617
     *
2618
     * @param AbstractASTNode $node The context source node.
2619
     * @return AbstractASTNode
2620
     * @since 0.9.12
2621
     */
2622 64
    private function parseArrayIndexExpression(AbstractASTNode $node)
2623
    {
2624 64
        $expr = $this->builder->buildAstArrayIndexExpression();
2625 64
        $expr->addChild($node);
2626
2627 64
        return $this->parseIndexExpression(
2628 64
            $node,
2629 64
            $expr,
2630 64
            Tokens::T_SQUARED_BRACKET_OPEN,
2631 64
            Tokens::T_SQUARED_BRACKET_CLOSE,
2632 64
        );
2633
    }
2634
2635
    /**
2636
     * Parses a mandatory array index expression.
2637
     *
2638
     * <code>
2639
     * //     ---
2640
     * $string{0};
2641
     * //     ---
2642
     * </code>
2643
     *
2644
     * @param AbstractASTNode $node The context source node.
2645
     * @return AbstractASTNode
2646
     * @since 0.9.12
2647
     */
2648 1
    private function parseStringIndexExpression(AbstractASTNode $node)
2649
    {
2650 1
        $expr = $this->builder->buildAstStringIndexExpression();
2651 1
        $expr->addChild($node);
2652
2653 1
        return $this->parseIndexExpression(
2654 1
            $node,
2655 1
            $expr,
2656 1
            Tokens::T_CURLY_BRACE_OPEN,
2657 1
            Tokens::T_CURLY_BRACE_CLOSE,
2658 1
        );
2659
    }
2660
2661
    /**
2662
     * This method checks if the next available token starts an arguments node.
2663
     *
2664
     * @return bool
2665
     * @since 0.9.8
2666
     */
2667 104
    private function isNextTokenArguments()
2668
    {
2669 104
        $this->consumeComments();
2670
2671 104
        return $this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN;
2672
    }
2673
2674
    /**
2675
     * This method configures the given node with its start and end positions.
2676
     *
2677
     * @template T of ASTNode
2678
     *
2679
     * @param T $node
2680
     * @param array<int, Token> $tokens
2681
     * @return T
2682
     * @since 0.9.8
2683
     */
2684 1361
    protected function setNodePositionsAndReturn(ASTNode $node, array &$tokens = []): ASTNode
2685
    {
2686 1361
        $tokens = $this->stripTrailingComments($this->tokenStack->pop());
2687
2688 1361
        $end = $tokens[count($tokens) - 1];
2689 1361
        $start = $tokens[0];
2690
2691 1361
        $node->configureLinesAndColumns(
2692 1361
            $start->startLine,
2693 1361
            $end->endLine,
2694 1361
            $start->startColumn,
2695 1361
            $end->endColumn,
2696 1361
        );
2697
2698 1361
        return $node;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node returns the type PDepend\Source\AST\ASTNode which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
2699
    }
2700
2701
    /**
2702
     * Strips all trailing comments from the given token stream.
2703
     *
2704
     * @template T of Token[]
2705
     * @param T $tokens Original token stream.
2706
     * @return T
2707
     * @since 1.0.0
2708
     */
2709 1361
    private function stripTrailingComments(array $tokens): array
2710
    {
2711 1361
        $comments = [Tokens::T_COMMENT, Tokens::T_DOC_COMMENT];
2712
2713 1361
        while (count($tokens) > 1 && in_array(end($tokens)->type, $comments, true)) {
0 ignored issues
show
Coding Style Performance introduced by
The use of count() inside a loop condition is not allowed; assign the return value to a variable and use the variable in the loop condition instead
Loading history...
2714 31
            array_pop($tokens);
2715
        }
2716
2717 1361
        return $tokens;
2718
    }
2719
2720
    /**
2721
     * This method parse an instance of expression with its associated class or
2722
     * interface reference.
2723
     *
2724
     * <code>
2725
     *          ----------------
2726
     * ($object instanceof Clazz);
2727
     *          ----------------
2728
     *
2729
     *          ------------------------
2730
     * ($object instanceof Clazz::$clazz);
2731
     *          ------------------------
2732
     *
2733
     *          -----------------
2734
     * ($object instanceof $clazz);
2735
     *          -----------------
2736
     *
2737
     *          -----------------------
2738
     * ($object instanceof $clazz->type);
2739
     *          -----------------------
2740
     *
2741
     *          -----------------------------
2742
     * ($object instanceof static|self|parent);
2743
     *          -----------------------------
2744
     * </code>
2745
     *
2746
     * @return ASTInstanceOfExpression
2747
     * @since 0.9.6
2748
     */
2749 18
    private function parseInstanceOfExpression()
2750
    {
2751
        // Consume the "instanceof" keyword and strip comments
2752 18
        $token = $this->consumeToken(Tokens::T_INSTANCEOF);
2753
2754 18
        return $this->parseExpressionTypeReference(
2755 18
            $this->builder->buildAstInstanceOfExpression($token->image),
2756 18
            false,
2757 18
        );
2758
    }
2759
2760
    /**
2761
     * Parses an isset-expression node.
2762
     *
2763
     * <code>
2764
     * //  -----------
2765
     * if (isset($foo)) {
2766
     * //  -----------
2767
     * }
2768
     *
2769
     * //  -----------------------
2770
     * if (isset($foo, $bar, $baz)) {
2771
     * //  -----------------------
2772
     * }
2773
     *
2774
     * //  -----------------------
2775
     * if (isset($foo['bar'], BAR['baz']['foo'])) {
2776
     * //  -----------------------
2777
     * }
2778
     * </code>
2779
     *
2780
     * @return ASTIssetExpression
2781
     * @since 0.9.12
2782
     */
2783 10
    private function parseIssetExpression()
2784
    {
2785 10
        $startToken = $this->consumeToken(Tokens::T_ISSET);
2786 10
        $this->consumeComments();
2787 10
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
2788 10
        $this->consumeComments();
2789
2790 10
        $expr = $this->builder->buildAstIssetExpression();
2791 10
        $expr = $this->parseVariableList($expr, true);
2792 10
        $this->consumeComments();
2793
2794 10
        $stopToken = $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
2795
2796 10
        $expr->configureLinesAndColumns(
2797 10
            $startToken->startLine,
2798 10
            $stopToken->endLine,
2799 10
            $startToken->startColumn,
2800 10
            $stopToken->endColumn,
2801 10
        );
2802
2803 10
        return $expr;
2804
    }
2805
2806
    /**
2807
     * @param int $tokenType
2808
     * @return ASTClassOrInterfaceReference
2809
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
2810 24
    private function parseStandAloneExpressionTypeReference($tokenType)
2811
    {
2812 24
        return match ($tokenType) {
2813 24
            Tokens::T_SELF => $this->parseSelfReference($this->consumeToken(Tokens::T_SELF)),
2814 24
            Tokens::T_PARENT => $this->parseParentReference($this->consumeToken(Tokens::T_PARENT)),
2815 24
            Tokens::T_STATIC => $this->parseStaticReference($this->consumeToken(Tokens::T_STATIC)),
2816 24
            default => throw $this->getUnexpectedNextTokenException(),
2817 24
        };
2818
    }
2819
2820
    /**
2821
     * @param bool $classRef
2822
     * @return AbstractASTNode
2823
     * @throws ParserException
2824
     */
2825 123
    private function parseStandAloneExpressionType($classRef)
2826
    {
2827
        // Peek next token and look for a static type identifier
2828 123
        $this->consumeComments();
2829 123
        $tokenType = $this->tokenizer->peek();
2830
2831 123
        return match ($tokenType) {
2832
            // TODO: Parse variable or Member Primary Prefix + Property Postfix
2833 123
            Tokens::T_DOLLAR,
2834 123
            Tokens::T_VARIABLE => $this->parseVariableOrFunctionPostfixOrMemberPrimaryPrefix(),
2835 123
            Tokens::T_SELF,
2836 123
            Tokens::T_PARENT,
2837 123
            Tokens::T_STATIC => $this->parseStandAloneExpressionTypeReference($tokenType),
2838 123
            default => $this->parseClassOrInterfaceReference($classRef),
2839 123
        };
2840
    }
2841
2842
    /**
2843
     * This method parses a type identifier as it is used in expression nodes
2844
     * like {@link ASTInstanceOfExpression} or an object
2845
     * allocation node like {@link ASTAllocationExpression}.
2846
     *
2847
     * @template T of AbstractASTNode
2848
     *
2849
     * @param T $expr
2850
     * @param bool $classRef
2851
     * @return T
2852
     * @throws ParserException
2853
     */
2854 123
    private function parseExpressionTypeReference(ASTNode $expr, $classRef): AbstractASTNode
2855
    {
2856 123
        $expr->addChild(
0 ignored issues
show
Bug introduced by
The method addChild() does not exist on PDepend\Source\AST\ASTNode. It seems like you code against a sub-type of said class. However, the method does not exist in PDepend\Source\AST\ASTCallable or PDepend\Source\AST\ASTArtifact or PDepend\Source\AST\AbstractASTArtifact or PDepend\Source\AST\ASTNamespace or PDepend\Source\AST\ASTParameter or PDepend\Source\AST\ASTProperty. Are you sure you never get one of those? ( Ignorable by Annotation )

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

2856
        $expr->/** @scrutinizer ignore-call */ 
2857
               addChild(
Loading history...
2857 123
            $this->parseOptionalMemberPrimaryPrefix(
2858 123
                $this->parseOptionalStaticMemberPrimaryPrefix(
2859 123
                    $this->parseStandAloneExpressionType($classRef),
2860 123
                ),
2861 123
            ),
2862 123
        );
2863
2864 119
        return $expr;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $expr returns the type PDepend\Source\AST\ASTNode which includes types incompatible with the type-hinted return PDepend\Source\AST\AbstractASTNode.
Loading history...
2865
    }
2866
2867
    /**
2868
     * This method parses a conditional-expression.
2869
     *
2870
     * <code>
2871
     *         --------------
2872
     * $foo = ($bar ? 42 : 23);
2873
     *         --------------
2874
     * </code>
2875
     *
2876
     * @return ASTConditionalExpression
2877
     * @since 0.9.8
2878
     */
2879 9
    private function parseConditionalExpression()
2880
    {
2881 9
        $this->tokenStack->push();
2882 9
        $this->consumeToken(Tokens::T_QUESTION_MARK);
2883
2884 9
        $expr = $this->builder->buildAstConditionalExpression();
2885 9
        if (($child = $this->parseOptionalExpression()) !== null) {
2886 7
            $expr->addChild($child);
2887
        }
2888
2889 9
        $this->consumeToken(Tokens::T_COLON);
2890
2891 9
        $expr->addChild($this->parseExpression());
2892
2893 9
        return $this->setNodePositionsAndReturn($expr);
2894
    }
2895
2896
    /**
2897
     * This method parses a shift left expression node.
2898
     *
2899
     * @return ASTShiftLeftExpression
2900
     * @since 1.0.1
2901
     */
2902 2
    private function parseShiftLeftExpression()
2903
    {
2904 2
        $token = $this->consumeToken(Tokens::T_SL);
2905
2906 2
        $expr = $this->builder->buildAstShiftLeftExpression();
2907 2
        $expr->configureLinesAndColumns(
2908 2
            $token->startLine,
2909 2
            $token->endLine,
2910 2
            $token->startColumn,
2911 2
            $token->endColumn,
2912 2
        );
2913
2914 2
        return $expr;
2915
    }
2916
2917
    /**
2918
     * This method parses a shift right expression node.
2919
     *
2920
     * @return ASTShiftRightExpression
2921
     * @since 1.0.1
2922
     */
2923 2
    private function parseShiftRightExpression()
2924
    {
2925 2
        $token = $this->consumeToken(Tokens::T_SR);
2926
2927 2
        $expr = $this->builder->buildAstShiftRightExpression();
2928 2
        $expr->configureLinesAndColumns(
2929 2
            $token->startLine,
2930 2
            $token->endLine,
2931 2
            $token->startColumn,
2932 2
            $token->endColumn,
2933 2
        );
2934
2935 2
        return $expr;
2936
    }
2937
2938
    /**
2939
     * This method parses a boolean and-expression.
2940
     *
2941
     * @return ASTBooleanAndExpression
2942
     * @since 0.9.8
2943
     */
2944 19
    private function parseBooleanAndExpression()
2945
    {
2946 19
        $token = $this->consumeToken(Tokens::T_BOOLEAN_AND);
2947
2948 19
        $expr = $this->builder->buildAstBooleanAndExpression();
2949 19
        $expr->configureLinesAndColumns(
2950 19
            $token->startLine,
2951 19
            $token->endLine,
2952 19
            $token->startColumn,
2953 19
            $token->endColumn,
2954 19
        );
2955
2956 19
        return $expr;
2957
    }
2958
2959
    /**
2960
     * This method parses a boolean or-expression.
2961
     *
2962
     * @return ASTBooleanOrExpression
2963
     * @since 0.9.8
2964
     */
2965 27
    private function parseBooleanOrExpression()
2966
    {
2967 27
        $token = $this->consumeToken(Tokens::T_BOOLEAN_OR);
2968
2969 27
        $expr = $this->builder->buildAstBooleanOrExpression();
2970 27
        $expr->configureLinesAndColumns(
2971 27
            $token->startLine,
2972 27
            $token->endLine,
2973 27
            $token->startColumn,
2974 27
            $token->endColumn,
2975 27
        );
2976
2977 27
        return $expr;
2978
    }
2979
2980
    /**
2981
     * This method parses a logical <b>and</b>-expression.
2982
     *
2983
     * @return ASTLogicalAndExpression
2984
     * @since 0.9.8
2985
     */
2986 6
    private function parseLogicalAndExpression()
2987
    {
2988 6
        $token = $this->consumeToken(Tokens::T_LOGICAL_AND);
2989
2990 6
        $expr = $this->builder->buildAstLogicalAndExpression();
2991 6
        $expr->configureLinesAndColumns(
2992 6
            $token->startLine,
2993 6
            $token->endLine,
2994 6
            $token->startColumn,
2995 6
            $token->endColumn,
2996 6
        );
2997
2998 6
        return $expr;
2999
    }
3000
3001
    /**
3002
     * This method parses a logical <b>or</b>-expression.
3003
     *
3004
     * @return ASTLogicalOrExpression
3005
     * @since 0.9.8
3006
     */
3007 4
    private function parseLogicalOrExpression()
3008
    {
3009 4
        $token = $this->consumeToken(Tokens::T_LOGICAL_OR);
3010
3011 4
        $expr = $this->builder->buildAstLogicalOrExpression();
3012 4
        $expr->configureLinesAndColumns(
3013 4
            $token->startLine,
3014 4
            $token->endLine,
3015 4
            $token->startColumn,
3016 4
            $token->endColumn,
3017 4
        );
3018
3019 4
        return $expr;
3020
    }
3021
3022
    /**
3023
     * This method parses a logical <b>xor</b>-expression.
3024
     *
3025
     * @return ASTLogicalXorExpression
3026
     * @since 0.9.8
3027
     */
3028 4
    private function parseLogicalXorExpression()
3029
    {
3030 4
        $token = $this->consumeToken(Tokens::T_LOGICAL_XOR);
3031
3032 4
        $expr = $this->builder->buildAstLogicalXorExpression();
3033 4
        $expr->configureLinesAndColumns(
3034 4
            $token->startLine,
3035 4
            $token->endLine,
3036 4
            $token->startColumn,
3037 4
            $token->endColumn,
3038 4
        );
3039
3040 4
        return $expr;
3041
    }
3042
3043
    /**
3044
     * Parses a class or interface reference node.
3045
     *
3046
     * @param bool $classReference Force a class reference.
3047
     * @return ASTClassOrInterfaceReference
3048
     * @since 0.9.8
3049
     */
3050 96
    private function parseClassOrInterfaceReference($classReference)
3051
    {
3052 96
        $this->tokenStack->push();
3053
3054 96
        return $this->setNodePositionsAndReturn(
3055 96
            $this->builder->buildAstNeededReference(
3056 96
                $this->parseQualifiedName(),
3057 96
                $classReference,
3058 96
            ),
3059 96
        );
3060
    }
3061
3062
    /**
3063
     * This method parses a brace expression and adds all parsed node instances
3064
     * to the given {@link ASTNode} object. Finally it returns
3065
     * the prepared input node.
3066
     *
3067
     * A brace expression can be a compound:
3068
     *
3069
     * <code>
3070
     * $this->{$foo ? 'foo' : 'bar'}();
3071
     * </code>
3072
     *
3073
     * or a parameter list:
3074
     *
3075
     * <code>
3076
     * $this->foo($bar, $baz);
3077
     * </code>
3078
     *
3079
     * or an array index:
3080
     *
3081
     * <code>
3082
     * $foo[$bar];
3083
     * </code>
3084
     *
3085
     * @template T of AbstractASTNode
3086
     *
3087
     * @param T $node
3088
     * @param int $closeToken
3089
     * @param int|null $separatorToken
3090
     * @return T
3091
     * @throws TokenStreamEndException
3092
     * @since 0.9.6
3093
     */
3094 265
    private function parseBraceExpression(
3095
        ASTNode $node,
3096
        Token $start,
3097
        $closeToken,
3098
        $separatorToken = null,
3099
    ): AbstractASTNode {
3100 265
        if (is_object($expr = $this->parseOptionalExpression())) {
3101 263
            $node->addChild($expr);
3102
        }
3103
3104 265
        $this->consumeComments();
3105
3106 265
        while ($separatorToken && $this->tokenizer->peek() === $separatorToken) {
3107
            $this->consumeToken($separatorToken);
3108
3109
            if (is_object($expr = $this->parseOptionalExpression())) {
3110
                $node->addChild($expr);
3111
            }
3112
3113
            $this->consumeComments();
3114
        }
3115
3116 265
        $end = $this->consumeToken($closeToken);
3117
3118 265
        $node->configureLinesAndColumns(
3119 265
            $start->startLine,
3120 265
            $end->endLine,
3121 265
            $start->startColumn,
3122 265
            $end->endColumn,
3123 265
        );
3124
3125 265
        return $node;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node returns the type PDepend\Source\AST\ASTNode which includes types incompatible with the type-hinted return PDepend\Source\AST\AbstractASTNode.
Loading history...
3126
    }
3127
3128
    /**
3129
     * Parses the body of the given statement instance and adds all parsed nodes
3130
     * to that statement.
3131
     *
3132
     * @template T of ASTStatement
3133
     *
3134
     * @param T $stmt The owning statement.
3135
     * @return T
3136
     * @since 0.9.12
3137
     */
3138 209
    private function parseStatementBody(ASTStatement $stmt): ASTStatement
3139
    {
3140 209
        $this->consumeComments();
3141 209
        $tokenType = $this->tokenizer->peek();
3142
3143 209
        if ($tokenType === Tokens::T_CURLY_BRACE_OPEN) {
3144 163
            $stmt->addChild($this->parseRegularScope());
3145 51
        } elseif ($tokenType === Tokens::T_COLON) {
3146 35
            $stmt->addChild($this->parseAlternativeScope());
3147
        } else {
3148 16
            $stmt->addChild($this->parseStatement());
3149
        }
3150
3151 208
        return $stmt;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $stmt returns the type PDepend\Source\AST\ASTStatement which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
3152
    }
3153
3154
    /**
3155
     * Parse a scope enclosed by curly braces.
3156
     *
3157
     * @return ASTScopeStatement
3158
     * @throws ParserException
3159
     * @since 0.9.12
3160
     */
3161 196
    private function parseRegularScope()
3162
    {
3163 196
        $this->tokenStack->push();
3164
3165 196
        $this->consumeComments();
3166 196
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
3167
3168 196
        $scope = $this->parseScopeStatements();
3169
3170 196
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
3171
3172 196
        return $this->setNodePositionsAndReturn($scope);
3173
    }
3174
3175
    /**
3176
     * Parses the scope of a statement that is surrounded with PHP's alternative
3177
     * syntax for statements.
3178
     *
3179
     * @return ASTScopeStatement
3180
     * @since 0.10.0
3181
     */
3182 35
    private function parseAlternativeScope()
3183
    {
3184 35
        $this->tokenStack->push();
3185 35
        $this->consumeToken(Tokens::T_COLON);
3186
3187 35
        $scope = $this->parseScopeStatements();
3188
3189 35
        $this->parseOptionalAlternativeScopeTermination();
3190
3191 35
        return $this->setNodePositionsAndReturn($scope);
3192
    }
3193
3194
    /**
3195
     * Parses all statements that exist in a scope an adds them to a scope
3196
     * instance.
3197
     *
3198
     * @return ASTScopeStatement
3199
     * @throws ParserException
3200
     * @since 0.10.0
3201
     */
3202 227
    private function parseScopeStatements()
3203
    {
3204 227
        $scope = $this->builder->buildAstScopeStatement();
3205 227
        while (($child = $this->parseOptionalStatement()) !== null) {
3206 135
            if ($child instanceof ASTNode) {
3207 135
                $scope->addChild($child);
3208
            }
3209
        }
3210
3211 227
        return $scope;
3212
    }
3213
3214
    /**
3215
     * Parses the termination of a scope statement that uses PHP's laternative
3216
     * syntax format.
3217
     *
3218
     * @since 0.10.0
3219
     */
3220 35
    private function parseOptionalAlternativeScopeTermination(): void
3221
    {
3222 35
        $tokenType = $this->tokenizer->peek();
3223 35
        if ($this->isAlternativeScopeTermination($tokenType)) {
3224 35
            $this->parseAlternativeScopeTermination($tokenType);
3225
        }
3226
    }
3227
3228
    /**
3229
     * This method returns <b>true</b> when the given token identifier represents
3230
     * the end token of a alternative scope termination symbol. Otherwise this
3231
     * method will return <b>false</b>.
3232
     *
3233
     * @param int $tokenType The token type identifier.
3234
     * @return bool
3235
     * @since 0.10.0
3236
     */
3237 35
    private function isAlternativeScopeTermination($tokenType)
3238
    {
3239 35
        return in_array(
3240 35
            $tokenType,
3241 35
            [
3242 35
                Tokens::T_ENDDECLARE,
3243 35
                Tokens::T_ENDFOR,
3244 35
                Tokens::T_ENDFOREACH,
3245 35
                Tokens::T_ENDIF,
3246 35
                Tokens::T_ENDSWITCH,
3247 35
                Tokens::T_ENDWHILE,
3248 35
            ],
3249 35
            true,
3250 35
        );
3251
    }
3252
3253
    /**
3254
     * Parses a series of tokens that represent an alternative scope termination.
3255
     *
3256
     * @param int $tokenType The token type identifier.
3257
     * @since 0.10.0
3258
     */
3259 39
    private function parseAlternativeScopeTermination($tokenType): void
3260
    {
3261 39
        $this->consumeToken($tokenType);
3262 39
        $this->consumeComments();
3263
3264 39
        if ($this->tokenizer->peek() === Tokens::T_SEMICOLON) {
3265 34
            $this->consumeToken(Tokens::T_SEMICOLON);
3266
        } else {
3267 5
            $this->parseNonePhpCode();
3268
        }
3269
    }
3270
3271
    /**
3272
     * This method parses multiple expressions and adds them as children to the
3273
     * given <b>$exprList</b> node.
3274
     *
3275
     * @template T of AbstractASTNode
3276
     *
3277
     * @param T $exprList
3278
     * @return T
3279
     * @since 1.0.0
3280
     */
3281 146
    private function parseExpressionList(ASTNode $exprList): AbstractASTNode
3282
    {
3283 146
        $this->consumeComments();
3284
3285
        do {
3286 146
            $expr = $this->parseOptionalExpression();
3287 146
        } while ($expr && $this->addChildToList($exprList, $expr));
3288
3289 146
        return $exprList;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $exprList returns the type PDepend\Source\AST\ASTNode which includes types incompatible with the type-hinted return PDepend\Source\AST\AbstractASTNode.
Loading history...
3290
    }
3291
3292
    /**
3293
     * Return true if children remain to be added, false else.
3294
     *
3295
     * @param AbstractASTNode $exprList
3296
     * @return bool
3297
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
3298 302
    private function addChildToList(ASTNode $exprList, ASTNode $expr)
3299
    {
3300 302
        $exprList->addChild($expr);
3301
3302 302
        $this->consumeComments();
3303
3304 302
        if ($this->tokenizer->peek() !== Tokens::T_COMMA) {
3305 300
            return false;
3306
        }
3307
3308 120
        if ($exprList instanceof ASTArguments && !$exprList->acceptsMoreArguments()) {
3309 1
            throw $this->getUnexpectedNextTokenException();
3310
        }
3311
3312 119
        $this->consumeToken(Tokens::T_COMMA);
3313 119
        $this->consumeComments();
3314
3315 119
        return true;
3316
    }
3317
3318
    /**
3319
     * This method parses an expression node and returns it. When no expression
3320
     * was found this method will throw an InvalidStateException.
3321
     *
3322
     * @return ASTNode
3323
     * @throws ParserException
3324
     * @since 1.0.1
3325
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag for "InvalidStateException" exception
Loading history...
3326 190
    private function parseExpression()
3327
    {
3328 190
        if (null === ($expr = $this->parseOptionalExpression())) {
3329
            $token = $this->consumeToken($this->tokenizer->peek());
3330
3331
            throw new InvalidStateException(
3332
                $token->startLine,
3333
                $this->compilationUnit->getFileName() ?? 'unknown',
3334
                'Mandatory expression expected.',
3335
            );
3336
        }
3337
3338 190
        return $expr;
3339
    }
3340
3341
    /**
3342
     * This method optionally parses an expression node and returns it. When no
3343
     * expression was found this method will return <b>null</b>.
3344
     *
3345
     * @return ASTNode|null
3346
     * @throws ParserException
3347
     * @since 0.9.6
3348
     */
3349 918
    private function parseOptionalExpression()
3350
    {
3351 918
        $expressions = [];
3352
3353 918
        while (($tokenType = $this->tokenizer->peek()) !== Tokenizer::T_EOF) {
3354 918
            $expr = null;
3355
3356
            switch ($tokenType) {
3357
                case Tokens::T_COMMA:
3358
                case Tokens::T_AS:
3359
                case Tokens::T_BREAK:
3360
                case Tokens::T_CLOSE_TAG:
3361
                case Tokens::T_COLON:
3362
                case Tokens::T_CONTINUE:
3363
                case Tokens::T_CURLY_BRACE_CLOSE:
3364
                case Tokens::T_DECLARE:
3365
                case Tokens::T_DO:
3366
                case Tokens::T_DOUBLE_ARROW:
3367
                case Tokens::T_ECHO:
3368
                case Tokens::T_END_HEREDOC:
3369
                case Tokens::T_ENDFOREACH:
3370
                case Tokens::T_FOR:
3371
                case Tokens::T_FOREACH:
3372
                case Tokens::T_GLOBAL:
3373
                case Tokens::T_GOTO:
3374
                case Tokens::T_IF:
3375
                case Tokens::T_PARENTHESIS_CLOSE:
3376
                case Tokens::T_RETURN:
3377
                case Tokens::T_SEMICOLON:
3378
                case Tokens::T_SQUARED_BRACKET_CLOSE:
3379
                case Tokens::T_SWITCH:
3380
                case Tokens::T_TRY:
3381
                case Tokens::T_UNSET:
3382
                case Tokens::T_WHILE:
3383 903
                    break 2;
3384
3385
                case Tokens::T_THROW:
3386 5
                    $expressions[] = $this->parseThrowExpression();
3387
3388 5
                    break;
3389
3390
                case Tokens::T_SELF:
3391
                case Tokens::T_STRING:
3392
                case Tokens::T_PARENT:
3393
                case Tokens::T_STATIC:
3394
                case Tokens::T_DOLLAR:
3395
                case Tokens::T_VARIABLE:
3396
                case Tokens::T_BACKSLASH:
3397
                case Tokens::T_NAMESPACE:
3398 714
                    $expressions[] = $this->parseVariableOrConstantOrPrimaryPrefix();
3399
3400 706
                    break;
3401
3402 709
                case $this->isArrayStartDelimiter():
3403 94
                    $expressions[] = $this->doParseArray();
3404
3405 93
                    break;
3406
3407 698
                case Tokens::T_NULL:
3408 697
                case Tokens::T_TRUE:
3409 690
                case Tokens::T_FALSE:
3410 677
                case Tokens::T_LNUMBER:
3411 612
                case Tokens::T_DNUMBER:
3412 611
                case Tokens::T_BACKTICK:
3413 610
                case Tokens::T_DOUBLE_QUOTE:
3414 595
                case Tokens::T_CONSTANT_ENCAPSED_STRING:
3415 536
                    $expressions[] = $this->parseLiteralOrString();
3416
3417 535
                    break;
3418
3419 521
                case Tokens::T_NEW:
3420 106
                    $expressions[] = $this->parseAllocationExpression();
3421
3422 102
                    break;
3423
3424 462
                case Tokens::T_EVAL:
3425 4
                    $expressions[] = $this->parseEvalExpression();
3426
3427 4
                    break;
3428
3429 462
                case Tokens::T_CLONE:
3430 4
                    $expressions[] = $this->parseCloneExpression();
3431
3432 4
                    break;
3433
3434 458
                case Tokens::T_INSTANCEOF:
3435 18
                    $expressions[] = $this->parseInstanceOfExpression();
3436
3437 18
                    break;
3438
3439 454
                case Tokens::T_ISSET:
3440 9
                    $expressions[] = $this->parseIssetExpression();
3441
3442 9
                    break;
3443
3444 447
                case Tokens::T_LIST:
3445 447
                case Tokens::T_SQUARED_BRACKET_OPEN:
3446 13
                    $expressions[] = $this->parseListExpression();
3447
3448 13
                    break;
3449
3450 447
                case Tokens::T_QUESTION_MARK:
3451 9
                    $expressions[] = $this->parseConditionalExpression();
3452
3453 9
                    break;
3454
3455 445
                case Tokens::T_BOOLEAN_AND:
3456 19
                    $expressions[] = $this->parseBooleanAndExpression();
3457
3458 19
                    break;
3459
3460 445
                case Tokens::T_BOOLEAN_OR:
3461 27
                    $expressions[] = $this->parseBooleanOrExpression();
3462
3463 27
                    break;
3464
3465 430
                case Tokens::T_LOGICAL_AND:
3466 6
                    $expressions[] = $this->parseLogicalAndExpression();
3467
3468 6
                    break;
3469
3470 428
                case Tokens::T_LOGICAL_OR:
3471 4
                    $expressions[] = $this->parseLogicalOrExpression();
3472
3473 4
                    break;
3474
3475 428
                case Tokens::T_LOGICAL_XOR:
3476 4
                    $expressions[] = $this->parseLogicalXorExpression();
3477
3478 4
                    break;
3479
3480 428
                case Tokens::T_FUNCTION:
3481 20
                    $expressions[] = $this->parseClosureDeclaration();
3482
3483 20
                    break;
3484
3485 420
                case Tokens::T_FN:
3486 4
                    $expressions[] = $this->parseLambdaFunctionDeclaration();
3487
3488 4
                    break;
3489
3490 419
                case Tokens::T_PARENTHESIS_OPEN:
3491 77
                    $expressions[] = $this->parseParenthesisExpressionOrPrimaryPrefix();
3492
3493 77
                    break;
3494
3495 375
                case Tokens::T_EXIT:
3496 6
                    $expressions[] = $this->parseExitExpression();
3497
3498 6
                    break;
3499
3500 370
                case Tokens::T_START_HEREDOC:
3501 7
                    $expressions[] = $this->parseHeredoc();
3502
3503 7
                    break;
3504
3505 369
                case Tokens::T_CURLY_BRACE_OPEN:
3506 1
                    $expressions[] = $this->parseBraceExpression(
3507 1
                        $this->builder->buildAstExpression(),
3508 1
                        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN),
3509 1
                        Tokens::T_CURLY_BRACE_CLOSE,
3510 1
                    );
3511
3512 1
                    break;
3513
3514 368
                case Tokens::T_INCLUDE:
3515 8
                    $expressions[] = $this->parseIncludeExpression();
3516
3517 8
                    break;
3518
3519 360
                case Tokens::T_INCLUDE_ONCE:
3520 1
                    $expressions[] = $this->parseIncludeOnceExpression();
3521
3522 1
                    break;
3523
3524 359
                case Tokens::T_REQUIRE:
3525 2
                    $expressions[] = $this->parseRequireExpression();
3526
3527 2
                    break;
3528
3529 357
                case Tokens::T_REQUIRE_ONCE:
3530 1
                    $expressions[] = $this->parseRequireOnceExpression();
3531
3532 1
                    break;
3533
3534 356
                case Tokens::T_DEC:
3535 33
                    $expressions = $this->parseDecrementExpression($expressions);
3536
3537 33
                    break;
3538
3539 348
                case Tokens::T_INC:
3540 69
                    $expressions = $this->parseIncrementExpression($expressions);
3541
3542 69
                    break;
3543
3544 333
                case Tokens::T_SL:
3545 2
                    $expressions[] = $this->parseShiftLeftExpression();
3546
3547 2
                    break;
3548
3549 332
                case Tokens::T_SR:
3550 2
                    $expressions[] = $this->parseShiftRightExpression();
3551
3552 2
                    break;
3553
3554 331
                case Tokens::T_DIR:
3555 331
                case Tokens::T_FILE:
3556 330
                case Tokens::T_LINE:
3557 330
                case Tokens::T_NS_C:
3558 330
                case Tokens::T_FUNC_C:
3559 328
                case Tokens::T_CLASS_C:
3560 324
                case Tokens::T_METHOD_C:
3561 24
                    $expression = $this->parseConstant();
3562 24
                    if ($expression) {
3563 24
                        $expressions[] = $expression;
3564
                    }
3565
3566 24
                    break;
3567
3568 324
                case Tokens::T_INT_CAST:
3569 324
                case Tokens::T_BOOL_CAST:
3570 324
                case Tokens::T_ARRAY_CAST:
3571 324
                case Tokens::T_UNSET_CAST:
3572 324
                case Tokens::T_OBJECT_CAST:
3573 309
                case Tokens::T_DOUBLE_CAST:
3574 309
                case Tokens::T_STRING_CAST:
3575 20
                    $expressions[] = $this->parseCastExpression();
3576
3577 20
                    break;
3578
3579 309
                case Tokens::T_EQUAL:
3580 210
                case Tokens::T_OR_EQUAL:
3581 209
                case Tokens::T_SL_EQUAL:
3582 208
                case Tokens::T_SR_EQUAL:
3583 207
                case Tokens::T_AND_EQUAL:
3584 206
                case Tokens::T_DIV_EQUAL:
3585 205
                case Tokens::T_MOD_EQUAL:
3586 204
                case Tokens::T_MUL_EQUAL:
3587 203
                case Tokens::T_XOR_EQUAL:
3588 203
                case Tokens::T_PLUS_EQUAL:
3589 202
                case Tokens::T_MINUS_EQUAL:
3590 201
                case Tokens::T_CONCAT_EQUAL:
3591 199
                case Tokens::T_COALESCE_EQUAL:
3592 189
                    $expression = array_pop($expressions);
3593 189
                    if ($expression) {
3594 189
                        $expressions[] = $this->parseAssignmentExpression($expression);
3595
                    }
3596
3597 187
                    break;
3598
3599 198
                case Tokens::T_COMMENT:
3600 195
                case Tokens::T_DOC_COMMENT:
3601 3
                    $this->consumeToken($tokenType);
3602
3603 3
                    break;
3604
3605 195
                case Tokens::T_PRINT: // TODO: Implement print expression
3606 1
                    $token = $this->consumeToken($tokenType);
3607
3608 1
                    $expr = $this->builder->buildAstPrintExpression();
3609 1
                    $expr->configureLinesAndColumns(
3610 1
                        $token->startLine,
3611 1
                        $token->endLine,
3612 1
                        $token->startColumn,
3613 1
                        $token->endColumn,
3614 1
                    );
3615
3616 1
                    $expressions[] = $expr;
3617
3618 1
                    break;
3619
3620 194
                case Tokens::T_ELLIPSIS:
3621 192
                case Tokens::T_STRING_VARNAME: // TODO: Implement this
3622 187
                case Tokens::T_PLUS: // TODO: Make this a arithmetic expression
3623 177
                case Tokens::T_MINUS:
3624 176
                case Tokens::T_MUL:
3625 148
                case Tokens::T_DIV:
3626 148
                case Tokens::T_MOD:
3627 148
                case Tokens::T_IS_EQUAL: // TODO: Implement compare expressions
3628 147
                case Tokens::T_IS_NOT_EQUAL:
3629 147
                case Tokens::T_IS_IDENTICAL:
3630 115
                case Tokens::T_IS_NOT_IDENTICAL:
3631 101
                case Tokens::T_IS_GREATER_OR_EQUAL:
3632 99
                case Tokens::T_IS_SMALLER_OR_EQUAL:
3633 99
                case Tokens::T_ANGLE_BRACKET_OPEN:
3634 45
                case Tokens::T_ANGLE_BRACKET_CLOSE:
3635 36
                case Tokens::T_EMPTY:
3636 36
                case Tokens::T_CONCAT:
3637 22
                case Tokens::T_BITWISE_OR:
3638 21
                case Tokens::T_BITWISE_AND:
3639 21
                case Tokens::T_BITWISE_NOT:
3640 21
                case Tokens::T_BITWISE_XOR:
3641 178
                    $token = $this->consumeToken($tokenType);
3642
3643 178
                    $expr = $this->builder->buildAstExpression($token->image);
3644 178
                    $expr->configureLinesAndColumns(
3645 178
                        $token->startLine,
3646 178
                        $token->endLine,
3647 178
                        $token->startColumn,
3648 178
                        $token->endColumn,
3649 178
                    );
3650
3651 178
                    $expressions[] = $expr;
3652
3653 178
                    break;
3654
3655 20
                case Tokens::T_AT:
3656 19
                case Tokens::T_EXCLAMATION_MARK:
3657 6
                    $token = $this->consumeToken($tokenType);
3658
3659 6
                    $expr = $this->builder->buildAstUnaryExpression($token->image);
3660 6
                    $expr->configureLinesAndColumns(
3661 6
                        $token->startLine,
3662 6
                        $token->endLine,
3663 6
                        $token->startColumn,
3664 6
                        $token->endColumn,
3665 6
                    );
3666
3667 6
                    $expressions[] = $expr;
3668
3669 6
                    break;
3670
3671 14
                case Tokens::T_YIELD:
3672 3
                    $expressions[] = $this->parseYield(false);
3673
3674 3
                    break;
3675
3676
                default:
3677 11
                    $expression = $this->parseOptionalExpressionForVersion();
3678 8
                    if ($expression) {
3679 8
                        $expressions[] = $expression;
3680
                    }
3681
3682 8
                    break;
3683
            }
3684
        }
3685
3686 903
        $expressions = $this->reduce($expressions);
3687
3688 903
        $count = count($expressions);
3689 903
        if ($count === 0) {
3690 50
            return null;
3691
        }
3692 895
        if ($count === 1) {
3693 860
            return $expressions[0];
3694
        }
3695
3696 248
        $expr = $this->builder->buildAstExpression();
3697 248
        foreach ($expressions as $node) {
3698 248
            $expr->addChild($node);
3699
        }
3700 248
        $expr->configureLinesAndColumns(
3701 248
            $expressions[0]->getStartLine(),
3702 248
            $expressions[$count - 1]->getEndLine(),
3703 248
            $expressions[0]->getStartColumn(),
3704 248
            $expressions[$count - 1]->getEndColumn(),
3705 248
        );
3706
3707 248
        return $expr;
3708
    }
3709
3710
    /**
3711
     * This method will be called when the base parser cannot handle an expression
3712
     * in the base version. In this method you can implement version specific
3713
     * expressions.
3714
     *
3715
     * @return ?ASTNode
3716
     * @throws UnexpectedTokenException
3717
     * @since 2.2
3718
     */
3719 11
    private function parseOptionalExpressionForVersion()
3720
    {
3721 11
        $this->consumeComments();
3722 11
        $nextTokenType = $this->tokenizer->peek();
3723
3724
        switch ($nextTokenType) {
3725
            case Tokens::T_NULLSAFE_OBJECT_OPERATOR:
3726
            case Tokens::T_SPACESHIP:
3727
            case Tokens::T_COALESCE:
3728
            case Tokens::T_POW:
3729 7
                $token = $this->consumeToken($nextTokenType);
3730
3731 7
                $expr = $this->builder->buildAstExpression($token->image);
3732 7
                $expr->configureLinesAndColumns(
3733 7
                    $token->startLine,
3734 7
                    $token->endLine,
3735 7
                    $token->startColumn,
3736 7
                    $token->endColumn
3737 7
                );
3738
3739 7
                return $expr;
3740
3741
            case Tokens::T_TRAIT_C:
3742 1
                return $this->parseConstant();
3743
3744
            default:
3745 3
                throw $this->getUnexpectedNextTokenException();
3746
        }
3747
    }
3748
3749
    /**
3750
     * Applies all reduce rules against the given expression list.
3751
     *
3752
     * @template T of ASTNode[]
3753
     * @param T $expressions Unprepared input array with parsed expression nodes found in the source tree.
3754
     * @return T
3755
     * @since 0.10.0
3756
     */
3757 906
    private function reduce(array $expressions): array
3758
    {
3759 906
        return $this->reduceUnaryExpression($expressions);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->reduceUnaryExpression($expressions) returns the type array which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
3760
    }
3761
3762
    /**
3763
     * Reduces all unary-expressions in the given expression list.
3764
     *
3765
     * @template T of ASTNode[]
3766
     *
3767
     * @param T $expressions Unprepared input array with parsed expression nodes found in the source tree.
3768
     * @return T
3769
     * @since 0.10.0
3770
     */
3771 906
    private function reduceUnaryExpression(array $expressions): array
3772
    {
3773 906
        for ($i = count($expressions) - 2; $i >= 0; --$i) {
3774 287
            $expr = $expressions[$i];
3775 287
            if ($expr instanceof ASTUnaryExpression) {
3776 93
                $child = $expressions[$i + 1];
3777
3778 93
                $expr->addChild($child);
3779
3780 93
                $expr->configureLinesAndColumns(
3781 93
                    $expr->getStartLine(),
3782 93
                    $child->getEndLine(),
3783 93
                    $expr->getStartColumn(),
3784 93
                    $child->getEndColumn(),
3785 93
                );
3786
3787 93
                unset($expressions[$i + 1]);
3788
            }
3789
        }
3790
3791 906
        return array_values($expressions);
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_values($expressions) returns the type array which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
3792
    }
3793
3794
    /**
3795
     * This method parses a switch statement.
3796
     *
3797
     * @return ASTSwitchStatement
3798
     * @throws ParserException
3799
     * @since 0.9.8
3800
     */
3801 17
    private function parseSwitchStatement()
3802
    {
3803 17
        $this->tokenStack->push();
3804 17
        $this->consumeToken(Tokens::T_SWITCH);
3805
3806 17
        $switch = $this->builder->buildAstSwitchStatement();
3807 17
        $switch->addChild($this->parseParenthesisExpression());
3808 17
        $this->parseSwitchStatementBody($switch);
3809
3810 15
        return $this->setNodePositionsAndReturn($switch);
3811
    }
3812
3813
    /**
3814
     * Parses the body of a switch statement.
3815
     *
3816
     * @template T of ASTSwitchStatement
3817
     * @param T $switch The parent switch stmt.
3818
     * @return T
3819
     * @throws ParserException
3820
     * @since 0.9.8
3821
     */
3822 17
    private function parseSwitchStatementBody(ASTSwitchStatement $switch): ASTSwitchStatement
3823
    {
3824 17
        $this->consumeComments();
3825 17
        if ($this->tokenizer->peek() === Tokens::T_CURLY_BRACE_OPEN) {
3826 12
            $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
3827
        } else {
3828 5
            $this->consumeToken(Tokens::T_COLON);
3829
        }
3830
3831 17
        while (($tokenType = $this->tokenizer->peek()) !== Tokenizer::T_EOF) {
3832
            switch ($tokenType) {
3833
                case Tokens::T_CLOSE_TAG:
3834 3
                    $this->parseNonePhpCode();
3835
3836 3
                    break;
3837
3838
                case Tokens::T_ENDSWITCH:
3839 5
                    $this->parseAlternativeScopeTermination(Tokens::T_ENDSWITCH);
3840
3841 5
                    return $switch;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $switch returns the type PDepend\Source\AST\ASTSwitchStatement which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
3842
3843
                case Tokens::T_CURLY_BRACE_CLOSE:
3844 10
                    $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
3845
3846 10
                    return $switch;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $switch returns the type PDepend\Source\AST\ASTSwitchStatement which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
3847
3848
                case Tokens::T_CASE:
3849 15
                    $switch->addChild($this->parseSwitchLabel());
3850
3851 14
                    break;
3852
3853
                case Tokens::T_DEFAULT:
3854 12
                    $switch->addChild($this->parseSwitchLabelDefault());
3855
3856 12
                    break;
3857
3858
                case Tokens::T_COMMENT:
3859
                case Tokens::T_DOC_COMMENT:
3860
                    $this->consumeToken($tokenType);
3861
3862
                    break;
3863
3864
                default:
3865 1
                    break 2;
3866
            }
3867
        }
3868
3869 1
        throw $this->getUnexpectedNextTokenException();
3870
    }
3871
3872
    /**
3873
     * This method parses a case label of a switch statement.
3874
     *
3875
     * @return ASTSwitchLabel
3876
     * @throws ParserException
3877
     * @since 0.9.8
3878
     */
3879 15
    private function parseSwitchLabel()
3880
    {
3881 15
        $this->tokenStack->push();
3882 15
        $token = $this->consumeToken(Tokens::T_CASE);
3883
3884 15
        $label = $this->builder->buildAstSwitchLabel($token->image);
3885 15
        $label->addChild($this->parseExpression());
3886
3887 15
        if ($this->tokenizer->peek() === Tokens::T_COLON) {
3888 15
            $this->consumeToken(Tokens::T_COLON);
3889
        } else {
3890
            $this->consumeToken(Tokens::T_SEMICOLON);
3891
        }
3892
3893 15
        $this->parseSwitchLabelBody($label);
3894
3895 14
        return $this->setNodePositionsAndReturn($label);
3896
    }
3897
3898
    /**
3899
     * This method parses the default label of a switch statement.
3900
     *
3901
     * @return ASTSwitchLabel
3902
     * @since 0.9.8
3903
     */
3904 12
    private function parseSwitchLabelDefault()
3905
    {
3906 12
        $this->tokenStack->push();
3907 12
        $token = $this->consumeToken(Tokens::T_DEFAULT);
3908
3909 12
        $this->consumeComments();
3910 12
        if ($this->tokenizer->peek() === Tokens::T_COLON) {
3911 12
            $this->consumeToken(Tokens::T_COLON);
3912
        } else {
3913
            $this->consumeToken(Tokens::T_SEMICOLON);
3914
        }
3915
3916 12
        $label = $this->builder->buildAstSwitchLabel($token->image);
3917 12
        $label->setDefault();
3918
3919 12
        $this->parseSwitchLabelBody($label);
3920
3921 12
        return $this->setNodePositionsAndReturn($label);
3922
    }
3923
3924
    /**
3925
     * Parses the body of an switch label node.
3926
     *
3927
     * @template T of ASTSwitchLabel
3928
     * @param T $label The context switch label.
3929
     * @return T
3930
     * @throws ParserException
3931
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag for "TokenStreamEndException" exception
Loading history...
3932 16
    private function parseSwitchLabelBody(ASTSwitchLabel $label): ASTSwitchLabel
3933
    {
3934 16
        $curlyBraceCount = 0;
3935
3936 16
        $tokenType = $this->tokenizer->peek();
3937 16
        while ($tokenType !== Tokenizer::T_EOF) {
3938
            switch ($tokenType) {
3939
                case Tokens::T_CURLY_BRACE_OPEN:
3940
                    $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
3941
                    ++$curlyBraceCount;
3942
3943
                    break;
3944
3945
                case Tokens::T_CURLY_BRACE_CLOSE:
3946 10
                    if ($curlyBraceCount === 0) {
3947 10
                        return $label;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $label returns the type PDepend\Source\AST\ASTSwitchLabel which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
3948
                    }
3949
                    $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
3950
                    --$curlyBraceCount;
3951
3952
                    break;
3953
3954
                case Tokens::T_CLOSE_TAG:
3955 3
                    $this->parseNonePhpCode();
3956
3957 3
                    break;
3958
3959
                case Tokens::T_CASE:
3960
                case Tokens::T_DEFAULT:
3961
                case Tokens::T_ENDSWITCH:
3962 13
                    return $label;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $label returns the type PDepend\Source\AST\ASTSwitchLabel which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
3963
3964
                default:
3965 16
                    $statement = $this->parseOptionalStatement();
3966 16
                    if ($statement === null) {
3967
                        $this->consumeToken($tokenType);
3968 16
                    } elseif ($statement instanceof ASTNode) {
3969 16
                        $label->addChild($statement);
3970
                    }
3971
3972
                    // TODO: Change the <else if> into and <else> when the ast
3973
                    //       implementation is finished.
3974 16
                    break;
3975
            }
3976 16
            $tokenType = $this->tokenizer->peek();
3977
        }
3978
3979 1
        throw new TokenStreamEndException($this->tokenizer);
3980
    }
3981
3982
    /**
3983
     * Parses the termination token for a statement. This termination token can
3984
     * be a semicolon or a closing php tag.
3985
     *
3986
     * @param int[] $allowedTerminationTokens list of extra token types that can terminate the statement
3987
     * @since 0.9.12
3988
     */
3989 790
    private function parseStatementTermination(array $allowedTerminationTokens = []): void
3990
    {
3991 790
        $this->consumeComments();
3992 790
        $this->echoing = false;
3993
3994 790
        if (in_array($this->tokenizer->peek(), $allowedTerminationTokens, true)) {
3995 8
            return;
3996
        }
3997
3998 790
        if ($this->tokenizer->peek() === Tokens::T_SEMICOLON) {
3999 788
            $this->consumeToken(Tokens::T_SEMICOLON);
4000
        } else {
4001 4
            $this->parseNonePhpCode();
4002
        }
4003
    }
4004
4005
    /**
4006
     * This method parses a try-statement + associated catch-statements.
4007
     *
4008
     * @return ASTTryStatement
4009
     * @throws ParserException
4010
     * @since 0.9.12
4011
     */
4012 32
    private function parseTryStatement()
4013
    {
4014 32
        $this->tokenStack->push();
4015 32
        $token = $this->consumeToken(Tokens::T_TRY);
4016
4017 32
        $stmt = $this->builder->buildAstTryStatement($token->image);
4018 32
        $stmt->addChild($this->parseRegularScope());
4019
4020 32
        $this->consumeComments();
4021
4022 32
        if (false === in_array($this->tokenizer->peek(), [Tokens::T_CATCH, Tokens::T_FINALLY], true)) {
4023 1
            throw $this->getUnexpectedNextTokenException();
4024
        }
4025
4026 31
        while ($this->tokenizer->peek() === Tokens::T_CATCH) {
4027 27
            $stmt->addChild($this->parseCatchStatement());
4028 27
            $this->consumeComments();
4029
        }
4030
4031 31
        while ($this->tokenizer->peek() === Tokens::T_FINALLY) {
4032 9
            $stmt->addChild($this->parseFinallyStatement());
4033 9
            $this->consumeComments();
4034
        }
4035
4036 31
        return $this->setNodePositionsAndReturn($stmt);
4037
    }
4038
4039
    /**
4040
     * This method parses a throw-statement.
4041
     *
4042
     * @param int[] $allowedTerminationTokens list of extra token types that can terminate the statement
4043
     * @return ASTThrowStatement
4044
     * @since 0.9.12
4045
     */
4046 27
    private function parseThrowStatement(array $allowedTerminationTokens = [])
4047
    {
4048 27
        $this->tokenStack->push();
4049 27
        $token = $this->consumeToken(Tokens::T_THROW);
4050
4051 27
        $stmt = $this->builder->buildAstThrowStatement($token->image);
4052 27
        $stmt->addChild($this->parseExpression());
4053
4054 27
        $this->parseStatementTermination($allowedTerminationTokens);
4055
4056 27
        return $this->setNodePositionsAndReturn($stmt);
4057
    }
4058
4059
    /**
4060
     * This method parses a goto-statement.
4061
     *
4062
     * @return ASTGotoStatement
4063
     * @since 0.9.12
4064
     */
4065 8
    private function parseGotoStatement()
4066
    {
4067 8
        $this->tokenStack->push();
4068
4069 8
        $this->consumeToken(Tokens::T_GOTO);
4070 8
        $this->consumeComments();
4071
4072 8
        $token = $this->consumeToken(Tokens::T_STRING);
4073
4074 8
        $this->parseStatementTermination();
4075
4076 8
        $stmt = $this->builder->buildAstGotoStatement($token->image);
4077
4078 8
        return $this->setNodePositionsAndReturn($stmt);
4079
    }
4080
4081
    /**
4082
     * This method parses a label-statement.
4083
     *
4084
     * @return ASTLabelStatement
4085
     * @since 0.9.12
4086
     */
4087 8
    private function parseLabelStatement()
4088
    {
4089 8
        $this->tokenStack->push();
4090
4091 8
        $token = $this->consumeToken(Tokens::T_STRING);
4092 8
        $this->consumeComments();
4093 8
        $this->consumeToken(Tokens::T_COLON);
4094
4095 8
        return $this->setNodePositionsAndReturn(
4096 8
            $this->builder->buildAstLabelStatement($token->image),
4097 8
        );
4098
    }
4099
4100
    /**
4101
     * This method parses a global-statement.
4102
     *
4103
     * @return ASTGlobalStatement
4104
     * @since 0.9.12
4105
     */
4106 4
    private function parseGlobalStatement()
4107
    {
4108 4
        $this->tokenStack->push();
4109 4
        $this->consumeToken(Tokens::T_GLOBAL);
4110
4111 4
        $stmt = $this->builder->buildAstGlobalStatement();
4112 4
        $stmt = $this->parseVariableList($stmt);
4113
4114 4
        $this->parseStatementTermination();
4115
4116 4
        return $this->setNodePositionsAndReturn($stmt);
4117
    }
4118
4119
    /**
4120
     * This method parses a unset-statement.
4121
     *
4122
     * @return ASTUnsetStatement
4123
     * @since 0.9.12
4124
     */
4125 2
    private function parseUnsetStatement()
4126
    {
4127 2
        $this->tokenStack->push();
4128
4129 2
        $this->consumeToken(Tokens::T_UNSET);
4130 2
        $this->consumeComments();
4131 2
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
4132 2
        $this->consumeComments();
4133
4134 2
        $stmt = $this->builder->buildAstUnsetStatement();
4135 2
        $stmt = $this->parseVariableList($stmt, true);
4136 2
        $this->consumeComments();
4137
4138 2
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
4139
4140 2
        $this->parseStatementTermination();
4141
4142 2
        return $this->setNodePositionsAndReturn($stmt);
4143
    }
4144
4145
    /**
4146
     * This method parses a catch-statement.
4147
     *
4148
     * @return ASTCatchStatement
4149
     * @since 0.9.8
4150
     */
4151 27
    private function parseCatchStatement()
4152
    {
4153 27
        $this->tokenStack->push();
4154 27
        $this->consumeComments();
4155
4156 27
        $token = $this->consumeToken(Tokens::T_CATCH);
4157
4158 27
        $catch = $this->builder->buildAstCatchStatement($token->image);
4159
4160 27
        $this->consumeComments();
4161 27
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
4162
4163 27
        $this->parseCatchExceptionClass($catch);
4164
4165 27
        $this->consumeComments();
4166 27
        $this->parseCatchVariable($catch);
4167
4168 27
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
4169
4170 27
        $catch->addChild($this->parseRegularScope());
4171
4172 27
        return $this->setNodePositionsAndReturn($catch);
4173
    }
4174
4175
    /**
4176
     * This method parses assigned variable in catch statement.
4177
     *
4178
     * @param ASTCatchStatement $stmt The owning catch statement.
4179
     */
4180 27
    private function parseCatchVariable(ASTCatchStatement $stmt): void
4181
    {
4182 27
        if ($this->tokenizer->peek() !== Tokens::T_VARIABLE) {
4183 1
            return;
4184
        }
4185
4186 26
        $stmt->addChild($this->parseVariable());
4187
4188 26
        $this->consumeComments();
4189
    }
4190
4191
    /**
4192
     * This method parses class references in catch statement.
4193
     *
4194
     * @param ASTCatchStatement $stmt The owning catch statement.
4195
     */
4196 27
    private function parseCatchExceptionClass(ASTCatchStatement $stmt): void
4197
    {
4198
        do {
4199 27
            $repeat = false;
4200 27
            $stmt->addChild(
4201 27
                $this->builder->buildAstClassOrInterfaceReference(
4202 27
                    $this->parseQualifiedName()
4203 27
                )
4204 27
            );
4205
4206 27
            if (Tokens::T_BITWISE_OR === $this->tokenizer->peek()) {
4207 1
                $this->consumeToken(Tokens::T_BITWISE_OR);
4208 1
                $repeat = true;
4209
            }
4210 27
        } while ($repeat);
4211
    }
4212
4213
    /**
4214
     * This method parses a finally-statement.
4215
     *
4216
     * @return ASTFinallyStatement
4217
     * @since 2.0.0
4218
     */
4219 9
    private function parseFinallyStatement()
4220
    {
4221 9
        $this->tokenStack->push();
4222 9
        $this->consumeComments();
4223
4224 9
        $token = $this->consumeToken(Tokens::T_FINALLY);
0 ignored issues
show
Unused Code introduced by
The assignment to $token is dead and can be removed.
Loading history...
4225
4226 9
        $finally = $this->builder->buildAstFinallyStatement();
4227 9
        $finally->addChild($this->parseRegularScope());
4228
4229 9
        return $this->setNodePositionsAndReturn($finally);
4230
    }
4231
4232
    /**
4233
     * This method parses a single if-statement node.
4234
     *
4235
     * @return ASTIfStatement
4236
     * @since 0.9.8
4237
     */
4238 108
    private function parseIfStatement()
4239
    {
4240 108
        $this->tokenStack->push();
4241 108
        $token = $this->consumeToken(Tokens::T_IF);
4242
4243 108
        $stmt = $this->builder->buildAstIfStatement($token->image);
4244 108
        $stmt->addChild($this->parseParenthesisExpression());
4245
4246 108
        $this->parseStatementBody($stmt);
4247 107
        $this->parseOptionalElseOrElseIfStatement($stmt);
4248
4249 107
        return $this->setNodePositionsAndReturn($stmt);
4250
    }
4251
4252
    /**
4253
     * This method parses a single elseif-statement node.
4254
     *
4255
     * @return ASTElseIfStatement
4256
     * @since 0.9.8
4257
     */
4258 23
    private function parseElseIfStatement()
4259
    {
4260 23
        $this->tokenStack->push();
4261 23
        $token = $this->consumeToken(Tokens::T_ELSEIF);
4262
4263 23
        $stmt = $this->builder->buildAstElseIfStatement($token->image);
4264 23
        $stmt->addChild($this->parseParenthesisExpression());
4265
4266 23
        $this->parseStatementBody($stmt);
4267 23
        $this->parseOptionalElseOrElseIfStatement($stmt);
4268
4269 23
        return $this->setNodePositionsAndReturn($stmt);
4270
    }
4271
4272
    /**
4273
     * This method parses an optional else-, else+if- or elseif-statement.
4274
     *
4275
     * @template T of ASTStatement
4276
     * @param T $stmt The owning if/elseif statement.
4277
     * @return T
4278
     * @since 0.9.12
4279
     */
4280 107
    private function parseOptionalElseOrElseIfStatement(ASTStatement $stmt): ASTStatement
4281
    {
4282 107
        $this->consumeComments();
4283
4284 107
        switch ($this->tokenizer->peek()) {
4285
            case Tokens::T_ELSE:
4286 24
                $this->consumeToken(Tokens::T_ELSE);
4287 24
                $this->consumeComments();
4288 24
                if ($this->tokenizer->peek() === Tokens::T_IF) {
4289 6
                    $stmt->addChild($this->parseIfStatement());
4290
                } else {
4291 18
                    $this->parseStatementBody($stmt);
4292
                }
4293
4294 24
                break;
4295
4296
            case Tokens::T_ELSEIF:
4297 23
                $stmt->addChild($this->parseElseIfStatement());
4298
4299 23
                break;
4300
        }
4301
4302 107
        return $stmt;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $stmt returns the type PDepend\Source\AST\ASTStatement which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
4303
    }
4304
4305
    /**
4306
     * This method parses a single for-statement node.
4307
     *
4308
     * @return ASTForStatement
4309
     * @throws ParserException
4310
     * @since 0.9.8
4311
     */
4312 50
    private function parseForStatement()
4313
    {
4314 50
        $this->tokenStack->push();
4315 50
        $token = $this->consumeToken(Tokens::T_FOR);
4316
4317 50
        $this->consumeComments();
4318 50
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
4319
4320 50
        $stmt = $this->builder->buildAstForStatement($token->image);
4321
4322 50
        if (($init = $this->parseForInit()) !== null) {
4323 46
            $stmt->addChild($init);
4324
        }
4325 50
        $this->consumeToken(Tokens::T_SEMICOLON);
4326
4327 50
        if (($expr = $this->parseForExpression()) !== null) {
4328 47
            $stmt->addChild($expr);
4329
        }
4330 50
        $this->consumeToken(Tokens::T_SEMICOLON);
4331
4332 50
        if (($update = $this->parseForUpdate()) !== null) {
4333 48
            $stmt->addChild($update);
4334
        }
4335 50
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
4336
4337 50
        return $this->setNodePositionsAndReturn($this->parseStatementBody($stmt));
4338
    }
4339
4340
    /**
4341
     * Parses the init part of a for-statement.
4342
     *
4343
     * <code>
4344
     *      ------------------------
4345
     * for ($x = 0, $y = 23, $z = 42; $x < $y; ++$x) {}
4346
     *      ------------------------
4347
     * </code>
4348
     *
4349
     * @return ASTForInit|null
4350
     * @since 0.9.8
4351
     */
4352 50
    private function parseForInit()
4353
    {
4354 50
        $this->consumeComments();
4355
4356 50
        if (Tokens::T_SEMICOLON === $this->tokenizer->peek()) {
4357 4
            return null;
4358
        }
4359
4360 46
        $this->tokenStack->push();
4361
4362 46
        $init = $this->builder->buildAstForInit();
4363 46
        $this->parseExpressionList($init);
4364
4365 46
        return $this->setNodePositionsAndReturn($init);
4366
    }
4367
4368
    /**
4369
     * Parses the expression part of a for-statement.
4370
     *
4371
     * @return ASTNode|null
4372
     * @throws ParserException
4373
     * @since 0.9.12
4374
     */
4375 50
    private function parseForExpression()
4376
    {
4377 50
        return $this->parseOptionalExpression();
4378
    }
4379
4380
    /**
4381
     * Parses the update part of a for-statement.
4382
     *
4383
     * <code>
4384
     *                                        -------------------------------
4385
     * for ($x = 0, $y = 23, $z = 42; $x < $y; ++$x, $y = $x + 1, $z = $x + 2) {}
4386
     *                                        -------------------------------
4387
     * </code>
4388
     *
4389
     * @return ASTForUpdate|null
4390
     * @since 0.9.12
4391
     */
4392 50
    private function parseForUpdate()
4393
    {
4394 50
        $this->consumeComments();
4395 50
        if (Tokens::T_PARENTHESIS_CLOSE === $this->tokenizer->peek()) {
4396 2
            return null;
4397
        }
4398
4399 48
        $this->tokenStack->push();
4400
4401 48
        $update = $this->builder->buildAstForUpdate();
4402 48
        $this->parseExpressionList($update);
4403
4404 48
        return $this->setNodePositionsAndReturn($update);
4405
    }
4406
4407
    /**
4408
     * This methods return true if the token matches a list opening in the current PHP version level.
4409
     *
4410
     * @param int $tokenType
4411
     * @return bool
4412
     * @since 2.6.0
4413
     */
4414 85
    private function isListUnpacking($tokenType = null)
4415
    {
4416 85
        return in_array($tokenType ?: $this->tokenizer->peek(), [Tokens::T_LIST, Tokens::T_SQUARED_BRACKET_OPEN], true);
4417
    }
4418
4419
    /**
4420
     * Get the parsed list of a foreach statement children.
4421
     *
4422
     * @return ASTNode[]
4423
     */
4424 57
    private function parseForeachChildren()
4425
    {
4426 57
        if ($this->tokenizer->peek() === Tokens::T_BITWISE_AND) {
4427 4
            return [$this->parseVariableOrMemberByReference()];
4428
        }
4429
4430 53
        if ($this->isListUnpacking()) {
4431 4
            return [$this->parseListExpression()];
4432
        }
4433
4434 49
        $children = [
4435 49
            $this->parseVariableOrConstantOrPrimaryPrefix(),
4436 49
        ];
4437
4438 49
        if ($this->tokenizer->peek() === Tokens::T_DOUBLE_ARROW) {
4439 12
            $this->consumeToken(Tokens::T_DOUBLE_ARROW);
4440
4441 12
            $children[] = $this->isListUnpacking()
4442 2
                ? $this->parseListExpression()
4443 10
                : $this->parseVariableOrMemberOptionalByReference();
4444
        }
4445
4446 49
        return $children;
4447
    }
4448
4449
    /**
4450
     * This method parses a single foreach-statement node.
4451
     *
4452
     * @return ASTForeachStatement
4453
     * @since 0.9.8
4454
     */
4455 57
    private function parseForeachStatement()
4456
    {
4457 57
        $this->tokenStack->push();
4458 57
        $token = $this->consumeToken(Tokens::T_FOREACH);
4459
4460 57
        $foreach = $this->builder->buildAstForeachStatement($token->image);
4461
4462 57
        $this->consumeComments();
4463 57
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
4464
4465 57
        $foreach->addChild($this->parseExpression());
4466
4467 57
        $this->consumeToken(Tokens::T_AS);
4468 57
        $this->consumeComments();
4469
4470 57
        foreach ($this->parseForeachChildren() as $child) {
4471 57
            $foreach->addChild($child);
4472
        }
4473
4474 57
        $this->consumeComments();
4475 57
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
4476
4477 56
        return $this->setNodePositionsAndReturn(
4478 56
            $this->parseStatementBody($foreach),
4479 56
        );
4480
    }
4481
4482
    /**
4483
     * This method parses a single while-statement node.
4484
     *
4485
     * @return ASTWhileStatement
4486
     * @since 0.9.8
4487
     */
4488 6
    private function parseWhileStatement()
4489
    {
4490 6
        $this->tokenStack->push();
4491 6
        $token = $this->consumeToken(Tokens::T_WHILE);
4492
4493 6
        $stmt = $this->builder->buildAstWhileStatement($token->image);
4494 6
        $stmt->addChild($this->parseParenthesisExpression());
4495
4496 6
        return $this->setNodePositionsAndReturn(
4497 6
            $this->parseStatementBody($stmt),
4498 6
        );
4499
    }
4500
4501
    /**
4502
     * This method parses a do/while-statement.
4503
     *
4504
     * @return ASTDoWhileStatement
4505
     * @since 0.9.12
4506
     */
4507 8
    private function parseDoWhileStatement()
4508
    {
4509 8
        $this->tokenStack->push();
4510 8
        $token = $this->consumeToken(Tokens::T_DO);
4511
4512 8
        $stmt = $this->builder->buildAstDoWhileStatement($token->image);
4513 8
        $stmt = $this->parseStatementBody($stmt);
4514
4515 8
        $this->consumeComments();
4516 8
        $this->consumeToken(Tokens::T_WHILE);
4517
4518 8
        $stmt->addChild($this->parseParenthesisExpression());
4519
4520 8
        $this->parseStatementTermination();
4521
4522 8
        return $this->setNodePositionsAndReturn($stmt);
4523
    }
4524
4525
    /**
4526
     * This method parses a declare-statement.
4527
     *
4528
     * <code>
4529
     * -------------------------------
4530
     * declare(encoding='ISO-8859-1');
4531
     * -------------------------------
4532
     *
4533
     * -------------------
4534
     * declare(ticks=42) {
4535
     *     // ...
4536
     * }
4537
     * -
4538
     *
4539
     * ------------------
4540
     * declare(ticks=42):
4541
     *     // ...
4542
     * enddeclare;
4543
     * -----------
4544
     * </code>
4545
     *
4546
     * @return ASTDeclareStatement
4547
     * @since 0.10.0
4548
     */
4549 14
    private function parseDeclareStatement()
4550
    {
4551 14
        $this->tokenStack->push();
4552 14
        $this->consumeToken(Tokens::T_DECLARE);
4553
4554 14
        $stmt = $this->builder->buildAstDeclareStatement();
4555 14
        $stmt = $this->parseDeclareList($stmt);
4556 14
        $stmt = $this->parseStatementBody($stmt);
4557
4558 14
        return $this->setNodePositionsAndReturn($stmt);
4559
    }
4560
4561
    /**
4562
     * This method parses a list of declare values. A declare list value always
4563
     * consists of a string token and a static scalar.
4564
     *
4565
     * @template T of ASTDeclareStatement
4566
     * @param T $stmt The declare statement that is the owner of this list.
4567
     * @return T
4568
     * @since 0.10.0
4569
     */
4570 14
    private function parseDeclareList(ASTDeclareStatement $stmt): ASTDeclareStatement
4571
    {
4572 14
        $this->consumeComments();
4573 14
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
4574
4575 14
        while (true) {
4576 14
            $this->consumeComments();
4577 14
            $name = $this->consumeToken(Tokens::T_STRING)->image;
4578
4579 14
            $this->consumeComments();
4580 14
            $this->consumeToken(Tokens::T_EQUAL);
4581
4582 14
            $this->consumeComments();
4583 14
            $value = $this->parseStaticValue();
4584
4585 14
            if ($value) {
4586 14
                $stmt->addValue($name, $value);
4587
            }
4588
4589 14
            $this->consumeComments();
4590 14
            if ($this->tokenizer->peek() === Tokens::T_COMMA) {
4591 1
                $this->consumeToken(Tokens::T_COMMA);
4592
4593 1
                continue;
4594
            }
4595
4596 14
            break;
4597
        }
4598
4599 14
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
4600
4601 14
        return $stmt;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $stmt returns the type PDepend\Source\AST\ASTDeclareStatement which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
4602
    }
4603
4604
    /**
4605
     * This method builds a return statement from a given token.
4606
     *
4607
     * @return ASTReturnStatement
4608
     * @since 2.7.0
4609
     */
4610 358
    private function buildReturnStatement(Token $token)
4611
    {
4612 358
        $stmt = $this->builder->buildAstReturnStatement($token->image);
4613
4614 358
        if (($expr = $this->parseOptionalExpression()) !== null) {
4615 353
            $stmt->addChild($expr);
4616
        }
4617
4618 357
        return $stmt;
4619
    }
4620
4621
    /**
4622
     * This method parses a single return-statement node.
4623
     *
4624
     * @return ASTReturnStatement
4625
     * @since 0.9.12
4626
     */
4627 358
    private function parseReturnStatement()
4628
    {
4629 358
        $this->tokenStack->push();
4630
4631 358
        $stmt = $this->buildReturnStatement(
4632 358
            $this->consumeToken(Tokens::T_RETURN),
4633 358
        );
4634
4635 357
        $this->parseStatementTermination();
4636
4637 356
        return $this->setNodePositionsAndReturn($stmt);
4638
    }
4639
4640
    /**
4641
     * This method parses a break-statement node.
4642
     *
4643
     * @return ASTBreakStatement
4644
     * @since 0.9.12
4645
     */
4646 21
    private function parseBreakStatement()
4647
    {
4648 21
        $this->tokenStack->push();
4649 21
        $token = $this->consumeToken(Tokens::T_BREAK);
4650
4651 21
        $stmt = $this->builder->buildAstBreakStatement($token->image);
4652 21
        if (($expr = $this->parseOptionalExpression()) !== null) {
4653 5
            $stmt->addChild($expr);
4654
        }
4655 21
        $this->parseStatementTermination();
4656
4657 21
        return $this->setNodePositionsAndReturn($stmt);
4658
    }
4659
4660
    /**
4661
     * This method parses a continue-statement node.
4662
     *
4663
     * @return ASTContinueStatement
4664
     * @since 0.9.12
4665
     */
4666 4
    private function parseContinueStatement()
4667
    {
4668 4
        $this->tokenStack->push();
4669 4
        $token = $this->consumeToken(Tokens::T_CONTINUE);
4670
4671 4
        $stmt = $this->builder->buildAstContinueStatement($token->image);
4672 4
        if (($expr = $this->parseOptionalExpression()) !== null) {
4673 4
            $stmt->addChild($expr);
4674
        }
4675 4
        $this->parseStatementTermination();
4676
4677 4
        return $this->setNodePositionsAndReturn($stmt);
4678
    }
4679
4680
    /**
4681
     * This method parses a echo-statement node.
4682
     *
4683
     * @return ASTEchoStatement
4684
     * @since 0.9.12
4685
     */
4686 105
    private function parseEchoStatement()
4687
    {
4688 105
        $this->tokenStack->push();
4689 105
        $token = $this->consumeToken(Tokens::T_ECHO);
4690
4691 105
        $stmt = $this->parseExpressionList(
4692 105
            $this->builder->buildAstEchoStatement($token->image),
4693 105
        );
4694
4695 105
        $this->parseStatementTermination();
4696
4697 105
        return $this->setNodePositionsAndReturn($stmt);
4698
    }
4699
4700
    /**
4701
     * Parses a simple parenthesis expression or a direct object access, which
4702
     * was introduced with PHP 5.4.0:
4703
     *
4704
     * <code>
4705
     * (new MyClass())->bar();
4706
     * </code>
4707
     *
4708
     * @return ASTNode
4709
     * @since 1.0.0
4710
     */
4711 77
    private function parseParenthesisExpressionOrPrimaryPrefix()
4712
    {
4713 77
        return $this->parseParenthesisExpressionOrPrimaryPrefixForVersion(
4714 77
            $this->parseParenthesisExpression(),
4715 77
        );
4716
    }
4717
4718 77
    private function parseParenthesisExpressionOrPrimaryPrefixForVersion(ASTExpression $expr): AbstractASTNode
4719
    {
4720 77
        $this->consumeComments();
4721
4722 77
        if (Tokens::T_DOUBLE_COLON === $this->tokenizer->peek()) {
4723 1
            return $this->parseStaticMemberPrimaryPrefix($expr->getChild(0));
4724
        }
4725
4726 77
        if ($this->isNextTokenObjectOperator()) {
4727 4
            $node = count($expr->getChildren()) === 0 ? $expr : $expr->getChild(0);
4728
4729 4
            return $this->parseMemberPrimaryPrefix($node);
4730
        }
4731
4732 73
        return $expr;
4733
    }
4734
4735
    /**
4736
     * @return bool
4737
     */
4738 592
    private function isNextTokenObjectOperator()
4739
    {
4740 592
        return in_array($this->tokenizer->peek(), [
4741 592
            Tokens::T_OBJECT_OPERATOR,
4742 592
            Tokens::T_NULLSAFE_OBJECT_OPERATOR,
4743 592
        ], true);
4744
    }
4745
4746
    /**
4747
     * @return Token
4748
     * @throws TokenStreamEndException
4749
     * @throws UnexpectedTokenException
4750
     */
4751 155
    private function consumeObjectOperatorToken()
4752
    {
4753 155
        return $this->consumeToken(
4754 155
            $this->tokenizer->peek() === Tokens::T_NULLSAFE_OBJECT_OPERATOR
4755 2
                ? Tokens::T_NULLSAFE_OBJECT_OPERATOR
4756 155
                : Tokens::T_OBJECT_OPERATOR
4757 155
        );
4758
    }
4759
4760
    /**
4761
     * Parses any expression that is surrounded by an opening and a closing
4762
     * parenthesis
4763
     *
4764
     * @return ASTExpression
4765
     * @since 0.9.8
4766
     */
4767 217
    private function parseParenthesisExpression()
4768
    {
4769 217
        $this->tokenStack->push();
4770 217
        $this->consumeComments();
4771
4772 217
        $expr = $this->builder->buildAstExpression();
4773 217
        $expr = $this->parseBraceExpression(
4774 217
            $expr,
4775 217
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN),
4776 217
            Tokens::T_PARENTHESIS_CLOSE,
4777 217
            Tokens::T_COMMA,
4778 217
        );
4779
4780 217
        while ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN) {
4781 2
            $function = $this->builder->buildAstFunctionPostfix($expr->getImage());
4782 2
            $function->addChild($expr);
4783 2
            $function->addChild($this->parseArguments());
4784 2
            $expr = $function;
4785
        }
4786
4787 217
        return $this->setNodePositionsAndReturn($expr);
4788
    }
4789
4790
    /**
4791
     * This method parses a member primary prefix expression or a function
4792
     * postfix expression node.
4793
     *
4794
     * A member primary prefix can be a method call:
4795
     *
4796
     * <code>
4797
     * $object->foo();
4798
     *
4799
     * clazz::foo();
4800
     * </code>
4801
     *
4802
     * a property access:
4803
     *
4804
     * <code>
4805
     * $object->foo;
4806
     *
4807
     * clazz::$foo;
4808
     * </code>
4809
     *
4810
     * or a class constant access:
4811
     *
4812
     * <code>
4813
     * clazz::FOO;
4814
     * </code>
4815
     *
4816
     * A function postfix represents any kind of function call:
4817
     *
4818
     * <code>
4819
     * $function();
4820
     *
4821
     * func();
4822
     * </code>
4823
     *
4824
     * @return ASTNode
4825
     * @throws ParserException
4826
     * @since 0.9.6
4827
     */
4828 315
    private function parseMemberPrefixOrFunctionPostfix()
4829
    {
4830 315
        $this->tokenStack->push();
4831 315
        $this->tokenStack->push();
4832
4833 315
        $qName = $this->parseQualifiedName();
4834
4835
        // Remove comments
4836 315
        $this->consumeComments();
4837
4838
        // Get next token type
4839 315
        $tokenType = $this->tokenizer->peek();
4840
4841
        switch ($tokenType) {
4842
            case Tokens::T_DOUBLE_COLON:
4843 85
                $node = $this->builder->buildAstClassOrInterfaceReference($qName);
4844 85
                $node = $this->setNodePositionsAndReturn($node);
4845 85
                $node = $this->parseStaticMemberPrimaryPrefix($node);
4846
4847 85
                break;
4848
4849
            case Tokens::T_PARENTHESIS_OPEN:
4850 218
                $node = $this->builder->buildAstIdentifier($qName);
4851 218
                $node = $this->setNodePositionsAndReturn($node);
4852 218
                $node = $this->parseFunctionPostfix($node);
4853
4854 215
                break;
4855
4856
            default:
4857 66
                $node = $this->builder->buildAstConstant($qName);
4858 66
                $node = $this->setNodePositionsAndReturn($node);
4859
4860 66
                break;
4861
        }
4862
4863 313
        return $this->setNodePositionsAndReturn($node);
4864
    }
4865
4866
    /**
4867
     * This method will parse an optional function postfix.
4868
     *
4869
     * If the next available token is an opening parenthesis, this method will
4870
     * wrap the given <b>$node</b> with a {@link ASTFunctionPostfix}
4871
     * node.
4872
     *
4873
     * @param AbstractASTNode $node The previously parsed node.
4874
     * @return AbstractASTNode The original input node or this node wrapped with a function postfix instance.
4875
     * @since 1.0.0
4876
     */
4877 3
    private function parseOptionalFunctionPostfix(ASTNode $node): AbstractASTNode
4878
    {
4879 3
        $this->consumeComments();
4880 3
        if (Tokens::T_PARENTHESIS_OPEN === $this->tokenizer->peek()) {
4881 3
            return $this->parseFunctionPostfix($node);
4882
        }
4883
4884
        return $node;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node returns the type PDepend\Source\AST\ASTNode which includes types incompatible with the type-hinted return PDepend\Source\AST\AbstractASTNode.
Loading history...
4885
    }
4886
4887
    /**
4888
     * This method parses a function postfix expression. An object of type
4889
     * {@link ASTFunctionPostfix} represents any valid php
4890
     * function call.
4891
     *
4892
     * This method will delegate the call to another method that returns a
4893
     * member primary prefix object when the function postfix expression is
4894
     * followed by an object operator.
4895
     *
4896
     * @param ASTNode $node This node represents the function identifier. An identifier can be a static string,
4897
     *                      a variable, a compound variable or any other valid php function identifier.
4898
     * @return AbstractASTNode
4899
     * @throws ParserException
4900
     * @since 0.9.6
4901
     */
4902 242
    private function parseFunctionPostfix(ASTNode $node)
4903
    {
4904 242
        $image = $this->extractPostfixImage($node);
4905
4906 242
        $function = $this->builder->buildAstFunctionPostfix($image);
4907 242
        $function->addChild($node);
4908
4909 242
        if (!($node instanceof ASTIdentifier) || $node->getImageWithoutNamespace() !== 'match') {
4910 237
            $function->addChild($this->parseArguments());
4911
4912 235
            return $this->parseOptionalMemberPrimaryPrefix(
4913 235
                $this->parseOptionalIndexExpression($function),
4914 235
            );
4915
        }
4916
4917 5
        $this->consumeComments();
4918
4919 5
        $this->tokenStack->push();
4920
4921 5
        $function->addChild(
4922 5
            $this->parseArgumentsParenthesesContent(
4923 5
                $this->builder->buildAstMatchArgument()
4924 5
            )
4925 5
        );
4926
4927 4
        $this->consumeComments();
4928 4
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
4929
4930 4
        $matchBlock = $this->builder->buildAstMatchBlock();
4931
4932 4
        while ($this->tokenizer->peek() !== Tokens::T_CURLY_BRACE_CLOSE) {
4933 4
            $matchBlock->addChild($this->parseMatchEntry());
4934
4935 4
            $this->consumeComments();
4936
4937 4
            if ($this->tokenizer->peek() === Tokens::T_COMMA) {
4938 4
                $this->consumeToken(Tokens::T_COMMA);
4939 4
                $this->consumeComments();
4940
            }
4941
        }
4942
4943 4
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
4944
4945 4
        $function->addChild($matchBlock);
4946
4947 4
        return $function;
4948
    }
4949
4950
    /**
4951
     * This method parses a PHP version specific identifier for method and
4952
     * property postfix expressions.
4953
     *
4954
     * @return ASTNode
4955
     * @since 1.0.0
4956
     */
4957 54
    private function parsePostfixIdentifier()
4958
    {
4959 54
        $tokenType = $this->tokenizer->peek();
4960 54
        if ($this->isConstantName($tokenType)) {
4961 3
            return $this->parseOptionalIndexExpression($this->parseLiteral());
4962
        }
4963
4964 51
        $node = match ($tokenType) {
4965 51
            Tokens::T_CURLY_BRACE_OPEN => $this->parseCompoundExpression(),
4966 51
            Tokens::T_STRING => $this->parseLiteral(),
4967 51
            default => $this->parseCompoundVariableOrVariableVariableOrVariable(),
4968 51
        };
4969
4970 51
        return $this->parseOptionalIndexExpression($node);
4971
    }
4972
4973
    /**
4974
     * This method parses an optional member primary expression. It will parse
4975
     * the primary expression when an object operator can be found at the actual
4976
     * token stream position. Otherwise this method simply returns the input
4977
     * {@link ASTNode} instance.
4978
     *
4979
     * @param AbstractASTNode $node This node represents primary prefix
4980
     *                              left expression. It will be the first child of the parsed member
4981
     *                              primary expression.
4982
     * @throws ParserException
4983
     * @since 0.9.6
4984
     */
4985 551
    private function parseOptionalMemberPrimaryPrefix(AbstractASTNode $node): AbstractASTNode
4986
    {
4987 551
        $this->consumeComments();
4988
4989 551
        if (Tokens::T_DOUBLE_COLON === $this->tokenizer->peek()) {
4990 1
            return $this->parseStaticMemberPrimaryPrefix($node);
4991
        }
4992
4993 551
        if ($this->isNextTokenObjectOperator()) {
4994 29
            return $this->parseMemberPrimaryPrefix($node);
4995
        }
4996
4997 551
        return $node;
4998
    }
4999
5000
    /**
5001
     * This method parses a dynamic or object bound member primary expression.
5002
     * A member primary prefix can be a method call:
5003
     *
5004
     * <code>
5005
     * $object->foo();
5006
     * </code>
5007
     *
5008
     * or a property access:
5009
     *
5010
     * <code>
5011
     * $object->foo;
5012
     * </code>
5013
     *
5014
     * @param ASTNode $node The left node in the parsed member primary expression.
5015
     * @return AbstractASTNode
5016
     * @throws ParserException
5017
     * @since 0.9.6
5018
     */
5019 155
    private function parseMemberPrimaryPrefix(ASTNode $node)
5020
    {
5021
        // Consume double colon and optional comments
5022 155
        $token = $this->consumeObjectOperatorToken();
5023
5024 155
        $prefix = $this->builder->buildAstMemberPrimaryPrefix($token->image);
5025 155
        $prefix->addChild($node);
5026
5027 155
        $this->consumeComments();
5028 155
        $tokenType = $this->tokenizer->peek();
5029
5030
        switch ($tokenType) {
5031 155
            case $this->isMethodName($tokenType):
5032 132
                $child = $this->parseIdentifier($tokenType);
5033 132
                $child = $this->parseOptionalIndexExpression($child);
5034
5035
                // TODO: Move this in a separate method
5036 132
                if ($child instanceof ASTIndexExpression) {
0 ignored issues
show
introduced by
$child is never a sub-type of PDepend\Source\AST\ASTIndexExpression.
Loading history...
5037 17
                    $this->consumeComments();
5038 17
                    if (Tokens::T_PARENTHESIS_OPEN === $this->tokenizer->peek()) {
5039 3
                        $prefix->addChild($this->parsePropertyPostfix($child));
5040
5041 3
                        return $this->parseOptionalFunctionPostfix($prefix);
5042
                    }
5043
                }
5044
5045 129
                break;
5046
5047 23
            case Tokens::T_CURLY_BRACE_OPEN:
5048 6
                $child = $this->parseCompoundExpression();
5049
5050 6
                break;
5051
5052
            default:
5053 17
                $child = $this->parseCompoundVariableOrVariableVariableOrVariable();
5054
5055 17
                break;
5056
        }
5057
5058 152
        $prefix->addChild(
5059 152
            $this->parseMethodOrPropertyPostfix(
5060 152
                $this->parseOptionalIndexExpression($child),
5061 152
            ),
5062 152
        );
5063
5064 152
        return $this->parseOptionalMemberPrimaryPrefix(
5065 152
            $this->parseOptionalIndexExpression($prefix),
5066 152
        );
5067
    }
5068
5069
    /**
5070
     * This method parses an optional member primary expression. It will parse
5071
     * the primary expression when a double colon operator can be found at the
5072
     * actual token stream position. Otherwise this method simply returns the
5073
     * input {@link ASTNode} instance.
5074
     *
5075
     * @param AbstractASTNode $node This node represents primary prefix left expression. It will
5076
     *                              be the first child of the parsed member primary expression.
5077
     * @return AbstractASTNode
5078
     * @throws ParserException
5079
     * @since 1.0.1
5080
     */
5081 119
    private function parseOptionalStaticMemberPrimaryPrefix(AbstractASTNode $node)
5082
    {
5083 119
        $this->consumeComments();
5084
5085 119
        if ($this->tokenizer->peek() === Tokens::T_DOUBLE_COLON) {
5086 3
            return $this->parseStaticMemberPrimaryPrefix($node);
5087
        }
5088
5089 116
        return $node;
5090
    }
5091
5092
    /**
5093
     * This method parses a static member primary expression. The given node
5094
     * contains the used static class or interface identifier. A static member
5095
     * primary prefix can represent the following code expressions:
5096
     *
5097
     * A static method class:
5098
     *
5099
     * <code>
5100
     * Foo::bar();
5101
     * </code>
5102
     *
5103
     * a static property access:
5104
     *
5105
     * <code>
5106
     * Foo::$bar;
5107
     * </code>
5108
     *
5109
     * or a static constant access:
5110
     *
5111
     * <code>
5112
     * Foo::BAR;
5113
     * </code>
5114
     *
5115
     * @param ASTNode $node The left node in the parsed member primary expression.
5116
     * @return AbstractASTNode
5117
     * @throws ParserException
5118
     * @since 0.9.6
5119
     */
5120 152
    private function parseStaticMemberPrimaryPrefix(ASTNode $node)
5121
    {
5122 152
        $token = $this->consumeToken(Tokens::T_DOUBLE_COLON);
5123
5124 152
        $prefix = $this->builder->buildAstMemberPrimaryPrefix($token->image);
5125 152
        $prefix->addChild($node);
5126
5127 152
        $this->consumeComments();
5128
5129 152
        $postfix = match ($this->tokenizer->peek()) {
5130 152
            Tokens::T_STRING => $this->parseMethodOrConstantPostfix(),
5131 152
            Tokens::T_CLASS_FQN => $this->parseFullQualifiedClassNamePostfix(),
5132 152
            default => $this->parseMethodOrPropertyPostfix(
5133 152
                $this->parsePostfixIdentifier(),
5134 152
            ),
5135 152
        };
5136
5137 152
        $prefix->addChild($postfix);
5138
5139 152
        return $this->parseOptionalMemberPrimaryPrefix(
5140 152
            $this->parseOptionalIndexExpression($prefix),
5141 152
        );
5142
    }
5143
5144
    /**
5145
     * This method parses a method- or constant-postfix expression. This expression
5146
     * will contain an identifier node as nested child.
5147
     *
5148
     * @return ASTNode
5149
     * @since 0.9.6
5150
     */
5151 82
    private function parseMethodOrConstantPostfix()
5152
    {
5153 82
        $this->tokenStack->push();
5154
5155 82
        $node = $this->parseIdentifier();
5156
5157 82
        $this->consumeComments();
5158 82
        if ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN) {
5159 65
            $postfix = $this->parseMethodPostfix($node);
5160
        } else {
5161 19
            $postfix = $this->builder->buildAstConstantPostfix($node->getImage());
5162 19
            $postfix->addChild($node);
5163
        }
5164
5165 82
        return $this->setNodePositionsAndReturn($postfix);
5166
    }
5167
5168
    /**
5169
     * This method parses a method- or property-postfix expression. This expression
5170
     * will contain the given node as method or property identifier.
5171
     *
5172
     * @param ASTNode $node The identifier for the parsed postfix expression node. This node
5173
     *                      will be the first child of the returned postfix node instance.
5174
     * @return ASTNode
5175
     * @throws ParserException
5176
     * @since 0.9.6
5177
     */
5178 200
    private function parseMethodOrPropertyPostfix(ASTNode $node)
5179
    {
5180
        // Strip optional comments
5181 200
        $this->consumeComments();
5182
5183 200
        $postfix = match ($this->tokenizer->peek()) {
5184 200
            Tokens::T_PARENTHESIS_OPEN => $this->parseMethodPostfix($node),
5185 200
            default => $this->parsePropertyPostfix($node),
5186 200
        };
5187
5188 200
        return $this->parseOptionalMemberPrimaryPrefix($postfix);
5189
    }
5190
5191
    /**
5192
     * Parses/Creates a property postfix node instance.
5193
     *
5194
     * @param ASTNode $node Node that represents the image of the property postfix node.
5195
     * @return ASTPropertyPostfix
5196
     * @since 0.10.2
5197
     */
5198 114
    private function parsePropertyPostfix(ASTNode $node)
5199
    {
5200 114
        $image = $this->extractPostfixImage($node);
5201
5202 114
        $postfix = $this->builder->buildAstPropertyPostfix($image);
5203 114
        $postfix->addChild($node);
5204
5205 114
        $postfix->configureLinesAndColumns(
5206 114
            $node->getStartLine(),
5207 114
            $node->getEndLine(),
5208 114
            $node->getStartColumn(),
5209 114
            $node->getEndColumn(),
5210 114
        );
5211
5212 114
        return $postfix;
5213
    }
5214
5215
    /**
5216
     * Parses a full qualified class name postfix.
5217
     *
5218
     * @return ASTClassFqnPostfix
5219
     * @since 2.0.0
5220
     */
5221 22
    private function parseFullQualifiedClassNamePostfix()
5222
    {
5223 22
        $this->tokenStack->push();
5224
5225 22
        $this->consumeToken(Tokens::T_CLASS_FQN);
5226
5227 22
        return $this->setNodePositionsAndReturn(
5228 22
            $this->builder->buildAstClassFqnPostfix()
5229 22
        );
5230
    }
5231
5232
    /**
5233
     * This method will extract the image/name of the real property/variable
5234
     * that is wrapped by {@link ASTIndexExpression} nodes. If
5235
     * the given node is now wrapped by index expressions, this method will
5236
     * return the image of the entire <b>$node</b>.
5237
     *
5238
     * @param ASTNode $node The context node that may be wrapped by multiple array or string index expressions.
5239
     * @return string
5240
     * @since 1.0.0
5241
     */
5242 448
    private function extractPostfixImage(ASTNode $node)
5243
    {
5244 448
        while ($node instanceof ASTIndexExpression) {
5245 36
            $node = $node->getChild(0);
5246
        }
5247
5248 448
        return $node->getImage();
5249
    }
5250
5251
    /**
5252
     * Parses a method postfix node instance.
5253
     *
5254
     * @param ASTNode $node Node that represents the image of the method postfix node.
5255
     * @return AbstractASTNode
5256
     * @since 1.0.0
5257
     */
5258 138
    private function parseMethodPostfix(ASTNode $node)
5259
    {
5260 138
        $args = $this->parseArguments();
5261 138
        $image = $this->extractPostfixImage($node);
5262
5263 138
        $postfix = $this->builder->buildAstMethodPostfix($image);
5264 138
        $postfix->addChild($node);
5265 138
        $postfix->addChild($args);
5266
5267 138
        $postfix->configureLinesAndColumns(
5268 138
            $node->getStartLine(),
5269 138
            $args->getEndLine(),
5270 138
            $node->getStartColumn(),
5271 138
            $args->getEndColumn(),
5272 138
        );
5273
5274 138
        return $this->parseOptionalMemberPrimaryPrefix($postfix);
5275
    }
5276
5277
    /**
5278
     * This method parses the arguments passed to a function- or method-call.
5279
     *
5280
     * @return ASTArguments
5281
     * @throws ParserException
5282
     * @since 0.9.6
5283
     */
5284 410
    private function parseArguments()
5285
    {
5286 410
        $this->consumeComments();
5287
5288 410
        $this->tokenStack->push();
5289
5290 410
        return $this->parseArgumentsParenthesesContent(
5291 410
            $this->builder->buildAstArguments(),
5292 410
        );
5293
    }
5294
5295
    /**
5296
     * This method parses the tokens after arguments passed to a function- or method-call.
5297
     *
5298
     * @template T of ASTArguments
5299
     * @param T $arguments
5300
     * @return T
5301
     * @throws ParserException
5302
     * @since 0.9.6
5303
     */
5304 411
    private function parseArgumentsParenthesesContent(ASTArguments $arguments): ASTArguments
5305
    {
5306 411
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
5307 411
        $this->consumeComments();
5308
5309 411
        if (Tokens::T_PARENTHESIS_CLOSE !== $this->tokenizer->peek()) {
5310 207
            $arguments = $this->parseArgumentList($arguments);
5311
        }
5312
5313 409
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
5314
5315 408
        return $this->setNodePositionsAndReturn($arguments);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->setNodePos...nsAndReturn($arguments) returns the type PDepend\Source\AST\ASTArguments which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
5316
    }
5317
5318
    /**
5319
     * @template T of ASTArguments
5320
     *
5321
     * @param T $arguments
5322
     * @return T
5323
     */
5324 207
    private function parseArgumentList(ASTArguments $arguments): ASTArguments
5325
    {
5326 207
        $this->consumeComments();
5327
5328
        // peek if there's an ellipsis to determine variadic placeholder
5329 207
        $ellipsis = Tokens::T_ELLIPSIS === $this->tokenizer->peek();
5330
5331 207
        while (true) {
5332 207
            $this->consumeComments();
5333
5334 207
            if (Tokens::T_ELLIPSIS === $this->tokenizer->peek()) {
5335 9
                $this->consumeToken(Tokens::T_ELLIPSIS);
5336
            }
5337
5338 207
            $expr = $this->parseArgumentExpression();
5339
5340 206
            if ($expr instanceof ASTConstant) {
5341 24
                $expr = $this->parseConstantArgument($expr, $arguments);
5342
            }
5343
5344 206
            if (!$expr || !$this->addChildToList($arguments, $expr)) {
5345 205
                break;
5346
            }
5347
        }
5348
5349
        // ellipsis and no further arguments => variadic placeholder foo(...)
5350 205
        if ($ellipsis && count($arguments->getChildren()) === 0) {
5351 8
            $arguments->setVariadicPlaceholder();
5352
        }
5353
5354 205
        return $arguments;
5355
    }
5356
5357
    /**
5358
     * This method implements the parsing for various expression types like
5359
     * variables, object/static method. All these expressions are valid in
5360
     * several php language constructs like, isset, empty, unset etc.
5361
     *
5362
     * @return ASTNode
5363
     * @since 0.9.12
5364
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
5365 728
    private function parseVariableOrConstantOrPrimaryPrefix()
5366
    {
5367 728
        $this->consumeComments();
5368
5369 728
        return match ($this->tokenizer->peek()) {
5370 728
            Tokens::T_DOLLAR,
5371 728
            Tokens::T_VARIABLE => $this->parseVariableOrFunctionPostfixOrMemberPrimaryPrefix(),
5372 728
            Tokens::T_SELF => $this->parseConstantOrSelfMemberPrimaryPrefix(),
5373 728
            Tokens::T_PARENT => $this->parseConstantOrParentMemberPrimaryPrefix(),
5374 728
            Tokens::T_STATIC => $this->parseStaticVariableDeclarationOrMemberPrimaryPrefix(),
5375 728
            Tokens::T_STRING,
5376 728
            Tokens::T_BACKSLASH,
5377 728
            Tokens::T_NAMESPACE => $this->parseMemberPrefixOrFunctionPostfix(),
5378 728
            Tokens::T_PARENTHESIS_OPEN => $this->parseParenthesisExpression(),
5379 728
            default => throw $this->getUnexpectedNextTokenException(),
5380 728
        };
5381
    }
5382
5383
    /**
5384
     * This method parses any type of variable, function postfix expressions or
5385
     * any kind of member primary prefix.
5386
     *
5387
     * This method expects that the actual token represents any kind of valid
5388
     * php variable: simple variable, compound variable or variable variable.
5389
     *
5390
     * It will parse a function postfix or member primary expression when this
5391
     * variable is followed by an object operator, double colon or opening
5392
     * parenthesis.
5393
     *
5394
     * @return AbstractASTNode
5395
     * @throws ParserException
5396
     * @since 0.9.6
5397
     */
5398 536
    private function parseVariableOrFunctionPostfixOrMemberPrimaryPrefix()
5399
    {
5400 536
        $this->tokenStack->push();
5401
5402 536
        $variable = $this->parseCompoundVariableOrVariableVariableOrVariable();
5403 536
        $variable = $this->parseOptionalIndexExpression($variable);
5404
5405 536
        $this->consumeComments();
5406
5407 536
        $result = match ($this->tokenizer->peek()) {
5408 536
            Tokens::T_DOUBLE_COLON => $this->parseStaticMemberPrimaryPrefix($variable),
5409 536
            Tokens::T_NULLSAFE_OBJECT_OPERATOR,
5410 536
            Tokens::T_OBJECT_OPERATOR => $this->parseMemberPrimaryPrefix($variable),
5411 536
            Tokens::T_PARENTHESIS_OPEN => $this->parseFunctionPostfix($variable),
5412 536
            default => $variable,
5413 536
        };
5414
5415 536
        return $this->setNodePositionsAndReturn($result);
5416
    }
5417
5418
    /**
5419
     * Parses an assingment expression node.
5420
     *
5421
     * @param ASTNode $left The left part of the assignment expression that will be parsed by this method.
5422
     * @return ASTAssignmentExpression
5423
     * @since 0.9.12
5424
     */
5425 189
    private function parseAssignmentExpression(ASTNode $left)
5426
    {
5427 189
        $token = $this->consumeToken($this->tokenizer->peek());
5428
5429 189
        $node = $this->builder->buildAstAssignmentExpression($token->image);
5430 189
        $node->addChild($left);
5431
5432
        // TODO: Change this into a mandatory expression in later versions
5433 189
        if (($expr = $this->parseOptionalExpression()) !== null) {
5434 187
            $node->addChild($expr);
5435
        } else {
5436
            $expr = $left;
5437
        }
5438
5439 187
        $node->configureLinesAndColumns(
5440 187
            $left->getStartLine(),
5441 187
            $expr->getEndLine(),
5442 187
            $left->getStartColumn(),
5443 187
            $expr->getEndColumn(),
5444 187
        );
5445
5446 187
        return $node;
5447
    }
5448
5449
    /**
5450
     * This method parses a {@link ASTStaticReference} node.
5451
     *
5452
     * @param Token $token The "static" keyword token.
5453
     * @return ASTStaticReference
5454
     * @throws ParserException
5455
     * @throws InvalidStateException
5456
     * @since 0.9.6
5457
     */
5458 13
    private function parseStaticReference(Token $token)
5459
    {
5460
        // Strip optional comments
5461 13
        $this->consumeComments();
5462
5463 13
        if (!isset($this->classOrInterface)) {
5464 2
            throw new InvalidStateException(
5465 2
                $token->startLine,
5466 2
                (string) $this->compilationUnit,
5467 2
                'The keyword "static" was used outside of a class/method scope.',
5468 2
            );
5469
        }
5470
5471 11
        $ref = $this->builder->buildAstStaticReference($this->classOrInterface);
0 ignored issues
show
Bug introduced by
It seems like $this->classOrInterface can also be of type null; however, parameter $owner of PDepend\Source\Language\...ildAstStaticReference() does only seem to accept PDepend\Source\AST\AbstractASTClassOrInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

5471
        $ref = $this->builder->buildAstStaticReference(/** @scrutinizer ignore-type */ $this->classOrInterface);
Loading history...
5472 11
        $ref->configureLinesAndColumns(
5473 11
            $token->startLine,
5474 11
            $token->endLine,
5475 11
            $token->startColumn,
5476 11
            $token->endColumn,
5477 11
        );
5478
5479 11
        return $ref;
5480
    }
5481
5482
    /**
5483
     * This method parses a {@link ASTSelfReference} node.
5484
     *
5485
     * @param Token $token The "self" keyword token.
5486
     * @return ASTSelfReference
5487
     * @throws ParserException
5488
     * @throws InvalidStateException
5489
     * @since 0.9.6
5490
     */
5491 36
    private function parseSelfReference(Token $token)
5492
    {
5493 36
        if (!isset($this->classOrInterface)) {
5494 3
            throw new InvalidStateException(
5495 3
                $token->startLine,
5496 3
                (string) $this->compilationUnit,
5497 3
                'The keyword "self" was used outside of a class/method scope.',
5498 3
            );
5499
        }
5500
5501 33
        $ref = $this->builder->buildAstSelfReference($this->classOrInterface);
0 ignored issues
show
Bug introduced by
It seems like $this->classOrInterface can also be of type null; however, parameter $type of PDepend\Source\Language\...buildAstSelfReference() does only seem to accept PDepend\Source\AST\AbstractASTClassOrInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

5501
        $ref = $this->builder->buildAstSelfReference(/** @scrutinizer ignore-type */ $this->classOrInterface);
Loading history...
5502 33
        $ref->configureLinesAndColumns(
5503 33
            $token->startLine,
5504 33
            $token->endLine,
5505 33
            $token->startColumn,
5506 33
            $token->endColumn,
5507 33
        );
5508
5509 33
        return $ref;
5510
    }
5511
5512
    /**
5513
     * Parses a simple PHP constant use and returns a corresponding node.
5514
     *
5515
     * @return ASTNode|null
5516
     * @since 1.0.0
5517
     */
5518 25
    private function parseConstant()
5519
    {
5520 25
        $this->tokenStack->push();
5521
5522 25
        switch ($type = $this->tokenizer->peek()) {
5523
            case Tokens::T_STRING:
5524
                // TODO: Separate node classes for magic constants
5525
            case Tokens::T_DIR:
5526
            case Tokens::T_FILE:
5527
            case Tokens::T_LINE:
5528
            case Tokens::T_NS_C:
5529
            case Tokens::T_FUNC_C:
5530
            case Tokens::T_CLASS_C:
5531
            case Tokens::T_METHOD_C:
5532
            case Tokens::T_TRAIT_C:
5533 25
                $token = $this->consumeToken($type);
5534
5535 25
                return $this->setNodePositionsAndReturn(
5536 25
                    $this->builder->buildAstConstant($token->image),
5537 25
                );
5538
        }
5539
5540
        return null;
5541
    }
5542
5543
    /**
5544
     * This method parses a {@link ASTConstant} node or an instance of
5545
     * {@link ASTSelfReference} as part of a {@link ASTMemberPrimaryPrefix} that
5546
     * contains the self reference as its first child when the self token is
5547
     * followed by a double colon token.
5548
     *
5549
     * @return ASTNode
5550
     * @throws ParserException
5551
     * @throws InvalidStateException
5552
     * @since 0.9.6
5553
     */
5554 26
    private function parseConstantOrSelfMemberPrimaryPrefix()
5555
    {
5556
        // Read self token and strip optional comments
5557 26
        $token = $this->consumeToken(Tokens::T_SELF);
5558 26
        $this->consumeComments();
5559
5560 26
        if ($this->tokenizer->peek() === Tokens::T_DOUBLE_COLON) {
5561 25
            return $this->parseStaticMemberPrimaryPrefix(
5562 25
                $this->parseSelfReference($token),
5563 25
            );
5564
        }
5565
5566 1
        return $this->builder->buildAstConstant($token->image);
5567
    }
5568
5569
    /**
5570
     * This method parses a {@link ASTParentReference} node.
5571
     *
5572
     * @param Token $token The "self" keyword token.
5573
     * @return ASTParentReference
5574
     * @throws ParserException
5575
     * @throws InvalidStateException
5576
     * @since 0.9.6
5577
     */
5578 28
    private function parseParentReference(Token $token)
5579
    {
5580 28
        if (!isset($this->classOrInterface)) {
5581 5
            throw new InvalidStateException(
5582 5
                $token->startLine,
5583 5
                (string) $this->compilationUnit,
5584 5
                'The keyword "parent" was used as type hint but the parameter ' .
5585 5
                'declaration is not in a class scope.',
5586 5
            );
5587
        }
5588
5589 23
        $classReference = $this->classOrInterface instanceof ASTTrait
5590 2
            ? $this->builder->buildAstClassReference('__PDepend_TraitRuntimeReference')
5591 21
            : $this->classOrInterface->getParentClassReference();
5592
5593 23
        if ($classReference === null) {
5594 4
            throw new InvalidStateException(
5595 4
                $token->startLine,
5596 4
                (string) $this->compilationUnit,
5597 4
                sprintf(
5598 4
                    'The keyword "parent" was used as type hint but the ' .
5599 4
                    'class "%s" does not declare a parent.',
5600 4
                    $this->classOrInterface->getImage(),
5601 4
                ),
5602 4
            );
5603
        }
5604
5605 19
        $ref = $this->builder->buildAstParentReference($classReference);
5606 19
        $ref->configureLinesAndColumns(
5607 19
            $token->startLine,
5608 19
            $token->endLine,
5609 19
            $token->startColumn,
5610 19
            $token->endColumn,
5611 19
        );
5612
5613 19
        return $ref;
5614
    }
5615
5616
    /**
5617
     * This method parses a {@link ASTConstant} node or an instance of
5618
     * {@link ASTParentReference} as part of a {@link ASTMemberPrimaryPrefix}
5619
     * that contains the parent reference as its first child when the self token
5620
     * is followed by a double colon token.
5621
     *
5622
     * @return ASTNode
5623
     * @throws ParserException
5624
     * @throws InvalidStateException
5625
     * @since 0.9.6
5626
     */
5627 14
    private function parseConstantOrParentMemberPrimaryPrefix()
5628
    {
5629
        // Consume parent token and strip optional comments
5630 14
        $token = $this->consumeToken(Tokens::T_PARENT);
5631 14
        $this->consumeComments();
5632
5633 14
        if ($this->tokenizer->peek() === Tokens::T_DOUBLE_COLON) {
5634 13
            return $this->parseStaticMemberPrimaryPrefix(
5635 13
                $this->parseParentReference($token),
5636 13
            );
5637
        }
5638
5639 1
        return $this->builder->buildAstConstant($token->image);
5640
    }
5641
5642
    /**
5643
     * Parses a variable or any other valid member expression that is optionally
5644
     * prefixed with PHP's reference operator.
5645
     *
5646
     * <code>
5647
     * //                  -----------
5648
     * foreach ( $array as &$this->foo ) {}
5649
     * //                  -----------
5650
     *
5651
     * //     ----------
5652
     * $foo = &$bar->baz;
5653
     * //     ----------
5654
     * </code>
5655
     *
5656
     * @return ASTNode
5657
     * @since 0.9.18
5658
     */
5659 10
    private function parseVariableOrMemberOptionalByReference()
5660
    {
5661 10
        $this->consumeComments();
5662
5663 10
        if ($this->tokenizer->peek() === Tokens::T_BITWISE_AND) {
5664 2
            return $this->parseVariableOrMemberByReference();
5665
        }
5666
5667 8
        return $this->parseVariableOrConstantOrPrimaryPrefix();
5668
    }
5669
5670
    /**
5671
     * Parses a variable or any other valid member expression that is prefixed
5672
     * with PHP's reference operator.
5673
     *
5674
     * <code>
5675
     * //                  -----------
5676
     * foreach ( $array as &$this->foo ) {}
5677
     * //                  -----------
5678
     *
5679
     * //     ----------
5680
     * $foo = &$bar->baz;
5681
     * //     ----------
5682
     * </code>
5683
     *
5684
     * @return ASTUnaryExpression
5685
     * @since 0.9.18
5686
     */
5687 6
    private function parseVariableOrMemberByReference()
5688
    {
5689 6
        $this->tokenStack->push();
5690
5691 6
        $token = $this->consumeToken(Tokens::T_BITWISE_AND);
5692 6
        $this->consumeComments();
5693
5694 6
        $expr = $this->builder->buildAstUnaryExpression($token->image);
5695 6
        $expr->addChild($this->parseVariableOrConstantOrPrimaryPrefix());
5696
5697 6
        return $this->setNodePositionsAndReturn($expr);
5698
    }
5699
5700
    /**
5701
     * This method parses a simple PHP variable.
5702
     *
5703
     * @return ASTVariable
5704
     * @throws UnexpectedTokenException
5705
     * @since 0.9.6
5706
     */
5707 596
    private function parseVariable()
5708
    {
5709 596
        $token = $this->consumeToken(Tokens::T_VARIABLE);
5710
5711 596
        $variable = $this->builder->buildAstVariable($token->image);
5712 596
        $variable->configureLinesAndColumns(
5713 596
            $token->startLine,
5714 596
            $token->endLine,
5715 596
            $token->startColumn,
5716 596
            $token->endColumn,
5717 596
        );
5718
5719 596
        return $variable;
5720
    }
5721
5722
    /**
5723
     * This method parses a comma separated list of valid php variables and/or
5724
     * properties and adds them to the given node instance.
5725
     *
5726
     * @template T of AbstractASTNode
5727
     *
5728
     * @param T $node The context parent node.
5729
     * @param bool $inCall
5730
     * @return T The prepared entire node.
5731
     * @since 0.9.12
5732
     */
5733 16
    private function parseVariableList(ASTNode $node, $inCall = false): AbstractASTNode
5734
    {
5735 16
        $this->consumeComments();
5736 16
        while ($this->tokenizer->peek() !== Tokenizer::T_EOF) {
5737 16
            $node->addChild($this->parseVariableOrConstantOrPrimaryPrefix());
5738
5739 16
            $this->consumeComments();
5740
5741 16
            while ($this->tokenizer->peek() === Tokens::T_SQUARED_BRACKET_OPEN) {
5742 1
                $this->parseListExpression();
5743
            }
5744
5745 16
            if ($this->tokenizer->peek() === Tokens::T_COMMA) {
5746 8
                $this->consumeToken(Tokens::T_COMMA);
5747 8
                $this->consumeComments();
5748
5749
                if (
5750 8
                    $inCall &&
5751 8
                    $this->tokenizer->peek() === Tokens::T_PARENTHESIS_CLOSE
5752
                ) {
5753 1
                    break;
5754
                }
5755
            } else {
5756 15
                break;
5757
            }
5758
        }
5759
5760 16
        return $node;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node returns the type PDepend\Source\AST\ASTNode which includes types incompatible with the type-hinted return PDepend\Source\AST\AbstractASTNode.
Loading history...
5761
    }
5762
5763
    /**
5764
     * This method is a decision point between the different variable types
5765
     * availanle in PHP. It peeks the next token and then decides whether it is
5766
     * a regular variable or when the next token is of type <b>T_DOLLAR</b> a
5767
     * compound- or variable-variable.
5768
     *
5769
     * <code>
5770
     * ----
5771
     * $foo;
5772
     * ----
5773
     *
5774
     * -----
5775
     * $$foo;
5776
     * -----
5777
     *
5778
     * ------
5779
     * ${FOO};
5780
     * ------
5781
     * </code>
5782
     *
5783
     * @return AbstractASTNode
5784
     * @throws ParserException
5785
     * @throws UnexpectedTokenException
5786
     * @since 0.9.6
5787
     */
5788 570
    private function parseCompoundVariableOrVariableVariableOrVariable()
5789
    {
5790 570
        if ($this->tokenizer->peek() === Tokens::T_DOLLAR) {
5791 31
            return $this->parseCompoundVariableOrVariableVariable();
5792
        }
5793
5794 562
        return $this->parseVariable();
5795
    }
5796
5797
    /**
5798
     * Parses a PHP compound variable or a simple literal node.
5799
     *
5800
     * @return ASTNode
5801
     * @since 0.9.19
5802
     */
5803 8
    private function parseCompoundVariableOrLiteral()
5804
    {
5805 8
        $this->tokenStack->push();
5806
5807
        // Read the dollar token
5808 8
        $token = $this->consumeToken(Tokens::T_DOLLAR);
5809 8
        $this->consumeComments();
5810
5811
        // Get next token type
5812 8
        $tokenType = $this->tokenizer->peek();
5813
5814
        switch ($tokenType) {
5815
            case Tokens::T_CURLY_BRACE_OPEN:
5816 7
                $variable = $this->builder->buildAstCompoundVariable($token->image);
5817 7
                $variable->addChild($this->parseCompoundExpression());
5818
5819 7
                break;
5820
5821
            default:
5822 1
                $variable = $this->builder->buildAstLiteral($token->image);
5823
5824 1
                break;
5825
        }
5826
5827 8
        return $this->setNodePositionsAndReturn($variable);
5828
    }
5829
5830
    /**
5831
     * This method implements a decision point between compound-variables and
5832
     * variable-variable. It expects that the next token in the token-stream is
5833
     * of type <b>T_DOLLAR</b> and removes it from the stream. Then this method
5834
     * peeks the next available token when it is of type <b>T_CURLY_BRACE_OPEN</b>
5835
     * this is compound variable, otherwise it can be a variable-variable or a
5836
     * compound-variable.
5837
     *
5838
     * @return AbstractASTNode
5839
     * @throws ParserException
5840
     * @throws UnexpectedTokenException
5841
     * @since 0.9.6
5842
     */
5843 31
    private function parseCompoundVariableOrVariableVariable()
5844
    {
5845 31
        $this->tokenStack->push();
5846
5847
        // Read the dollar token
5848 31
        $token = $this->consumeToken(Tokens::T_DOLLAR);
5849 31
        $this->consumeComments();
5850
5851
        // Get next token type
5852 31
        $tokenType = $this->tokenizer->peek();
5853
5854
        // T_DOLLAR|T_VARIABLE === Variable variable,
5855
        // T_CURLY_BRACE_OPEN === Compound variable
5856
        switch ($tokenType) {
5857
            case Tokens::T_DOLLAR:
5858
            case Tokens::T_VARIABLE:
5859 10
                $variable = $this->builder->buildAstVariableVariable($token->image);
5860 10
                $variable->addChild(
5861 10
                    $this->parseCompoundVariableOrVariableVariableOrVariable(),
5862 10
                );
5863
5864 10
                break;
5865
5866
            default:
5867 22
                $variable = $this->parseCompoundVariable($token);
5868
5869 22
                break;
5870
        }
5871
5872 31
        return $this->setNodePositionsAndReturn($variable);
5873
    }
5874
5875
    /**
5876
     * This method parses a compound variable like:
5877
     *
5878
     * <code>
5879
     * //     ----------------
5880
     * return ${'Foo' . 'Bar'};
5881
     * //     ----------------
5882
     * </code>
5883
     *
5884
     * @param Token $token The dollar token.
5885
     * @return ASTCompoundVariable
5886
     * @since 0.10.0
5887
     */
5888 22
    private function parseCompoundVariable(Token $token)
5889
    {
5890 22
        return $this->parseBraceExpression(
5891 22
            $this->builder->buildAstCompoundVariable($token->image),
5892 22
            $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN),
5893 22
            Tokens::T_CURLY_BRACE_CLOSE,
5894 22
        );
5895
    }
5896
5897
    /**
5898
     * This method parses a compound expression like:
5899
     *
5900
     * <code>
5901
     * //      ------  ------
5902
     * $foo = "{$bar}, {$baz}\n";
5903
     * //      ------  ------
5904
     * </code>
5905
     *
5906
     * or a simple literal token:
5907
     *
5908
     * <code>
5909
     * //      -
5910
     * $foo = "{{$bar}, {$baz}\n";
5911
     * //      -
5912
     * </code>
5913
     *
5914
     * @return ASTNode
5915
     * @since 0.9.10
5916
     */
5917 11
    private function parseCompoundExpressionOrLiteral()
5918
    {
5919 11
        $token = $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
5920 11
        $this->consumeComments();
5921
5922 11
        switch ($this->tokenizer->peek()) {
5923
            case Tokens::T_DOLLAR:
5924
            case Tokens::T_VARIABLE:
5925 11
                return $this->parseBraceExpression(
5926 11
                    $this->builder->buildAstCompoundExpression(),
5927 11
                    $token,
5928 11
                    Tokens::T_CURLY_BRACE_CLOSE,
5929 11
                );
5930
        }
5931
5932 2
        $literal = $this->builder->buildAstLiteral($token->image);
5933 2
        $literal->configureLinesAndColumns(
5934 2
            $token->startLine,
5935 2
            $token->endLine,
5936 2
            $token->startColumn,
5937 2
            $token->endColumn,
5938 2
        );
5939
5940 2
        return $literal;
5941
    }
5942
5943
    /**
5944
     * This method parses a compound expression node.
5945
     *
5946
     * <code>
5947
     * ------------------
5948
     * {'_' . foo . $bar}
5949
     * ------------------
5950
     * </code>
5951
     *
5952
     * @return ASTCompoundExpression
5953
     * @throws ParserException
5954
     * @throws ParserException
5955
     * @since 0.9.6
5956
     */
5957 16
    private function parseCompoundExpression()
5958
    {
5959 16
        $this->consumeComments();
5960
5961 16
        return $this->parseBraceExpression(
5962 16
            $this->builder->buildAstCompoundExpression(),
5963 16
            $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN),
5964 16
            Tokens::T_CURLY_BRACE_CLOSE,
5965 16
        );
5966
    }
5967
5968
    /**
5969
     * Parses a static identifier expression, as it is used for method and
5970
     * function names.
5971
     *
5972
     * @param int $tokenType
5973
     * @return ASTIdentifier
5974
     * @since 0.9.12
5975
     */
5976 188
    private function parseIdentifier($tokenType = Tokens::T_STRING)
5977
    {
5978 188
        $token = $this->consumeToken($tokenType);
5979
5980 188
        $node = $this->builder->buildAstIdentifier($token->image);
5981 188
        $node->configureLinesAndColumns(
5982 188
            $token->startLine,
5983 188
            $token->endLine,
5984 188
            $token->startColumn,
5985 188
            $token->endColumn,
5986 188
        );
5987
5988 188
        return $node;
5989
    }
5990
5991
    /**
5992
     * This method parses a {@link ASTLiteral} node or an
5993
     * instance of {@link ASTString} that represents a string
5994
     * in double quotes or surrounded by backticks.
5995
     *
5996
     * @return ASTNode
5997
     * @throws UnexpectedTokenException
5998
     */
5999 539
    private function parseLiteralOrString()
6000
    {
6001 539
        $tokenType = $this->tokenizer->peek();
6002
6003
        switch ($tokenType) {
6004
            case Tokens::T_NULL:
6005
            case Tokens::T_TRUE:
6006
            case Tokens::T_FALSE:
6007
            case Tokens::T_DNUMBER:
6008
            case Tokens::T_CONSTANT_ENCAPSED_STRING:
6009 299
                $token = $this->consumeToken($tokenType);
6010
6011 299
                $literal = $this->builder->buildAstLiteral($token->image);
6012 299
                $literal->configureLinesAndColumns(
6013 299
                    $token->startLine,
6014 299
                    $token->endLine,
6015 299
                    $token->startColumn,
6016 299
                    $token->endColumn,
6017 299
                );
6018
6019 299
                return $literal;
6020
6021
            case Tokens::T_LNUMBER:
6022 295
                return $this->parseIntegerNumber();
6023
6024
            default:
6025 34
                return $this->parseString($tokenType);
6026
        }
6027
    }
6028
6029
    /**
6030
     * Parses an integer value.
6031
     *
6032
     * @return ASTLiteral
6033
     * @throws UnexpectedTokenException
6034
     * @since 1.0.0
6035
     */
6036 295
    private function parseIntegerNumber()
6037
    {
6038 295
        $token = $this->consumeToken(Tokens::T_LNUMBER);
6039 295
        $number = $token->image;
6040
6041 295
        while ($next = $this->addTokenToStackIfType(Tokens::T_STRING)) {
6042
            $number .= $next->image;
6043
        }
6044
6045 295
        if (!str_starts_with($number, '0')) {
6046 270
            goto BUILD_LITERAL;
6047
        }
6048
6049 93
        if (Tokens::T_STRING !== $this->tokenizer->peek()) {
6050 93
            goto BUILD_LITERAL;
6051
        }
6052
6053
        $token1 = $this->consumeToken(Tokens::T_STRING);
6054
        if (!preg_match('(^b[01]+$)i', $token1->image)) {
6055
            throw new UnexpectedTokenException(
6056
                $token1,
6057
                $this->tokenizer->getSourceFile() ?? 'unknown'
6058
            );
6059
        }
6060
6061
        $number .= $token1->image;
6062
        $token->endLine = $token1->endLine;
6063
        $token->endColumn = $token1->endColumn;
6064
6065
        BUILD_LITERAL:
6066
6067 295
        $literal = $this->builder->buildAstLiteral($number);
6068 295
        $literal->configureLinesAndColumns(
6069 295
            $token->startLine,
6070 295
            $token->endLine,
6071 295
            $token->startColumn,
6072 295
            $token->endColumn,
6073 295
        );
6074
6075 295
        return $literal;
6076
    }
6077
6078
    /**
6079
     * Parses an array structure.
6080
     *
6081
     * @param bool $static
6082
     * @return ASTArray
6083
     * @since 1.0.0
6084
     */
6085 131
    private function doParseArray($static = false)
6086
    {
6087 131
        $this->tokenStack->push();
6088
6089 131
        return $this->setNodePositionsAndReturn(
6090 131
            $this->parseArray(
6091 131
                $this->builder->buildAstArray(),
6092 131
                $static,
6093 131
            ),
6094 131
        );
6095
    }
6096
6097
    /**
6098
     * Tests if the next token is a valid array start delimiter in the supported
6099
     * PHP version.
6100
     *
6101
     * @return bool
6102
     * @since 1.0.0
6103
     */
6104 834
    private function isArrayStartDelimiter()
6105
    {
6106 834
        return match ($this->tokenizer->peek()) {
6107 834
            Tokens::T_ARRAY,
6108 834
            Tokens::T_SQUARED_BRACKET_OPEN => true,
6109 834
            default => false,
6110 834
        };
6111
    }
6112
6113
    /**
6114
     * Parses a php array declaration.
6115
     *
6116
     * @template T of ASTArray
6117
     * @param T $array
6118
     * @param bool $static
6119
     * @return T
6120
     * @since 1.0.0
6121
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
6122 132
    private function parseArray(ASTArray $array, $static = false): ASTArray
6123
    {
6124 132
        switch ($this->tokenizer->peek()) {
6125
            case Tokens::T_SQUARED_BRACKET_OPEN:
6126 28
                $this->consumeToken(Tokens::T_SQUARED_BRACKET_OPEN);
6127 28
                $this->parseArrayElements($array, Tokens::T_SQUARED_BRACKET_CLOSE, $static);
6128 27
                $this->consumeToken(Tokens::T_SQUARED_BRACKET_CLOSE);
6129
6130 27
                break;
6131
6132
            case Tokens::T_ARRAY:
6133 105
                $this->consumeToken(Tokens::T_ARRAY);
6134 105
                $this->consumeComments();
6135 105
                $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
6136 105
                $this->parseArrayElements($array, Tokens::T_PARENTHESIS_CLOSE, $static);
6137 104
                $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
6138
6139 104
                break;
6140
6141
            default:
6142 1
                throw $this->getUnexpectedNextTokenException();
6143
        }
6144
6145 129
        return $array;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $array returns the type PDepend\Source\AST\ASTArray which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
6146
    }
6147
6148
    /**
6149
     * Parses all elements in an array.
6150
     *
6151
     * @template T of ASTArray
6152
     * @param T $array
6153
     * @param int $endDelimiter
6154
     * @param bool $static
6155
     * @return T
6156
     * @since 1.0.0
6157
     */
6158 131
    private function parseArrayElements(ASTArray $array, $endDelimiter, $static = false): ASTArray
6159
    {
6160 131
        $consecutiveComma = null;
6161 131
        $openingToken = $this->tokenizer->prevToken();
6162 131
        $useSquaredBrackets = ($endDelimiter === Tokens::T_SQUARED_BRACKET_CLOSE);
6163 131
        $this->consumeComments();
6164
6165 131
        while ($endDelimiter !== $this->tokenizer->peek()) {
6166 98
            while (Tokens::T_COMMA === $this->tokenizer->peek()) {
6167 2
                $this->consumeToken(Tokens::T_COMMA);
6168 2
                $this->consumeComments();
6169
            }
6170
6171 98
            $array->addChild($this->parseArrayElement($static));
6172
6173 97
            $this->consumeComments();
6174
6175 97
            if (Tokens::T_COMMA === $this->tokenizer->peek()) {
6176 55
                $this->consumeToken(Tokens::T_COMMA);
6177 55
                $this->consumeComments();
6178
            }
6179
6180 97
            if ($useSquaredBrackets && $this->isListUnpacking(Tokens::T_SQUARED_BRACKET_OPEN)) {
6181 21
                while (Tokens::T_COMMA === $this->tokenizer->peek()) {
6182 2
                    $consecutiveComma = $this->tokenizer->prevToken();
6183 2
                    $this->consumeToken(Tokens::T_COMMA);
6184 2
                    $this->consumeComments();
6185
                }
6186
            }
6187
        }
6188
6189
        // Once we parsed the whole array, detect if it's a destructuring list or a value,
6190
        // then check the content is consistent
6191 130
        $this->ensureArrayIsValid($useSquaredBrackets, $openingToken, $consecutiveComma);
6192
6193 129
        return $array;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $array returns the type PDepend\Source\AST\ASTArray which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
6194
    }
6195
6196
    /**
6197
     * Check if the given array/list is a value and so does not have consecutive commas in it,
6198
     * or if it's a destructuring list and so check the syntax is valid in the current PHP level.
6199
     *
6200
     * @param bool $useSquaredBrackets
6201
     * @param Token|null $openingToken
6202
     * @param Token|null $consecutiveComma
6203
     * @throws UnexpectedTokenException
6204
     */
6205 130
    private function ensureArrayIsValid($useSquaredBrackets, $openingToken, $consecutiveComma): void
6206
    {
6207
        // If this array is followed by =, it's in fact a destructuring list
6208 130
        if ($this->tokenizer->peekNext() === Tokens::T_EQUAL) {
6209
            // If it uses [], check the PHP level allow it
6210 5
            if ($useSquaredBrackets) {
6211 5
                $this->ensureTokenIsListUnpackingOpening(Tokens::T_SQUARED_BRACKET_OPEN, $openingToken);
6212
            }
6213 130
        } elseif ($consecutiveComma) {
6214
            // If it's not a destructuring list, it must not contain 2 consecutive commas
6215 1
            throw $this->getUnexpectedTokenException($consecutiveComma);
6216
        }
6217
    }
6218
6219
    /**
6220
     * Parses a single match key.
6221
     *
6222
     * @return ASTNode
6223
     * @since 2.9.0
6224
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
6225 4
    private function parseMatchEntryKey()
6226
    {
6227 4
        $this->consumeComments();
6228
6229 4
        if ($this->tokenizer->peek() === Tokens::T_DEFAULT) {
6230 3
            $this->consumeToken(Tokens::T_DEFAULT);
6231 3
            $label = $this->builder->buildAstSwitchLabel('default');
6232 3
            $label->setDefault();
6233
6234 3
            return $label;
6235
        }
6236
6237 4
        if ($this->isKeyword($this->tokenizer->peek())) {
6238
            throw $this->getUnexpectedNextTokenException();
6239
        }
6240
6241 4
        return $this->parseExpression();
6242
    }
6243
6244
    /**
6245
     * Parses a single match value expression.
6246
     *
6247
     * @return ASTNode
6248
     * @since 2.9.0
6249
     */
6250 4
    private function parseMatchEntryValue()
6251
    {
6252 4
        $this->consumeComments();
6253
6254 4
        if ($this->tokenizer->peek() === Tokens::T_THROW) {
6255 3
            return $this->parseThrowStatement([Tokens::T_COMMA, Tokens::T_CURLY_BRACE_CLOSE]);
6256
        }
6257
6258 4
        return $this->parseExpression();
6259
    }
6260
6261
    /**
6262
     * Parses a single match entry key-expression pair.
6263
     *
6264
     * @return ASTMatchEntry
6265
     * @since 2.9.0
6266
     */
6267 4
    private function parseMatchEntry()
6268
    {
6269 4
        $this->consumeComments();
6270
6271 4
        $this->tokenStack->push();
6272
6273 4
        $matchEntry = $this->builder->buildAstMatchEntry();
6274
6275
        do {
6276 4
            $matchEntry->addChild($this->parseMatchEntryKey());
6277 4
            $this->consumeComments();
6278
6279 4
            if ($this->tokenizer->peek() === Tokens::T_COMMA) {
6280 2
                $this->consumeToken(Tokens::T_COMMA);
6281 2
                $this->consumeComments();
6282
            }
6283
6284 4
            $type = $this->tokenizer->peek();
6285 4
        } while ($type !== Tokens::T_DOUBLE_ARROW);
6286
6287 4
        $this->consumeComments();
6288 4
        $this->consumeToken(Tokens::T_DOUBLE_ARROW);
6289 4
        $this->consumeComments();
6290
6291 4
        $matchEntry->addChild($this->parseMatchEntryValue());
6292
6293 4
        return $this->setNodePositionsAndReturn($matchEntry);
6294
    }
6295
6296
    /**
6297
     * Parses a single array element.
6298
     *
6299
     * An array element can have a simple value, a key/value pair, a value by
6300
     * reference or a key/value pair with a referenced value.
6301
     *
6302
     * @param bool $static
6303
     * @return ASTArrayElement
6304
     * @since 1.0.0
6305
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
6306 98
    private function parseArrayElement($static = false)
6307
    {
6308 98
        $this->consumeComments();
6309
6310 98
        $this->tokenStack->push();
6311
6312 98
        $element = $this->builder->buildAstArrayElement();
6313 98
        if ($this->parseOptionalByReference()) {
6314 12
            if ($static) {
6315 1
                $tokens = $this->tokenStack->pop();
6316
6317 1
                throw $this->getUnexpectedTokenException(end($tokens) ?: null);
6318
            }
6319
6320 11
            $element->setByReference();
6321
        }
6322
6323 97
        $this->consumeComments();
6324 97
        if ($this->isKeyword($this->tokenizer->peek())) {
6325
            throw $this->getUnexpectedNextTokenException();
6326
        }
6327
6328 97
        $element->addChild($this->parseExpression());
6329
6330 97
        $this->consumeComments();
6331 97
        if (Tokens::T_DOUBLE_ARROW === $this->tokenizer->peek()) {
6332 44
            $this->consumeToken(Tokens::T_DOUBLE_ARROW);
6333 44
            $this->consumeComments();
6334
6335 44
            if ($this->parseOptionalByReference()) {
6336 9
                $element->setByReference();
6337
            }
6338 44
            $element->addChild($this->parseExpression());
6339
        }
6340
6341 97
        return $this->setNodePositionsAndReturn($element);
6342
    }
6343
6344
    /**
6345
     * Parses a here- or nowdoc string instance.
6346
     *
6347
     * @return ASTHeredoc
6348
     * @since 0.9.12
6349
     */
6350 17
    private function parseHeredoc()
6351
    {
6352 17
        $this->tokenStack->push();
6353 17
        $this->consumeToken(Tokens::T_START_HEREDOC);
6354
6355 17
        $heredoc = $this->builder->buildAstHeredoc();
6356 17
        $this->parseStringExpressions($heredoc, Tokens::T_END_HEREDOC);
6357
6358 17
        $token = $this->consumeToken(Tokens::T_END_HEREDOC);
6359 17
        $heredoc->setDelimiter($token->image);
6360
6361 17
        return $this->setNodePositionsAndReturn($heredoc);
6362
    }
6363
6364
    /**
6365
     * Parses a simple string sequence between two tokens of the same type.
6366
     *
6367
     * @param int $tokenType The start/stop token type.
6368
     * @return string
6369
     * @since 0.9.10
6370
     */
6371
    private function parseStringSequence($tokenType)
6372
    {
6373
        $type = $tokenType;
6374
        $string = '';
6375
6376
        do {
6377
            $string .= $this->consumeToken($type)->image;
6378
            $type = $this->tokenizer->peek();
6379
        } while ($type !== $tokenType && $type !== Tokenizer::T_EOF);
6380
6381
        return $string . $this->consumeToken($tokenType)->image;
6382
    }
6383
6384
    /**
6385
     * This method parses a php string with all possible embedded expressions.
6386
     *
6387
     * <code>
6388
     * $string = "Manuel $Pichler <{$email}>";
6389
     *
6390
     * // ASTSTring
6391
     * // |-- ASTLiteral             -  "Manuel ")
6392
     * // |-- ASTVariable            -  $Pichler
6393
     * // |-- ASTLiteral             -  " <"
6394
     * // |-- ASTCompoundExpression  -  {...}
6395
     * // |   |-- ASTVariable        -  $email
6396
     * // |-- ASTLiteral             -  ">"
6397
     * </code>
6398
     *
6399
     * @param int $delimiterType The start/stop token type.
6400
     * @return ASTString
6401
     * @throws UnexpectedTokenException
6402
     * @since 0.9.10
6403
     */
6404 34
    private function parseString($delimiterType)
6405
    {
6406 34
        $token = $this->consumeToken($delimiterType);
6407
6408 34
        $string = $this->builder->buildAstString();
6409 34
        $startLine = $token->startLine;
6410 34
        $startColumn = $token->startColumn;
6411
6412 34
        $this->parseStringExpressions($string, $delimiterType);
6413
6414 34
        $token = $this->consumeToken($delimiterType);
6415 33
        $endLine = $token->endLine;
6416 33
        $endColumn = $token->endColumn;
6417
6418 33
        $string->configureLinesAndColumns(
6419 33
            $startLine,
6420 33
            $endLine,
6421 33
            $startColumn,
6422 33
            $endColumn,
6423 33
        );
6424
6425 33
        return $string;
6426
    }
6427
6428
    /**
6429
     * This method parses the contents of a string or here-/now-doc node. It
6430
     * will not consume the given stop token, so it is up to the calling method
6431
     * to consume the stop token. The return value of this method is the prepared
6432
     * input string node.
6433
     *
6434
     * @template T of AbstractASTNode
6435
     * @param T $node
6436
     * @param int $stopToken
6437
     * @return T
6438
     * @since 0.9.12
6439
     */
6440 51
    private function parseStringExpressions(AbstractASTNode $node, $stopToken): AbstractASTNode
6441
    {
6442 51
        while (($tokenType = $this->tokenizer->peek()) !== Tokenizer::T_EOF) {
6443
            switch ($tokenType) {
6444 51
                case $stopToken:
6445 50
                    break 2;
6446
6447 51
                case Tokens::T_BACKSLASH:
6448
                    $node->addChild($this->parseEscapedAstLiteralString());
6449
6450
                    break;
6451
6452 51
                case Tokens::T_DOLLAR:
6453 8
                    $node->addChild($this->parseCompoundVariableOrLiteral());
6454
6455 8
                    break;
6456
6457 49
                case Tokens::T_VARIABLE:
6458 24
                    $node->addChild($this->parseVariable());
6459
6460 24
                    break;
6461
6462 47
                case Tokens::T_CURLY_BRACE_OPEN:
6463 11
                    $node->addChild($this->parseCompoundExpressionOrLiteral());
6464
6465 11
                    break;
6466
6467
                default:
6468 46
                    $node->addChild($this->parseLiteral());
6469
6470 46
                    break;
6471
            }
6472
        }
6473
6474 51
        return $node;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $node returns the type PDepend\Source\AST\AbstractASTNode which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
6475
    }
6476
6477
    /**
6478
     * This method parses an escaped sequence of literal tokens.
6479
     *
6480
     * @return ASTLiteral
6481
     * @since 0.9.10
6482
     */
6483
    private function parseEscapedAstLiteralString()
6484
    {
6485
        $this->tokenStack->push();
6486
6487
        $image = $this->consumeToken(Tokens::T_BACKSLASH)->image;
6488
        $escape = true;
6489
6490
        $tokenType = $this->tokenizer->peek();
6491
        while ($tokenType !== Tokenizer::T_EOF) {
6492
            if ($tokenType === Tokens::T_BACKSLASH) {
6493
                $escape = !$escape;
0 ignored issues
show
introduced by
The condition $escape is always true.
Loading history...
6494
                $image .= $this->consumeToken(Tokens::T_BACKSLASH)->image;
6495
6496
                $tokenType = $this->tokenizer->peek();
6497
6498
                continue;
6499
            }
6500
6501
            if ($escape) {
6502
                $image .= $this->consumeToken($tokenType)->image;
6503
6504
                break;
6505
            }
6506
        }
6507
6508
        return $this->setNodePositionsAndReturn(
6509
            $this->builder->buildAstLiteral($image),
6510
        );
6511
    }
6512
6513
    /**
6514
     * This method parses a simple literal and configures the position
6515
     * properties.
6516
     *
6517
     * @return ASTLiteral
6518
     * @since 0.9.10
6519
     */
6520 49
    private function parseLiteral()
6521
    {
6522 49
        $token = $this->consumeToken($this->tokenizer->peek());
6523
6524 49
        $node = $this->builder->buildAstLiteral($token->image);
6525 49
        $node->configureLinesAndColumns(
6526 49
            $token->startLine,
6527 49
            $token->endLine,
6528 49
            $token->startColumn,
6529 49
            $token->endColumn,
6530 49
        );
6531
6532 49
        return $node;
6533
    }
6534
6535
    /**
6536
     * @return int
6537
     */
6538 6
    private function checkReadonlyToken()
6539
    {
6540 6
        if ($this->addTokenToStackIfType(Tokens::T_READONLY)) {
6541 1
            return State::IS_READONLY;
6542
        }
6543
6544 6
        return 0;
6545
    }
6546
6547
    /**
6548
     * Parse the modifiers for construct parameter
6549
     *
6550
     * @return int
6551
     */
6552 6
    private function parseConstructFormalParameterModifiers()
6553
    {
6554 6
        static $states = [
6555 6
            Tokens::T_PUBLIC => State::IS_PUBLIC,
6556 6
            Tokens::T_PROTECTED => State::IS_PROTECTED,
6557 6
            Tokens::T_PRIVATE => State::IS_PRIVATE,
6558 6
        ];
6559
6560 6
        $modifier = $this->checkReadonlyToken();
6561 6
        $token = $this->tokenizer->peek();
6562
6563 6
        if (isset($states[$token])) {
6564 5
            $modifier |= $states[$token];
6565 5
            $next = $this->tokenizer->next();
6566
            assert($next instanceof Token);
6567 5
            $this->tokenStack->add($next);
6568
        }
6569
6570 6
        return $modifier | $this->checkReadonlyToken();
6571
    }
6572
6573
    /**
6574
     * This method parse a formal parameter and all the stuff that may be allowed
6575
     * before it according to the PHP level (type hint, passing by reference, property promotion).
6576
     *
6577
     * @param ASTCallable $callable the callable object (closure, function or method)
6578
     *                              requiring the given parameters list.
6579
     * @return ASTNode
6580
     */
6581 410
    private function parseFormalParameterOrPrefix(ASTCallable $callable)
6582
    {
6583 410
        $modifier = 0;
6584
6585 410
        if ($callable instanceof ASTMethod && $callable->getImage() === '__construct') {
6586 6
            $modifier = $this->parseConstructFormalParameterModifiers();
6587
        }
6588
6589 410
        $parameter = $this->parseFormalParameterOrTypeHintOrByReference();
6590
6591 398
        if ($modifier && $parameter instanceof ASTFormalParameter) {
6592 5
            $parameter->setModifiers($modifier);
6593
        }
6594
6595 398
        return $parameter;
6596
    }
6597
6598
    /**
6599
     * Extracts all dependencies from a callable signature.
6600
     *
6601
     * @param ASTCallable $callable the callable object (closure, function or method)
6602
     *                              requiring the given parameters list.
6603
     * @return ASTFormalParameters
6604
     * @since 0.9.5
6605
     */
6606 1142
    private function parseFormalParameters(ASTCallable $callable)
6607
    {
6608 1142
        $this->consumeComments();
6609
6610 1142
        $this->tokenStack->push();
6611
6612 1142
        $formalParameters = $this->builder->buildAstFormalParameters();
6613
6614 1142
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
6615 1141
        $this->consumeComments();
6616
6617 1141
        $tokenType = $this->tokenizer->peek();
6618
6619
        // Check for function without parameters
6620 1141
        if ($tokenType === Tokens::T_PARENTHESIS_CLOSE) {
6621 770
            $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
6622
6623 770
            return $this->setNodePositionsAndReturn($formalParameters);
6624
        }
6625
6626 410
        while ($tokenType !== Tokenizer::T_EOF) {
6627
            // check for trailing comma in parameter list
6628 410
            $this->consumeComments();
6629 410
            $tokenType = $this->tokenizer->peek();
6630
6631 410
            if ($tokenType === Tokens::T_PARENTHESIS_CLOSE) {
6632 4
                break;
6633
            }
6634
6635 410
            $formalParameters->addChild(
6636 410
                $this->parseFormalParameterOrPrefix($callable),
6637 410
            );
6638
6639 398
            $this->consumeComments();
6640 398
            $tokenType = $this->tokenizer->peek();
6641
6642
            // Check for following parameter
6643 398
            if ($tokenType !== Tokens::T_COMMA) {
6644 394
                break;
6645
            }
6646
6647 91
            $this->consumeToken(Tokens::T_COMMA);
6648
        }
6649
6650 398
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
6651
6652 397
        return $this->setNodePositionsAndReturn($formalParameters);
6653
    }
6654
6655
    /**
6656
     * This method parses a formal parameter in all it's variations.
6657
     *
6658
     * <code>
6659
     * //                ------------
6660
     * function traverse(Iterator $it) {}
6661
     * //                ------------
6662
     *
6663
     * //                ---------
6664
     * function traverse(array $ar) {}
6665
     * //                ---------
6666
     *
6667
     * //                ---
6668
     * function traverse(&$x) {}
6669
     * //                ---
6670
     * </code>
6671
     *
6672
     * @return ASTFormalParameter
6673
     * @since 0.9.6
6674
     */
6675 410
    private function parseFormalParameterOrTypeHintOrByReference()
6676
    {
6677 410
        $this->consumeComments();
6678 410
        $this->consumeQuestionMark();
6679 410
        $this->consumeComments();
6680 410
        $tokenType = $this->tokenizer->peek();
6681
6682 410
        $this->tokenStack->push();
6683
6684 410
        return $this->setNodePositionsAndReturn(
6685 410
            $this->parseFormalParameterFromType($tokenType),
6686 410
        );
6687
    }
6688
6689
    /**
6690
     * @param int $tokenType
6691
     * @return ASTFormalParameter
6692
     */
6693 410
    private function parseFormalParameterFromType($tokenType)
6694
    {
6695 410
        if ($this->isTypeHint($tokenType)) {
6696 125
            $typeHint = $this->parseOptionalTypeHint();
6697
6698 119
            return $this->parseFormalParameterAndTypeHint($typeHint);
6699
        }
6700
6701 324
        return match ($tokenType) {
6702 324
            Tokens::T_ARRAY => $this->parseFormalParameterAndArrayTypeHint(),
6703 324
            Tokens::T_SELF => $this->parseFormalParameterAndSelfTypeHint(),
6704 324
            Tokens::T_PARENT => $this->parseFormalParameterAndParentTypeHint(),
6705 324
            Tokens::T_STATIC => $this->parseFormalParameterAndStaticTypeHint(),
6706 324
            Tokens::T_BITWISE_AND => $this->parseFormalParameterAndByReference(),
6707 324
            default => $this->parseFormalParameter(),
6708 324
        };
6709
    }
6710
6711
    /**
6712
     * This method parses a formal parameter that has an array type hint.
6713
     *
6714
     * <code>
6715
     * //                ---------
6716
     * function traverse(array $ar) {}
6717
     * //                ---------
6718
     * </code>
6719
     *
6720
     * @return ASTFormalParameter
6721
     * @since 0.9.6
6722
     */
6723
    private function parseFormalParameterAndArrayTypeHint()
6724
    {
6725
        $node = $this->parseArrayType();
6726
6727
        $parameter = $this->parseFormalParameterOrByReference();
6728
        $parameter->prependChild($node);
6729
6730
        return $parameter;
6731
    }
6732
6733
    /**
6734
     * @return ASTTypeArray
6735
     */
6736 48
    private function parseArrayType()
6737
    {
6738 48
        $token = $this->consumeToken(Tokens::T_ARRAY);
6739
6740 48
        $type = $this->builder->buildAstTypeArray();
6741 48
        $type->configureLinesAndColumns(
6742 48
            $token->startLine,
6743 48
            $token->endLine,
6744 48
            $token->startColumn,
6745 48
            $token->endColumn,
6746 48
        );
6747
6748 48
        return $type;
6749
    }
6750
6751
    /**
6752
     * Parses a type hint that is valid in the supported PHP version after the next token.
6753
     *
6754
     * @return ASTType
6755
     * @since 2.9.2
6756
     */
6757 125
    private function parseOptionalTypeHint()
6758
    {
6759 125
        $this->tokenStack->push();
6760
6761 125
        return $this->parseTypeHint();
6762
    }
6763
6764
    /**
6765
     * This method parses a formal parameter that has a regular class type hint.
6766
     *
6767
     * <code>
6768
     * //                ------------
6769
     * function traverse(Iterator $it) {}
6770
     * //                ------------
6771
     * </code>
6772
     *
6773
     * @return ASTFormalParameter
6774
     * @since 0.9.6
6775
     */
6776 119
    private function parseFormalParameterAndTypeHint(ASTNode $typeHint)
6777
    {
6778 119
        $classReference = $this->setNodePositionsAndReturn($typeHint);
6779 119
        $parameter = $this->parseFormalParameterOrByReference();
6780 117
        $parameter->prependChild($classReference);
6781
6782 117
        return $parameter;
6783
    }
6784
6785
    /**
6786
     * This method will parse a formal parameter that has the keyword parent as
6787
     * parameter type hint.
6788
     *
6789
     * <code>
6790
     * class Foo extends Bar
6791
     * {
6792
     *     //                   ---------
6793
     *     public function test(parent $o) {}
6794
     *     //                   ---------
6795
     * }
6796
     * </code>
6797
     *
6798
     * @return ASTFormalParameter
6799
     * @since 0.9.6
6800
     */
6801
    private function parseFormalParameterAndParentTypeHint()
6802
    {
6803
        $reference = $this->parseParentType();
6804
        $parameter = $this->parseFormalParameterOrByReference();
6805
        $parameter->prependChild($reference);
6806
6807
        return $parameter;
6808
    }
6809
6810
    /**
6811
     * @return ASTParentReference
6812
     */
6813 7
    private function parseParentType()
6814
    {
6815 7
        return $this->parseParentReference($this->consumeToken(Tokens::T_PARENT));
6816
    }
6817
6818
    /**
6819
     * This method will parse a formal parameter that has the keyword self as
6820
     * parameter type hint.
6821
     *
6822
     * <code>
6823
     * class Foo
6824
     * {
6825
     *     //                   -------
6826
     *     public function test(self $o) {}
6827
     *     //                   -------
6828
     * }
6829
     * </code>
6830
     *
6831
     * @return ASTFormalParameter
6832
     * @since 0.9.6
6833
     */
6834
    private function parseFormalParameterAndSelfTypeHint()
6835
    {
6836
        $self = $this->parseSelfType();
6837
6838
        $parameter = $this->parseFormalParameterOrByReference();
6839
        $parameter->addChild($self);
6840
6841
        return $parameter;
6842
    }
6843
6844
    /**
6845
     * This method will parse a formal parameter that has the keyword static as
6846
     * parameter type hint.
6847
     *
6848
     * <code>
6849
     * class Foo
6850
     * {
6851
     *     //                   -------
6852
     *     public function test(static $o) {}
6853
     *     //                   -------
6854
     * }
6855
     * </code>
6856
     *
6857
     * @return ASTFormalParameter
6858
     * @since 2.9.2
6859
     */
6860
    private function parseFormalParameterAndStaticTypeHint()
6861
    {
6862
        $self = $this->parseStaticType();
6863
6864
        $parameter = $this->parseFormalParameterOrByReference();
6865
        $parameter->addChild($self);
6866
6867
        return $parameter;
6868
    }
6869
6870
    /**
6871
     * @return ASTSelfReference
6872
     */
6873 3
    private function parseSelfType()
6874
    {
6875 3
        return $this->parseSelfReference($this->consumeToken(Tokens::T_SELF));
6876
    }
6877
6878
    /**
6879
     * @return ASTStaticReference
6880
     */
6881 3
    private function parseStaticType()
6882
    {
6883 3
        return $this->parseStaticReference($this->consumeToken(Tokens::T_STATIC));
6884
    }
6885
6886
    /**
6887
     * This method will parse a formal parameter that can optionally be passed
6888
     * by reference.
6889
     *
6890
     * <code>
6891
     * //                 ---  -------
6892
     * function foo(array &$x, $y = 42) {}
6893
     * //                 ---  -------
6894
     * </code>
6895
     *
6896
     * @return ASTFormalParameter
6897
     * @since 0.9.6
6898
     */
6899 119
    private function parseFormalParameterOrByReference()
6900
    {
6901 119
        $this->consumeComments();
6902 119
        if ($this->tokenizer->peek() === Tokens::T_BITWISE_AND) {
6903 14
            return $this->parseFormalParameterAndByReference();
6904
        }
6905
6906 105
        return $this->parseFormalParameter();
6907
    }
6908
6909
    /**
6910
     * This method will parse a formal parameter that is passed by reference.
6911
     *
6912
     * <code>
6913
     * //                 ---  --------
6914
     * function foo(array &$x, &$y = 42) {}
6915
     * //                 ---  --------
6916
     * </code>
6917
     *
6918
     * @return ASTFormalParameter
6919
     * @since 0.9.6
6920
     */
6921 35
    private function parseFormalParameterAndByReference()
6922
    {
6923 35
        $this->consumeToken(Tokens::T_BITWISE_AND);
6924 35
        $this->consumeComments();
6925
6926 35
        $parameter = $this->parseFormalParameter();
6927 34
        $parameter->setPassedByReference();
6928
6929 34
        return $parameter;
6930
    }
6931
6932
    /**
6933
     * This method will parse a formal parameter. A formal parameter is at least
6934
     * a variable name, but can also contain a default parameter value.
6935
     *
6936
     * <code>
6937
     * //               --  -------
6938
     * function foo(Bar $x, $y = 42) {}
6939
     * //               --  -------
6940
     * </code>
6941
     *
6942
     * @return ASTFormalParameter
6943
     * @since 0.9.6
6944
     */
6945 404
    private function parseFormalParameter()
6946
    {
6947 404
        $parameter = $this->builder->buildAstFormalParameter();
6948
6949 404
        if (Tokens::T_ELLIPSIS === $this->tokenizer->peek()) {
6950 6
            $this->consumeToken(Tokens::T_ELLIPSIS);
6951 6
            $this->consumeComments();
6952
6953 6
            $parameter->setVariableArgList();
6954
        }
6955
6956 404
        $parameter->addChild($this->parseVariableDeclarator());
6957
6958 398
        return $parameter;
6959
    }
6960
6961
    /**
6962
     * Tests if the given token type is a valid formal parameter in the supported
6963
     * PHP version.
6964
     *
6965
     * @param int $tokenType
6966
     * @return bool
6967
     * @since 1.0.0
6968
     */
6969 409
    protected function isTypeHint($tokenType)
6970
    {
6971 409
        return match ($tokenType) {
6972 409
            Tokens::T_SELF,
6973 409
            Tokens::T_PARENT,
6974 409
            Tokens::T_CALLABLE,
6975 409
            Tokens::T_STRING,
6976 409
            Tokens::T_BACKSLASH,
6977 409
            Tokens::T_NAMESPACE,
6978 409
            Tokens::T_ARRAY,
6979 409
            Tokens::T_NULL,
6980 409
            Tokens::T_FALSE,
6981 409
            Tokens::T_STATIC => true,
6982 409
            default => false,
6983 409
        };
6984
    }
6985
6986
    /**
6987
     * Parses a type hint that is valid in the supported PHP version.
6988
     *
6989
     * @return ASTType
6990
     * @throws ParserException
6991
     * @since 1.0.0
6992
     */
6993 111
    private function parseBasicTypeHint()
6994
    {
6995 111
        $this->consumeQuestionMark();
6996
6997 111
        switch ($this->tokenizer->peek()) {
6998
            case Tokens::T_CALLABLE:
6999 7
                $this->consumeToken(Tokens::T_CALLABLE);
7000
7001 7
                return $this->builder->buildAstTypeCallable();
7002
7003
            case Tokens::T_ARRAY:
7004 1
                return $this->parseArrayType();
7005
7006
            case Tokens::T_SELF:
7007 1
                return $this->parseSelfType();
7008
7009
            case Tokens::T_STRING:
7010
            case Tokens::T_BACKSLASH:
7011
            case Tokens::T_NAMESPACE:
7012 107
                $name = $this->parseQualifiedName();
7013
7014 107
                return $this->isScalarOrCallableTypeHint($name)
7015 46
                    ? $this->parseScalarOrCallableTypeHint($name)
7016 107
                    : $this->builder->buildAstClassOrInterfaceReference($name);
7017
7018
            case Tokens::T_CLASS:
7019
                return $this->builder->buildAstClassOrInterfaceReference(
7020
                    $this->parseQualifiedName(),
7021
                );
7022
7023
            default:
7024
                throw new ParserException('Unsupported typehint');
7025
        }
7026
    }
7027
7028
    /**
7029
     * @return ASTType
7030
     */
7031 163
    protected function parseSingleTypeHint()
7032
    {
7033 163
        $this->consumeComments();
7034
7035 163
        switch ($this->tokenizer->peek()) {
7036
            case Tokens::T_ARRAY:
7037 48
                $type = $this->parseArrayType();
7038
7039 48
                break;
7040
7041
            case Tokens::T_SELF:
7042 3
                $type = $this->parseSelfType();
7043
7044 3
                break;
7045
7046
            case Tokens::T_PARENT:
7047 7
                $type = $this->parseParentType();
7048
7049 3
                break;
7050
7051
            case Tokens::T_STATIC:
7052 3
                $type = $this->parseStaticType();
7053
7054 3
                break;
7055
7056
            case Tokens::T_NULL:
7057 4
                $type = new ASTScalarType('null');
7058 4
                $token = $this->tokenizer->next();
7059
                assert($token instanceof Token);
7060 4
                $this->tokenStack->add($token);
7061
7062 4
                break;
7063
7064
            case Tokens::T_FALSE:
7065
                $type = new ASTScalarType('false');
7066
                $token = $this->tokenizer->next();
7067
                assert($token instanceof Token);
7068
                $this->tokenStack->add($token);
7069
7070
                break;
7071
7072
            case Tokens::T_ARRAY:
7073
                $type = $this->parseArrayType();
7074
7075
                break;
7076
7077
            case Tokens::T_SELF:
7078
                $type = $this->parseSelfType();
7079
7080
                break;
7081
7082
            case Tokens::T_PARENT:
7083
                $type = $this->parseParentType();
7084
7085
                break;
7086
7087
            default:
7088 111
                $type = $this->parseBasicTypeHint();
7089
7090 111
                break;
7091
        }
7092
7093 159
        $this->consumeComments();
7094
7095 159
        return $type;
7096
    }
7097
7098
    /**
7099
     * @param ASTType $firstType
7100
     *
7101
     * @return ASTUnionType
7102
     */
7103 6
    private function parseUnionTypeHint($firstType)
7104
    {
7105 6
        $types = [$firstType];
7106
7107 6
        while ($this->tokenizer->peek() === Tokens::T_BITWISE_OR) {
7108 6
            $token = $this->tokenizer->next();
7109
            assert($token instanceof Token);
7110 6
            $this->tokenStack->add($token);
7111 6
            $types[] = $this->parseSingleTypeHint();
7112
        }
7113
7114 6
        $unionType = $this->builder->buildAstUnionType();
7115 6
        foreach ($types as $type) {
7116 6
            $unionType->addChild($type);
7117
        }
7118
7119 6
        return $unionType;
7120
    }
7121
7122
    /**
7123
     * @param ASTType $firstType
7124
     *
7125
     * @return ASTIntersectionType
7126
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
7127 4
    private function parseIntersectionTypeHint($firstType)
7128
    {
7129 4
        $token = $this->tokenizer->currentToken();
7130 4
        $types = [$firstType];
7131
7132 4
        while ($this->tokenizer->peekNext() !== Tokens::T_VARIABLE && $this->addTokenToStackIfType(Tokens::T_BITWISE_AND)) {
7133 4
            $types[] = $this->parseSingleTypeHint();
7134
        }
7135
7136 4
        $intersectionType = $this->builder->buildAstIntersectionType();
7137 4
        foreach ($types as $type) {
7138
            // no scalars are allowed as intersection types
7139 4
            if ($type instanceof ASTScalarType) {
7140 1
                throw new ParserException(
7141 1
                    $type->getImage() . ' can not be used in an intersection type',
7142 1
                    0,
7143 1
                    $this->getUnexpectedTokenException($token)
7144 1
                );
7145
            }
7146
7147 4
            $intersectionType->addChild($type);
7148
        }
7149
7150 3
        return $intersectionType;
7151
    }
7152
7153
    /**
7154
     * @param ASTType $type
7155
     *
7156
     * @return ASTType
7157
     */
7158 161
    private function parseTypeHintCombination($type)
7159
    {
7160 161
        $peek = $this->tokenizer->peek();
7161
7162 161
        if ($peek === Tokens::T_BITWISE_OR) {
7163 6
            return $this->parseUnionTypeHint($type);
7164
        }
7165
7166 156
        $peekNext = $this->tokenizer->peekNext();
7167
        // sniff for &, but avoid by_reference &$variable and &...$variables.
7168 156
        if ($peek === Tokens::T_BITWISE_AND && $peekNext !== Tokens::T_VARIABLE && $peekNext !== Tokens::T_ELLIPSIS) {
7169 4
            return $this->parseIntersectionTypeHint($type);
7170
        }
7171
7172 152
        return $type;
7173
    }
7174
7175
    /**
7176
     * @param ASTNode $type
7177
     *
7178
     * @return bool
7179
     */
7180 66
    protected function canNotBeStandAloneType($type)
7181
    {
7182 66
        return $type instanceof ASTScalarType && ($type->isFalse() || $type->isNull());
7183
    }
7184
7185
    /**
7186
     * Parses a type hint that is valid in the supported PHP version.
7187
     *
7188
     * @return ASTType
7189
     * @throws ParserException
7190
     * @since 1.0.0
7191
     */
7192 165
    protected function parseTypeHint()
7193
    {
7194 165
        $this->consumeComments();
7195 165
        $token = $this->tokenizer->currentToken();
7196 165
        $type = $this->parseSingleTypeHint();
7197
7198 161
        $type = $this->parseTypeHintCombination($type);
7199
7200 160
        if ($this->canNotBeStandAloneType($type)) {
7201 1
            throw new ParserException(
7202 1
                $type->getImage() . ' can not be used as a standalone type',
7203 1
                0,
7204 1
                $this->getUnexpectedTokenException($token)
7205 1
            );
7206
        }
7207
7208 159
        return $type;
7209
    }
7210
7211
    /**
7212
     * Extracts all dependencies from a callable body.
7213
     *
7214
     * @return ASTScope
7215
     * @since 0.9.12
7216
     */
7217 1109
    private function parseScope()
7218
    {
7219 1109
        $scope = $this->builder->buildAstScope();
7220
7221 1109
        $this->tokenStack->push();
7222
7223 1109
        $this->consumeComments();
7224 1109
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
7225
7226 1109
        while (($stmt = $this->parseOptionalStatement()) !== null) {
7227
            // TODO: Remove if-statement once, we have translated functions and
7228
            //       closures into ast-nodes
7229 881
            if ($stmt instanceof ASTNode) {
7230 881
                $scope->addChild($stmt);
7231
            }
7232
        }
7233
7234 1081
        $this->consumeComments();
7235 1081
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
7236
7237 1081
        return $this->setNodePositionsAndReturn($scope);
7238
    }
7239
7240
    /**
7241
     * Parse a statement.
7242
     *
7243
     * @return ASTNode
7244
     * @throws UnexpectedTokenException
7245
     * @throws TokenStreamEndException
7246
     * @since 1.0.0
7247
     */
7248 16
    private function parseStatement()
7249
    {
7250 16
        if ($stmt = $this->parseOptionalStatement()) {
7251 15
            return $stmt;
7252
        }
7253
7254 1
        throw $this->getUnexpectedNextTokenException();
7255
    }
7256
7257
    /**
7258
     * Parses an optional statement or returns <b>null</b>.
7259
     *
7260
     * @return ASTNode|null
7261
     * @throws ParserException
7262
     * @since 0.9.8
7263
     */
7264 1436
    private function parseOptionalStatement()
7265
    {
7266 1436
        switch ($this->tokenizer->peek()) {
7267
            case Tokens::T_ECHO:
7268 105
                return $this->parseEchoStatement();
7269
7270
            case Tokens::T_SWITCH:
7271 17
                return $this->parseSwitchStatement();
7272
7273
            case Tokens::T_TRY:
7274 32
                return $this->parseTryStatement();
7275
7276
            case Tokens::T_THROW:
7277 19
                return $this->parseThrowStatement();
7278
7279
            case Tokens::T_IF:
7280 108
                return $this->parseIfStatement();
7281
7282
            case Tokens::T_FOR:
7283 50
                return $this->parseForStatement();
7284
7285
            case Tokens::T_FOREACH:
7286 57
                return $this->parseForeachStatement();
7287
7288
            case Tokens::T_DO:
7289 8
                return $this->parseDoWhileStatement();
7290
7291
            case Tokens::T_WHILE:
7292 6
                return $this->parseWhileStatement();
7293
7294
            case Tokens::T_RETURN:
7295 358
                return $this->parseReturnStatement();
7296
7297
            case Tokens::T_BREAK:
7298 21
                return $this->parseBreakStatement();
7299
7300
            case Tokens::T_CONTINUE:
7301 4
                return $this->parseContinueStatement();
7302
7303
            case Tokens::T_GOTO:
7304 8
                return $this->parseGotoStatement();
7305
7306
            case Tokens::T_GLOBAL:
7307 4
                return $this->parseGlobalStatement();
7308
7309
            case Tokens::T_UNSET:
7310 2
                return $this->parseUnsetStatement();
7311
7312
            case Tokens::T_ENUM:
7313
            case Tokens::T_STRING:
7314 189
                $token = $this->tokenizer->currentToken();
7315 189
                $nextType = $this->tokenizer->peekNext();
7316
7317 189
                if ($token && $token->image === 'enum' && $nextType === Tokens::T_STRING) {
7318 1
                    $package = $this->getNamespaceOrPackage();
7319 1
                    $package->addType($enum = $this->parseEnumDeclaration());
7320
7321 1
                    $this->builder->restoreEnum($enum);
7322 1
                    $this->compilationUnit->addChild($enum);
7323
7324 1
                    return $enum;
7325
                }
7326
7327 188
                if ($nextType === Tokens::T_COLON) {
7328 8
                    return $this->parseLabelStatement();
7329
                }
7330
7331 180
                break;
7332
7333
            case Tokens::T_CONST:
7334 2
                return $this->parseConstantDefinition();
7335
7336
            case Tokens::T_FN:
7337
                return $this->parseLambdaFunctionDeclaration();
7338
7339
            case Tokens::T_FUNCTION:
7340 844
                return $this->parseFunctionOrClosureDeclaration();
7341
7342
            case Tokens::T_COMMENT:
7343 25
                return $this->parseCommentWithOptionalInlineClassOrInterfaceReference();
7344
7345
            case Tokens::T_DOC_COMMENT:
7346 3
                return $this->builder->buildAstComment(
7347 3
                    $this->consumeToken(Tokens::T_DOC_COMMENT)->image,
7348 3
                );
7349
7350
            case Tokens::T_CURLY_BRACE_OPEN:
7351 1
                return $this->parseRegularScope();
7352
7353
            case Tokens::T_DECLARE:
7354 14
                return $this->parseDeclareStatement();
7355
7356
            case Tokens::T_ELSE:
7357
            case Tokens::T_ENDIF:
7358
            case Tokens::T_ELSEIF:
7359
            case Tokens::T_ENDFOR:
7360
            case Tokens::T_ENDWHILE:
7361
            case Tokens::T_ENDSWITCH:
7362
            case Tokens::T_ENDDECLARE:
7363
            case Tokens::T_ENDFOREACH:
7364
            case Tokens::T_CURLY_BRACE_CLOSE:
7365 1109
                return null;
7366
7367
            case Tokens::T_CLOSE_TAG:
7368 3
                if ($this->parseNonePhpCode() === Tokenizer::T_EOF) {
7369
                    return null;
7370
                }
7371
7372 3
                return $this->parseOptionalStatement();
7373
7374
            case Tokens::T_TRAIT:
7375 59
                $package = $this->getNamespaceOrPackage();
7376 59
                $package->addType($trait = $this->parseTraitDeclaration());
7377
7378 58
                $this->builder->restoreTrait($trait);
7379 58
                $this->compilationUnit->addChild($trait);
7380
7381 58
                return $trait;
7382
7383
            case Tokens::T_INTERFACE:
7384 100
                $package = $this->getNamespaceOrPackage();
7385 100
                $package->addType($interface = $this->parseInterfaceDeclaration());
7386
7387 92
                $this->builder->restoreInterface($interface);
7388 92
                $this->compilationUnit->addChild($interface);
7389
7390 92
                return $interface;
7391
7392
            case Tokens::T_CLASS:
7393
            case Tokens::T_FINAL:
7394
            case Tokens::T_ABSTRACT:
7395
            case Tokens::T_READONLY:
7396 523
                $package = $this->getNamespaceOrPackage();
7397 523
                $package->addType($class = $this->parseClassDeclaration());
7398
7399 500
                $this->builder->restoreClass($class);
7400 500
                $this->compilationUnit->addChild($class);
7401
7402 500
                return $class;
7403
7404
            case Tokens::T_YIELD:
7405 8
                return $this->parseYield(true);
7406
        }
7407
7408 434
        $this->tokenStack->push();
7409 434
        $stmt = $this->builder->buildAstStatement();
7410
7411 434
        if (($expr = $this->parseOptionalExpression()) !== null) {
7412 406
            $stmt->addChild($expr);
7413
        }
7414
7415 417
        if ($this->echoing && $this->tokenizer->peek() === Tokens::T_COMMA) {
7416
            $this->consumeToken(Tokens::T_COMMA);
7417
            $this->parseOptionalStatement();
7418
        } else {
7419 417
            $this->parseStatementTermination();
7420
        }
7421
7422 414
        return $this->setNodePositionsAndReturn($stmt);
7423
    }
7424
7425
    /**
7426
     * Parses a sequence of none php code tokens and returns the token type of
7427
     * the next token.
7428
     *
7429
     * @return int
7430
     * @since 0.9.12
7431
     */
7432 215
    private function parseNonePhpCode()
7433
    {
7434 215
        $this->consumeToken(Tokens::T_CLOSE_TAG);
7435
7436 211
        $this->tokenStack->push();
7437 211
        while (($tokenType = $this->tokenizer->peek()) !== Tokenizer::T_EOF) {
7438
            switch ($tokenType) {
7439
                case Tokens::T_OPEN_TAG:
7440
                case Tokens::T_OPEN_TAG_WITH_ECHO:
7441 8
                    $this->consumeToken($tokenType);
7442 8
                    $tokenType = $this->tokenizer->peek();
7443
7444 8
                    break 2;
7445
7446
                default:
7447 8
                    $this->consumeToken($tokenType);
7448
7449 8
                    break;
7450
            }
7451
        }
7452 211
        $this->tokenStack->pop();
7453
7454 211
        return $tokenType;
7455
    }
7456
7457
    /**
7458
     * Parses a comment and optionally an embedded class or interface type
7459
     * annotation.
7460
     *
7461
     * @return ASTComment
7462
     * @since 0.9.8
7463
     */
7464 25
    private function parseCommentWithOptionalInlineClassOrInterfaceReference()
7465
    {
7466 25
        $token = $this->consumeToken(Tokens::T_COMMENT);
7467
7468 25
        $comment = $this->builder->buildAstComment($token->image);
7469 25
        if (preg_match(self::REGEXP_INLINE_TYPE, $token->image, $match)) {
7470 4
            $image = $this->useSymbolTable->lookup($match[1]) ?: $match[1];
7471
7472 4
            $comment->addChild(
7473 4
                $this->builder->buildAstClassOrInterfaceReference($image),
7474 4
            );
7475
        }
7476
7477 25
        $comment->configureLinesAndColumns(
7478 25
            $token->startLine,
7479 25
            $token->endLine,
7480 25
            $token->startColumn,
7481 25
            $token->endColumn,
7482 25
        );
7483
7484 25
        return $comment;
7485
    }
7486
7487
    /**
7488
     * Parses an optional set of bound closure variables.
7489
     *
7490
     * @template T of ASTClosure
7491
     *
7492
     * @param T $closure The context closure instance.
7493
     * @return T
7494
     * @since 1.0.0
7495
     */
7496 29
    private function parseOptionalBoundVariables(
7497
        ASTClosure $closure,
7498
    ): ASTClosure {
7499 29
        $this->consumeComments();
7500
7501 29
        if (Tokens::T_USE === $this->tokenizer->peek()) {
7502 1
            return $this->parseBoundVariables($closure);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parseBoundVariables($closure) returns the type PDepend\Source\AST\ASTClosure which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
7503
        }
7504
7505 28
        return $closure;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $closure returns the type PDepend\Source\AST\ASTClosure which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
7506
    }
7507
7508
    /**
7509
     * Parses a list of bound closure variables.
7510
     *
7511
     * @template T of ASTClosure
7512
     *
7513
     * @param T $closure The parent closure instance.
7514
     * @return T
7515
     * @since 0.9.5
7516
     */
7517 1
    private function parseBoundVariables(ASTClosure $closure): ASTClosure
7518
    {
7519 1
        $this->consumeToken(Tokens::T_USE);
7520
7521 1
        $this->consumeComments();
7522 1
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
7523
7524 1
        while ($this->tokenizer->peek() !== Tokenizer::T_EOF) {
7525 1
            $this->consumeComments();
7526
7527 1
            if ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_CLOSE) {
7528
                break;
7529
            }
7530
7531 1
            if ($this->tokenizer->peek() === Tokens::T_BITWISE_AND) {
7532
                $this->consumeToken(Tokens::T_BITWISE_AND);
7533
                $this->consumeComments();
7534
            }
7535
7536 1
            $this->consumeToken(Tokens::T_VARIABLE);
7537
            $this->consumeComments();
7538
7539
            if ($this->tokenizer->peek() === Tokens::T_COMMA) {
7540
                $this->consumeToken(Tokens::T_COMMA);
7541
7542
                continue;
7543
            }
7544
7545
            break;
7546
        }
7547
7548
        $this->consumeComments();
7549
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
7550
7551
        return $closure;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $closure returns the type PDepend\Source\AST\ASTClosure which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
7552
    }
7553
7554
    /**
7555
     * Parses a php class/method name chain.
7556
     *
7557
     * <code>
7558
     * PDepend\Source\Parser::parse();
7559
     * </code>
7560
     *
7561
     * @return string
7562
     * @throws NoActiveScopeException
7563
     * @link   http://php.net/manual/en/language.namespaces.importing.php
7564
     */
7565 701
    private function parseQualifiedName()
7566
    {
7567 701
        $fragments = $this->parseQualifiedNameRaw();
7568
7569
        // Check for fully qualified name
7570 698
        if ($fragments[0] === '\\') {
7571 54
            return implode('', $fragments);
7572
        }
7573
7574 673
        if ($this->isScalarOrCallableTypeHint($fragments[0])) {
7575 47
            return $fragments[0];
7576
        }
7577
7578
        // Search for a use alias
7579 642
        $mapsTo = $this->useSymbolTable->lookup($fragments[0]);
7580
7581 642
        if ($mapsTo !== null) {
7582
            // Remove alias and add real namespace
7583 13
            array_shift($fragments);
7584 13
            array_unshift($fragments, $mapsTo);
7585
        } elseif (
7586 636
            isset($this->namespaceName)
7587 636
            && !$this->namespacePrefixReplaced
7588
        ) {
7589
            // Prepend current namespace
7590 26
            array_unshift($fragments, $this->namespaceName, '\\');
7591
        }
7592
7593 642
        return implode('', $fragments);
7594
    }
7595
7596
    /**
7597
     * This method parses a qualified PHP 5.3 class, interface and namespace
7598
     * identifier and returns the collected tokens as a string array.
7599
     *
7600
     * @return array<string>
7601
     * @since 0.9.5
7602
     */
7603 705
    private function parseQualifiedNameRaw()
7604
    {
7605
        // Reset namespace prefix flag
7606 705
        $this->namespacePrefixReplaced = false;
7607
7608
        // Consume comments and fetch first token type
7609 705
        $this->consumeComments();
7610 705
        $tokenType = $this->tokenizer->peek();
7611
7612 705
        $qualifiedName = [];
7613
7614 705
        if ($tokenType === Tokens::T_NAMESPACE) {
7615
            // Consume namespace keyword
7616 22
            $this->consumeToken(Tokens::T_NAMESPACE);
7617 22
            $this->consumeComments();
7618
7619
            // Add current namespace as first token
7620 22
            $qualifiedName = [$this->namespaceName ?? ''];
7621
7622
            // Set prefixed flag to true
7623 22
            $this->namespacePrefixReplaced = true;
7624 702
        } elseif ($this->isClassName($tokenType)) {
7625 672
            $qualifiedName[] = $this->parseClassName();
7626
7627 672
            $this->consumeComments();
7628 672
            $tokenType = $this->tokenizer->peek();
7629
7630
            // Stop here for simple identifier
7631 672
            if ($tokenType !== Tokens::T_BACKSLASH) {
7632 644
                return $qualifiedName;
7633
            }
7634
        }
7635
7636
        do {
7637
            // Next token must be a namespace separator
7638 127
            $this->consumeToken(Tokens::T_BACKSLASH);
7639 122
            $this->consumeComments();
7640
7641
            // Append to qualified name
7642 122
            $qualifiedName[] = '\\';
7643
7644 122
            if ($nextElement = $this->parseQualifiedNameElement($qualifiedName)) {
7645 120
                $qualifiedName[] = $nextElement;
7646
            }
7647
7648 121
            $this->consumeComments();
7649
7650
            // Get next token type
7651 121
            $tokenType = $this->tokenizer->peek();
7652 121
        } while ($tokenType === Tokens::T_BACKSLASH);
7653
7654 120
        return $qualifiedName;
7655
    }
7656
7657
    /**
7658
     * Determines if the given image is a PHP 7 type hint.
7659
     *
7660
     * @param string $image
7661
     * @return bool
7662
     */
7663 676
    protected function isScalarOrCallableTypeHint($image)
7664
    {
7665 676
        return match (strtolower($image)) {
7666 676
            'int',
7667 676
            'bool',
7668 676
            'float',
7669 676
            'string',
7670 676
            'callable',
7671 676
            'iterable',
7672 676
            'void',
7673 676
            'never' => true,
7674 676
            default => false,
7675 676
        };
7676
    }
7677
7678
    /**
7679
     * @param array<string> $previousElements
7680
     * @return string|null
7681
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
7682 122
    private function parseQualifiedNameElement(array $previousElements)
7683
    {
7684 122
        if (Tokens::T_CURLY_BRACE_OPEN !== $this->tokenizer->peek()) {
7685 120
            return $this->parseClassName();
7686
        }
7687
7688 3
        if (count($previousElements) >= 2 && '\\' === end($previousElements)) {
7689 2
            return null;
7690
        }
7691
7692 1
        throw $this->getUnexpectedNextTokenException();
7693
    }
7694
7695
    /**
7696
     * This method parses a PHP 5.3 namespace declaration.
7697
     *
7698
     * @throws NoActiveScopeException
7699
     * @since 0.9.5
7700
     */
7701 84
    private function parseNamespaceDeclaration(): void
7702
    {
7703
        // Consume namespace keyword and strip optional comments
7704 84
        $this->consumeToken(Tokens::T_NAMESPACE);
7705 84
        $this->consumeComments();
7706
7707 84
        $tokenType = $this->tokenizer->peek();
7708
7709
        // Search for a namespace identifier
7710 84
        if ($this->isClassName($tokenType)) {
7711
            // Reset namespace property
7712 79
            $this->namespaceName = null;
7713
7714 79
            $qualifiedName = $this->parseQualifiedName();
7715
7716 79
            $this->consumeComments();
7717 79
            if ($this->tokenizer->peek() === Tokens::T_CURLY_BRACE_OPEN) {
7718 28
                $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
7719
            } else {
7720 51
                $this->consumeToken(Tokens::T_SEMICOLON);
7721
            }
7722
7723
            // Create a package for this namespace
7724 79
            $this->namespaceName = $qualifiedName;
7725
7726 79
            $this->useSymbolTable->resetScope();
7727 10
        } elseif ($tokenType === Tokens::T_BACKSLASH) {
7728
            // Same namespace reference, something like:
7729
            //   new namespace\Foo();
7730
            // or:
7731
            //   $x = namespace\foo::bar();
7732
7733
            // Now parse a qualified name
7734 1
            $this->parseQualifiedNameRaw();
7735
        } else {
7736
            // Consume opening curly brace
7737 9
            $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
7738
7739
            // Create a package for this namespace
7740 8
            $this->namespaceName = '';
7741
7742 8
            $this->useSymbolTable->resetScope();
7743
        }
7744
7745 82
        $this->reset();
7746
    }
7747
7748
    /**
7749
     * Parses use declarations that are valid in the supported php version and
7750
     * adds a mapping between short name and full qualified name to the use
7751
     * symbol table.
7752
     *
7753
     * <code>
7754
     * use \foo\bar as fb,
7755
     *     \foobar\Bar;
7756
     * </code>
7757
     *
7758
     * @since 0.9.5
7759
     */
7760 18
    private function parseUseDeclarations(): void
7761
    {
7762
        // Consume use keyword
7763 18
        $this->consumeToken(Tokens::T_USE);
7764 18
        $this->consumeComments();
7765
7766
        // Consume const and function tokens
7767 18
        $nextToken = $this->tokenizer->peek();
7768
7769
        switch ($nextToken) {
7770
            case Tokens::T_CONST:
7771
            case Tokens::T_FUNCTION:
7772 4
                $this->consumeToken($nextToken);
7773
        }
7774
7775
        // Parse all use declarations
7776 18
        $this->parseUseDeclaration();
7777 17
        $this->consumeComments();
7778
7779
        // Consume closing semicolon
7780 17
        $this->consumeToken(Tokens::T_SEMICOLON);
7781
7782
        // Reset any previous state
7783 17
        $this->reset();
7784
    }
7785
7786
    /**
7787
     * This method parses a single use declaration and adds a mapping between
7788
     * short name and full qualified name to the use symbol table.
7789
     *
7790
     * @throws NoActiveScopeException
7791
     * @since 0.9.5
7792
     */
7793 18
    private function parseUseDeclaration(): void
7794
    {
7795 18
        $fragments = $this->parseQualifiedNameRaw();
7796 17
        $this->consumeComments();
7797
7798
        // Add leading backslash, because aliases must be full qualified
7799
        // http://php.net/manual/en/language.namespaces.importing.php
7800 17
        if ($fragments[0] !== '\\') {
7801 14
            array_unshift($fragments, '\\');
7802
        }
7803
7804 17
        $this->parseUseDeclarationForVersion($fragments);
7805
7806
        // Check for a following use declaration
7807 17
        if ($this->tokenizer->peek() === Tokens::T_COMMA) {
7808
            // Consume comma token and comments
7809 8
            $this->consumeToken(Tokens::T_COMMA);
7810 8
            $this->consumeComments();
7811
7812 8
            $this->parseUseDeclaration();
7813
        }
7814
    }
7815
7816
    /**
7817
     * @param array<string> $fragments
7818
     */
7819 17
    private function parseUseDeclarationForVersion(array $fragments): void
7820
    {
7821 17
        if (Tokens::T_CURLY_BRACE_OPEN === $this->tokenizer->peek()) {
7822 2
            $this->parseUseDeclarationVersion70($fragments);
7823
7824 2
            return;
7825
        }
7826
7827 15
        $image = $this->parseNamespaceImage($fragments);
7828 15
        if ($image === false) {
7829
            return;
7830
        }
7831
7832
        // Add mapping between image and qualified name to symbol table
7833 15
        $this->useSymbolTable->add($image, implode('', $fragments));
7834
    }
7835
7836
    /**
7837
     * @param array<string> $fragments
7838
     * @return false|string
7839
     */
7840 17
    private function parseNamespaceImage(array $fragments)
7841
    {
7842 17
        if ($this->tokenizer->peek() === Tokens::T_AS) {
7843 9
            $this->consumeToken(Tokens::T_AS);
7844 9
            $this->consumeComments();
7845
7846 9
            $image = $this->consumeToken(Tokens::T_STRING)->image;
7847 9
            $this->consumeComments();
7848
        } else {
7849 15
            $image = end($fragments);
7850
        }
7851
7852 17
        return $image;
7853
    }
7854
7855
    /**
7856
     * Parses a single constant definition with one or more constant declarators.
7857
     *
7858
     * <code>
7859
     * class Foo
7860
     * {
7861
     * //  ------------------------
7862
     *     const FOO = 42, BAR = 23;
7863
     * //  ------------------------
7864
     * }
7865
     * </code>
7866
     *
7867
     * @return ASTConstantDefinition
7868
     * @since 0.9.6
7869
     */
7870 74
    private function parseConstantDefinition()
7871
    {
7872 74
        $this->tokenStack->push();
7873
7874 74
        $token = $this->consumeToken(Tokens::T_CONST);
7875
7876 74
        $definition = $this->builder->buildAstConstantDefinition($token->image);
7877 74
        $definition->setComment($this->docComment);
7878
7879
        do {
7880 74
            $definition->addChild($this->parseConstantDeclarator());
7881
7882 71
            $this->consumeComments();
7883 71
            $tokenType = $this->tokenizer->peek();
7884
7885 71
            if ($tokenType === Tokens::T_SEMICOLON) {
7886 71
                break;
7887
            }
7888
7889 9
            $this->consumeToken(Tokens::T_COMMA);
7890 9
        } while ($tokenType !== Tokenizer::T_EOF);
7891
7892 71
        $definition = $this->setNodePositionsAndReturn($definition);
7893
7894 71
        $this->consumeToken(Tokens::T_SEMICOLON);
7895
7896 71
        return $definition;
7897
    }
7898
7899
    /**
7900
     * Constant cannot be typed before PHP 8.3.
7901
     *
7902
     * @return ASTConstantDeclarator
7903
     * @since  1.16.0
7904
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
7905 1
    protected function parseTypedConstantDeclarator()
7906
    {
7907 1
        throw $this->getUnexpectedNextTokenException();
7908
    }
7909
7910
    /**
7911
     * Parses a single constant declarator.
7912
     *
7913
     * <code>
7914
     * class Foo
7915
     * {
7916
     *     //    --------
7917
     *     const BAR = 42;
7918
     *     //    --------
7919
     * }
7920
     * </code>
7921
     *
7922
     * Or in a comma separated constant defintion:
7923
     *
7924
     * <code>
7925
     * class Foo
7926
     * {
7927
     *     //    --------
7928
     *     const BAR = 42,
7929
     *     //    --------
7930
     *
7931
     *     //    --------------
7932
     *     const BAZ = 'Foobar',
7933
     *     //    --------------
7934
     *
7935
     *     //    ----------
7936
     *     const FOO = 3.14;
7937
     *     //    ----------
7938
     * }
7939
     * </code>
7940
     *
7941
     * @return ASTConstantDeclarator
7942
     * @since 0.9.6
7943
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
7944 74
    private function parseConstantDeclarator()
7945
    {
7946
        // Remove leading comments and create a new token stack
7947 74
        $this->consumeComments();
7948 74
        $this->tokenStack->push();
7949
7950 74
        $nextToken = $this->tokenizer->peekNext();
7951
7952 74
        if (!$nextToken) {
7953
            throw $this->getUnexpectedNextTokenException();
7954
        }
7955
7956 74
        if ($this->isConstantName($nextToken)) {
7957 1
            return $this->parseTypedConstantDeclarator();
7958
        }
7959
7960 73
        $tokenType = $this->tokenizer->peek();
7961
7962 73
        if (!$this->isConstantName($tokenType)) {
7963 1
            throw $this->getUnexpectedNextTokenException();
7964
        }
7965
7966 72
        $token = $this->consumeToken($tokenType);
7967
7968 72
        $this->consumeComments();
7969 72
        $this->consumeToken(Tokens::T_EQUAL);
7970
7971 72
        $declarator = $this->builder->buildAstConstantDeclarator($token->image);
7972 72
        $declarator->setValue($this->parseConstantDeclaratorValue());
7973
7974 71
        return $this->setNodePositionsAndReturn($declarator);
7975
    }
7976
7977
    /**
7978
     * Parses the value of a php constant. By default this can be only static
7979
     * values that were allowed in the oldest supported PHP version.
7980
     *
7981
     * @return ?ASTValue
7982
     * @since 2.2.x
7983
     */
7984 72
    protected function parseConstantDeclaratorValue()
7985
    {
7986 72
        if ($this->isFollowedByStaticValueOrStaticArray()) {
7987 63
            return $this->parseVariableDefaultValue();
7988
        }
7989
7990
        // Else it would be provided as ASTLiteral or expressions object.
7991 13
        $value = new ASTValue();
7992 13
        $value->setValue($this->parseOptionalExpression());
7993
7994 13
        return $value;
7995
    }
7996
7997
    /**
7998
     * This method parses a static variable declaration list, a member primary
7999
     * prefix invoked in the static context of a class or it parses a static
8000
     * closure declaration.
8001
     *
8002
     * Static variable:
8003
     * <code>
8004
     * function foo() {
8005
     * //  ------------------------------
8006
     *     static $foo, $bar, $baz = null;
8007
     * //  ------------------------------
8008
     * }
8009
     * </code>
8010
     *
8011
     * Static method invocation:
8012
     * <code>
8013
     * class Foo {
8014
     *     public function baz() {
8015
     * //      ----------------
8016
     *         static::foobar();
8017
     * //      ----------------
8018
     *     }
8019
     *     public function foobar() {}
8020
     * }
8021
     *
8022
     * class Bar extends Foo {
8023
     *     public function foobar() {}
8024
     * }
8025
     * </code>
8026
     *
8027
     * Static closure declaration:
8028
     * <code>
8029
     * $closure = static function($x, $y) {
8030
     *     return ($x * $y);
8031
     * };
8032
     * </code>
8033
     *
8034
     * @return ASTNode
8035
     * @throws ParserException
8036
     * @throws UnexpectedTokenException
8037
     * @since 0.9.6
8038
     */
8039 25
    private function parseStaticVariableDeclarationOrMemberPrimaryPrefix()
8040
    {
8041 25
        $this->tokenStack->push();
8042
8043
        // Consume static token and strip optional comments
8044 25
        $token = $this->consumeToken(Tokens::T_STATIC);
8045 25
        $this->consumeComments();
8046
8047
        // Fetch next token type
8048 25
        $tokenType = $this->tokenizer->peek();
8049
8050
        if (
8051 25
            $tokenType === Tokens::T_PARENTHESIS_OPEN
8052 25
            || $tokenType === Tokens::T_DOUBLE_COLON
8053
        ) {
8054 5
            return $this->setNodePositionsAndReturn(
8055 5
                $this->parseStaticMemberPrimaryPrefix(
8056 5
                    $this->parseStaticReference($token),
8057 5
                ),
8058 5
            );
8059
        }
8060 20
        if ($tokenType === Tokens::T_FUNCTION) {
8061 7
            $closure = $this->parseClosureDeclaration();
8062 7
            $closure->setStatic(true);
8063
8064 7
            return $this->setNodePositionsAndReturn($closure);
8065
        }
8066 13
        if ($tokenType === Tokens::T_FN) {
8067
            $closure = $this->parseLambdaFunctionDeclaration();
8068
            $closure->setStatic(true);
8069
8070
            return $this->setNodePositionsAndReturn($closure);
8071
        }
8072
8073 13
        return $this->setNodePositionsAndReturn(
8074 13
            $this->parseStaticVariableDeclaration($token),
8075 13
        );
8076
    }
8077
8078
    /**
8079
     * This method will parse a static variable declaration.
8080
     *
8081
     * <code>
8082
     * function foo()
8083
     * {
8084
     *     // First declaration
8085
     *     static $foo;
8086
     *     // Second declaration
8087
     *     static $bar = array();
8088
     *     // Third declaration
8089
     *     static $baz    = array(),
8090
     *            $foobar = null,
8091
     *            $barbaz;
8092
     * }
8093
     * </code>
8094
     *
8095
     * @param Token $token Token with the "static" keyword.
8096
     * @return ASTStaticVariableDeclaration
8097
     * @since 0.9.6
8098
     */
8099 13
    private function parseStaticVariableDeclaration(Token $token)
8100
    {
8101 13
        $staticDeclaration = $this->builder->buildAstStaticVariableDeclaration(
8102 13
            $token->image,
8103 13
        );
8104
8105
        // Strip optional comments
8106 13
        $this->consumeComments();
8107
8108
        // Fetch next token type
8109 13
        $tokenType = $this->tokenizer->peek();
8110
8111 13
        while ($tokenType !== Tokenizer::T_EOF) {
8112 13
            $staticDeclaration->addChild($this->parseVariableDeclarator());
8113
8114 13
            $this->consumeComments();
8115
8116
            // Semicolon terminates static declaration
8117 13
            $tokenType = $this->tokenizer->peek();
8118
8119 13
            if ($tokenType === Tokens::T_SEMICOLON) {
8120 13
                break;
8121
            }
8122
8123
            // We are here, so there must be a next declarator
8124 8
            $this->consumeToken(Tokens::T_COMMA);
8125
        }
8126
8127 13
        return $staticDeclaration;
8128
    }
8129
8130
    /**
8131
     * This method will parse a variable declarator.
8132
     *
8133
     * <code>
8134
     * // Parameter declarator
8135
     * function foo($x = 23) {
8136
     * }
8137
     * // Property declarator
8138
     * class Foo{
8139
     *     protected $bar = 42;
8140
     * }
8141
     * // Static declarator
8142
     * function baz() {
8143
     *     static $foo;
8144
     * }
8145
     * </code>
8146
     *
8147
     * @return ASTVariableDeclarator
8148
     * @throws MissingValueException
8149
     * @since 0.9.6
8150
     */
8151 486
    private function parseVariableDeclarator()
8152
    {
8153 486
        $this->tokenStack->push();
8154
8155 486
        $name = $this->consumeToken(Tokens::T_VARIABLE)->image;
8156 482
        $this->consumeComments();
8157
8158 482
        $declarator = $this->builder->buildAstVariableDeclarator($name);
8159
8160 482
        if ($this->tokenizer->peek() === Tokens::T_EQUAL) {
8161 126
            $this->consumeToken(Tokens::T_EQUAL);
8162 126
            $value = $this->parseVariableDefaultValue();
8163 123
            if (!$value) {
8164
                throw new MissingValueException($this->tokenizer);
8165
            }
8166 123
            $declarator->setValue($value);
8167
        }
8168
8169 479
        return $this->setNodePositionsAndReturn($declarator);
8170
    }
8171
8172
    /**
8173
     * This method will parse a default value after a parameter/static variable/constant
8174
     * declaration.
8175
     *
8176
     * @return ?ASTValue
8177
     * @since 2.11.0
8178
     */
8179 174
    private function parseVariableDefaultValue()
8180
    {
8181 174
        if ($this->tokenizer->peek() === Tokens::T_NEW) {
8182 2
            $defaultValue = new ASTValue();
8183 2
            $defaultValue->setValue($this->parseAllocationExpression());
8184
8185 2
            return $defaultValue;
8186
        }
8187
8188 173
        return $this->parseStaticValueOrStaticArray();
8189
    }
8190
8191
    /**
8192
     * This method will parse a static value or a static array as it is
8193
     * used as default value for a parameter or property declaration.
8194
     *
8195
     * @return ?ASTValue
8196
     * @since 0.9.6
8197
     */
8198 173
    private function parseStaticValueOrStaticArray()
8199
    {
8200 173
        $this->consumeComments();
8201
8202 173
        if ($this->isArrayStartDelimiter()) {
8203
            // TODO: Use default value as value!
8204 40
            $defaultValue = $this->doParseArray(true);
0 ignored issues
show
Unused Code introduced by
The assignment to $defaultValue is dead and can be removed.
Loading history...
8205
8206 39
            $value = new ASTValue();
8207 39
            $value->setValue([]);
8208
8209 39
            return $value;
8210
        }
8211
8212 155
        return $this->parseStaticValue();
8213
    }
8214
8215
    /**
8216
     * This method will parse a static default value as it is used for a
8217
     * parameter, property or constant declaration.
8218
     *
8219
     * @return ?ASTValue
8220
     * @since 0.9.5
8221
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
8222 169
    private function parseStaticValue()
8223
    {
8224 169
        $defaultValue = new ASTValue();
8225
8226 169
        $this->consumeComments();
8227
8228
        // By default all parameters positive signed
8229 169
        $signed = 1;
8230
8231 169
        $tokenType = $this->tokenizer->peek();
8232
8233 169
        while ($tokenType !== Tokenizer::T_EOF) {
8234
            switch ($tokenType) {
8235
                case Tokens::T_COMMA:
8236
                case Tokens::T_SEMICOLON:
8237
                case Tokens::T_PARENTHESIS_CLOSE:
8238 165
                    if ($defaultValue->isValueAvailable()) {
8239 163
                        return $defaultValue;
8240
                    }
8241
8242 2
                    throw new MissingValueException($this->tokenizer);
8243
8244
                case Tokens::T_NULL:
8245 37
                    $this->consumeToken(Tokens::T_NULL);
8246 37
                    $defaultValue->setValue(null);
8247
8248 37
                    break;
8249
8250
                case Tokens::T_TRUE:
8251 6
                    $this->consumeToken(Tokens::T_TRUE);
8252 6
                    $defaultValue->setValue(true);
8253
8254 6
                    break;
8255
8256
                case Tokens::T_FALSE:
8257 5
                    $this->consumeToken(Tokens::T_FALSE);
8258 5
                    $defaultValue->setValue(false);
8259
8260 5
                    break;
8261
8262
                case Tokens::T_LNUMBER:
8263 121
                    $defaultValue->setValue($signed * $this->parseIntegerNumberImage(
8264 121
                        $this->parseNumber(Tokens::T_LNUMBER),
8265 121
                    ));
8266
8267 121
                    break;
8268
8269
                case Tokens::T_DNUMBER:
8270 6
                    $defaultValue->setValue($signed * (float) $this->getNumberFromImage(
8271 6
                        $this->parseNumber(Tokens::T_DNUMBER),
8272 6
                    ));
8273
8274 6
                    break;
8275
8276
                case Tokens::T_CONSTANT_ENCAPSED_STRING:
8277 21
                    $token = $this->consumeToken(Tokens::T_CONSTANT_ENCAPSED_STRING);
8278 21
                    $defaultValue->setValue(substr($token->image, 1, -1));
8279
8280 21
                    break;
8281
8282
                case Tokens::T_DOUBLE_COLON:
8283
                    $this->consumeToken(Tokens::T_DOUBLE_COLON);
8284
8285
                    break;
8286
8287
                case Tokens::T_CLASS_FQN:
8288
                    $this->consumeToken(Tokens::T_CLASS_FQN);
8289
8290
                    break;
8291
8292
                case Tokens::T_PLUS:
8293 3
                    $this->consumeToken(Tokens::T_PLUS);
8294
8295 3
                    break;
8296
8297
                case Tokens::T_ELLIPSIS:
8298
                    $this->consumeToken(Tokens::T_ELLIPSIS);
8299
8300
                    break;
8301
8302
                case Tokens::T_MINUS:
8303 5
                    $this->consumeToken(Tokens::T_MINUS);
8304 5
                    $signed *= -1;
8305
8306 5
                    break;
8307
8308
                case Tokens::T_DOUBLE_QUOTE:
8309
                    $defaultValue->setValue($this->parseStringSequence($tokenType));
8310
8311
                    break;
8312
8313
                case Tokens::T_STATIC:
8314
                case Tokens::T_SELF:
8315
                case Tokens::T_PARENT:
8316 5
                    $node = $this->parseStandAloneExpressionTypeReference($tokenType);
8317
8318 5
                    if ($this->tokenizer->peek() === Tokens::T_DOUBLE_COLON) {
8319 5
                        $node->addChild($this->parseStaticMemberPrimaryPrefix($node));
8320
                    }
8321
8322 5
                    $defaultValue->setValue($node);
8323
8324 5
                    break;
8325
8326
                case Tokens::T_STRING:
8327
                case Tokens::T_BACKSLASH:
8328 8
                    $node = $this->builder->buildAstClassOrInterfaceReference(
8329 8
                        $this->parseQualifiedName(),
8330 8
                    );
8331
8332 8
                    if ($this->tokenizer->peek() === Tokens::T_DOUBLE_COLON) {
8333 4
                        $node->addChild($this->parseStaticMemberPrimaryPrefix($node));
8334
                    }
8335
8336 8
                    $defaultValue->setValue($node);
8337
8338 8
                    break;
8339
8340
                case Tokens::T_DIR:
8341
                case Tokens::T_FILE:
8342
                case Tokens::T_LINE:
8343
                case Tokens::T_NS_C:
8344
                case Tokens::T_FUNC_C:
8345
                case Tokens::T_CLASS_C:
8346
                case Tokens::T_METHOD_C:
8347
                case Tokens::T_SQUARED_BRACKET_OPEN:
8348
                case Tokens::T_SQUARED_BRACKET_CLOSE:
8349
                    // There is a default value but we don't handle it at the moment.
8350
                    $defaultValue->setValue(null);
8351
                    $this->consumeToken($tokenType);
8352
8353
                    break;
8354
8355
                case Tokens::T_START_HEREDOC:
8356 10
                    $defaultValue->setValue(
8357 10
                        $this->parseHeredoc()->getChild(0)->getImage(),
8358 10
                    );
8359
8360 10
                    break;
8361
8362
                default:
8363 3
                    return $this->parseStaticValueVersionSpecific($defaultValue);
8364
            }
8365
8366 166
            $this->consumeComments();
8367
8368 166
            $tokenType = $this->tokenizer->peek();
8369
        }
8370
8371
        // We should never reach this, so throw an exception
8372 1
        throw new TokenStreamEndException($this->tokenizer);
8373
    }
8374
8375
    /**
8376
     * Parses additional static values that are valid in the supported php version.
8377
     *
8378
     * @template T of ASTValue
8379
     * @param T $value
8380
     * @return T|null
8381
     * @throws UnexpectedTokenException
8382
     */
8383 4
    private function parseStaticValueVersionSpecific(ASTValue $value): ?ASTValue
8384
    {
8385 4
        $expressions = [];
8386
8387 4
        while (($tokenType = $this->tokenizer->peek()) !== Tokenizer::T_EOF) {
8388
            switch ($tokenType) {
8389
                case Tokens::T_COMMA:
8390
                case Tokens::T_CLOSE_TAG:
8391
                case Tokens::T_COLON:
8392
                case Tokens::T_DOUBLE_ARROW:
8393
                case Tokens::T_END_HEREDOC:
8394
                case Tokens::T_PARENTHESIS_CLOSE:
8395
                case Tokens::T_SEMICOLON:
8396
                case Tokens::T_SQUARED_BRACKET_CLOSE:
8397 3
                    break 2;
8398
8399
                case Tokens::T_SELF:
8400
                case Tokens::T_STRING:
8401
                case Tokens::T_PARENT:
8402
                case Tokens::T_STATIC:
8403
                case Tokens::T_DOLLAR:
8404
                case Tokens::T_VARIABLE:
8405
                case Tokens::T_BACKSLASH:
8406
                case Tokens::T_NAMESPACE:
8407 1
                    $expressions[] = $this->parseVariableOrConstantOrPrimaryPrefix();
8408
8409 1
                    break;
8410
8411 4
                case $this->isArrayStartDelimiter():
8412
                    $expressions[] = $this->doParseArray(true);
8413
8414
                    break;
8415
8416 4
                case Tokens::T_NULL:
8417 4
                case Tokens::T_TRUE:
8418 4
                case Tokens::T_FALSE:
8419 4
                case Tokens::T_LNUMBER:
8420 4
                case Tokens::T_DNUMBER:
8421 4
                case Tokens::T_BACKTICK:
8422 4
                case Tokens::T_DOUBLE_QUOTE:
8423 4
                case Tokens::T_CONSTANT_ENCAPSED_STRING:
8424 3
                    $expressions[] = $this->parseLiteralOrString();
8425
8426 3
                    break;
8427
8428 4
                case Tokens::T_QUESTION_MARK:
8429
                    $expressions[] = $this->parseConditionalExpression();
8430
8431
                    break;
8432
8433 4
                case Tokens::T_BOOLEAN_AND:
8434
                    $expressions[] = $this->parseBooleanAndExpression();
8435
8436
                    break;
8437
8438 4
                case Tokens::T_BOOLEAN_OR:
8439
                    $expressions[] = $this->parseBooleanOrExpression();
8440
8441
                    break;
8442
8443 4
                case Tokens::T_LOGICAL_AND:
8444
                    $expressions[] = $this->parseLogicalAndExpression();
8445
8446
                    break;
8447
8448 4
                case Tokens::T_LOGICAL_OR:
8449
                    $expressions[] = $this->parseLogicalOrExpression();
8450
8451
                    break;
8452
8453 4
                case Tokens::T_LOGICAL_XOR:
8454
                    $expressions[] = $this->parseLogicalXorExpression();
8455
8456
                    break;
8457
8458 4
                case Tokens::T_PARENTHESIS_OPEN:
8459
                    $expressions[] = $this->parseParenthesisExpressionOrPrimaryPrefix();
8460
8461
                    break;
8462
8463 4
                case Tokens::T_START_HEREDOC:
8464
                    $expressions[] = $this->parseHeredoc();
8465
8466
                    break;
8467
8468 4
                case Tokens::T_SL:
8469
                    $expressions[] = $this->parseShiftLeftExpression();
8470
8471
                    break;
8472
8473 4
                case Tokens::T_SR:
8474
                    $expressions[] = $this->parseShiftRightExpression();
8475
8476
                    break;
8477
8478 4
                case Tokens::T_ELLIPSIS:
8479 4
                case Tokens::T_STRING_VARNAME: // TODO: Implement this
8480 4
                case Tokens::T_PLUS: // TODO: Make this a arithmetic expression
8481 4
                case Tokens::T_MINUS:
8482 4
                case Tokens::T_MUL:
8483 3
                case Tokens::T_DIV:
8484 3
                case Tokens::T_MOD:
8485 3
                case Tokens::T_POW:
8486 2
                case Tokens::T_IS_EQUAL: // TODO: Implement compare expressions
8487 2
                case Tokens::T_IS_NOT_EQUAL:
8488 2
                case Tokens::T_IS_IDENTICAL:
8489 2
                case Tokens::T_IS_NOT_IDENTICAL:
8490 2
                case Tokens::T_BITWISE_OR:
8491 2
                case Tokens::T_BITWISE_AND:
8492 2
                case Tokens::T_BITWISE_NOT:
8493 2
                case Tokens::T_BITWISE_XOR:
8494 2
                case Tokens::T_IS_GREATER_OR_EQUAL:
8495 2
                case Tokens::T_IS_SMALLER_OR_EQUAL:
8496 2
                case Tokens::T_ANGLE_BRACKET_OPEN:
8497 2
                case Tokens::T_ANGLE_BRACKET_CLOSE:
8498 2
                case Tokens::T_EMPTY:
8499 2
                case Tokens::T_CONCAT:
8500 3
                    $token = $this->consumeToken($tokenType);
8501
8502 3
                    $expr = $this->builder->buildAstExpression($token->image);
8503 3
                    $expr->configureLinesAndColumns(
8504 3
                        $token->startLine,
8505 3
                        $token->endLine,
8506 3
                        $token->startColumn,
8507 3
                        $token->endColumn
8508 3
                    );
8509
8510 3
                    $expressions[] = $expr;
8511
8512 3
                    break;
8513
8514 1
                case Tokens::T_EQUAL:
8515 1
                case Tokens::T_OR_EQUAL:
8516 1
                case Tokens::T_SL_EQUAL:
8517 1
                case Tokens::T_SR_EQUAL:
8518 1
                case Tokens::T_AND_EQUAL:
8519 1
                case Tokens::T_DIV_EQUAL:
8520 1
                case Tokens::T_MOD_EQUAL:
8521 1
                case Tokens::T_MUL_EQUAL:
8522 1
                case Tokens::T_XOR_EQUAL:
8523 1
                case Tokens::T_PLUS_EQUAL:
8524 1
                case Tokens::T_MINUS_EQUAL:
8525 1
                case Tokens::T_CONCAT_EQUAL:
8526 1
                case Tokens::T_COALESCE_EQUAL:
8527
                    $expression = array_pop($expressions);
8528
                    if ($expression) {
8529
                        $expressions[] = $this->parseAssignmentExpression($expression);
8530
                    }
8531
8532
                    break;
8533
8534 1
                case Tokens::T_DIR:
8535 1
                case Tokens::T_FILE:
8536 1
                case Tokens::T_LINE:
8537 1
                case Tokens::T_NS_C:
8538 1
                case Tokens::T_FUNC_C:
8539 1
                case Tokens::T_CLASS_C:
8540 1
                case Tokens::T_METHOD_C:
8541
                    $expression = $this->parseConstant();
8542
                    if ($expression) {
8543
                        $expressions[] = $expression;
8544
                    }
8545
8546
                    break;
8547
8548
                    // TODO: Handle comments here
8549 1
                case Tokens::T_COMMENT:
8550 1
                case Tokens::T_DOC_COMMENT:
8551
                    $this->consumeToken($tokenType);
8552
8553
                    break;
8554
8555 1
                case Tokens::T_AT:
8556 1
                case Tokens::T_EXCLAMATION_MARK:
8557
                    $token = $this->consumeToken($tokenType);
8558
8559
                    $expr = $this->builder->buildAstUnaryExpression($token->image);
8560
                    $expr->configureLinesAndColumns(
8561
                        $token->startLine,
8562
                        $token->endLine,
8563
                        $token->startColumn,
8564
                        $token->endColumn
8565
                    );
8566
8567
                    $expressions[] = $expr;
8568
8569
                    break;
8570
8571
                default:
8572 1
                    throw $this->getUnexpectedNextTokenException();
8573
            }
8574
        }
8575
8576 3
        $expressions = $this->reduce($expressions);
8577
8578 3
        $count = count($expressions);
8579 3
        if ($count === 0) {
8580
            return null;
8581
        }
8582 3
        if ($count === 1) {
8583
            // @todo ASTValue must be a valid node.
8584
            $value->setValue($expressions[0]);
8585
8586
            return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value returns the type PDepend\Source\AST\ASTValue which is incompatible with the documented return type PDepend\Source\Language\PHP\T|null.
Loading history...
8587
        }
8588
8589 3
        $expr = $this->builder->buildAstExpression();
8590 3
        foreach ($expressions as $node) {
8591 3
            $expr->addChild($node);
8592
        }
8593 3
        $expr->configureLinesAndColumns(
8594 3
            $expressions[0]->getStartLine(),
8595 3
            $expressions[$count - 1]->getEndLine(),
8596 3
            $expressions[0]->getStartColumn(),
8597 3
            $expressions[$count - 1]->getEndColumn()
8598 3
        );
8599
8600
        // @todo ASTValue must be a valid node.
8601 3
        $value->setValue($expr);
8602
8603 3
        return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value returns the type PDepend\Source\AST\ASTValue which is incompatible with the documented return type PDepend\Source\Language\PHP\T|null.
Loading history...
8604
    }
8605
8606
    /**
8607
     * Parses fn operator of lambda function for syntax fn() => available since PHP 7.4.
8608
     *
8609
     * @return ASTClosure
8610
     * @throws UnexpectedTokenException
8611
     */
8612 4
    private function parseLambdaFunctionDeclaration()
8613
    {
8614 4
        $this->tokenStack->push();
8615
8616 4
        if ($this->tokenizer->peek() === Tokens::T_FN) {
8617 4
            $this->consumeToken(Tokens::T_FN);
8618
        }
8619
8620 4
        $closure = $this->builder->buildAstClosure();
8621 4
        $closure->setReturnsByReference($this->parseOptionalByReference());
8622 4
        $closure->addChild($this->parseFormalParameters($closure));
8623 4
        $this->parseCallableDeclarationAddition($closure);
8624
8625 4
        $closure->addChild(
8626 4
            $this->buildReturnStatement(
8627 4
                $this->consumeToken(Tokens::T_DOUBLE_ARROW)
8628 4
            )
8629 4
        );
8630
8631 4
        return $this->setNodePositionsAndReturn($closure);
8632
    }
8633
8634
    /**
8635
     * Checks if the given expression is a read/write variable as defined in
8636
     * the PHP zend_language_parser.y definition.
8637
     *
8638
     * @param ASTNode $expr The context node instance.
8639
     * @return bool
8640
     * @since 0.10.0
8641
     */
8642 24
    private function isReadWriteVariable($expr)
8643
    {
8644 24
        return $expr instanceof ASTVariable
8645 24
            || $expr instanceof ASTFunctionPostfix
8646 24
            || $expr instanceof ASTVariableVariable
8647 24
            || $expr instanceof ASTCompoundVariable
8648 24
            || $expr instanceof ASTMemberPrimaryPrefix;
8649
    }
8650
8651
    /**
8652
     * This method creates a qualified class or interface name based on the
8653
     * current parser state. By default method uses the current namespace scope
8654
     * as prefix for the given local name. And it will fallback to a previously
8655
     * parsed package annotation, when no namespace declaration was parsed.
8656
     *
8657
     * @param string $localName The local class or interface name.
8658
     * @return string
8659
     */
8660 604
    private function createQualifiedTypeName($localName)
8661
    {
8662 604
        return ltrim($this->getNamespaceOrPackageName() . '\\' . $localName, '\\');
8663
    }
8664
8665
    /**
8666
     * Returns the name of a declared names. When the parsed code is not namespaced
8667
     * this method will return the name from the @package annotation.
8668
     *
8669
     * @return ?string
8670
     * @since 0.9.8
8671
     */
8672 607
    private function getNamespaceOrPackageName()
8673
    {
8674 607
        if (!isset($this->namespaceName)) {
8675 556
            return $this->packageName;
8676
        }
8677
8678 51
        return $this->namespaceName;
8679
    }
8680
8681
    /**
8682
     * Returns the currently active package or namespace.
8683
     *
8684
     * @return ASTNamespace
8685
     * @since 1.0.0
8686
     */
8687 607
    private function getNamespaceOrPackage()
8688
    {
8689 607
        $namespace = $this->builder->buildNamespace((string) $this->getNamespaceOrPackageName());
8690 607
        $namespace->setPackageAnnotation(null === $this->namespaceName);
8691
8692 607
        return $namespace;
8693
    }
8694
8695
    /**
8696
     * Extracts the @package information from the given comment.
8697
     *
8698
     * @param string $comment
8699
     * @return string|null
8700
     */
8701 89
    private function parsePackageAnnotation($comment)
8702
    {
8703 89
        if (getenv('DISMISS_PACKAGES')) {
8704
            $this->packageName = null;
8705
            $this->globalPackageName = null;
8706
8707
            return null;
8708
        }
8709
8710 89
        $package = Builder::DEFAULT_NAMESPACE;
8711 89
        if (preg_match('#\*\s*@package\s+(\S+)#', $comment, $match)) {
8712 61
            $package = trim($match[1]);
8713 61
            if (preg_match('#\*\s*@subpackage\s+(\S+)#', $comment, $match)) {
8714 4
                $package .= '\\' . trim($match[1]);
8715
            }
8716
        }
8717
8718
        // Check for doc level comment
8719
        if (
8720 89
            $this->globalPackageName === Builder::DEFAULT_NAMESPACE
8721 89
            && $this->isFileComment()
8722
        ) {
8723 6
            $this->globalPackageName = $package;
8724
8725 6
            $this->compilationUnit->setComment($comment);
8726
        }
8727
8728 89
        return $package;
8729
    }
8730
8731
    /**
8732
     * Checks that the current token could be used as file comment.
8733
     *
8734
     * This method checks that the previous token is an open tag and the following
8735
     * token is not a class, a interface, final, abstract or a function.
8736
     *
8737
     * @return bool
8738
     */
8739 89
    private function isFileComment()
8740
    {
8741 89
        if ($this->tokenizer->prev() !== Tokens::T_OPEN_TAG) {
8742 33
            return false;
8743
        }
8744
8745 82
        $notExpectedTags = [
8746 82
            Tokens::T_CLASS,
8747 82
            Tokens::T_FINAL,
8748 82
            Tokens::T_TRAIT,
8749 82
            Tokens::T_ABSTRACT,
8750 82
            Tokens::T_FUNCTION,
8751 82
            Tokens::T_INTERFACE,
8752 82
        ];
8753
8754 82
        return !in_array($this->tokenizer->peek(), $notExpectedTags, true);
8755
    }
8756
8757
    /**
8758
     * Returns the class names of all <b>throws</b> annotations with in the
8759
     * given comment block.
8760
     *
8761
     * @param string $comment The context doc comment block.
8762
     * @return array<int, string>
8763
     */
8764 62
    private function parseThrowsAnnotations($comment)
8765
    {
8766 62
        $throws = [];
8767
8768 62
        if (preg_match_all(self::REGEXP_THROWS_TYPE, $comment, $matches) > 0) {
8769 10
            foreach ($matches[1] as $match) {
8770 10
                $throws[] = $this->useSymbolTable->lookup($match) ?: $match;
8771
            }
8772
        }
8773
8774 62
        return $throws;
8775
    }
8776
8777
    /**
8778
     * This method parses the given doc comment text for a return annotation and
8779
     * it returns the found return type.
8780
     *
8781
     * @param string $comment A doc comment text.
8782
     * @return string|null
8783
     */
8784 62
    private function parseReturnAnnotation($comment)
8785
    {
8786 62
        if (!preg_match(self::REGEXP_RETURN_TYPE, $comment, $match)) {
8787 34
            return null;
8788
        }
8789
8790 44
        foreach (explode('|', end($match) ?: '') as $image) {
8791 44
            $image = $this->useSymbolTable->lookup($image) ?: $image;
8792
8793 44
            if (Type::isScalarType($image)) {
8794 32
                continue;
8795
            }
8796
8797 18
            return $image;
8798
        }
8799
8800 30
        return null;
8801
    }
8802
8803
    /**
8804
     * This method parses the given doc comment text for a var annotation and
8805
     * it returns the found property types.
8806
     *
8807
     * @param string $comment A doc comment text.
8808
     * @return array<string>
8809
     */
8810 25
    private function parseVarAnnotation($comment)
8811
    {
8812 25
        if (preg_match(self::REGEXP_VAR_TYPE, (string) $comment, $match) > 0) {
8813 22
            $useSymbolTable = $this->useSymbolTable;
8814
8815 22
            return array_map(
8816 22
                static fn($image) => $useSymbolTable->lookup($image) ?: $image,
8817 22
                array_map('trim', explode('|', end($match) ?: '')),
8818 22
            );
8819
        }
8820
8821 3
        return [];
8822
    }
8823
8824
    /**
8825
     * This method will extract the type information of a property from it's
8826
     * doc comment information. The returned value will be <b>null</b> when no
8827
     * type information exists.
8828
     *
8829
     * @return ASTType|null
8830
     * @since 0.9.6
8831
     */
8832 87
    private function parseFieldDeclarationType()
8833
    {
8834
        // Skip, if ignore annotations is set
8835 87
        if ($this->ignoreAnnotations) {
8836 2
            return null;
8837
        }
8838
8839 85
        if (!$this->docComment) {
8840 62
            return null;
8841
        }
8842
8843 25
        $reference = $this->parseFieldDeclarationClassOrInterfaceReference();
8844
8845 25
        if ($reference !== null) {
8846 11
            return $reference;
8847
        }
8848
8849 16
        $annotations = $this->parseVarAnnotation($this->docComment);
8850
8851 16
        foreach ($annotations as $annotation) {
8852 13
            if (Type::isPrimitiveType($annotation)) {
8853 11
                $type = Type::getPrimitiveType($annotation);
8854 11
                if ($type) {
8855 11
                    return $this->builder->buildAstScalarType($type);
8856
                }
8857
            }
8858
8859 2
            if (Type::isArrayType($annotation)) {
8860 2
                return $this->builder->buildAstTypeArray();
8861
            }
8862
        }
8863
8864 3
        return null;
8865
    }
8866
8867
    /**
8868
     * Extracts non scalar types from a field doc comment and creates a
8869
     * matching type instance.
8870
     *
8871
     * @return ASTClassOrInterfaceReference|null
8872
     * @since 0.9.6
8873
     */
8874 25
    private function parseFieldDeclarationClassOrInterfaceReference()
8875
    {
8876 25
        if (!$this->docComment) {
8877
            return null;
8878
        }
8879
8880 25
        $annotations = $this->parseVarAnnotation($this->docComment);
8881
8882 25
        foreach ($annotations as $annotation) {
8883 22
            if (!Type::isScalarType($annotation)) {
8884 11
                return $this->builder->buildAstClassOrInterfaceReference(
8885 11
                    $annotation,
8886 11
                );
8887
            }
8888
        }
8889
8890 16
        return null;
8891
    }
8892
8893
    /**
8894
     * This method parses a yield-statement node.
8895
     *
8896
     * @param bool $standalone Either yield is the statement (true), or nested in
8897
     *                         an expression (false).
8898
     * @return ASTYieldStatement
8899
     */
8900 11
    private function parseYield($standalone)
8901
    {
8902 11
        $this->tokenStack->push();
8903
8904 11
        $token = $this->consumeToken(Tokens::T_YIELD);
8905 11
        $this->consumeComments();
8906
8907 11
        $yield = $this->builder->buildAstYieldStatement($token->image);
8908
8909 11
        $node = $this->parseOptionalExpression();
8910 11
        if ($node) {
8911 11
            $yield->addChild($node);
8912
8913 11
            if ($this->tokenizer->peek() === Tokens::T_DOUBLE_ARROW) {
8914 4
                $this->consumeToken(Tokens::T_DOUBLE_ARROW);
8915
8916 4
                $child = $this->parseOptionalExpression();
8917 4
                if ($child) {
8918 4
                    $yield->addChild($child);
8919
                }
8920
            }
8921
        }
8922
8923 11
        $this->consumeComments();
8924
8925 11
        if ($standalone) {
8926 8
            $this->parseStatementTermination();
8927
        }
8928
8929 11
        return $this->setNodePositionsAndReturn($yield);
8930
    }
8931
8932
    /**
8933
     * Extracts documented <b>throws</b> and <b>return</b> types and sets them
8934
     * to the given <b>$callable</b> instance.
8935
     */
8936 1099
    private function prepareCallable(AbstractASTCallable $callable): void
8937
    {
8938
        // Skip, if ignore annotations is set
8939 1099
        if ($this->ignoreAnnotations) {
8940 2
            return;
8941
        }
8942
8943
        // Get all @throws Types
8944 1097
        $comment = $callable->getComment();
8945 1097
        $throws = $comment === null ? [] : $this->parseThrowsAnnotations($comment);
8946
8947 1097
        foreach ($throws as $qualifiedName) {
8948 10
            $callable->addExceptionClassReference(
8949 10
                $this->builder->buildAstClassOrInterfaceReference($qualifiedName),
8950 10
            );
8951
        }
8952
8953
        // Stop here if return class already exists.
8954 1097
        if ($callable->hasReturnClass()) {
8955 8
            return;
8956
        }
8957
8958
        // Get return annotation
8959 1091
        $qualifiedName = $comment === null ? null : $this->parseReturnAnnotation($comment);
8960
8961 1091
        if ($qualifiedName !== null) {
8962 18
            $callable->setReturnClassReference(
8963 18
                $this->builder->buildAstClassOrInterfaceReference($qualifiedName),
8964 18
            );
8965
        }
8966
    }
8967
8968
    /**
8969
     * This method will consume the next token in the token stream. It will
8970
     * throw an exception if the type of this token is not identical with
8971
     * <b>$tokenType</b>.
8972
     *
8973
     * @param int $tokenType The next expected token type.
8974
     * @return Token
8975
     * @throws TokenStreamEndException
8976
     * @throws UnexpectedTokenException
8977
     */
8978 1441
    protected function consumeToken($tokenType)
8979
    {
8980
        assert($tokenType > 0);
8981
8982 1441
        switch ($this->tokenizer->peek()) {
8983 1441
            case $tokenType:
8984 1441
                $next = $this->tokenizer->next();
8985
                assert($next instanceof Token);
8986
8987 1441
                return $this->tokenStack->add($next);
8988
        }
8989
8990 24
        throw $this->getUnexpectedNextTokenException();
8991
    }
8992
8993
    /**
8994
     * This method will consume all comment tokens from the token stream.
8995
     */
8996 1441
    protected function consumeComments(): void
8997
    {
8998 1441
        $type = $this->tokenizer->peek();
8999 1441
        while ($type === Tokens::T_COMMENT || $type === Tokens::T_DOC_COMMENT) {
9000 61
            $token = $this->consumeToken($type);
9001 61
            $type = $this->tokenizer->peek();
9002
9003 61
            if (Tokens::T_COMMENT === $token->type) {
9004 55
                continue;
9005
            }
9006
9007 9
            $this->docComment = $token->image;
9008 9
            if (preg_match('(\s+@package\s+[^\s]+\s+)', $token->image)) {
9009 2
                $this->packageName = $this->parsePackageAnnotation($token->image);
9010
            }
9011
        }
9012
    }
9013
9014
    /**
9015
     * Return the appropriate exception for either UnexpectedTokenException
9016
     * configured with the next token, or TokenStreamEndException if there
9017
     * is no more token.
9018
     *
9019
     * @return TokenStreamEndException|UnexpectedTokenException
9020
     */
9021 47
    private function getUnexpectedNextTokenException()
9022
    {
9023 47
        return $this->getUnexpectedTokenException($this->tokenizer->next());
9024
    }
9025
9026
    /**
9027
     * Return the appropriate exception for either UnexpectedTokenException
9028
     * configured with the given token, or TokenStreamEndException if given
9029
     * null.
9030
     *
9031
     * @param Token|Tokenizer::T_*|null $token
9032
     * @return TokenStreamEndException|UnexpectedTokenException
9033
     */
9034 51
    private function getUnexpectedTokenException($token)
9035
    {
9036 51
        if ($token instanceof Token) {
9037 39
            return new UnexpectedTokenException($token, $this->tokenizer->getSourceFile() ?? 'unknown');
9038
        }
9039
9040 12
        return new TokenStreamEndException($this->tokenizer);
9041
    }
9042
9043
    protected function checkEllipsisInExpressionSupport(): void
9044
    {
9045
        throw $this->getUnexpectedNextTokenException();
9046
    }
9047
9048
    /**
9049
     * Parses throw expression syntax. available since PHP 8.0. Ex.:
9050
     *  $callable = fn() => throw new Exception();
9051
     *  $value = $nullableValue ?? throw new InvalidArgumentException();
9052
     *  $value = $falsableValue ?: throw new InvalidArgumentException();
9053
     *
9054
     * @return ASTThrowStatement
9055
     * @throws UnexpectedTokenException
9056
     */
9057 5
    private function parseThrowExpression()
9058
    {
9059 5
        if ($this->tokenizer->peek() === Tokens::T_THROW) {
9060 5
            return $this->parseThrowStatement([
9061 5
                Tokens::T_SEMICOLON,
9062 5
                Tokens::T_COMMA,
9063 5
                Tokens::T_COLON,
9064 5
                Tokens::T_PARENTHESIS_CLOSE,
9065 5
                Tokens::T_SQUARED_BRACKET_CLOSE,
9066 5
            ]);
9067
        }
9068
9069
        throw $this->getUnexpectedNextTokenException();
9070
    }
9071
9072
    /**
9073
     * Parses enum declaration. available since PHP 8.1. Ex.:
9074
     *  enum Suit: string { case HEARTS = 'hearts'; }
9075
     *
9076
     * @return ASTEnum
9077
     * @throws UnexpectedTokenException
9078
     */
9079 1
    private function parseEnumDeclaration()
9080
    {
9081 1
        $this->tokenStack->push();
9082
9083 1
        $enum = $this->parseEnumSignature();
9084 1
        $enum = $this->parseTypeBody($enum);
9085 1
        $enum->setTokens($this->tokenStack->pop());
9086
9087 1
        $this->reset();
9088
9089 1
        return $enum;
9090
    }
9091
9092
    /**
9093
     * Parses enum declaration signature. available since PHP 8.1. Ex.:
9094
     *  enum Suit: string
9095
     *
9096
     * @return ASTEnum
9097
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
9098 1
    private function parseEnumSignature()
9099
    {
9100 1
        $this->tokenStack->add($this->requireNextToken());
9101 1
        $this->consumeComments();
9102
9103 1
        if ($this->tokenizer->peek() !== Tokens::T_STRING) {
9104
            throw $this->getUnexpectedNextTokenException();
9105
        }
9106
9107 1
        $name = $this->tokenizer->currentToken()?->image;
9108 1
        if (!$name) {
9109
            throw new TokenException('Invalid Enum name');
9110
        }
9111 1
        $this->consumeToken(Tokens::T_STRING);
9112 1
        $this->consumeComments();
9113 1
        $type = null;
9114
9115 1
        if ($this->tokenizer->peek() === Tokens::T_COLON) {
9116 1
            $this->consumeToken(Tokens::T_COLON);
9117 1
            $type = $this->parseTypeHint();
9118
9119
            if (
9120 1
                !($type instanceof ASTScalarType) ||
9121 1
                !in_array($type->getImage(), ['int', 'string'], true)
9122
            ) {
9123
                throw new TokenException(
9124
                    "Enum backing type must be 'int' or 'string'",
9125
                );
9126
            }
9127
        }
9128
9129 1
        $enum = $this->builder->buildEnum($name, $type);
9130 1
        $enum->setCompilationUnit($this->compilationUnit);
9131 1
        $enum->setModifiers($this->modifiers);
9132 1
        $enum->setComment($this->docComment);
9133 1
        $enum->setId($this->idBuilder->forClassOrInterface($enum));
9134 1
        $enum->setUserDefined();
9135
9136 1
        $this->consumeComments();
9137 1
        $tokenType = $this->tokenizer->peek();
9138
9139 1
        if ($tokenType === Tokens::T_IMPLEMENTS) {
9140 1
            $this->consumeToken(Tokens::T_IMPLEMENTS);
9141 1
            $this->parseInterfaceList($enum);
9142
        }
9143
9144 1
        return $enum;
9145
    }
9146
9147
    /**
9148
     * Peek the next token if it's of the given type, add it to current tokenStack, and return it if so.
9149
     *
9150
     * @param string $type
9151
     *
9152
     * @psalm-param Tokens::T_* $type
9153
     *
9154
     * @return Token|null
9155
     */
9156 409
    private function addTokenToStackIfType($type)
9157
    {
9158 409
        if ($this->tokenizer->peek() === $type) {
0 ignored issues
show
introduced by
The condition $this->tokenizer->peek() === $type is always false.
Loading history...
9159 5
            $next = $this->tokenizer->next();
9160
            assert($next instanceof Token);
9161 5
            $this->tokenStack->add($next);
9162
9163 5
            return $next;
9164
        }
9165
9166 408
        return null;
9167
    }
9168
9169
    /**
9170
     * Return the next token if it exists, else throw a TokenStreamEndException.
9171
     *
9172
     * @return Token
9173
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
9174 4
    private function requireNextToken()
9175
    {
9176 4
        $next = $this->tokenizer->next();
9177
9178 4
        if ($next instanceof Token) {
9179 4
            return $next;
9180
        }
9181
9182
        throw new TokenStreamEndException($this->tokenizer);
9183
    }
9184
9185
    /**
9186
     * @param string $numberRepresentation integer number as it appears in the code, `0xfe4`, `1_000_000`
9187
     * @return int
9188
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
9189 121
    private function parseIntegerNumberImage($numberRepresentation)
9190
    {
9191 121
        $numberRepresentation = trim($numberRepresentation);
9192
9193 121
        if (!preg_match(self::REGEXP_INTEGER, $numberRepresentation)) {
9194
            throw new InvalidArgumentException("Invalid number $numberRepresentation");
9195
        }
9196
9197 121
        return (int) $this->getNumberFromImage($numberRepresentation);
9198
    }
9199
9200
    /**
9201
     * @param string $numberRepresentation
9202
     * @return float|int|string
9203
     */
9204 125
    private function getNumberFromImage($numberRepresentation)
9205
    {
9206 125
        $numberRepresentation = str_replace('_', '', $numberRepresentation);
9207
9208 125
        switch (substr($numberRepresentation, 0, 2)) {
9209 125
            case '0x':
9210 125
            case '0X':
9211 15
                return hexdec(substr($numberRepresentation, 2));
9212
9213 125
            case '0b':
9214 125
            case '0B':
9215 1
                return bindec(substr($numberRepresentation, 2));
9216
9217
            default:
9218 125
                if (preg_match('/^0+[oO]?(\d+)$/', $numberRepresentation, $match)) {
9219 1
                    return octdec($match[1]);
9220
                }
9221
9222 125
                return $numberRepresentation;
9223
        }
9224
    }
9225
9226
    /**
9227
     * @return ASTEnumCase
9228
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
9229 1
    private function parseEnumCase()
9230
    {
9231 1
        $this->tokenStack->add($this->requireNextToken());
9232 1
        $this->tokenStack->push();
9233 1
        $this->consumeComments();
9234 1
        $caseName = $this->tokenizer->currentToken()?->image ?? '';
9235
9236 1
        if (!preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $caseName)) {
9237
            throw $this->getUnexpectedNextTokenException();
9238
        }
9239
9240 1
        $this->tokenStack->add($this->requireNextToken());
9241 1
        $this->consumeComments();
9242 1
        $case = $this->builder->buildEnumCase($caseName, $this->parseEnumCaseValue());
9243 1
        $this->consumeComments();
9244 1
        $this->consumeToken(Tokens::T_SEMICOLON);
9245
9246 1
        return $this->setNodePositionsAndReturn($case);
9247
    }
9248
9249
    /**
9250
     * @return ASTNode|null
9251
     */
0 ignored issues
show
Coding Style Documentation introduced by
Missing @throws tag in function comment
Loading history...
9252 1
    private function parseEnumCaseValue()
9253
    {
9254 1
        if ($this->tokenizer->peek() !== Tokens::T_EQUAL) {
9255 1
            return null;
9256
        }
9257
9258 1
        $this->consumeToken(Tokens::T_EQUAL);
9259 1
        $this->consumeComments();
9260
9261 1
        $expression = $this->parseOptionalExpression();
9262
9263 1
        if (!$expression instanceof AbstractASTNode) {
9264
            throw new MissingValueException($this->tokenizer);
9265
        }
9266
9267 1
        return $expression;
9268
    }
9269
9270
    /**
9271
     * @param string $type
9272
     *
9273
     * @psalm-param Tokens::T_* $type
9274
     *
9275
     * @return string
9276
     */
9277 125
    private function parseNumber($type)
9278
    {
9279 125
        $token = $this->consumeToken($type);
0 ignored issues
show
Bug introduced by
$type of type string is incompatible with the type integer expected by parameter $tokenType of PDepend\Source\Language\...PParser::consumeToken(). ( Ignorable by Annotation )

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

9279
        $token = $this->consumeToken(/** @scrutinizer ignore-type */ $type);
Loading history...
9280 125
        $number = (string) $token->image;
9281
9282 125
        while ($next = $this->addTokenToStackIfType(Tokens::T_STRING)) {
9283
            $number .= $next->image;
9284
        }
9285
9286 125
        return $number;
9287
    }
9288
}
9289