AbstractPHPParser::parseOptionalStatement()   F
last analyzed

Complexity

Conditions 50
Paths 56

Size

Total Lines 130
Code Lines 106

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 106
dl 0
loc 130
rs 3.3333
c 0
b 0
f 0
cc 50
nc 56
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

465
            ->/** @scrutinizer ignore-call */ 
466
              setCache($this->cache)

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...
466
            ->setId($this->idBuilder->forFile($this->compilationUnit));
0 ignored issues
show
Bug introduced by
It seems like $this->compilationUnit can also be of type null; however, parameter $compilationUnit of PDepend\Util\IdBuilder::forFile() does only seem to accept PDepend\Source\AST\ASTCompilationUnit, 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

466
            ->setId($this->idBuilder->forFile(/** @scrutinizer ignore-type */ $this->compilationUnit));
Loading history...
467
468
        $hash = $this->compilationUnit->getFileName() === 'php://stdin'
469
            ? md5($this->compilationUnit->getSource())
0 ignored issues
show
Bug introduced by
It seems like $this->compilationUnit->getSource() can also be of type null; however, parameter $string of md5() does only seem to accept string, 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

469
            ? md5(/** @scrutinizer ignore-type */ $this->compilationUnit->getSource())
Loading history...
470
            : md5_file($this->compilationUnit->getFileName());
471
472
        if ($hash && $this->cache->restore($this->compilationUnit->getId(), $hash)) {
473
            return;
474
        }
475
476
        $this->cache->remove($this->compilationUnit->getId());
477
478
        $this->setUpEnvironment();
479
480
        $this->tokenStack->push();
481
482
        Log::debug('Processing file ' . $this->compilationUnit);
483
484
        $tokenType = $this->tokenizer->peek();
485
486
        while ($tokenType !== Tokenizer::T_EOF) {
487
            switch ($tokenType) {
488
                case Tokens::T_COMMENT:
489
                    $this->consumeToken(Tokens::T_COMMENT);
490
                    break;
491
                case Tokens::T_DOC_COMMENT:
492
                    $comment = $this->consumeToken(Tokens::T_DOC_COMMENT)->image;
493
494
                    $this->packageName = $this->parsePackageAnnotation($comment);
495
                    $this->docComment  = $comment;
496
                    break;
497
                case Tokens::T_USE:
498
                    // Parse a use statement. This method has no return value but it
499
                    // creates a new entry in the symbol map.
500
                    $this->parseUseDeclarations();
501
                    break;
502
                case Tokens::T_NAMESPACE:
503
                    $this->parseNamespaceDeclaration();
504
                    break;
505
                case Tokens::T_NO_PHP:
506
                case Tokens::T_OPEN_TAG:
507
                case Tokens::T_OPEN_TAG_WITH_ECHO:
508
                    $this->consumeToken($tokenType);
509
                    $this->reset(0, $tokenType === Tokens::T_OPEN_TAG_WITH_ECHO);
510
                    break;
511
                case Tokens::T_CLOSE_TAG:
512
                    $this->parseNonePhpCode();
513
                    $this->reset();
514
                    break;
515
                default:
516
                    if (null === $this->parseOptionalStatement()) {
517
                        // Consume whatever token
518
                        $this->consumeToken($tokenType);
519
                    }
520
                    break;
521
            }
522
523
            $tokenType = $this->tokenizer->peek();
524
        }
525
526
        $this->compilationUnit->setTokens($this->tokenStack->pop());
527
        $this->cache->store(
528
            $this->compilationUnit->getId(),
529
            $this->compilationUnit,
530
            $hash
531
        );
532
533
        $this->tearDownEnvironment();
534
    }
535
536
    /**
537
     * Initializes the parser environment.
538
     *
539
     * @return void
540
     *
541
     * @since 0.9.12
542
     */
543
    protected function setUpEnvironment()
544
    {
545
        ini_set('xdebug.max_nesting_level', (string)$this->getMaxNestingLevel());
546
547
        $this->useSymbolTable->createScope();
548
549
        $this->reset();
550
    }
551
552
    /**
553
     * Restores the parser environment back.
554
     *
555
     * @throws NoActiveScopeException
556
     *
557
     * @return void
558
     *
559
     * @since 0.9.12
560
     */
561
    protected function tearDownEnvironment()
562
    {
563
        ini_restore('xdebug.max_nesting_level');
564
565
        $this->useSymbolTable->destroyScope();
566
    }
567
568
    /**
569
     * Resets some object properties.
570
     *
571
     * @param int  $modifiers Optional default modifiers.
572
     * @param bool $echoing   True if current statement is echoing (such as after <?=).
573
     *
574
     * @return void
575
     */
576
    protected function reset($modifiers = 0, $echoing = false)
577
    {
578
        $this->packageName = Builder::DEFAULT_NAMESPACE;
579
        $this->docComment  = null;
580
        $this->modifiers   = $modifiers;
581
        $this->echoing     = $echoing;
582
    }
583
584
    /**
585
     * Tests if the given token type is a reserved keyword in the supported PHP
586
     * version.
587
     *
588
     * @param int $tokenType
589
     *
590
     * @return bool
591
     *
592
     * @since 1.1.1
593
     */
594
    abstract protected function isKeyword($tokenType);
595
596
    /**
597
     * Parses a valid class or interface name and returns the image of the parsed
598
     * token.
599
     *
600
     * @throws TokenStreamEndException
601
     * @throws UnexpectedTokenException
602
     *
603
     * @return string
604
     */
605
    protected function parseClassName()
606
    {
607
        $type = $this->tokenizer->peek();
608
609
        if ($this->isClassName($type)) {
610
            return $this->consumeToken($type)->image;
611
        }
612
613
        throw $this->getUnexpectedNextTokenException();
614
    }
615
616
    /**
617
     * Will return <b>true</b> if the given <b>$tokenType</b> is a valid class
618
     * name part.
619
     *
620
     * @param int $tokenType
621
     *
622
     * @return bool
623
     *
624
     * @since 0.10.6
625
     */
626
    protected function isClassName($tokenType)
627
    {
628
        switch ($tokenType) {
629
            case Tokens::T_NULL:
630
            case Tokens::T_TRUE:
631
            case Tokens::T_FALSE:
632
            case Tokens::T_TRAIT:
633
            case Tokens::T_YIELD:
634
            case Tokens::T_STRING:
635
            case Tokens::T_TRAIT_C:
636
            case Tokens::T_CALLABLE:
637
            case Tokens::T_INSTEADOF:
638
            case Tokens::T_READONLY:
639
                return true;
640
        }
641
642
        return false;
643
    }
644
645
    /**
646
     * Parses a function name from the given tokenizer and returns the string
647
     * literal representing the function name. If no valid token exists in the
648
     * token stream, this method will throw an exception.
649
     *
650
     * @throws UnexpectedTokenException
651
     * @throws TokenStreamEndException
652
     *
653
     * @return string
654
     *
655
     * @since 0.10.0
656
     */
657
    protected function parseFunctionName()
658
    {
659
        $tokenType = $this->tokenizer->peek();
660
661
        if ($this->isFunctionName($tokenType)) {
662
            return $this->consumeToken($tokenType)->image;
663
        }
664
665
        throw $this->getUnexpectedNextTokenException();
666
    }
667
668
    /**
669
     * @param int $tokenType
670
     *
671
     * @return bool
672
     */
673
    private function isAllowedName($tokenType)
674
    {
675
        switch ($tokenType) {
676
            case Tokens::T_NULL:
677
            case Tokens::T_SELF:
678
            case Tokens::T_TRUE:
679
            case Tokens::T_FALSE:
680
            case Tokens::T_TRAIT:
681
            case Tokens::T_YIELD:
682
            case Tokens::T_PARENT:
683
            case Tokens::T_STRING:
684
            case Tokens::T_TRAIT_C:
685
            case Tokens::T_CALLABLE:
686
            case Tokens::T_INSTEADOF:
687
                return true;
688
        }
689
        return false;
690
    }
691
692
    /**
693
     * @param int $tokenType
694
     *
695
     * @return bool
696
     */
697
    protected function isConstantName($tokenType)
698
    {
699
        return $this->isAllowedName($tokenType);
700
    }
701
702
    /**
703
     * Tests if the give token is a valid function name in the supported PHP
704
     * version.
705
     *
706
     * @param int $tokenType
707
     *
708
     * @return bool
709
     *
710
     * @since 2.3
711
     */
712
    protected function isFunctionName($tokenType)
713
    {
714
        return $this->isAllowedName($tokenType);
715
    }
716
717
    /**
718
     * @throws UnexpectedTokenException
719
     * @throws TokenStreamEndException
720
     *
721
     * @return string
722
     */
723
    protected function parseMethodName()
724
    {
725
        $tokenType = $this->tokenizer->peek();
726
727
        if ($this->isMethodName($tokenType)) {
728
            return $this->consumeToken($tokenType)->image;
729
        }
730
731
        throw $this->getUnexpectedNextTokenException();
732
    }
733
734
    /**
735
     * @param int $tokenType
736
     *
737
     * @return bool
738
     */
739
    protected function isMethodName($tokenType)
740
    {
741
        return $this->isAllowedName($tokenType);
742
    }
743
744
    /**
745
     * Parses a trait declaration.
746
     *
747
     * @return ASTTrait
748
     *
749
     * @since 1.0.0
750
     */
751
    private function parseTraitDeclaration()
752
    {
753
        $this->tokenStack->push();
754
755
        $trait = $this->parseTraitSignature();
756
        $trait = $this->parseTypeBody($trait);
757
        $trait->setTokens($this->tokenStack->pop());
758
759
        $this->reset();
760
761
        return $trait;
762
    }
763
764
    /**
765
     * Parses the signature of a trait.
766
     *
767
     * @return ASTTrait
768
     */
769
    private function parseTraitSignature()
770
    {
771
        $this->consumeToken(Tokens::T_TRAIT);
772
        $this->consumeComments();
773
774
        $qualifiedName = $this->createQualifiedTypeName($this->parseClassName());
775
776
        $trait = $this->builder->buildTrait($qualifiedName);
777
        $trait->setCompilationUnit($this->compilationUnit);
778
        $trait->setComment($this->docComment);
779
        $trait->setId($this->idBuilder->forClassOrInterface($trait));
780
        $trait->setUserDefined();
781
782
        return $trait;
783
    }
784
785
    /**
786
     * Parses the dependencies in a interface signature.
787
     *
788
     * @return ASTInterface
789
     */
790
    private function parseInterfaceDeclaration()
791
    {
792
        $this->tokenStack->push();
793
794
        $interface = $this->parseInterfaceSignature();
795
        $interface = $this->parseTypeBody($interface);
796
        $interface->setTokens($this->tokenStack->pop());
797
798
        $this->reset();
799
800
        return $interface;
801
    }
802
803
    /**
804
     * Parses the signature of an interface and finally returns a configured
805
     * interface instance.
806
     *
807
     * @return ASTInterface
808
     *
809
     * @since 0.10.2
810
     */
811
    private function parseInterfaceSignature()
812
    {
813
        $this->consumeToken(Tokens::T_INTERFACE);
814
        $this->consumeComments();
815
816
        $qualifiedName = $this->createQualifiedTypeName($this->parseClassName());
817
818
        $interface = $this->builder->buildInterface($qualifiedName);
819
        $interface->setCompilationUnit($this->compilationUnit);
820
        $interface->setComment($this->docComment);
821
        $interface->setId($this->idBuilder->forClassOrInterface($interface));
822
        $interface->setUserDefined();
823
824
        return $this->parseOptionalExtendsList($interface);
825
    }
826
827
    /**
828
     * Parses an optional interface list of an interface declaration.
829
     *
830
     * @return ASTInterface
831
     *
832
     * @since 0.10.2
833
     */
834
    private function parseOptionalExtendsList(ASTInterface $interface)
835
    {
836
        $this->consumeComments();
837
        $tokenType = $this->tokenizer->peek();
838
839
        if ($tokenType === Tokens::T_EXTENDS) {
840
            $this->consumeToken(Tokens::T_EXTENDS);
841
            $this->parseInterfaceList($interface);
842
        }
843
        return $interface;
844
    }
845
846
    /**
847
     * Parses the dependencies in a class signature.
848
     *
849
     * @return ASTClass
850
     */
851
    protected function parseClassDeclaration()
852
    {
853
        $startToken = $this->tokenizer->currentToken();
854
        $this->tokenStack->push();
855
856
        $class = $this->parseClassSignature();
857
        $class = $this->parseTypeBody($class);
858
        $class->setTokens($this->tokenStack->pop(), $startToken);
859
860
        $this->reset();
861
862
        return $class;
863
    }
864
865
    /**
866
     * Parses the signature of a class.
867
     *
868
     * The signature of a class consists of optional class modifiers like, final
869
     * or abstract, the T_CLASS token, the class name, an optional parent class
870
     * and an optional list of implemented interfaces.
871
     *
872
     * @return ASTClass
873
     *
874
     * @since 1.0.0
875
     */
876
    protected function parseClassSignature()
877
    {
878
        $this->parseClassModifiers();
879
        $this->consumeToken(Tokens::T_CLASS);
880
        $this->consumeComments();
881
882
        $qualifiedName = $this->createQualifiedTypeName($this->parseClassName());
883
884
        $class = $this->builder->buildClass($qualifiedName);
885
        $class->setCompilationUnit($this->compilationUnit);
886
        $class->setModifiers($this->modifiers);
887
        $class->setComment($this->docComment);
888
        $class->setId($this->idBuilder->forClassOrInterface($class));
889
        $class->setUserDefined();
890
891
        $this->consumeComments();
892
        $tokenType = $this->tokenizer->peek();
893
894
        if ($tokenType === Tokens::T_EXTENDS) {
895
            $class = $this->parseClassExtends($class);
896
897
            $this->consumeComments();
898
            $tokenType = $this->tokenizer->peek();
899
        }
900
901
        if ($tokenType === Tokens::T_IMPLEMENTS) {
902
            $this->consumeToken(Tokens::T_IMPLEMENTS);
903
            $this->parseInterfaceList($class);
904
        }
905
906
        return $class;
907
    }
908
909
    /**
910
     * This method parses an optional class modifier. Valid class modifiers are
911
     * <b>final</b> or <b>abstract</b>.
912
     *
913
     * @return void
914
     */
915
    private function parseClassModifiers()
916
    {
917
        $this->consumeComments();
918
        $tokenType = $this->tokenizer->peek();
919
920
        $validModifiers = array(
921
            Tokens::T_ABSTRACT,
922
            Tokens::T_FINAL,
923
            Tokens::T_READONLY
924
        );
925
926
        $finalAllowed = true;
927
        $abstractAllowed = true;
928
929
        while (in_array($tokenType, $validModifiers, true)) {
930
            if ($tokenType === Tokens::T_ABSTRACT) {
931
                if (!$abstractAllowed) {
932
                    throw $this->getUnexpectedNextTokenException();
933
                }
934
                $finalAllowed = false;
935
                $this->consumeToken(Tokens::T_ABSTRACT);
936
                $this->modifiers |= State::IS_EXPLICIT_ABSTRACT;
937
            } elseif ($tokenType === Tokens::T_FINAL) {
938
                if (!$finalAllowed) {
939
                    throw $this->getUnexpectedNextTokenException();
940
                }
941
                $abstractAllowed = false;
942
                $this->consumeToken(Tokens::T_FINAL);
943
                $this->modifiers |= State::IS_FINAL;
944
            } elseif ($tokenType === Tokens::T_READONLY) {
945
                if (!static::READONLY_CLASS_ALLOWED) {
946
                    throw $this->getUnexpectedNextTokenException();
947
                }
948
949
                $this->consumeToken(Tokens::T_READONLY);
950
                $this->modifiers |= State::IS_READONLY;
951
            }
952
953
            $tokenType = $this->tokenizer->peek();
954
        }
955
956
        $this->consumeComments();
957
    }
958
959
    /**
960
     * Parses a parent class declaration for the given <b>$class</b>.
961
     *
962
     * @template T of ASTClass
963
     *
964
     * @param T $class
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...
965
     *
966
     * @return T
967
     *
968
     * @since 1.0.0
969
     */
970
    protected function parseClassExtends(ASTClass $class)
971
    {
972
        $this->consumeToken(Tokens::T_EXTENDS);
973
        $this->tokenStack->push();
974
975
        $class->setParentClassReference(
976
            $this->setNodePositionsAndReturn(
977
                $this->builder->buildAstClassReference(
978
                    $this->parseQualifiedName()
979
                )
980
            )
981
        );
982
983
        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...
984
    }
985
986
    /**
987
     * This method parses a list of interface names as used in the <b>extends</b>
988
     * part of a interface declaration or in the <b>implements</b> part of a
989
     * class declaration.
990
     *
991
     * @return void
992
     */
993
    protected function parseInterfaceList(AbstractASTClassOrInterface $abstractType)
994
    {
995
        while (true) {
996
            $this->tokenStack->push();
997
998
            $abstractType->addInterfaceReference(
999
                $this->setNodePositionsAndReturn(
1000
                    $this->builder->buildAstClassOrInterfaceReference(
1001
                        $this->parseQualifiedName()
1002
                    )
1003
                )
1004
            );
1005
1006
            $this->consumeComments();
1007
            $tokenType = $this->tokenizer->peek();
1008
1009
            if ($tokenType === Tokens::T_CURLY_BRACE_OPEN) {
1010
                break;
1011
            }
1012
            $this->consumeToken(Tokens::T_COMMA);
1013
        }
1014
    }
1015
1016
    /**
1017
     * Parses a class/interface/trait body.
1018
     *
1019
     * @template T of AbstractASTClassOrInterface
1020
     *
1021
     * @param T $classOrInterface
1022
     *
1023
     * @throws UnexpectedTokenException
1024
     * @throws TokenStreamEndException
1025
     *
1026
     * @return T
1027
     */
1028
    protected function parseTypeBody(AbstractASTClassOrInterface $classOrInterface)
1029
    {
1030
        $this->classOrInterface = $classOrInterface;
1031
1032
        // Consume comments and read opening curly brace
1033
        $this->consumeComments();
1034
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
1035
1036
        $defaultModifier = State::IS_PUBLIC;
1037
        if ($classOrInterface instanceof ASTInterface) {
1038
            $defaultModifier |= State::IS_ABSTRACT;
1039
        }
1040
        $this->reset();
1041
1042
        $tokenType = $this->tokenizer->peek();
1043
1044
        while ($tokenType !== Tokenizer::T_EOF) {
1045
            switch ($tokenType) {
1046
                case Tokens::T_ABSTRACT:
1047
                case Tokens::T_PUBLIC:
1048
                case Tokens::T_PRIVATE:
1049
                case Tokens::T_PROTECTED:
1050
                case Tokens::T_STATIC:
1051
                case Tokens::T_FINAL:
1052
                case Tokens::T_READONLY:
1053
                case Tokens::T_FUNCTION:
1054
                case Tokens::T_VARIABLE:
1055
                case Tokens::T_VAR:
1056
                    $methodOrProperty = $this->parseMethodOrFieldDeclaration(
1057
                        $defaultModifier
1058
                    );
1059
1060
                    if ($methodOrProperty instanceof ASTNode) {
1061
                        $classOrInterface->addChild($methodOrProperty);
1062
                    }
1063
1064
                    $this->reset();
1065
                    break;
1066
                case Tokens::T_CONST:
1067
                    $classOrInterface->addChild($this->parseConstantDefinition());
1068
                    $this->reset();
1069
                    break;
1070
                case Tokens::T_CURLY_BRACE_CLOSE:
1071
                    $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
1072
1073
                    $this->reset();
1074
1075
                    // Reset context class or interface instance
1076
                    $this->classOrInterface = null;
1077
1078
                    // Stop processing
1079
                    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...
1080
                case Tokens::T_COMMENT:
1081
                    $token = $this->consumeToken(Tokens::T_COMMENT);
1082
1083
                    $comment = $this->builder->buildAstComment($token->image);
1084
                    $comment->configureLinesAndColumns(
1085
                        $token->startLine,
1086
                        $token->endLine,
1087
                        $token->startColumn,
1088
                        $token->endColumn
1089
                    );
1090
1091
                    $classOrInterface->addChild($comment);
1092
                    break;
1093
                case Tokens::T_DOC_COMMENT:
1094
                    $token = $this->consumeToken(Tokens::T_DOC_COMMENT);
1095
1096
                    $comment = $this->builder->buildAstComment($token->image);
1097
                    $comment->configureLinesAndColumns(
1098
                        $token->startLine,
1099
                        $token->endLine,
1100
                        $token->startColumn,
1101
                        $token->endColumn
1102
                    );
1103
1104
                    $classOrInterface->addChild($comment);
1105
1106
                    $this->docComment = $token->image;
1107
                    break;
1108
                case Tokens::T_USE:
1109
                    $classOrInterface->addChild($this->parseTraitUseStatement());
1110
                    break;
1111
                case Tokens::T_CASE:
1112
                    if (!($classOrInterface instanceof ASTEnum)) {
1113
                        throw new TokenException(
1114
                            'Enum case should be located only inside enum classes'
1115
                        );
1116
                    }
1117
1118
                    $case = $this->parseEnumCase();
1119
                    $case->setEnum($classOrInterface);
1120
                    $classOrInterface->addChild($case);
1121
                    break;
1122
                default:
1123
                    throw $this->getUnexpectedNextTokenException();
1124
            }
1125
1126
            $tokenType = $this->tokenizer->peek();
1127
        }
1128
1129
        throw new TokenStreamEndException($this->tokenizer);
1130
    }
1131
1132
    /**
1133
     * This method will parse a list of modifiers and a following property or
1134
     * method.
1135
     *
1136
     * @param int $modifiers
1137
     *
1138
     * @return ASTConstantDefinition|ASTFieldDeclaration|ASTMethod
1139
     *
1140
     * @since 0.9.6
1141
     */
1142
    protected function parseMethodOrFieldDeclaration($modifiers = 0)
1143
    {
1144
        $this->tokenStack->push();
1145
        $tokenType = $this->tokenizer->peek();
1146
1147
        while ($tokenType !== Tokenizer::T_EOF) {
1148
            switch ($tokenType) {
1149
                case Tokens::T_PRIVATE:
1150
                    $modifiers |= State::IS_PRIVATE;
1151
                    $modifiers = $modifiers & ~State::IS_PUBLIC;
1152
                    break;
1153
                case Tokens::T_PROTECTED:
1154
                    $modifiers |= State::IS_PROTECTED;
1155
                    $modifiers = $modifiers & ~State::IS_PUBLIC;
1156
                    break;
1157
                case Tokens::T_VAR:
1158
                case Tokens::T_PUBLIC:
1159
                    $modifiers |= State::IS_PUBLIC;
1160
                    break;
1161
                case Tokens::T_STATIC:
1162
                    $modifiers |= State::IS_STATIC;
1163
                    break;
1164
                case Tokens::T_ABSTRACT:
1165
                    $modifiers |= State::IS_ABSTRACT;
1166
                    break;
1167
                case Tokens::T_FINAL:
1168
                    $modifiers |= State::IS_FINAL;
1169
                    break;
1170
                case Tokens::T_READONLY:
1171
                    $modifiers |= State::IS_READONLY;
1172
                    break;
1173
                case Tokens::T_FUNCTION:
1174
                    $method = $this->parseMethodDeclaration();
1175
                    $method->setModifiers($modifiers);
1176
                    $method->setCompilationUnit($this->compilationUnit);
1177
                    $method->setId($this->idBuilder->forMethod($method));
1178
                    $method->setTokens($this->tokenStack->pop());
1179
1180
                    return $method;
1181
                case Tokens::T_VARIABLE:
1182
                    $declaration = $this->parseFieldDeclaration();
1183
                    $declaration->setModifiers($modifiers);
1184
1185
                    return $declaration;
1186
                default:
1187
                    return $this->parseUnknownDeclaration($tokenType, $modifiers);
1188
            }
1189
1190
            $this->consumeToken($tokenType);
1191
            $this->consumeComments();
1192
1193
            $tokenType = $this->tokenizer->peek();
1194
        }
1195
1196
        throw $this->getUnexpectedNextTokenException();
1197
    }
1198
1199
    /**
1200
     * Override this in later PHPParserVersions as necessary
1201
     *
1202
     * @param int $tokenType
1203
     * @param int $modifiers
1204
     *
1205
     * @throws UnexpectedTokenException
1206
     *
1207
     * @return ASTConstantDefinition|ASTFieldDeclaration
1208
     */
1209
    protected function parseUnknownDeclaration($tokenType, $modifiers)
1210
    {
1211
        throw $this->getUnexpectedNextTokenException();
1212
    }
1213
1214
    /**
1215
     * This method will parse a class field declaration with all it's variables.
1216
     *
1217
     * <code>
1218
     * // Simple field declaration
1219
     * class Foo {
1220
     *     protected $foo;
1221
     * }
1222
     *
1223
     * // Field declaration with multiple properties
1224
     * class Foo {
1225
     *     protected $foo = 23
1226
     *               $bar = 42,
1227
     *               $baz = null;
1228
     * }
1229
     * </code>
1230
     *
1231
     * @return ASTFieldDeclaration
1232
     *
1233
     * @since 0.9.6
1234
     */
1235
    protected function parseFieldDeclaration()
1236
    {
1237
        $declaration = $this->builder->buildAstFieldDeclaration();
1238
        $declaration->setComment($this->docComment);
1239
1240
        $type = $this->parseFieldDeclarationType();
1241
1242
        if ($type !== null) {
1243
            $declaration->addChild($type);
1244
        }
1245
1246
        $this->consumeComments();
1247
        $tokenType = $this->tokenizer->peek();
1248
1249
        while ($tokenType !== Tokenizer::T_EOF) {
1250
            $declaration->addChild($this->parseVariableDeclarator());
1251
1252
            $this->consumeComments();
1253
            $tokenType = $this->tokenizer->peek();
1254
1255
            if ($tokenType !== Tokens::T_COMMA) {
1256
                break;
1257
            }
1258
1259
            $this->consumeToken(Tokens::T_COMMA);
1260
1261
            $this->consumeComments();
1262
            $tokenType = $this->tokenizer->peek();
1263
        }
1264
1265
        $this->setNodePositionsAndReturn($declaration);
1266
1267
        $this->consumeToken(Tokens::T_SEMICOLON);
1268
1269
        return $declaration;
1270
    }
1271
1272
    /**
1273
     * This method parses a simple function or a PHP 5.3 lambda function or
1274
     * closure.
1275
     *
1276
     * @return ASTCallable
1277
     *
1278
     * @since 0.9.5
1279
     */
1280
    private function parseFunctionOrClosureDeclaration()
1281
    {
1282
        $this->tokenStack->push();
1283
1284
        $this->consumeToken(Tokens::T_FUNCTION);
1285
        $this->consumeComments();
1286
1287
        $returnReference = $this->parseOptionalByReference();
1288
1289
        if ($this->isNextTokenFormalParameterList()) {
1290
            return $this->setNodePositionsAndReturn(
1291
                $this->parseClosureDeclaration()
1292
            );
1293
        }
1294
1295
        $docComment = $this->docComment;
1296
        $callable = $this->parseFunctionDeclaration();
1297
        $this->compilationUnit->addChild($callable);
1298
1299
        $callable->setComment($docComment);
1300
        $callable->setTokens($this->tokenStack->pop());
1301
        $this->prepareCallable($callable);
1302
1303
        if ($returnReference) {
1304
            $callable->setReturnsReference();
1305
        }
1306
1307
        $this->reset();
1308
1309
        return $callable;
1310
    }
1311
1312
    /**
1313
     * Parses an optional by reference token. The return value will be
1314
     * <b>true</b> when a reference token was found, otherwise this method will
1315
     * return <b>false</b>.
1316
     *
1317
     * @return bool
1318
     *
1319
     * @since 0.9.8
1320
     */
1321
    protected function parseOptionalByReference()
1322
    {
1323
        return $this->isNextTokenByReference() && $this->parseByReference();
1324
    }
1325
1326
    /**
1327
     * Tests that the next available token is the returns by reference token.
1328
     *
1329
     * @return bool
1330
     *
1331
     * @since 0.9.8
1332
     */
1333
    private function isNextTokenByReference()
1334
    {
1335
        return ($this->tokenizer->peek() === Tokens::T_BITWISE_AND);
1336
    }
1337
1338
    /**
1339
     * This method parses a returns by reference token and returns <b>true</b>.
1340
     *
1341
     * @return bool
1342
     */
1343
    private function parseByReference()
1344
    {
1345
        $this->consumeToken(Tokens::T_BITWISE_AND);
1346
        $this->consumeComments();
1347
1348
        return true;
1349
    }
1350
1351
    /**
1352
     * Tests that the next available token is an opening parenthesis.
1353
     *
1354
     * @return bool
1355
     *
1356
     * @since 0.9.10
1357
     */
1358
    private function isNextTokenFormalParameterList()
1359
    {
1360
        $this->consumeComments();
1361
        return ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN);
1362
    }
1363
1364
    /**
1365
     * This method parses a function declaration.
1366
     *
1367
     * @return ASTFunction
1368
     *
1369
     * @since 0.9.5
1370
     */
1371
    private function parseFunctionDeclaration()
1372
    {
1373
        $this->consumeComments();
1374
1375
        // Next token must be the function identifier
1376
        $functionName = $this->parseFunctionName();
1377
1378
        $function = $this->builder->buildFunction($functionName);
1379
        $function->setCompilationUnit($this->compilationUnit);
1380
        $function->setId($this->idBuilder->forFunction($function));
1381
1382
        $this->parseCallableDeclaration($function);
1383
1384
        // First check for an existing namespace
1385
        if ($this->namespaceName !== null) {
1386
            $namespaceName = $this->namespaceName;
1387
        } elseif ($this->packageName !== Builder::DEFAULT_NAMESPACE) {
1388
            $namespaceName = $this->packageName;
1389
        } else {
1390
            $namespaceName = $this->globalPackageName;
1391
        }
1392
1393
        $namespace = $this->builder->buildNamespace($namespaceName);
1394
        $namespace->setPackageAnnotation(null === $this->namespaceName);
1395
        $namespace->addFunction($function);
1396
1397
        // Store function in source file, because we need them during the file's
1398
        // __wakeup() phase for function declarations within another function or
1399
        // method declaration.
1400
        $this->compilationUnit->addChild($function);
1401
1402
        return $function;
1403
    }
1404
1405
    /**
1406
     * This method parses a method declaration.
1407
     *
1408
     * @return ASTMethod
1409
     *
1410
     * @since 0.9.5
1411
     */
1412
    private function parseMethodDeclaration()
1413
    {
1414
        // Read function keyword
1415
        $this->consumeToken(Tokens::T_FUNCTION);
1416
        $this->consumeComments();
1417
1418
        $returnsReference = $this->parseOptionalByReference();
1419
1420
        $methodName = $this->parseMethodName();
1421
1422
        $method = $this->builder->buildMethod($methodName);
1423
        $method->setComment($this->docComment);
1424
        $method->setCompilationUnit($this->compilationUnit);
1425
1426
        $this->classOrInterface->addMethod($method);
0 ignored issues
show
Bug introduced by
The method addMethod() 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

1426
        $this->classOrInterface->/** @scrutinizer ignore-call */ 
1427
                                 addMethod($method);

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...
1427
1428
        $this->parseCallableDeclaration($method);
1429
        $this->prepareCallable($method);
1430
1431
        if ($returnsReference === true) {
1432
            $method->setReturnsReference();
1433
        }
1434
1435
        return $method;
1436
    }
1437
1438
    /**
1439
     * This method parses a PHP 5.3 closure or lambda function.
1440
     *
1441
     * @return ASTClosure
1442
     *
1443
     * @since 0.9.5
1444
     */
1445
    private function parseClosureDeclaration()
1446
    {
1447
        $this->tokenStack->push();
1448
1449
        if (Tokens::T_FUNCTION === $this->tokenizer->peek()) {
1450
            $this->consumeToken(Tokens::T_FUNCTION);
1451
        }
1452
1453
        $closure = $this->builder->buildAstClosure();
1454
        $closure->setReturnsByReference($this->parseOptionalByReference());
1455
        $closure->addChild($this->parseFormalParameters($closure));
1456
        $closure = $this->parseOptionalBoundVariables($closure);
1457
        $this->parseCallableDeclarationAddition($closure);
1458
        $closure->addChild($this->parseScope());
1459
1460
        return $this->setNodePositionsAndReturn($closure);
1461
    }
1462
1463
    /**
1464
     * Parses a function or a method and adds it to the parent context node.
1465
     *
1466
     * @return void
1467
     */
1468
    private function parseCallableDeclaration(AbstractASTCallable $callable)
1469
    {
1470
        $callable->addChild($this->parseFormalParameters($callable));
1471
        $this->parseCallableDeclarationAddition($callable);
1472
1473
        $this->consumeComments();
1474
1475
        if ($this->tokenizer->peek() == Tokens::T_CURLY_BRACE_OPEN) {
1476
            $callable->addChild($this->parseScope());
1477
1478
            return;
1479
        }
1480
1481
        $this->consumeToken(Tokens::T_SEMICOLON);
1482
    }
1483
1484
    /**
1485
     * Extension for version specific additions.
1486
     *
1487
     * @template T of AbstractASTCallable|ASTClosure
1488
     *
1489
     * @param T $callable
1490
     *
1491
     * @return T
1492
     */
1493
    protected function parseCallableDeclarationAddition($callable)
1494
    {
1495
        return $callable;
1496
    }
1497
1498
    /**
1499
     * Parses a trait use statement.
1500
     *
1501
     * @return ASTTraitUseStatement
1502
     *
1503
     * @since 1.0.0
1504
     */
1505
    private function parseTraitUseStatement()
1506
    {
1507
        $this->tokenStack->push();
1508
        $this->consumeToken(Tokens::T_USE);
1509
1510
        $useStatement = $this->builder->buildAstTraitUseStatement();
1511
        $useStatement->addChild($this->parseTraitReference());
1512
1513
        $this->consumeComments();
1514
1515
        while (Tokens::T_COMMA === $this->tokenizer->peek()) {
1516
            $this->consumeToken(Tokens::T_COMMA);
1517
            $useStatement->addChild($this->parseTraitReference());
1518
        }
1519
1520
        return $this->setNodePositionsAndReturn(
1521
            $this->parseOptionalTraitAdaptation($useStatement)
1522
        );
1523
    }
1524
1525
    /**
1526
     * Parses a trait reference instance.
1527
     *
1528
     * @return ASTTraitReference
1529
     *
1530
     * @since 1.0.0
1531
     */
1532
    private function parseTraitReference()
1533
    {
1534
        $this->consumeComments();
1535
        $this->tokenStack->push();
1536
1537
        return $this->setNodePositionsAndReturn(
1538
            $this->builder->buildAstTraitReference(
1539
                $this->parseQualifiedName()
1540
            )
1541
        );
1542
    }
1543
1544
    /**
1545
     * Parses the adaptation list of the given use statement or simply reads
1546
     * the terminating semicolon, when no adaptation list exists.
1547
     *
1548
     * @return ASTTraitUseStatement
1549
     *
1550
     * @since 1.0.0
1551
     */
1552
    private function parseOptionalTraitAdaptation(ASTTraitUseStatement $useStatement)
1553
    {
1554
        $this->consumeComments();
1555
1556
        if (Tokens::T_CURLY_BRACE_OPEN === $this->tokenizer->peek()) {
1557
            $useStatement->addChild($this->parseTraitAdaptation());
1558
        } else {
1559
            $this->consumeToken(Tokens::T_SEMICOLON);
1560
        }
1561
1562
        return $useStatement;
1563
    }
1564
1565
    /**
1566
     * Parses the adaptation expression of a trait use statement.
1567
     *
1568
     * @return ASTTraitAdaptation
1569
     *
1570
     * @since 1.0.0
1571
     */
1572
    private function parseTraitAdaptation()
1573
    {
1574
        $this->tokenStack->push();
1575
1576
        $adaptation = $this->builder->buildAstTraitAdaptation();
1577
1578
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
1579
1580
        do {
1581
            $this->tokenStack->push();
1582
1583
            $reference = $this->parseTraitMethodReference();
1584
            $this->consumeComments();
1585
1586
            $stmt = Tokens::T_AS === $this->tokenizer->peek()
1587
                ? $this->parseTraitAdaptationAliasStatement($reference)
1588
                : $this->parseTraitAdaptationPrecedenceStatement($reference);
1589
1590
            $this->consumeComments();
1591
            $this->consumeToken(Tokens::T_SEMICOLON);
1592
1593
            $adaptation->addChild($this->setNodePositionsAndReturn($stmt));
1594
1595
            $this->consumeComments();
1596
        } while (Tokens::T_CURLY_BRACE_CLOSE !== $this->tokenizer->peek());
1597
1598
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
1599
1600
        return $this->setNodePositionsAndReturn($adaptation);
1601
    }
1602
1603
    /**
1604
     * Parses a trait method reference and returns the found reference as an
1605
     * <b>array</b>.
1606
     *
1607
     * The returned array with contain only one element, when the referenced
1608
     * method is specified by the method's name, without the declaring trait.
1609
     * When the method reference contains the declaring trait the returned
1610
     * <b>array</b> will contain two elements. The first element is the plain
1611
     * method name and the second element is an instance of the
1612
     * {@link ASTTraitReference} class that represents the
1613
     * declaring trait.
1614
     *
1615
     * @return array<int, mixed>
1616
     *
1617
     * @since 1.0.0
1618
     */
1619
    private function parseTraitMethodReference()
1620
    {
1621
        $this->tokenStack->push();
1622
1623
        $qualifiedName = $this->parseQualifiedName();
1624
1625
        $this->consumeComments();
1626
1627
        if (Tokens::T_DOUBLE_COLON === $this->tokenizer->peek()) {
1628
            $traitReference = $this->setNodePositionsAndReturn(
1629
                $this->builder->buildAstTraitReference($qualifiedName)
1630
            );
1631
1632
            $this->consumeToken(Tokens::T_DOUBLE_COLON);
1633
            $this->consumeComments();
1634
1635
            return array($this->parseMethodName(), $traitReference);
1636
        }
1637
1638
        $this->tokenStack->pop();
1639
1640
        return array($qualifiedName);
1641
    }
1642
1643
    /**
1644
     * Parses a trait adaptation alias statement.
1645
     *
1646
     * @param array<int, mixed> $reference Parsed method reference array.
1647
     *
1648
     * @return ASTTraitAdaptationAlias
1649
     *
1650
     * @since 1.0.0
1651
     */
1652
    private function parseTraitAdaptationAliasStatement(array $reference)
1653
    {
1654
        $stmt = $this->builder->buildAstTraitAdaptationAlias($reference[0]);
1655
1656
        if (2 === count($reference)) {
1657
            $stmt->addChild($reference[1]);
1658
        }
1659
1660
        $this->consumeToken(Tokens::T_AS);
1661
        $this->consumeComments();
1662
1663
        switch ($this->tokenizer->peek()) {
1664
            case Tokens::T_PUBLIC:
1665
                $stmt->setNewModifier(State::IS_PUBLIC);
1666
                $this->consumeToken(Tokens::T_PUBLIC);
1667
                $this->consumeComments();
1668
                break;
1669
            case Tokens::T_PROTECTED:
1670
                $stmt->setNewModifier(State::IS_PROTECTED);
1671
                $this->consumeToken(Tokens::T_PROTECTED);
1672
                $this->consumeComments();
1673
                break;
1674
            case Tokens::T_PRIVATE:
1675
                $stmt->setNewModifier(State::IS_PRIVATE);
1676
                $this->consumeToken(Tokens::T_PRIVATE);
1677
                $this->consumeComments();
1678
                break;
1679
        }
1680
1681
        if (Tokens::T_SEMICOLON !== $this->tokenizer->peek()) {
1682
            $stmt->setNewName($this->parseMethodName());
1683
        }
1684
        return $stmt;
1685
    }
1686
1687
    /**
1688
     * Parses a trait adaptation precedence statement.
1689
     *
1690
     * @param array<int, mixed> $reference Parsed method reference array.
1691
     *
1692
     * @throws InvalidStateException
1693
     *
1694
     * @return ASTTraitAdaptationPrecedence
1695
     *
1696
     * @since 1.0.0
1697
     */
1698
    private function parseTraitAdaptationPrecedenceStatement(array $reference)
1699
    {
1700
        if (count($reference) < 2) {
1701
            throw new InvalidStateException(
1702
                $this->requireNextToken()->startLine,
1703
                $this->compilationUnit->getFileName(),
1704
                'Expecting full qualified trait method name.'
1705
            );
1706
        }
1707
1708
        $stmt = $this->builder->buildAstTraitAdaptationPrecedence($reference[0]);
1709
        $stmt->addChild($reference[1]);
1710
1711
        $this->consumeToken(Tokens::T_INSTEADOF);
1712
        $this->consumeComments();
1713
1714
        $stmt->addChild($this->parseTraitReference());
1715
1716
        $this->consumeComments();
1717
        while (Tokens::T_COMMA === $this->tokenizer->peek()) {
1718
            $this->consumeToken(Tokens::T_COMMA);
1719
            $stmt->addChild($this->parseTraitReference());
1720
            $this->consumeComments();
1721
        }
1722
1723
        return $stmt;
1724
    }
1725
1726
    /**
1727
     * Parses an allocation expression.
1728
     *
1729
     * <code>
1730
     * function foo()
1731
     * {
1732
     * //  -------------
1733
     *     new bar\Baz();
1734
     * //  -------------
1735
     *
1736
     * //  ---------
1737
     *     new Foo();
1738
     * //  ---------
1739
     * }
1740
     * </code>
1741
     *
1742
     * @return ASTAllocationExpression
1743
     *
1744
     * @since 0.9.6
1745
     */
1746
    protected function parseAllocationExpression()
1747
    {
1748
        $this->tokenStack->push();
1749
1750
        $allocation = $this->parseAllocationExpressionValue();
1751
1752
        if ($this->isNextTokenArguments()) {
1753
            $allocation->addChild($this->parseArguments());
1754
        }
1755
1756
        return $this->setNodePositionsAndReturn($allocation);
1757
    }
1758
1759
    /**
1760
     * Parse the type reference used in an allocation expression.
1761
     *
1762
     * @template T of ASTAllocationExpression
1763
     *
1764
     * @param T $allocation
1765
     *
1766
     * @return T
1767
     *
1768
     * @since 2.3
1769
     */
1770
    protected function parseAllocationExpressionTypeReference(ASTAllocationExpression $allocation)
1771
    {
1772
        return $this->parseExpressionTypeReference($allocation, true);
1773
    }
1774
1775
    /**
1776
     * Parse the instanciation for new keyword without arguments.
1777
     *
1778
     * @return ASTAllocationExpression
1779
     */
1780
    private function parseAllocationExpressionValue()
1781
    {
1782
        $token = $this->consumeToken(Tokens::T_NEW);
1783
        $this->consumeComments();
1784
        $inParentheses = ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN);
1785
1786
        if ($inParentheses) {
1787
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
1788
        }
1789
1790
        $allocation = $this->builder->buildAstAllocationExpression($token->image);
1791
1792
        if (!$inParentheses) {
1793
            return $this->parseAllocationExpressionTypeReference($allocation);
1794
        }
1795
1796
        $expression = $this->parseOptionalExpression();
1797
1798
        if (!$expression) {
1799
            throw $this->getUnexpectedNextTokenException();
1800
        }
1801
1802
        $allocation->addChild($expression);
1803
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
1804
1805
        return $allocation;
1806
    }
1807
1808
    /**
1809
     * Parses a eval-expression node.
1810
     *
1811
     * @return ASTEvalExpression
1812
     *
1813
     * @since 0.9.12
1814
     */
1815
    private function parseEvalExpression()
1816
    {
1817
        $this->tokenStack->push();
1818
        $token = $this->consumeToken(Tokens::T_EVAL);
1819
1820
        $expr = $this->builder->buildAstEvalExpression($token->image);
1821
        $expr->addChild($this->parseParenthesisExpression());
1822
1823
        return $this->setNodePositionsAndReturn($expr);
1824
    }
1825
1826
    /**
1827
     * This method parses an exit-expression.
1828
     *
1829
     * @return ASTExitExpression
1830
     *
1831
     * @since 0.9.12
1832
     */
1833
    private function parseExitExpression()
1834
    {
1835
        $this->tokenStack->push();
1836
        $token = $this->consumeToken(Tokens::T_EXIT);
1837
1838
        $expr = $this->builder->buildAstExitExpression($token->image);
1839
1840
        $this->consumeComments();
1841
        if ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN) {
1842
            $expr->addChild($this->parseParenthesisExpression());
1843
        }
1844
        return $this->setNodePositionsAndReturn($expr);
1845
    }
1846
1847
    /**
1848
     * Parses a clone-expression node.
1849
     *
1850
     * @return ASTCloneExpression
1851
     *
1852
     * @since 0.9.12
1853
     */
1854
    private function parseCloneExpression()
1855
    {
1856
        $this->tokenStack->push();
1857
        $token = $this->consumeToken(Tokens::T_CLONE);
1858
1859
        $expr = $this->builder->buildAstCloneExpression($token->image);
1860
        $expr->addChild($this->parseExpression());
1861
1862
        return $this->setNodePositionsAndReturn($expr);
1863
    }
1864
1865
    /**
1866
     * Throws an exception if the given token is not a valid list unpacking opening token for current PHP level.
1867
     *
1868
     * @param int   $tokenType
1869
     * @param Token $unexpectedToken
1870
     *
1871
     * @return void
1872
     */
1873
    private function ensureTokenIsListUnpackingOpening($tokenType, $unexpectedToken = null)
1874
    {
1875
        if (!$this->isListUnpacking($tokenType)) {
1876
            throw $this->getUnexpectedTokenException($unexpectedToken ?: $this->tokenizer->prevToken());
1877
        }
1878
    }
1879
1880
    /**
1881
     * Return true if current PHP level supports keys in lists.
1882
     *
1883
     * @return bool
1884
     */
1885
    protected function supportsKeysInList()
1886
    {
1887
        return false;
1888
    }
1889
1890
    /**
1891
     * This method parses a single list-statement node.
1892
     *
1893
     * @return ASTListExpression
1894
     *
1895
     * @since 0.9.12
1896
     */
1897
    private function parseListExpression()
1898
    {
1899
        $this->tokenStack->push();
1900
1901
        $tokenType = $this->tokenizer->peek();
1902
        $this->ensureTokenIsListUnpackingOpening($tokenType);
1903
        $shortSyntax = ($tokenType !== Tokens::T_LIST);
1904
1905
        if ($shortSyntax) {
1906
            $token = $this->consumeToken(Tokens::T_SQUARED_BRACKET_OPEN);
1907
            $list = $this->builder->buildAstListExpression($token->image);
1908
        } else {
1909
            $token = $this->consumeToken(Tokens::T_LIST);
1910
            $this->consumeComments();
1911
1912
            $list = $this->builder->buildAstListExpression($token->image);
1913
1914
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
1915
        }
1916
1917
        $this->consumeComments();
1918
1919
        while (($tokenType = $this->tokenizer->peek()) !== Tokenizer::T_EOF) {
1920
            // The variable is optional:
1921
            //   list(, , , , $something) = ...;
1922
            // is valid.
1923
            switch ($tokenType) {
1924
                case Tokens::T_COMMA:
1925
                    $this->consumeToken(Tokens::T_COMMA);
1926
                    $this->consumeComments();
1927
                    break;
1928
                case Tokens::T_SQUARED_BRACKET_CLOSE:
1929
                case Tokens::T_PARENTHESIS_CLOSE:
1930
                    break 2;
1931
                case Tokens::T_LIST:
1932
                case Tokens::T_SQUARED_BRACKET_OPEN:
1933
                    $list->addChild($this->parseListExpression());
1934
                    $this->consumeComments();
1935
                    break;
1936
                default:
1937
                    $list->addChild($this->parseListSlotExpression());
1938
                    $this->consumeComments();
1939
                    break;
1940
            }
1941
        }
1942
1943
        $closeToken = $shortSyntax ? Tokens::T_SQUARED_BRACKET_CLOSE : Tokens::T_PARENTHESIS_CLOSE;
1944
        $this->consumeToken($closeToken);
1945
1946
        return $this->setNodePositionsAndReturn($list);
1947
    }
1948
1949
    /**
1950
     * Return true if the current node can be used as a list key.
1951
     *
1952
     * @param ASTNode|null $node
1953
     *
1954
     * @return bool
1955
     */
1956
    protected function canBeListKey($node)
1957
    {
1958
        return !$this->isReadWriteVariable($node);
1959
    }
1960
1961
    /**
1962
     * Parse individual slot of a list() expression.
1963
     *
1964
     * @return ASTListExpression|ASTNode
1965
     */
1966
    private function parseListSlotExpression()
1967
    {
1968
        $startToken = $this->tokenizer->currentToken();
1969
        $node = $this->parseOptionalExpression();
1970
1971
        if ($node && $this->canBeListKey($node) && $this->tokenizer->peek() === Tokens::T_DOUBLE_ARROW) {
1972
            if (!$this->supportsKeysInList()) {
1973
                throw $this->getUnexpectedTokenException($startToken);
1974
            }
1975
1976
            $this->consumeComments();
1977
            $this->consumeToken(Tokens::T_DOUBLE_ARROW);
1978
            $this->consumeComments();
1979
1980
            return in_array($this->tokenizer->peek(), array(Tokens::T_LIST, Tokens::T_SQUARED_BRACKET_OPEN))
1981
                ? $this->parseListExpression()
1982
                : $this->parseVariableOrConstantOrPrimaryPrefix();
1983
        }
1984
1985
        return $node ?: $this->parseVariableOrConstantOrPrimaryPrefix();
1986
    }
1987
1988
    /**
1989
     * Parses a include-expression node.
1990
     *
1991
     * @return ASTIncludeExpression
1992
     *
1993
     * @since 0.9.12
1994
     */
1995
    private function parseIncludeExpression()
1996
    {
1997
        $expr = $this->builder->buildAstIncludeExpression();
1998
1999
        return $this->parseRequireOrIncludeExpression($expr, Tokens::T_INCLUDE);
2000
    }
2001
2002
    /**
2003
     * Parses a include_once-expression node.
2004
     *
2005
     * @return ASTIncludeExpression
2006
     *
2007
     * @since 0.9.12
2008
     */
2009
    private function parseIncludeOnceExpression()
2010
    {
2011
        $expr = $this->builder->buildAstIncludeExpression();
2012
        $expr->setOnce();
2013
2014
        return $this->parseRequireOrIncludeExpression($expr, Tokens::T_INCLUDE_ONCE);
2015
    }
2016
2017
    /**
2018
     * Parses a require-expression node.
2019
     *
2020
     * @return ASTRequireExpression
2021
     *
2022
     * @since 0.9.12
2023
     */
2024
    private function parseRequireExpression()
2025
    {
2026
        $expr = $this->builder->buildAstRequireExpression();
2027
2028
        return $this->parseRequireOrIncludeExpression($expr, Tokens::T_REQUIRE);
2029
    }
2030
2031
    /**
2032
     * Parses a require_once-expression node.
2033
     *
2034
     * @return ASTRequireExpression
2035
     *
2036
     * @since 0.9.12
2037
     */
2038
    private function parseRequireOnceExpression()
2039
    {
2040
        $expr = $this->builder->buildAstRequireExpression();
2041
        $expr->setOnce();
2042
2043
        return $this->parseRequireOrIncludeExpression($expr, Tokens::T_REQUIRE_ONCE);
2044
    }
2045
2046
    /**
2047
     * Parses a <b>require_once</b>-, <b>require</b>-, <b>include_once</b>- or
2048
     * <b>include</b>-expression node.
2049
     *
2050
     * @template T of ASTExpression
2051
     *
2052
     * @param T   $expr
2053
     * @param int $type
2054
     *
2055
     * @return T
2056
     *
2057
     * @since 0.9.12
2058
     */
2059
    private function parseRequireOrIncludeExpression(ASTExpression $expr, $type)
2060
    {
2061
        $this->tokenStack->push();
2062
2063
        $this->consumeToken($type);
2064
        $this->consumeComments();
2065
2066
        if ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN) {
2067
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
2068
            $expr->addChild($this->parseOptionalExpression());
0 ignored issues
show
Bug introduced by
It seems like $this->parseOptionalExpression() can also be of type null; however, parameter $node of PDepend\Source\AST\AbstractASTNode::addChild() does only seem to accept PDepend\Source\AST\ASTNode, 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

2068
            $expr->addChild(/** @scrutinizer ignore-type */ $this->parseOptionalExpression());
Loading history...
2069
            $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
2070
        } else {
2071
            $expr->addChild($this->parseOptionalExpression());
2072
        }
2073
2074
        return $this->setNodePositionsAndReturn($expr);
2075
    }
2076
2077
    /**
2078
     * Parses a cast-expression node.
2079
     *
2080
     * @return ASTCastExpression
2081
     *
2082
     * @since 0.10.0
2083
     */
2084
    protected function parseCastExpression()
2085
    {
2086
        $token = $this->consumeToken($this->tokenizer->peek());
2087
2088
        $expr = $this->builder->buildAstCastExpression($token->image);
2089
        $expr->configureLinesAndColumns(
2090
            $token->startLine,
2091
            $token->endLine,
2092
            $token->startColumn,
2093
            $token->endColumn
2094
        );
2095
2096
        return $expr;
2097
    }
2098
2099
    /**
2100
     * This method will parse an increment-expression. Depending on the previous node
2101
     * this can be a {@link ASTPostIncrementExpression} or {@link ASTPostfixExpression}.
2102
     *
2103
     * @param array<ASTNode> $expressions List of previous parsed expression nodes.
2104
     *
2105
     * @return ASTExpression
2106
     *
2107
     * @since 0.10.0
2108
     */
2109
    private function parseIncrementExpression(array &$expressions)
2110
    {
2111
        if ($this->isReadWriteVariable(end($expressions))) {
2112
            return $this->parsePostIncrementExpression(array_pop($expressions));
2113
        }
2114
        return $this->parsePreIncrementExpression();
2115
    }
2116
2117
    /**
2118
     * Parses a post increment-expression and adds the given child to that node.
2119
     *
2120
     * @param ASTNode $child The child expression node.
2121
     *
2122
     * @return ASTPostfixExpression
2123
     *
2124
     * @since 0.10.0
2125
     */
2126
    private function parsePostIncrementExpression(ASTNode $child)
2127
    {
2128
        $token = $this->consumeToken(Tokens::T_INC);
2129
2130
        $expr = $this->builder->buildAstPostfixExpression($token->image);
2131
        $expr->addChild($child);
2132
        $expr->configureLinesAndColumns(
2133
            $child->getStartLine(),
2134
            $token->endLine,
2135
            $child->getStartColumn(),
2136
            $token->endColumn
2137
        );
2138
2139
        return $expr;
2140
    }
2141
2142
    /**
2143
     * Parses a pre increment-expression and adds the given child to that node.
2144
     *
2145
     * @return ASTPreIncrementExpression
2146
     *
2147
     * @since 0.10.0
2148
     */
2149
    private function parsePreIncrementExpression()
2150
    {
2151
        $token = $this->consumeToken(Tokens::T_INC);
2152
2153
        $expr = $this->builder->buildAstPreIncrementExpression();
2154
        $expr->configureLinesAndColumns(
2155
            $token->startLine,
2156
            $token->endLine,
2157
            $token->startColumn,
2158
            $token->endColumn
2159
        );
2160
2161
        return $expr;
2162
    }
2163
2164
    /**
2165
     * This method will parse an decrement-expression. Depending on the previous node
2166
     * this can be a {@link ASTPostDecrementExpression} or {@link ASTPostfixExpression}.
2167
     *
2168
     * @param array<ASTNode> $expressions List of previous parsed expression nodes.
2169
     *
2170
     * @return ASTExpression
2171
     *
2172
     * @since 0.10.0
2173
     */
2174
    private function parseDecrementExpression(array &$expressions)
2175
    {
2176
        if ($this->isReadWriteVariable(end($expressions))) {
2177
            return $this->parsePostDecrementExpression(array_pop($expressions));
2178
        }
2179
        return $this->parsePreDecrementExpression();
2180
    }
2181
2182
    /**
2183
     * Parses a post decrement-expression and adds the given child to that node.
2184
     *
2185
     * @param ASTNode $child The child expression node.
2186
     *
2187
     * @return ASTPostfixExpression
2188
     *
2189
     * @since 0.10.0
2190
     */
2191
    private function parsePostDecrementExpression(ASTNode $child)
2192
    {
2193
        $token = $this->consumeToken(Tokens::T_DEC);
2194
2195
        $expr = $this->builder->buildAstPostfixExpression($token->image);
2196
        $expr->addChild($child);
2197
        $expr->configureLinesAndColumns(
2198
            $child->getStartLine(),
2199
            $token->endLine,
2200
            $child->getStartColumn(),
2201
            $token->endColumn
2202
        );
2203
2204
        return $expr;
2205
    }
2206
2207
    /**
2208
     * Parses a pre decrement-expression and adds the given child to that node.
2209
     *
2210
     * @return ASTPreDecrementExpression
2211
     *
2212
     * @since 0.10.0
2213
     */
2214
    private function parsePreDecrementExpression()
2215
    {
2216
        $token = $this->consumeToken(Tokens::T_DEC);
2217
2218
        $expr = $this->builder->buildAstPreDecrementExpression();
2219
        $expr->configureLinesAndColumns(
2220
            $token->startLine,
2221
            $token->endLine,
2222
            $token->startColumn,
2223
            $token->endColumn
2224
        );
2225
2226
        return $expr;
2227
    }
2228
2229
    /**
2230
     * Parses one or more optional php <b>array</b> or <b>string</b> expressions.
2231
     *
2232
     * <code>
2233
     * ---------
2234
     * $array[0];
2235
     * ---------
2236
     *
2237
     * ----------------
2238
     * $array[1]['foo'];
2239
     * ----------------
2240
     *
2241
     * ----------------
2242
     * $string{1}[0]{0};
2243
     * ----------------
2244
     * </code>
2245
     *
2246
     * @template T of ASTNode
2247
     *
2248
     * @param T $node The parent/context node instance.
2249
     *
2250
     * @return ASTIndexExpression|T
2251
     *
2252
     * @since 0.9.12
2253
     */
2254
    protected function parseOptionalIndexExpression(ASTNode $node)
2255
    {
2256
        $this->consumeComments();
2257
2258
        switch ($this->tokenizer->peek()) {
2259
            case Tokens::T_CURLY_BRACE_OPEN:
2260
                return $this->parseStringIndexExpression($node);
2261
            case Tokens::T_SQUARED_BRACKET_OPEN:
2262
                return $this->parseArrayIndexExpression($node);
2263
        }
2264
2265
        return $node;
2266
    }
2267
2268
    /**
2269
     * Parses an index expression as it is valid to access elements in a php
2270
     * string or array.
2271
     *
2272
     * @template T of ASTExpression
2273
     *
2274
     * @param ASTNode $node  The context source node.
2275
     * @param T       $expr  The concrete index expression.
2276
     * @param int     $open  The open token type.
2277
     * @param int     $close The close token type.
2278
     *
2279
     * @return ASTIndexExpression|T
2280
     *
2281
     * @since 0.9.12
2282
     */
2283
    private function parseIndexExpression(
2284
        ASTNode $node,
2285
        ASTExpression $expr,
2286
        $open,
2287
        $close
2288
    ) {
2289
        $this->consumeToken($open);
2290
2291
        if (($child = $this->parseOptionalExpression()) != null) {
2292
            $expr->addChild($child);
2293
        }
2294
2295
        $token = $this->consumeToken($close);
2296
2297
        $expr->configureLinesAndColumns(
2298
            $node->getStartLine(),
2299
            $token->endLine,
2300
            $node->getStartColumn(),
2301
            $token->endColumn
2302
        );
2303
2304
        return $this->parseOptionalIndexExpression($expr);
2305
    }
2306
2307
    /**
2308
     * Parses a mandatory array index expression.
2309
     *
2310
     * <code>
2311
     * //    ---
2312
     * $array[0];
2313
     * //    ---
2314
     * </code>
2315
     *
2316
     * @param ASTNode $node The context source node.
2317
     *
2318
     * @return ASTIndexExpression
2319
     *
2320
     * @since 0.9.12
2321
     */
2322
    private function parseArrayIndexExpression(ASTNode $node)
2323
    {
2324
        $expr = $this->builder->buildAstArrayIndexExpression();
2325
        $expr->addChild($node);
2326
2327
        return $this->parseIndexExpression(
2328
            $node,
2329
            $expr,
2330
            Tokens::T_SQUARED_BRACKET_OPEN,
2331
            Tokens::T_SQUARED_BRACKET_CLOSE
2332
        );
2333
    }
2334
2335
    /**
2336
     * Parses a mandatory array index expression.
2337
     *
2338
     * <code>
2339
     * //     ---
2340
     * $string{0};
2341
     * //     ---
2342
     * </code>
2343
     *
2344
     * @param ASTNode $node The context source node.
2345
     *
2346
     * @return ASTIndexExpression
2347
     *
2348
     * @since 0.9.12
2349
     */
2350
    private function parseStringIndexExpression(ASTNode $node)
2351
    {
2352
        $expr = $this->builder->buildAstStringIndexExpression();
2353
        $expr->addChild($node);
2354
2355
        return $this->parseIndexExpression(
2356
            $node,
2357
            $expr,
2358
            Tokens::T_CURLY_BRACE_OPEN,
2359
            Tokens::T_CURLY_BRACE_CLOSE
2360
        );
2361
    }
2362
2363
    /**
2364
     * This method checks if the next available token starts an arguments node.
2365
     *
2366
     * @return bool
2367
     *
2368
     * @since 0.9.8
2369
     */
2370
    protected function isNextTokenArguments()
2371
    {
2372
        $this->consumeComments();
2373
        return $this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN;
2374
    }
2375
2376
    /**
2377
     * This method configures the given node with its start and end positions.
2378
     *
2379
     * @template T of ASTNode
2380
     *
2381
     * @param T                      $node
2382
     * @param array<int, Token>|null $tokens
2383
     *
2384
     * @return T
2385
     *
2386
     * @since 0.9.8
2387
     */
2388
    protected function setNodePositionsAndReturn(ASTNode $node, array &$tokens = null)
2389
    {
2390
        $tokens = $this->stripTrailingComments($this->tokenStack->pop());
2391
2392
        $end   = $tokens[count($tokens) - 1];
2393
        $start = $tokens[0];
2394
2395
        $node->configureLinesAndColumns(
2396
            $start->startLine,
2397
            $end->endLine,
2398
            $start->startColumn,
2399
            $end->endColumn
2400
        );
2401
2402
        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...
2403
    }
2404
2405
    /**
2406
     * Strips all trailing comments from the given token stream.
2407
     *
2408
     * @param Token[] $tokens Original token stream.
2409
     *
2410
     * @return Token[]
2411
     *
2412
     * @since 1.0.0
2413
     */
2414
    private function stripTrailingComments(array $tokens)
2415
    {
2416
        $comments = array(Tokens::T_COMMENT, Tokens::T_DOC_COMMENT);
2417
2418
        while (count($tokens) > 1 && in_array(end($tokens)->type, $comments)) {
2419
            array_pop($tokens);
2420
        }
2421
        return $tokens;
2422
    }
2423
2424
    /**
2425
     * This method parse an instance of expression with its associated class or
2426
     * interface reference.
2427
     *
2428
     * <code>
2429
     *          ----------------
2430
     * ($object instanceof Clazz);
2431
     *          ----------------
2432
     *
2433
     *          ------------------------
2434
     * ($object instanceof Clazz::$clazz);
2435
     *          ------------------------
2436
     *
2437
     *          -----------------
2438
     * ($object instanceof $clazz);
2439
     *          -----------------
2440
     *
2441
     *          -----------------------
2442
     * ($object instanceof $clazz->type);
2443
     *          -----------------------
2444
     *
2445
     *          -----------------------------
2446
     * ($object instanceof static|self|parent);
2447
     *          -----------------------------
2448
     * </code>
2449
     *
2450
     * @return ASTInstanceOfExpression
2451
     *
2452
     * @since 0.9.6
2453
     */
2454
    private function parseInstanceOfExpression()
2455
    {
2456
        // Consume the "instanceof" keyword and strip comments
2457
        $token = $this->consumeToken(Tokens::T_INSTANCEOF);
2458
2459
        return $this->parseExpressionTypeReference(
2460
            $this->builder->buildAstInstanceOfExpression($token->image),
2461
            false
2462
        );
2463
    }
2464
2465
    /**
2466
     * Parses an isset-expression node.
2467
     *
2468
     * <code>
2469
     * //  -----------
2470
     * if (isset($foo)) {
2471
     * //  -----------
2472
     * }
2473
     *
2474
     * //  -----------------------
2475
     * if (isset($foo, $bar, $baz)) {
2476
     * //  -----------------------
2477
     * }
2478
     * </code>
2479
     *
2480
     * @return ASTIssetExpression
2481
     *
2482
     * @since 0.9.12
2483
     */
2484
    private function parseIssetExpression()
2485
    {
2486
        $startToken = $this->consumeToken(Tokens::T_ISSET);
2487
        $this->consumeComments();
2488
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
2489
        $this->consumeComments();
2490
2491
        $expr = $this->builder->buildAstIssetExpression();
2492
        $expr = $this->parseVariableList($expr, true);
2493
        $this->consumeComments();
2494
2495
        $stopToken = $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
2496
2497
        $expr->configureLinesAndColumns(
2498
            $startToken->startLine,
2499
            $stopToken->endLine,
2500
            $startToken->startColumn,
2501
            $stopToken->endColumn
2502
        );
2503
2504
        return $expr;
2505
    }
2506
2507
    /**
2508
     * @return bool
2509
     */
2510
    protected function allowTrailingCommaInSpecialFunctions()
2511
    {
2512
        return false;
2513
    }
2514
2515
    /**
2516
     * @param int $tokenType
2517
     *
2518
     * @return ASTClassOrInterfaceReference
2519
     */
2520
    private function parseStandAloneExpressionTypeReference($tokenType)
2521
    {
2522
        switch ($tokenType) {
2523
            case Tokens::T_SELF:
2524
                return $this->parseSelfReference($this->consumeToken(Tokens::T_SELF));
2525
            case Tokens::T_PARENT:
2526
                return $this->parseParentReference($this->consumeToken(Tokens::T_PARENT));
2527
            case Tokens::T_STATIC:
2528
                return $this->parseStaticReference($this->consumeToken(Tokens::T_STATIC));
2529
        }
2530
2531
        throw $this->getUnexpectedNextTokenException();
2532
    }
2533
2534
    /**
2535
     * @param bool $classRef
2536
     *
2537
     * @return ASTNode
2538
     */
2539
    private function parseStandAloneExpressionType($classRef)
2540
    {
2541
        // Peek next token and look for a static type identifier
2542
        $this->consumeComments();
2543
        $tokenType = $this->tokenizer->peek();
2544
2545
        switch ($tokenType) {
2546
            case Tokens::T_DOLLAR:
2547
            case Tokens::T_VARIABLE:
2548
                // TODO: Parse variable or Member Primary Prefix + Property Postfix
2549
                return $this->parseVariableOrFunctionPostfixOrMemberPrimaryPrefix();
2550
            case Tokens::T_SELF:
2551
            case Tokens::T_PARENT:
2552
            case Tokens::T_STATIC:
2553
                return $this->parseStandAloneExpressionTypeReference($tokenType);
2554
            default:
2555
                return $this->parseClassOrInterfaceReference($classRef);
2556
        }
2557
    }
2558
2559
    /**
2560
     * This method parses a type identifier as it is used in expression nodes
2561
     * like {@link ASTInstanceOfExpression} or an object
2562
     * allocation node like {@link ASTAllocationExpression}.
2563
     *
2564
     * @template T of AbstractASTNode
2565
     *
2566
     * @param T    $expr
2567
     * @param bool $classRef
2568
     *
2569
     * @return T
2570
     */
2571
    protected function parseExpressionTypeReference(ASTNode $expr, $classRef)
2572
    {
2573
        $expr->addChild(
0 ignored issues
show
Bug introduced by
The method addChild() does not exist on PDepend\Source\AST\ASTNode. Since it exists in all sub-types, consider adding an abstract or default implementation to PDepend\Source\AST\ASTNode. ( Ignorable by Annotation )

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

2573
        $expr->/** @scrutinizer ignore-call */ 
2574
               addChild(
Loading history...
2574
            $this->parseOptionalMemberPrimaryPrefix(
2575
                $this->parseOptionalStaticMemberPrimaryPrefix(
2576
                    $this->parseStandAloneExpressionType($classRef)
2577
                )
2578
            )
2579
        );
2580
2581
        return $expr;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $expr returns the type PDepend\Source\AST\ASTNode which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
2582
    }
2583
2584
    /**
2585
     * This method parses a conditional-expression.
2586
     *
2587
     * <code>
2588
     *         --------------
2589
     * $foo = ($bar ? 42 : 23);
2590
     *         --------------
2591
     * </code>
2592
     *
2593
     * @return ASTConditionalExpression
2594
     *
2595
     * @since 0.9.8
2596
     */
2597
    protected function parseConditionalExpression()
2598
    {
2599
        $this->tokenStack->push();
2600
        $this->consumeToken(Tokens::T_QUESTION_MARK);
2601
2602
        $expr = $this->builder->buildAstConditionalExpression();
2603
        if (($child = $this->parseOptionalExpression()) != null) {
2604
            $expr->addChild($child);
2605
        }
2606
2607
        $this->consumeToken(Tokens::T_COLON);
2608
2609
        $expr->addChild($this->parseExpression());
2610
2611
        return $this->setNodePositionsAndReturn($expr);
2612
    }
2613
2614
    /**
2615
     * This method parses a shift left expression node.
2616
     *
2617
     * @return ASTShiftLeftExpression
2618
     *
2619
     * @since 1.0.1
2620
     */
2621
    protected function parseShiftLeftExpression()
2622
    {
2623
        $token = $this->consumeToken(Tokens::T_SL);
2624
2625
        $expr = $this->builder->buildAstShiftLeftExpression();
2626
        $expr->configureLinesAndColumns(
2627
            $token->startLine,
2628
            $token->endLine,
2629
            $token->startColumn,
2630
            $token->endColumn
2631
        );
2632
        return $expr;
2633
    }
2634
2635
    /**
2636
     * This method parses a shift right expression node.
2637
     *
2638
     * @return ASTShiftRightExpression
2639
     *
2640
     * @since 1.0.1
2641
     */
2642
    protected function parseShiftRightExpression()
2643
    {
2644
        $token = $this->consumeToken(Tokens::T_SR);
2645
2646
        $expr = $this->builder->buildAstShiftRightExpression();
2647
        $expr->configureLinesAndColumns(
2648
            $token->startLine,
2649
            $token->endLine,
2650
            $token->startColumn,
2651
            $token->endColumn
2652
        );
2653
        return $expr;
2654
    }
2655
2656
    /**
2657
     * This method parses a boolean and-expression.
2658
     *
2659
     * @return ASTBooleanAndExpression
2660
     *
2661
     * @since 0.9.8
2662
     */
2663
    protected function parseBooleanAndExpression()
2664
    {
2665
        $token = $this->consumeToken(Tokens::T_BOOLEAN_AND);
2666
2667
        $expr = $this->builder->buildAstBooleanAndExpression();
2668
        $expr->configureLinesAndColumns(
2669
            $token->startLine,
2670
            $token->endLine,
2671
            $token->startColumn,
2672
            $token->endColumn
2673
        );
2674
        return $expr;
2675
    }
2676
2677
    /**
2678
     * This method parses a boolean or-expression.
2679
     *
2680
     * @return ASTBooleanOrExpression
2681
     *
2682
     * @since 0.9.8
2683
     */
2684
    protected function parseBooleanOrExpression()
2685
    {
2686
        $token = $this->consumeToken(Tokens::T_BOOLEAN_OR);
2687
2688
        $expr = $this->builder->buildAstBooleanOrExpression();
2689
        $expr->configureLinesAndColumns(
2690
            $token->startLine,
2691
            $token->endLine,
2692
            $token->startColumn,
2693
            $token->endColumn
2694
        );
2695
        return $expr;
2696
    }
2697
2698
    /**
2699
     * This method parses a logical <b>and</b>-expression.
2700
     *
2701
     * @return ASTLogicalAndExpression
2702
     *
2703
     * @since 0.9.8
2704
     */
2705
    protected function parseLogicalAndExpression()
2706
    {
2707
        $token = $this->consumeToken(Tokens::T_LOGICAL_AND);
2708
2709
        $expr = $this->builder->buildAstLogicalAndExpression();
2710
        $expr->configureLinesAndColumns(
2711
            $token->startLine,
2712
            $token->endLine,
2713
            $token->startColumn,
2714
            $token->endColumn
2715
        );
2716
        return $expr;
2717
    }
2718
2719
    /**
2720
     * This method parses a logical <b>or</b>-expression.
2721
     *
2722
     * @return ASTLogicalOrExpression
2723
     *
2724
     * @since 0.9.8
2725
     */
2726
    protected function parseLogicalOrExpression()
2727
    {
2728
        $token = $this->consumeToken(Tokens::T_LOGICAL_OR);
2729
2730
        $expr = $this->builder->buildAstLogicalOrExpression();
2731
        $expr->configureLinesAndColumns(
2732
            $token->startLine,
2733
            $token->endLine,
2734
            $token->startColumn,
2735
            $token->endColumn
2736
        );
2737
        return $expr;
2738
    }
2739
2740
    /**
2741
     * This method parses a logical <b>xor</b>-expression.
2742
     *
2743
     * @return ASTLogicalXorExpression
2744
     *
2745
     * @since 0.9.8
2746
     */
2747
    protected function parseLogicalXorExpression()
2748
    {
2749
        $token = $this->consumeToken(Tokens::T_LOGICAL_XOR);
2750
2751
        $expr = $this->builder->buildAstLogicalXorExpression();
2752
        $expr->configureLinesAndColumns(
2753
            $token->startLine,
2754
            $token->endLine,
2755
            $token->startColumn,
2756
            $token->endColumn
2757
        );
2758
        return $expr;
2759
    }
2760
2761
    /**
2762
     * Parses a class or interface reference node.
2763
     *
2764
     * @param bool $classReference Force a class reference.
2765
     *
2766
     * @return ASTClassOrInterfaceReference
2767
     *
2768
     * @since 0.9.8
2769
     */
2770
    private function parseClassOrInterfaceReference($classReference)
2771
    {
2772
        $this->tokenStack->push();
2773
2774
        return $this->setNodePositionsAndReturn(
2775
            $this->builder->buildAstNeededReference(
2776
                $this->parseQualifiedName(),
2777
                $classReference
2778
            )
2779
        );
2780
    }
2781
2782
    /**
2783
     * This method parses a brace expression and adds all parsed node instances
2784
     * to the given {@link ASTNode} object. Finally it returns
2785
     * the prepared input node.
2786
     *
2787
     * A brace expression can be a compound:
2788
     *
2789
     * <code>
2790
     * $this->{$foo ? 'foo' : 'bar'}();
2791
     * </code>
2792
     *
2793
     * or a parameter list:
2794
     *
2795
     * <code>
2796
     * $this->foo($bar, $baz);
2797
     * </code>
2798
     *
2799
     * or an array index:
2800
     *
2801
     * <code>
2802
     * $foo[$bar];
2803
     * </code>
2804
     *
2805
     * @template T of AbstractASTNode
2806
     *
2807
     * @param T   $node
2808
     * @param int $closeToken
2809
     * @param int|null $separatorToken
2810
     *
2811
     * @throws TokenStreamEndException
2812
     *
2813
     * @return T
2814
     *
2815
     * @since 0.9.6
2816
     */
2817
    protected function parseBraceExpression(
2818
        ASTNode $node,
2819
        Token $start,
2820
        $closeToken,
2821
        $separatorToken = null
2822
    ) {
2823
        if (is_object($expr = $this->parseOptionalExpression())) {
2824
            $node->addChild($expr);
2825
        }
2826
2827
        $this->consumeComments();
2828
2829
        while ($separatorToken && $this->tokenizer->peek() === $separatorToken) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $separatorToken of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2830
            $this->consumeToken($separatorToken);
2831
2832
            if (is_object($expr = $this->parseOptionalExpression())) {
2833
                $node->addChild($expr);
2834
            }
2835
2836
            $this->consumeComments();
2837
        }
2838
2839
        $end = $this->consumeToken($closeToken);
2840
2841
        $node->configureLinesAndColumns(
2842
            $start->startLine,
2843
            $end->endLine,
2844
            $start->startColumn,
2845
            $end->endColumn
2846
        );
2847
        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...
2848
    }
2849
2850
    /**
2851
     * Parses the body of the given statement instance and adds all parsed nodes
2852
     * to that statement.
2853
     *
2854
     * @template T of ASTStatement
2855
     *
2856
     * @param T $stmt The owning statement.
2857
     *
2858
     * @return T
2859
     *
2860
     * @since 0.9.12
2861
     */
2862
    private function parseStatementBody(ASTStatement $stmt)
2863
    {
2864
        $this->consumeComments();
2865
        $tokenType = $this->tokenizer->peek();
2866
2867
        if ($tokenType === Tokens::T_CURLY_BRACE_OPEN) {
2868
            $stmt->addChild($this->parseRegularScope());
2869
        } elseif ($tokenType === Tokens::T_COLON) {
2870
            $stmt->addChild($this->parseAlternativeScope());
2871
        } else {
2872
            $stmt->addChild($this->parseStatement());
2873
        }
2874
        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...
2875
    }
2876
2877
    /**
2878
     * Parse a scope enclosed by curly braces.
2879
     *
2880
     * @return ASTScopeStatement
2881
     *
2882
     * @since 0.9.12
2883
     */
2884
    private function parseRegularScope()
2885
    {
2886
        $this->tokenStack->push();
2887
2888
        $this->consumeComments();
2889
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
2890
2891
        $scope = $this->parseScopeStatements();
2892
2893
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
2894
        return $this->setNodePositionsAndReturn($scope);
2895
    }
2896
2897
    /**
2898
     * Parses the scope of a statement that is surrounded with PHP's alternative
2899
     * syntax for statements.
2900
     *
2901
     * @return ASTScopeStatement
2902
     *
2903
     * @since 0.10.0
2904
     */
2905
    private function parseAlternativeScope()
2906
    {
2907
        $this->tokenStack->push();
2908
        $this->consumeToken(Tokens::T_COLON);
2909
2910
        $scope = $this->parseScopeStatements();
2911
2912
        $this->parseOptionalAlternativeScopeTermination();
2913
        return $this->setNodePositionsAndReturn($scope);
2914
    }
2915
2916
    /**
2917
     * Parses all statements that exist in a scope an adds them to a scope
2918
     * instance.
2919
     *
2920
     * @return ASTScopeStatement
2921
     *
2922
     * @since 0.10.0
2923
     */
2924
    private function parseScopeStatements()
2925
    {
2926
        $scope = $this->builder->buildAstScopeStatement();
2927
        while (($child = $this->parseOptionalStatement()) != null) {
2928
            if ($child instanceof ASTNode) {
2929
                $scope->addChild($child);
2930
            }
2931
        }
2932
        return $scope;
2933
    }
2934
2935
    /**
2936
     * Parses the termination of a scope statement that uses PHP's laternative
2937
     * syntax format.
2938
     *
2939
     * @return void
2940
     *
2941
     * @since 0.10.0
2942
     */
2943
    private function parseOptionalAlternativeScopeTermination()
2944
    {
2945
        $tokenType = $this->tokenizer->peek();
2946
        if ($this->isAlternativeScopeTermination($tokenType)) {
2947
            $this->parseAlternativeScopeTermination($tokenType);
2948
        }
2949
    }
2950
2951
2952
    /**
2953
     * This method returns <b>true</b> when the given token identifier represents
2954
     * the end token of a alternative scope termination symbol. Otherwise this
2955
     * method will return <b>false</b>.
2956
     *
2957
     * @param int $tokenType The token type identifier.
2958
     *
2959
     * @return bool
2960
     *
2961
     * @since 0.10.0
2962
     */
2963
    private function isAlternativeScopeTermination($tokenType)
2964
    {
2965
        return in_array(
2966
            $tokenType,
2967
            array(
2968
                Tokens::T_ENDDECLARE,
2969
                Tokens::T_ENDFOR,
2970
                Tokens::T_ENDFOREACH,
2971
                Tokens::T_ENDIF,
2972
                Tokens::T_ENDSWITCH,
2973
                Tokens::T_ENDWHILE
2974
            )
2975
        );
2976
    }
2977
2978
    /**
2979
     * Parses a series of tokens that represent an alternative scope termination.
2980
     *
2981
     * @param int $tokenType The token type identifier.
2982
     *
2983
     * @return void
2984
     *
2985
     * @since 0.10.0
2986
     */
2987
    private function parseAlternativeScopeTermination($tokenType)
2988
    {
2989
        $this->consumeToken($tokenType);
2990
        $this->consumeComments();
2991
2992
        if ($this->tokenizer->peek() === Tokens::T_SEMICOLON) {
2993
            $this->consumeToken(Tokens::T_SEMICOLON);
2994
        } else {
2995
            $this->parseNonePhpCode();
2996
        }
2997
    }
2998
2999
    /**
3000
     * This method parses multiple expressions and adds them as children to the
3001
     * given <b>$exprList</b> node.
3002
     *
3003
     * @template T of AbstractASTNode
3004
     *
3005
     * @param T $exprList
3006
     *
3007
     * @return T
3008
     *
3009
     * @since 1.0.0
3010
     */
3011
    private function parseExpressionList(ASTNode $exprList)
3012
    {
3013
        $this->consumeComments();
3014
3015
        do {
3016
            $expr = $this->parseOptionalExpression();
3017
        } while ($expr && $this->addChildToList($exprList, $expr));
3018
3019
        return $exprList;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $exprList returns the type PDepend\Source\AST\ASTNode which is incompatible with the documented return type PDepend\Source\Language\PHP\T.
Loading history...
3020
    }
3021
3022
    /**
3023
     * Return true if children remain to be added, false else.
3024
     *
3025
     * @param AbstractASTNode $exprList
3026
     *
3027
     * @return bool
3028
     */
3029
    protected function addChildToList(ASTNode $exprList, ASTNode $expr)
3030
    {
3031
        $exprList->addChild($expr);
3032
3033
        $this->consumeComments();
3034
3035
        if ($this->tokenizer->peek() !== Tokens::T_COMMA) {
3036
            return false;
3037
        }
3038
3039
        if ($exprList instanceof ASTArguments && !$exprList->acceptsMoreArguments()) {
3040
            throw $this->getUnexpectedNextTokenException();
3041
        }
3042
3043
        $this->consumeToken(Tokens::T_COMMA);
3044
        $this->consumeComments();
3045
3046
        return true;
3047
    }
3048
3049
    /**
3050
     * This method parses an expression node and returns it. When no expression
3051
     * was found this method will throw an InvalidStateException.
3052
     *
3053
     * @throws ParserException
3054
     *
3055
     * @return ASTNode
3056
     *
3057
     * @since 1.0.1
3058
     */
3059
    private function parseExpression()
3060
    {
3061
        if (null === ($expr = $this->parseOptionalExpression())) {
3062
            $token = $this->consumeToken($this->tokenizer->peek());
3063
3064
            throw new InvalidStateException(
3065
                $token->startLine,
3066
                $this->compilationUnit->getFileName(),
3067
                'Mandatory expression expected.'
3068
            );
3069
        }
3070
        return $expr;
3071
    }
3072
3073
    /**
3074
     * This method optionally parses an expression node and returns it. When no
3075
     * expression was found this method will return <b>null</b>.
3076
     *
3077
     * @throws ParserException
3078
     *
3079
     * @return ASTNode|null
3080
     *
3081
     * @since 0.9.6
3082
     */
3083
    protected function parseOptionalExpression()
3084
    {
3085
        $expressions = array();
3086
3087
        while (($tokenType = $this->tokenizer->peek()) != Tokenizer::T_EOF) {
3088
            $expr = null;
3089
3090
            switch ($tokenType) {
3091
                case Tokens::T_COMMA:
3092
                case Tokens::T_AS:
3093
                case Tokens::T_BREAK:
3094
                case Tokens::T_CLOSE_TAG:
3095
                case Tokens::T_COLON:
3096
                case Tokens::T_CONTINUE:
3097
                case Tokens::T_CURLY_BRACE_CLOSE:
3098
                case Tokens::T_DECLARE:
3099
                case Tokens::T_DO:
3100
                case Tokens::T_DOUBLE_ARROW:
3101
                case Tokens::T_ECHO:
3102
                case Tokens::T_END_HEREDOC:
3103
                case Tokens::T_ENDFOREACH:
3104
                case Tokens::T_FOR:
3105
                case Tokens::T_FOREACH:
3106
                case Tokens::T_GLOBAL:
3107
                case Tokens::T_GOTO:
3108
                case Tokens::T_IF:
3109
                case Tokens::T_PARENTHESIS_CLOSE:
3110
                case Tokens::T_RETURN:
3111
                case Tokens::T_SEMICOLON:
3112
                case Tokens::T_SQUARED_BRACKET_CLOSE:
3113
                case Tokens::T_SWITCH:
3114
                case Tokens::T_TRY:
3115
                case Tokens::T_UNSET:
3116
                case Tokens::T_WHILE:
3117
                    break 2;
3118
                case Tokens::T_THROW:
3119
                    $expressions[] = $this->parseThrowExpression();
3120
                    break;
3121
                case Tokens::T_SELF:
3122
                case Tokens::T_STRING:
3123
                case Tokens::T_PARENT:
3124
                case Tokens::T_STATIC:
3125
                case Tokens::T_DOLLAR:
3126
                case Tokens::T_VARIABLE:
3127
                case Tokens::T_BACKSLASH:
3128
                case Tokens::T_NAMESPACE:
3129
                    $expressions[] = $this->parseVariableOrConstantOrPrimaryPrefix();
3130
                    break;
3131
                case ($this->isArrayStartDelimiter()):
3132
                    $expressions[] = $this->doParseArray();
3133
                    break;
3134
                case Tokens::T_NULL:
3135
                case Tokens::T_TRUE:
3136
                case Tokens::T_FALSE:
3137
                case Tokens::T_LNUMBER:
3138
                case Tokens::T_DNUMBER:
3139
                case Tokens::T_BACKTICK:
3140
                case Tokens::T_DOUBLE_QUOTE:
3141
                case Tokens::T_CONSTANT_ENCAPSED_STRING:
3142
                    $expressions[] = $this->parseLiteralOrString();
3143
                    break;
3144
                case Tokens::T_NEW:
3145
                    $expressions[] = $this->parseAllocationExpression();
3146
                    break;
3147
                case Tokens::T_EVAL:
3148
                    $expressions[] = $this->parseEvalExpression();
3149
                    break;
3150
                case Tokens::T_CLONE:
3151
                    $expressions[] = $this->parseCloneExpression();
3152
                    break;
3153
                case Tokens::T_INSTANCEOF:
3154
                    $expressions[] = $this->parseInstanceOfExpression();
3155
                    break;
3156
                case Tokens::T_ISSET:
3157
                    $expressions[] = $this->parseIssetExpression();
3158
                    break;
3159
                case Tokens::T_LIST:
3160
                case Tokens::T_SQUARED_BRACKET_OPEN:
3161
                    $expressions[] = $this->parseListExpression();
3162
                    break;
3163
                case Tokens::T_QUESTION_MARK:
3164
                    $expressions[] = $this->parseConditionalExpression();
3165
                    break;
3166
                case Tokens::T_BOOLEAN_AND:
3167
                    $expressions[] = $this->parseBooleanAndExpression();
3168
                    break;
3169
                case Tokens::T_BOOLEAN_OR:
3170
                    $expressions[] = $this->parseBooleanOrExpression();
3171
                    break;
3172
                case Tokens::T_LOGICAL_AND:
3173
                    $expressions[] = $this->parseLogicalAndExpression();
3174
                    break;
3175
                case Tokens::T_LOGICAL_OR:
3176
                    $expressions[] = $this->parseLogicalOrExpression();
3177
                    break;
3178
                case Tokens::T_LOGICAL_XOR:
3179
                    $expressions[] = $this->parseLogicalXorExpression();
3180
                    break;
3181
                case Tokens::T_FUNCTION:
3182
                    $expressions[] = $this->parseClosureDeclaration();
3183
                    break;
3184
                case Tokens::T_FN:
3185
                    $expressions[] = $this->parseLambdaFunctionDeclaration();
3186
                    break;
3187
                case Tokens::T_PARENTHESIS_OPEN:
3188
                    $expressions[] = $this->parseParenthesisExpressionOrPrimaryPrefix();
3189
                    break;
3190
                case Tokens::T_EXIT:
3191
                    $expressions[] = $this->parseExitExpression();
3192
                    break;
3193
                case Tokens::T_START_HEREDOC:
3194
                    $expressions[] = $this->parseHeredoc();
3195
                    break;
3196
                case Tokens::T_CURLY_BRACE_OPEN:
3197
                    $expressions[] = $this->parseBraceExpression(
3198
                        $this->builder->buildAstExpression(),
3199
                        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN),
3200
                        Tokens::T_CURLY_BRACE_CLOSE
3201
                    );
3202
                    break;
3203
                case Tokens::T_INCLUDE:
3204
                    $expressions[] = $this->parseIncludeExpression();
3205
                    break;
3206
                case Tokens::T_INCLUDE_ONCE:
3207
                    $expressions[] = $this->parseIncludeOnceExpression();
3208
                    break;
3209
                case Tokens::T_REQUIRE:
3210
                    $expressions[] = $this->parseRequireExpression();
3211
                    break;
3212
                case Tokens::T_REQUIRE_ONCE:
3213
                    $expressions[] = $this->parseRequireOnceExpression();
3214
                    break;
3215
                case Tokens::T_DEC:
3216
                    $expressions[] = $this->parseDecrementExpression($expressions);
3217
                    break;
3218
                case Tokens::T_INC:
3219
                    $expressions[] = $this->parseIncrementExpression($expressions);
3220
                    break;
3221
                case Tokens::T_SL:
3222
                    $expressions[] = $this->parseShiftLeftExpression();
3223
                    break;
3224
                case Tokens::T_SR:
3225
                    $expressions[] = $this->parseShiftRightExpression();
3226
                    break;
3227
                case Tokens::T_DIR:
3228
                case Tokens::T_FILE:
3229
                case Tokens::T_LINE:
3230
                case Tokens::T_NS_C:
3231
                case Tokens::T_FUNC_C:
3232
                case Tokens::T_CLASS_C:
3233
                case Tokens::T_METHOD_C:
3234
                    $expressions[] = $this->parseConstant();
3235
                    break;
3236
                case Tokens::T_INT_CAST:
3237
                case Tokens::T_BOOL_CAST:
3238
                case Tokens::T_ARRAY_CAST:
3239
                case Tokens::T_UNSET_CAST:
3240
                case Tokens::T_OBJECT_CAST:
3241
                case Tokens::T_DOUBLE_CAST:
3242
                case Tokens::T_STRING_CAST:
3243
                    $expressions[] = $this->parseCastExpression();
3244
                    break;
3245
                case Tokens::T_EQUAL:
3246
                case Tokens::T_OR_EQUAL:
3247
                case Tokens::T_SL_EQUAL:
3248
                case Tokens::T_SR_EQUAL:
3249
                case Tokens::T_AND_EQUAL:
3250
                case Tokens::T_DIV_EQUAL:
3251
                case Tokens::T_MOD_EQUAL:
3252
                case Tokens::T_MUL_EQUAL:
3253
                case Tokens::T_XOR_EQUAL:
3254
                case Tokens::T_PLUS_EQUAL:
3255
                case Tokens::T_MINUS_EQUAL:
3256
                case Tokens::T_CONCAT_EQUAL:
3257
                case Tokens::T_COALESCE_EQUAL:
3258
                    $expressions[] = $this->parseAssignmentExpression(
3259
                        array_pop($expressions)
3260
                    );
3261
                    break;
3262
                // TODO: Handle comments here
3263
                case Tokens::T_COMMENT:
3264
                case Tokens::T_DOC_COMMENT:
3265
                    $this->consumeToken($tokenType);
3266
                    break;
3267
                case Tokens::T_PRINT: // TODO: Implement print expression
3268
                    $token = $this->consumeToken($tokenType);
3269
3270
                    $expr = $this->builder->buildAstPrintExpression();
3271
                    $expr->configureLinesAndColumns(
3272
                        $token->startLine,
3273
                        $token->endLine,
3274
                        $token->startColumn,
3275
                        $token->endColumn
3276
                    );
3277
3278
                    $expressions[] = $expr;
3279
                    break;
3280
                case Tokens::T_ELLIPSIS:
3281
                    $this->checkEllipsisInExpressionSupport();
3282
                    // no break
3283
                case Tokens::T_STRING_VARNAME: // TODO: Implement this
3284
                case Tokens::T_PLUS: // TODO: Make this a arithmetic expression
3285
                case Tokens::T_MINUS:
3286
                case Tokens::T_MUL:
3287
                case Tokens::T_DIV:
3288
                case Tokens::T_MOD:
3289
                case Tokens::T_IS_EQUAL: // TODO: Implement compare expressions
3290
                case Tokens::T_IS_NOT_EQUAL:
3291
                case Tokens::T_IS_IDENTICAL:
3292
                case Tokens::T_IS_NOT_IDENTICAL:
3293
                case Tokens::T_IS_GREATER_OR_EQUAL:
3294
                case Tokens::T_IS_SMALLER_OR_EQUAL:
3295
                case Tokens::T_ANGLE_BRACKET_OPEN:
3296
                case Tokens::T_ANGLE_BRACKET_CLOSE:
3297
                case Tokens::T_EMPTY:
3298
                case Tokens::T_CONCAT:
3299
                case Tokens::T_BITWISE_OR:
3300
                case Tokens::T_BITWISE_AND:
3301
                case Tokens::T_BITWISE_NOT:
3302
                case Tokens::T_BITWISE_XOR:
3303
                    $token = $this->consumeToken($tokenType);
3304
3305
                    $expr = $this->builder->buildAstExpression($token->image);
3306
                    $expr->configureLinesAndColumns(
3307
                        $token->startLine,
3308
                        $token->endLine,
3309
                        $token->startColumn,
3310
                        $token->endColumn
3311
                    );
3312
3313
                    $expressions[] = $expr;
3314
                    break;
3315
                case Tokens::T_AT:
3316
                case Tokens::T_EXCLAMATION_MARK:
3317
                    $token = $this->consumeToken($tokenType);
3318
3319
                    $expr = $this->builder->buildAstUnaryExpression($token->image);
3320
                    $expr->configureLinesAndColumns(
3321
                        $token->startLine,
3322
                        $token->endLine,
3323
                        $token->startColumn,
3324
                        $token->endColumn
3325
                    );
3326
3327
                    $expressions[] = $expr;
3328
                    break;
3329
                case Tokens::T_YIELD:
3330
                    $expressions[] = $this->parseYield(false);
3331
                    break;
3332
                default:
3333
                    $expressions[] = $this->parseOptionalExpressionForVersion();
3334
                    break;
3335
            }
3336
        }
3337
3338
        $expressions = $this->reduce($expressions);
3339
3340
        $count = count($expressions);
3341
        if ($count == 0) {
3342
            return null;
3343
        } elseif ($count == 1) {
3344
            return $expressions[0];
3345
        }
3346
3347
        $expr = $this->builder->buildAstExpression();
3348
        foreach ($expressions as $node) {
3349
            $expr->addChild($node);
3350
        }
3351
        $expr->configureLinesAndColumns(
3352
            $expressions[0]->getStartLine(),
3353
            $expressions[$count - 1]->getEndLine(),
3354
            $expressions[0]->getStartColumn(),
3355
            $expressions[$count - 1]->getEndColumn()
3356
        );
3357
3358
        return $expr;
3359
    }
3360
3361
    /**
3362
     * This method will be called when the base parser cannot handle an expression
3363
     * in the base version. In this method you can implement version specific
3364
     * expressions.
3365
     *
3366
     * @throws UnexpectedTokenException
3367
     *
3368
     * @return ASTNode
3369
     *
3370
     * @since 2.2
3371
     */
3372
    protected function parseOptionalExpressionForVersion()
3373
    {
3374
        throw $this->getUnexpectedNextTokenException();
3375
    }
3376
3377
    /**
3378
     * Applies all reduce rules against the given expression list.
3379
     *
3380
     * @param ASTNode[] $expressions Unprepared input array with parsed expression nodes found in the source tree.
3381
     *
3382
     * @return ASTNode[]
3383
     *
3384
     * @since 0.10.0
3385
     */
3386
    protected function reduce(array $expressions)
3387
    {
3388
        return $this->reduceUnaryExpression($expressions);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->reduceUnaryExpression($expressions) returns the type PDepend\Source\Language\PHP\T which is incompatible with the documented return type PDepend\Source\AST\ASTNode[].
Loading history...
3389
    }
3390
3391
    /**
3392
     * Reduces all unary-expressions in the given expression list.
3393
     *
3394
     * @template T of ASTNode[]
3395
     *
3396
     * @param T $expressions Unprepared input array with parsed expression nodes found in the source tree.
3397
     *
3398
     * @return T
3399
     *
3400
     * @since 0.10.0
3401
     */
3402
    private function reduceUnaryExpression(array $expressions)
3403
    {
3404
        for ($i = count($expressions) - 2; $i >= 0; --$i) {
3405
            $expr = $expressions[$i];
3406
            if ($expr instanceof ASTUnaryExpression) {
3407
                $child = $expressions[$i + 1];
3408
3409
                $expr->addChild($child);
3410
3411
                $expr->configureLinesAndColumns(
3412
                    $expr->getStartLine(),
3413
                    $child->getEndLine(),
3414
                    $expr->getStartColumn(),
3415
                    $child->getEndColumn()
3416
                );
3417
3418
                unset($expressions[$i + 1]);
3419
            }
3420
        }
3421
        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...
3422
    }
3423
3424
    /**
3425
     * This method parses a switch statement.
3426
     *
3427
     * @return ASTSwitchStatement
3428
     *
3429
     * @since 0.9.8
3430
     */
3431
    private function parseSwitchStatement()
3432
    {
3433
        $this->tokenStack->push();
3434
        $this->consumeToken(Tokens::T_SWITCH);
3435
3436
        $switch = $this->builder->buildAstSwitchStatement();
3437
        $switch->addChild($this->parseParenthesisExpression());
3438
        $this->parseSwitchStatementBody($switch);
3439
3440
        return $this->setNodePositionsAndReturn($switch);
3441
    }
3442
3443
    /**
3444
     * Parses the body of a switch statement.
3445
     *
3446
     * @param ASTSwitchStatement $switch The parent switch stmt.
3447
     *
3448
     * @return ASTSwitchStatement
3449
     *
3450
     * @since 0.9.8
3451
     */
3452
    private function parseSwitchStatementBody(ASTSwitchStatement $switch)
3453
    {
3454
        $this->consumeComments();
3455
        if ($this->tokenizer->peek() === Tokens::T_CURLY_BRACE_OPEN) {
3456
            $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
3457
        } else {
3458
            $this->consumeToken(Tokens::T_COLON);
3459
        }
3460
3461
        while (($tokenType = $this->tokenizer->peek()) !== Tokenizer::T_EOF) {
3462
            switch ($tokenType) {
3463
                case Tokens::T_CLOSE_TAG:
3464
                    $this->parseNonePhpCode();
3465
                    break;
3466
                case Tokens::T_ENDSWITCH:
3467
                    $this->parseAlternativeScopeTermination(Tokens::T_ENDSWITCH);
3468
                    return $switch;
3469
                case Tokens::T_CURLY_BRACE_CLOSE:
3470
                    $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
3471
                    return $switch;
3472
                case Tokens::T_CASE:
3473
                    $switch->addChild($this->parseSwitchLabel());
3474
                    break;
3475
                case Tokens::T_DEFAULT:
3476
                    $switch->addChild($this->parseSwitchLabelDefault());
3477
                    break;
3478
                case Tokens::T_COMMENT:
3479
                case Tokens::T_DOC_COMMENT:
3480
                    $this->consumeToken($tokenType);
3481
                    break;
3482
                default:
3483
                    break 2;
3484
            }
3485
        }
3486
3487
        throw $this->getUnexpectedNextTokenException();
3488
    }
3489
3490
    /**
3491
     * This method parses a case label of a switch statement.
3492
     *
3493
     * @return ASTSwitchLabel
3494
     *
3495
     * @since 0.9.8
3496
     */
3497
    private function parseSwitchLabel()
3498
    {
3499
        $this->tokenStack->push();
3500
        $token = $this->consumeToken(Tokens::T_CASE);
3501
3502
        $label = $this->builder->buildAstSwitchLabel($token->image);
3503
        $label->addChild($this->parseExpression());
3504
3505
        if ($this->tokenizer->peek() === Tokens::T_COLON) {
3506
            $this->consumeToken(Tokens::T_COLON);
3507
        } else {
3508
            $this->consumeToken(Tokens::T_SEMICOLON);
3509
        }
3510
3511
        $this->parseSwitchLabelBody($label);
3512
3513
        return $this->setNodePositionsAndReturn($label);
3514
    }
3515
3516
    /**
3517
     * This method parses the default label of a switch statement.
3518
     *
3519
     * @return ASTSwitchLabel
3520
     *
3521
     * @since 0.9.8
3522
     */
3523
    private function parseSwitchLabelDefault()
3524
    {
3525
        $this->tokenStack->push();
3526
        $token = $this->consumeToken(Tokens::T_DEFAULT);
3527
3528
        $this->consumeComments();
3529
        if ($this->tokenizer->peek() === Tokens::T_COLON) {
3530
            $this->consumeToken(Tokens::T_COLON);
3531
        } else {
3532
            $this->consumeToken(Tokens::T_SEMICOLON);
3533
        }
3534
3535
        $label = $this->builder->buildAstSwitchLabel($token->image);
3536
        $label->setDefault();
3537
3538
        $this->parseSwitchLabelBody($label);
3539
3540
        return $this->setNodePositionsAndReturn($label);
3541
    }
3542
3543
    /**
3544
     * Parses the body of an switch label node.
3545
     *
3546
     * @param ASTSwitchLabel $label The context switch label.
3547
     *
3548
     * @return ASTSwitchLabel
3549
     */
3550
    private function parseSwitchLabelBody(ASTSwitchLabel $label)
3551
    {
3552
        $curlyBraceCount = 0;
3553
3554
        $tokenType = $this->tokenizer->peek();
3555
        while ($tokenType !== Tokenizer::T_EOF) {
3556
            switch ($tokenType) {
3557
                case Tokens::T_CURLY_BRACE_OPEN:
3558
                    $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
3559
                    ++$curlyBraceCount;
3560
                    break;
3561
                case Tokens::T_CURLY_BRACE_CLOSE:
3562
                    if ($curlyBraceCount === 0) {
3563
                        return $label;
3564
                    }
3565
                    $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
3566
                    --$curlyBraceCount;
3567
                    break;
3568
                case Tokens::T_CLOSE_TAG:
3569
                    $this->parseNonePhpCode();
3570
                    break;
3571
                case Tokens::T_CASE:
3572
                case Tokens::T_DEFAULT:
3573
                case Tokens::T_ENDSWITCH:
3574
                    return $label;
3575
                default:
3576
                    $statement = $this->parseOptionalStatement();
3577
                    if ($statement === null) {
3578
                        $this->consumeToken($tokenType);
3579
                    } elseif ($statement instanceof ASTNode) {
3580
                        $label->addChild($statement);
3581
                    }
3582
                    // TODO: Change the <else if> into and <else> when the ast
3583
                    //       implementation is finished.
3584
                    break;
3585
            }
3586
            $tokenType = $this->tokenizer->peek();
3587
        }
3588
        throw new TokenStreamEndException($this->tokenizer);
3589
    }
3590
3591
    /**
3592
     * Parses the termination token for a statement. This termination token can
3593
     * be a semicolon or a closing php tag.
3594
     *
3595
     * @param int[] $allowedTerminationTokens list of extra token types that can terminate the statement
3596
     *
3597
     * @return void
3598
     *
3599
     * @since 0.9.12
3600
     */
3601
    private function parseStatementTermination(array $allowedTerminationTokens = array())
3602
    {
3603
        $this->consumeComments();
3604
        $this->echoing = false;
3605
3606
        if (in_array($this->tokenizer->peek(), $allowedTerminationTokens, true)) {
3607
            return;
3608
        }
3609
3610
        if ($this->tokenizer->peek() === Tokens::T_SEMICOLON) {
3611
            $this->consumeToken(Tokens::T_SEMICOLON);
3612
        } else {
3613
            $this->parseNonePhpCode();
3614
        }
3615
    }
3616
3617
    /**
3618
     * This method parses a try-statement + associated catch-statements.
3619
     *
3620
     * @return ASTTryStatement
3621
     *
3622
     * @since 0.9.12
3623
     */
3624
    private function parseTryStatement()
3625
    {
3626
        $this->tokenStack->push();
3627
        $token = $this->consumeToken(Tokens::T_TRY);
3628
3629
        $stmt = $this->builder->buildAstTryStatement($token->image);
3630
        $stmt->addChild($this->parseRegularScope());
3631
3632
        $this->consumeComments();
3633
3634
        if (false === in_array($this->tokenizer->peek(), array(Tokens::T_CATCH, Tokens::T_FINALLY))) {
3635
            throw $this->getUnexpectedNextTokenException();
3636
        }
3637
3638
        while ($this->tokenizer->peek() === Tokens::T_CATCH) {
3639
            $stmt->addChild($this->parseCatchStatement());
3640
            $this->consumeComments();
3641
        }
3642
3643
        while ($this->tokenizer->peek() === Tokens::T_FINALLY) {
3644
            $stmt->addChild($this->parseFinallyStatement());
3645
            $this->consumeComments();
3646
        }
3647
3648
        return $this->setNodePositionsAndReturn($stmt);
3649
    }
3650
3651
    /**
3652
     * This method parses a throw-statement.
3653
     *
3654
     * @param int[] $allowedTerminationTokens list of extra token types that can terminate the statement
3655
     *
3656
     * @return ASTThrowStatement
3657
     *
3658
     * @since 0.9.12
3659
     */
3660
    protected function parseThrowStatement(array $allowedTerminationTokens = array())
3661
    {
3662
        $this->tokenStack->push();
3663
        $token = $this->consumeToken(Tokens::T_THROW);
3664
3665
        $stmt = $this->builder->buildAstThrowStatement($token->image);
3666
        $stmt->addChild($this->parseExpression());
3667
3668
        $this->parseStatementTermination($allowedTerminationTokens);
3669
3670
        return $this->setNodePositionsAndReturn($stmt);
3671
    }
3672
3673
    /**
3674
     * This method parses a goto-statement.
3675
     *
3676
     * @return ASTGotoStatement
3677
     *
3678
     * @since 0.9.12
3679
     */
3680
    private function parseGotoStatement()
3681
    {
3682
        $this->tokenStack->push();
3683
3684
        $this->consumeToken(Tokens::T_GOTO);
3685
        $this->consumeComments();
3686
3687
        $token = $this->consumeToken(Tokens::T_STRING);
3688
3689
        $this->parseStatementTermination();
3690
3691
        $stmt = $this->builder->buildAstGotoStatement($token->image);
3692
        return $this->setNodePositionsAndReturn($stmt);
3693
    }
3694
3695
    /**
3696
     * This method parses a label-statement.
3697
     *
3698
     * @return ASTLabelStatement
3699
     *
3700
     * @since 0.9.12
3701
     */
3702
    private function parseLabelStatement()
3703
    {
3704
        $this->tokenStack->push();
3705
3706
        $token = $this->consumeToken(Tokens::T_STRING);
3707
        $this->consumeComments();
3708
        $this->consumeToken(Tokens::T_COLON);
3709
3710
        return $this->setNodePositionsAndReturn(
3711
            $this->builder->buildAstLabelStatement($token->image)
3712
        );
3713
    }
3714
3715
    /**
3716
     * This method parses a global-statement.
3717
     *
3718
     * @return ASTGlobalStatement
3719
     *
3720
     * @since 0.9.12
3721
     */
3722
    private function parseGlobalStatement()
3723
    {
3724
        $this->tokenStack->push();
3725
        $this->consumeToken(Tokens::T_GLOBAL);
3726
3727
        $stmt = $this->builder->buildAstGlobalStatement();
3728
        $stmt = $this->parseVariableList($stmt);
3729
3730
        $this->parseStatementTermination();
3731
3732
        return $this->setNodePositionsAndReturn($stmt);
3733
    }
3734
3735
    /**
3736
     * This method parses a unset-statement.
3737
     *
3738
     * @return ASTUnsetStatement
3739
     *
3740
     * @since 0.9.12
3741
     */
3742
    private function parseUnsetStatement()
3743
    {
3744
        $this->tokenStack->push();
3745
3746
        $this->consumeToken(Tokens::T_UNSET);
3747
        $this->consumeComments();
3748
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
3749
        $this->consumeComments();
3750
3751
        $stmt = $this->builder->buildAstUnsetStatement();
3752
        $stmt = $this->parseVariableList($stmt, true);
3753
        $this->consumeComments();
3754
3755
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
3756
3757
        $this->parseStatementTermination();
3758
3759
        return $this->setNodePositionsAndReturn($stmt);
3760
    }
3761
3762
    /**
3763
     * This method parses a catch-statement.
3764
     *
3765
     * @return ASTCatchStatement
3766
     *
3767
     * @since 0.9.8
3768
     */
3769
    private function parseCatchStatement()
3770
    {
3771
        $this->tokenStack->push();
3772
        $this->consumeComments();
3773
3774
        $token = $this->consumeToken(Tokens::T_CATCH);
3775
3776
        $catch = $this->builder->buildAstCatchStatement($token->image);
3777
3778
        $this->consumeComments();
3779
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
3780
3781
        $this->parseCatchExceptionClass($catch);
3782
3783
        $this->consumeComments();
3784
        $this->parseCatchVariable($catch);
3785
3786
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
3787
3788
        $catch->addChild($this->parseRegularScope());
3789
3790
        return $this->setNodePositionsAndReturn($catch);
3791
    }
3792
3793
    /**
3794
     * This method parses assigned variable in catch statement.
3795
     *
3796
     * @param ASTCatchStatement $stmt The owning catch statement.
3797
     *
3798
     * @return void
3799
     */
3800
    protected function parseCatchVariable(ASTCatchStatement $stmt)
3801
    {
3802
        $stmt->addChild($this->parseVariable());
3803
3804
        $this->consumeComments();
3805
    }
3806
3807
    /**
3808
     * This method parses class references in catch statement.
3809
     *
3810
     * @param ASTCatchStatement $stmt The owning catch statement.
3811
     *
3812
     * @return void
3813
     */
3814
    protected function parseCatchExceptionClass(ASTCatchStatement $stmt)
3815
    {
3816
        $stmt->addChild(
3817
            $this->builder->buildAstClassOrInterfaceReference(
3818
                $this->parseQualifiedName()
3819
            )
3820
        );
3821
    }
3822
3823
    /**
3824
     * This method parses a finally-statement.
3825
     *
3826
     * @return ASTFinallyStatement
3827
     *
3828
     * @since 2.0.0
3829
     */
3830
    private function parseFinallyStatement()
3831
    {
3832
        $this->tokenStack->push();
3833
        $this->consumeComments();
3834
3835
        $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...
3836
3837
        $finally = $this->builder->buildAstFinallyStatement();
3838
        $finally->addChild($this->parseRegularScope());
3839
3840
        return $this->setNodePositionsAndReturn($finally);
3841
    }
3842
3843
    /**
3844
     * This method parses a single if-statement node.
3845
     *
3846
     * @return ASTIfStatement
3847
     *
3848
     * @since 0.9.8
3849
     */
3850
    private function parseIfStatement()
3851
    {
3852
        $this->tokenStack->push();
3853
        $token = $this->consumeToken(Tokens::T_IF);
3854
3855
        $stmt = $this->builder->buildAstIfStatement($token->image);
3856
        $stmt->addChild($this->parseParenthesisExpression());
3857
3858
        $this->parseStatementBody($stmt);
3859
        $this->parseOptionalElseOrElseIfStatement($stmt);
3860
3861
        return $this->setNodePositionsAndReturn($stmt);
3862
    }
3863
3864
    /**
3865
     * This method parses a single elseif-statement node.
3866
     *
3867
     * @return ASTElseIfStatement
3868
     *
3869
     * @since 0.9.8
3870
     */
3871
    private function parseElseIfStatement()
3872
    {
3873
        $this->tokenStack->push();
3874
        $token = $this->consumeToken(Tokens::T_ELSEIF);
3875
3876
        $stmt = $this->builder->buildAstElseIfStatement($token->image);
3877
        $stmt->addChild($this->parseParenthesisExpression());
3878
3879
        $this->parseStatementBody($stmt);
3880
        $this->parseOptionalElseOrElseIfStatement($stmt);
3881
3882
        return $this->setNodePositionsAndReturn($stmt);
3883
    }
3884
3885
    /**
3886
     * This method parses an optional else-, else+if- or elseif-statement.
3887
     *
3888
     * @param ASTStatement $stmt The owning if/elseif statement.
3889
     *
3890
     * @return ASTStatement
3891
     *
3892
     * @since 0.9.12
3893
     */
3894
    private function parseOptionalElseOrElseIfStatement(ASTStatement $stmt)
3895
    {
3896
        $this->consumeComments();
3897
        switch ($this->tokenizer->peek()) {
3898
            case Tokens::T_ELSE:
3899
                $this->consumeToken(Tokens::T_ELSE);
3900
                $this->consumeComments();
3901
                if ($this->tokenizer->peek() === Tokens::T_IF) {
3902
                    $stmt->addChild($this->parseIfStatement());
3903
                } else {
3904
                    $this->parseStatementBody($stmt);
3905
                }
3906
                break;
3907
            case Tokens::T_ELSEIF:
3908
                $stmt->addChild($this->parseElseIfStatement());
3909
                break;
3910
        }
3911
3912
        return $stmt;
3913
    }
3914
3915
    /**
3916
     * This method parses a single for-statement node.
3917
     *
3918
     * @return ASTForStatement
3919
     *
3920
     * @since 0.9.8
3921
     */
3922
    private function parseForStatement()
3923
    {
3924
        $this->tokenStack->push();
3925
        $token = $this->consumeToken(Tokens::T_FOR);
3926
3927
        $this->consumeComments();
3928
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
3929
3930
        $stmt = $this->builder->buildAstForStatement($token->image);
3931
3932
        if (($init = $this->parseForInit()) !== null) {
3933
            $stmt->addChild($init);
3934
        }
3935
        $this->consumeToken(Tokens::T_SEMICOLON);
3936
3937
        if (($expr = $this->parseForExpression()) !== null) {
3938
            $stmt->addChild($expr);
3939
        }
3940
        $this->consumeToken(Tokens::T_SEMICOLON);
3941
3942
        if (($update = $this->parseForUpdate()) !== null) {
3943
            $stmt->addChild($update);
3944
        }
3945
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
3946
3947
        return $this->setNodePositionsAndReturn($this->parseStatementBody($stmt));
3948
    }
3949
3950
    /**
3951
     * Parses the init part of a for-statement.
3952
     *
3953
     * <code>
3954
     *      ------------------------
3955
     * for ($x = 0, $y = 23, $z = 42; $x < $y; ++$x) {}
3956
     *      ------------------------
3957
     * </code>
3958
     *
3959
     * @return ASTForInit|null
3960
     *
3961
     * @since 0.9.8
3962
     */
3963
    private function parseForInit()
3964
    {
3965
        $this->consumeComments();
3966
3967
        if (Tokens::T_SEMICOLON === $this->tokenizer->peek()) {
3968
            return null;
3969
        }
3970
3971
        $this->tokenStack->push();
3972
3973
        $init = $this->builder->buildAstForInit();
3974
        $this->parseExpressionList($init);
3975
3976
        return $this->setNodePositionsAndReturn($init);
3977
    }
3978
3979
    /**
3980
     * Parses the expression part of a for-statement.
3981
     *
3982
     * @return ASTNode|null
3983
     *
3984
     * @since 0.9.12
3985
     */
3986
    private function parseForExpression()
3987
    {
3988
        return $this->parseOptionalExpression();
3989
    }
3990
3991
    /**
3992
     * Parses the update part of a for-statement.
3993
     *
3994
     * <code>
3995
     *                                        -------------------------------
3996
     * for ($x = 0, $y = 23, $z = 42; $x < $y; ++$x, $y = $x + 1, $z = $x + 2) {}
3997
     *                                        -------------------------------
3998
     * </code>
3999
     *
4000
     * @return ASTForUpdate|null
4001
     *
4002
     * @since 0.9.12
4003
     */
4004
    private function parseForUpdate()
4005
    {
4006
        $this->consumeComments();
4007
        if (Tokens::T_PARENTHESIS_CLOSE === $this->tokenizer->peek()) {
4008
            return null;
4009
        }
4010
4011
        $this->tokenStack->push();
4012
4013
        $update = $this->builder->buildAstForUpdate();
4014
        $this->parseExpressionList($update);
4015
4016
        return $this->setNodePositionsAndReturn($update);
4017
    }
4018
4019
    /**
4020
     * This methods return true if the token matches a list opening in the current PHP version level.
4021
     *
4022
     * @param int $tokenType
4023
     *
4024
     * @return bool
4025
     *
4026
     * @since 2.6.0
4027
     */
4028
    protected function isListUnpacking($tokenType = null)
4029
    {
4030
        return ($tokenType ?: $this->tokenizer->peek()) === Tokens::T_LIST;
4031
    }
4032
4033
    /**
4034
     * Get the parsed list of a foreach statement children.
4035
     *
4036
     * @return ASTNode[]
4037
     */
4038
    private function parseForeachChildren()
4039
    {
4040
        if ($this->tokenizer->peek() === Tokens::T_BITWISE_AND) {
4041
            return array($this->parseVariableOrMemberByReference());
4042
        }
4043
4044
        if ($this->isListUnpacking()) {
4045
            return array($this->parseListExpression());
4046
        }
4047
4048
        $children = array(
4049
            $this->parseVariableOrConstantOrPrimaryPrefix()
4050
        );
4051
4052
        if ($this->tokenizer->peek() === Tokens::T_DOUBLE_ARROW) {
4053
            $this->consumeToken(Tokens::T_DOUBLE_ARROW);
4054
4055
            $children[] = $this->isListUnpacking()
4056
                ? $this->parseListExpression()
4057
                : $this->parseVariableOrMemberOptionalByReference();
4058
        }
4059
4060
        return $children;
4061
    }
4062
4063
    /**
4064
     * This method parses a single foreach-statement node.
4065
     *
4066
     * @return ASTForeachStatement
4067
     *
4068
     * @since 0.9.8
4069
     */
4070
    private function parseForeachStatement()
4071
    {
4072
        $this->tokenStack->push();
4073
        $token = $this->consumeToken(Tokens::T_FOREACH);
4074
4075
        $foreach = $this->builder->buildAstForeachStatement($token->image);
4076
4077
        $this->consumeComments();
4078
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
4079
4080
        $foreach->addChild($this->parseExpression());
4081
4082
        $this->consumeToken(Tokens::T_AS);
4083
        $this->consumeComments();
4084
4085
        foreach ($this->parseForeachChildren() as $child) {
4086
            $foreach->addChild($child);
4087
        }
4088
4089
        $this->consumeComments();
4090
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
4091
4092
        return $this->setNodePositionsAndReturn(
4093
            $this->parseStatementBody($foreach)
4094
        );
4095
    }
4096
4097
    /**
4098
     * This method parses a single while-statement node.
4099
     *
4100
     * @return ASTWhileStatement
4101
     *
4102
     * @since 0.9.8
4103
     */
4104
    private function parseWhileStatement()
4105
    {
4106
        $this->tokenStack->push();
4107
        $token = $this->consumeToken(Tokens::T_WHILE);
4108
4109
        $stmt = $this->builder->buildAstWhileStatement($token->image);
4110
        $stmt->addChild($this->parseParenthesisExpression());
4111
4112
        return $this->setNodePositionsAndReturn(
4113
            $this->parseStatementBody($stmt)
4114
        );
4115
    }
4116
4117
    /**
4118
     * This method parses a do/while-statement.
4119
     *
4120
     * @return ASTDoWhileStatement
4121
     *
4122
     * @since 0.9.12
4123
     */
4124
    private function parseDoWhileStatement()
4125
    {
4126
        $this->tokenStack->push();
4127
        $token = $this->consumeToken(Tokens::T_DO);
4128
4129
        $stmt = $this->builder->buildAstDoWhileStatement($token->image);
4130
        $stmt = $this->parseStatementBody($stmt);
4131
4132
        $this->consumeComments();
4133
        $this->consumeToken(Tokens::T_WHILE);
4134
4135
        $stmt->addChild($this->parseParenthesisExpression());
4136
4137
        $this->parseStatementTermination();
4138
4139
        return $this->setNodePositionsAndReturn($stmt);
4140
    }
4141
4142
    /**
4143
     * This method parses a declare-statement.
4144
     *
4145
     * <code>
4146
     * -------------------------------
4147
     * declare(encoding='ISO-8859-1');
4148
     * -------------------------------
4149
     *
4150
     * -------------------
4151
     * declare(ticks=42) {
4152
     *     // ...
4153
     * }
4154
     * -
4155
     *
4156
     * ------------------
4157
     * declare(ticks=42):
4158
     *     // ...
4159
     * enddeclare;
4160
     * -----------
4161
     * </code>
4162
     *
4163
     * @return ASTDeclareStatement
4164
     *
4165
     * @since 0.10.0
4166
     */
4167
    private function parseDeclareStatement()
4168
    {
4169
        $this->tokenStack->push();
4170
        $this->consumeToken(Tokens::T_DECLARE);
4171
4172
        $stmt = $this->builder->buildAstDeclareStatement();
4173
        $stmt = $this->parseDeclareList($stmt);
4174
        $stmt = $this->parseStatementBody($stmt);
4175
4176
        return $this->setNodePositionsAndReturn($stmt);
4177
    }
4178
4179
    /**
4180
     * This method parses a list of declare values. A declare list value always
4181
     * consists of a string token and a static scalar.
4182
     *
4183
     * @param ASTDeclareStatement $stmt The declare statement that is the owner of this list.
4184
     *
4185
     * @return ASTDeclareStatement
4186
     *
4187
     * @since 0.10.0
4188
     */
4189
    private function parseDeclareList(ASTDeclareStatement $stmt)
4190
    {
4191
        $this->consumeComments();
4192
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
4193
4194
        while (true) {
4195
            $this->consumeComments();
4196
            $name = $this->consumeToken(Tokens::T_STRING)->image;
4197
4198
            $this->consumeComments();
4199
            $this->consumeToken(Tokens::T_EQUAL);
4200
4201
            $this->consumeComments();
4202
            $value = $this->parseStaticValue();
4203
4204
            $stmt->addValue($name, $value);
4205
4206
            $this->consumeComments();
4207
            if ($this->tokenizer->peek() === Tokens::T_COMMA) {
4208
                $this->consumeToken(Tokens::T_COMMA);
4209
                continue;
4210
            }
4211
            break;
4212
        }
4213
4214
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
4215
        return $stmt;
4216
    }
4217
4218
    /**
4219
     * This method builds a return statement from a given token.
4220
     *
4221
     * @return ASTReturnStatement
4222
     *
4223
     * @since 2.7.0
4224
     */
4225
    protected function buildReturnStatement(Token $token)
4226
    {
4227
        $stmt = $this->builder->buildAstReturnStatement($token->image);
4228
4229
        if (($expr = $this->parseOptionalExpression()) != null) {
4230
            $stmt->addChild($expr);
4231
        }
4232
4233
        return $stmt;
4234
    }
4235
4236
    /**
4237
     * This method parses a single return-statement node.
4238
     *
4239
     * @return ASTReturnStatement
4240
     *
4241
     * @since 0.9.12
4242
     */
4243
    private function parseReturnStatement()
4244
    {
4245
        $this->tokenStack->push();
4246
4247
        $stmt = $this->buildReturnStatement(
4248
            $this->consumeToken(Tokens::T_RETURN)
4249
        );
4250
4251
        $this->parseStatementTermination();
4252
4253
        return $this->setNodePositionsAndReturn($stmt);
4254
    }
4255
4256
    /**
4257
     * This method parses a break-statement node.
4258
     *
4259
     * @return ASTBreakStatement
4260
     *
4261
     * @since 0.9.12
4262
     */
4263
    private function parseBreakStatement()
4264
    {
4265
        $this->tokenStack->push();
4266
        $token = $this->consumeToken(Tokens::T_BREAK);
4267
4268
        $stmt = $this->builder->buildAstBreakStatement($token->image);
4269
        if (($expr = $this->parseOptionalExpression()) != null) {
4270
            $stmt->addChild($expr);
4271
        }
4272
        $this->parseStatementTermination();
4273
4274
        return $this->setNodePositionsAndReturn($stmt);
4275
    }
4276
4277
    /**
4278
     * This method parses a continue-statement node.
4279
     *
4280
     * @return ASTContinueStatement
4281
     *
4282
     * @since 0.9.12
4283
     */
4284
    private function parseContinueStatement()
4285
    {
4286
        $this->tokenStack->push();
4287
        $token = $this->consumeToken(Tokens::T_CONTINUE);
4288
4289
        $stmt = $this->builder->buildAstContinueStatement($token->image);
4290
        if (($expr = $this->parseOptionalExpression()) != null) {
4291
            $stmt->addChild($expr);
4292
        }
4293
        $this->parseStatementTermination();
4294
4295
        return $this->setNodePositionsAndReturn($stmt);
4296
    }
4297
4298
    /**
4299
     * This method parses a echo-statement node.
4300
     *
4301
     * @return ASTEchoStatement
4302
     *
4303
     * @since 0.9.12
4304
     */
4305
    private function parseEchoStatement()
4306
    {
4307
        $this->tokenStack->push();
4308
        $token = $this->consumeToken(Tokens::T_ECHO);
4309
4310
        $stmt = $this->parseExpressionList(
4311
            $this->builder->buildAstEchoStatement($token->image)
4312
        );
4313
4314
        $this->parseStatementTermination();
4315
4316
        return $this->setNodePositionsAndReturn($stmt);
4317
    }
4318
4319
    /**
4320
     * Parses a simple parenthesis expression or a direct object access, which
4321
     * was introduced with PHP 5.4.0:
4322
     *
4323
     * <code>
4324
     * (new MyClass())->bar();
4325
     * </code>
4326
     *
4327
     * @return ASTNode
4328
     *
4329
     * @since 1.0.0
4330
     */
4331
    protected function parseParenthesisExpressionOrPrimaryPrefix()
4332
    {
4333
        return $this->parseParenthesisExpressionOrPrimaryPrefixForVersion(
4334
            $this->parseParenthesisExpression()
4335
        );
4336
    }
4337
4338
    /**
4339
     * @template T of ASTExpression
4340
     *
4341
     * @param T $expr
4342
     *
4343
     * @return ASTMemberPrimaryPrefix|T
4344
     */
4345
    protected function parseParenthesisExpressionOrPrimaryPrefixForVersion(ASTExpression $expr)
4346
    {
4347
        $this->consumeComments();
4348
4349
        if ($this->isNextTokenObjectOperator()) {
4350
            return $this->parseMemberPrimaryPrefix($expr->getChild(0));
4351
        }
4352
4353
        return $expr;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $expr returns the type PDepend\Source\AST\ASTExpression which is incompatible with the documented return type PDepend\Source\AST\ASTMe...d\Source\Language\PHP\T.
Loading history...
4354
    }
4355
4356
    /**
4357
     * @return bool
4358
     */
4359
    protected function isNextTokenObjectOperator()
4360
    {
4361
        return $this->tokenizer->peek() === Tokens::T_OBJECT_OPERATOR;
4362
    }
4363
4364
    /**
4365
     * @throws TokenStreamEndException
4366
     * @throws UnexpectedTokenException
4367
     *
4368
     * @return Token
4369
     */
4370
    protected function consumeObjectOperatorToken()
4371
    {
4372
        return $this->consumeToken(Tokens::T_OBJECT_OPERATOR);
4373
    }
4374
4375
    /**
4376
     * Parses any expression that is surrounded by an opening and a closing
4377
     * parenthesis
4378
     *
4379
     * @return ASTExpression
4380
     *
4381
     * @since 0.9.8
4382
     */
4383
    protected function parseParenthesisExpression()
4384
    {
4385
        $this->tokenStack->push();
4386
        $this->consumeComments();
4387
4388
        $expr = $this->builder->buildAstExpression();
4389
        $expr = $this->parseBraceExpression(
4390
            $expr,
4391
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN),
4392
            Tokens::T_PARENTHESIS_CLOSE
4393
        );
4394
4395
        return $this->setNodePositionsAndReturn($expr);
4396
    }
4397
4398
    /**
4399
     * This method parses a member primary prefix expression or a function
4400
     * postfix expression node.
4401
     *
4402
     * A member primary prefix can be a method call:
4403
     *
4404
     * <code>
4405
     * $object->foo();
4406
     *
4407
     * clazz::foo();
4408
     * </code>
4409
     *
4410
     * a property access:
4411
     *
4412
     * <code>
4413
     * $object->foo;
4414
     *
4415
     * clazz::$foo;
4416
     * </code>
4417
     *
4418
     * or a class constant access:
4419
     *
4420
     * <code>
4421
     * clazz::FOO;
4422
     * </code>
4423
     *
4424
     * A function postfix represents any kind of function call:
4425
     *
4426
     * <code>
4427
     * $function();
4428
     *
4429
     * func();
4430
     * </code>
4431
     *
4432
     * @throws ParserException
4433
     *
4434
     * @return ASTNode
4435
     *
4436
     * @since 0.9.6
4437
     */
4438
    private function parseMemberPrefixOrFunctionPostfix()
4439
    {
4440
        $this->tokenStack->push();
4441
        $this->tokenStack->push();
4442
4443
        $qName = $this->parseQualifiedName();
4444
4445
        // Remove comments
4446
        $this->consumeComments();
4447
4448
        // Get next token type
4449
        $tokenType = $this->tokenizer->peek();
4450
4451
        switch ($tokenType) {
4452
            case Tokens::T_DOUBLE_COLON:
4453
                $node = $this->builder->buildAstClassOrInterfaceReference($qName);
4454
                $node = $this->setNodePositionsAndReturn($node);
4455
                $node = $this->parseStaticMemberPrimaryPrefix($node);
4456
                break;
4457
            case Tokens::T_PARENTHESIS_OPEN:
4458
                $node = $this->builder->buildAstIdentifier($qName);
4459
                $node = $this->setNodePositionsAndReturn($node);
4460
                $node = $this->parseFunctionPostfix($node);
4461
                break;
4462
            default:
4463
                $node = $this->builder->buildAstConstant($qName);
4464
                $node = $this->setNodePositionsAndReturn($node);
4465
                break;
4466
        }
4467
4468
        return $this->setNodePositionsAndReturn($node);
4469
    }
4470
4471
    /**
4472
     * This method will parse an optional function postfix.
4473
     *
4474
     * If the next available token is an opening parenthesis, this method will
4475
     * wrap the given <b>$node</b> with a {@link ASTFunctionPostfix}
4476
     * node.
4477
     *
4478
     * @template T of ASTNode
4479
     *
4480
     * @param T $node The previously parsed node.
4481
     *
4482
     * @return ASTFunctionPostfix|T The original input node or this node wrapped with a function postfix instance.
4483
     *
4484
     * @since 1.0.0
4485
     */
4486
    private function parseOptionalFunctionPostfix(ASTNode $node)
4487
    {
4488
        $this->consumeComments();
4489
        if (Tokens::T_PARENTHESIS_OPEN === $this->tokenizer->peek()) {
4490
            return $this->parseFunctionPostfix($node);
4491
        }
4492
        return $node;
4493
    }
4494
4495
    /**
4496
     * This method parses a function postfix expression. An object of type
4497
     * {@link ASTFunctionPostfix} represents any valid php
4498
     * function call.
4499
     *
4500
     * This method will delegate the call to another method that returns a
4501
     * member primary prefix object when the function postfix expression is
4502
     * followed by an object operator.
4503
     *
4504
     * @param ASTNode $node This node represents the function identifier. An identifier can be a static string,
4505
     *                      a variable, a compound variable or any other valid php function identifier.
4506
     *
4507
     * @throws ParserException
4508
     *
4509
     * @return ASTFunctionPostfix
4510
     *
4511
     * @since 0.9.6
4512
     */
4513
    protected function parseFunctionPostfix(ASTNode $node)
4514
    {
4515
        $image = $this->extractPostfixImage($node);
4516
4517
        $function = $this->builder->buildAstFunctionPostfix($image);
4518
        $function->addChild($node);
4519
        $function->addChild($this->parseArguments());
4520
4521
        return $this->parseOptionalMemberPrimaryPrefix(
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parseOptio...xExpression($function)) returns the type PDepend\Source\AST\ASTMemberPrimaryPrefix which is incompatible with the documented return type PDepend\Source\AST\ASTFunctionPostfix.
Loading history...
4522
            $this->parseOptionalIndexExpression($function)
4523
        );
4524
    }
4525
4526
    /**
4527
     * This method parses a PHP version specific identifier for method and
4528
     * property postfix expressions.
4529
     *
4530
     * @return ASTNode
4531
     *
4532
     * @since 1.0.0
4533
     */
4534
    protected function parsePostfixIdentifier()
4535
    {
4536
        switch ($this->tokenizer->peek()) {
4537
            case Tokens::T_STRING:
4538
                $node = $this->parseLiteral();
4539
                break;
4540
            default:
4541
                $node = $this->parseCompoundVariableOrVariableVariableOrVariable();
4542
                break;
4543
        }
4544
        return $this->parseOptionalIndexExpression($node);
4545
    }
4546
4547
    /**
4548
     * This method parses an optional member primary expression. It will parse
4549
     * the primary expression when an object operator can be found at the actual
4550
     * token stream position. Otherwise this method simply returns the input
4551
     * {@link ASTNode} instance.
4552
     *
4553
     * @template T of ASTNode
4554
     *
4555
     * @param T $node This node represents primary prefix
4556
     *                left expression. It will be the first child of the parsed member
4557
     *                primary expression.
4558
     *
4559
     * @throws ParserException
4560
     *
4561
     * @return ASTMemberPrimaryPrefix|T
4562
     *
4563
     * @since 0.9.6
4564
     */
4565
    protected function parseOptionalMemberPrimaryPrefix(ASTNode $node)
4566
    {
4567
        $this->consumeComments();
4568
4569
        if ($this->isNextTokenObjectOperator()) {
4570
            return $this->parseMemberPrimaryPrefix($node);
4571
        }
4572
4573
        return $node;
4574
    }
4575
4576
    /**
4577
     * This method parses a dynamic or object bound member primary expression.
4578
     * A member primary prefix can be a method call:
4579
     *
4580
     * <code>
4581
     * $object->foo();
4582
     * </code>
4583
     *
4584
     * or a property access:
4585
     *
4586
     * <code>
4587
     * $object->foo;
4588
     * </code>
4589
     *
4590
     * @param ASTNode $node The left node in the parsed member primary expression.
4591
     *
4592
     * @throws ParserException
4593
     *
4594
     * @return ASTMemberPrimaryPrefix
4595
     *
4596
     * @since 0.9.6
4597
     */
4598
    protected function parseMemberPrimaryPrefix(ASTNode $node)
4599
    {
4600
        // Consume double colon and optional comments
4601
        $token = $this->consumeObjectOperatorToken();
4602
4603
        $prefix = $this->builder->buildAstMemberPrimaryPrefix($token->image);
4604
        $prefix->addChild($node);
4605
4606
        $this->consumeComments();
4607
        $tokenType = $this->tokenizer->peek();
4608
4609
        switch ($tokenType) {
4610
            case ($this->isMethodName($tokenType)):
4611
                $child = $this->parseIdentifier($tokenType);
4612
                $child = $this->parseOptionalIndexExpression($child);
4613
4614
                // TODO: Move this in a separate method
4615
                if ($child instanceof ASTIndexExpression) {
0 ignored issues
show
introduced by
$child is always a sub-type of PDepend\Source\AST\ASTIndexExpression.
Loading history...
4616
                    $this->consumeComments();
4617
                    if (Tokens::T_PARENTHESIS_OPEN === $this->tokenizer->peek()) {
4618
                        $prefix->addChild($this->parsePropertyPostfix($child));
4619
                        return $this->parseOptionalFunctionPostfix($prefix);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parseOptio...unctionPostfix($prefix) returns the type PDepend\Source\AST\ASTFunctionPostfix which is incompatible with the documented return type PDepend\Source\AST\ASTMemberPrimaryPrefix.
Loading history...
4620
                    }
4621
                }
4622
                break;
4623
            case Tokens::T_CURLY_BRACE_OPEN:
4624
                $child = $this->parseCompoundExpression();
4625
                break;
4626
            default:
4627
                $child = $this->parseCompoundVariableOrVariableVariableOrVariable();
4628
                break;
4629
        }
4630
4631
        $prefix->addChild(
4632
            $this->parseMethodOrPropertyPostfix(
4633
                $this->parseOptionalIndexExpression($child)
4634
            )
4635
        );
4636
4637
        return $this->parseOptionalMemberPrimaryPrefix(
4638
            $this->parseOptionalIndexExpression($prefix)
4639
        );
4640
    }
4641
4642
    /**
4643
     * This method parses an optional member primary expression. It will parse
4644
     * the primary expression when a double colon operator can be found at the
4645
     * actual token stream position. Otherwise this method simply returns the
4646
     * input {@link ASTNode} instance.
4647
     *
4648
     * @param ASTNode $node This node represents primary prefix left expression. It will
4649
     *                      be the first child of the parsed member primary expression.
4650
     *
4651
     * @throws ParserException
4652
     *
4653
     * @return ASTNode
4654
     *
4655
     * @since 1.0.1
4656
     */
4657
    private function parseOptionalStaticMemberPrimaryPrefix(ASTNode $node)
4658
    {
4659
        $this->consumeComments();
4660
4661
        if ($this->tokenizer->peek() === Tokens::T_DOUBLE_COLON) {
4662
            return $this->parseStaticMemberPrimaryPrefix($node);
4663
        }
4664
4665
        return $node;
4666
    }
4667
4668
    /**
4669
     * This method parses a static member primary expression. The given node
4670
     * contains the used static class or interface identifier. A static member
4671
     * primary prefix can represent the following code expressions:
4672
     *
4673
     * A static method class:
4674
     *
4675
     * <code>
4676
     * Foo::bar();
4677
     * </code>
4678
     *
4679
     * a static property access:
4680
     *
4681
     * <code>
4682
     * Foo::$bar;
4683
     * </code>
4684
     *
4685
     * or a static constant access:
4686
     *
4687
     * <code>
4688
     * Foo::BAR;
4689
     * </code>
4690
     *
4691
     * @param ASTNode $node The left node in the parsed member primary expression.
4692
     *
4693
     * @throws ParserException
4694
     *
4695
     * @return ASTMemberPrimaryPrefix
4696
     *
4697
     * @since 0.9.6
4698
     */
4699
    protected function parseStaticMemberPrimaryPrefix(ASTNode $node)
4700
    {
4701
        $token = $this->consumeToken(Tokens::T_DOUBLE_COLON);
4702
4703
        $prefix = $this->builder->buildAstMemberPrimaryPrefix($token->image);
4704
        $prefix->addChild($node);
4705
4706
        $this->consumeComments();
4707
4708
        switch ($this->tokenizer->peek()) {
4709
            case Tokens::T_STRING:
4710
                $postfix = $this->parseMethodOrConstantPostfix();
4711
                break;
4712
            case Tokens::T_CLASS_FQN:
4713
                $postfix = $this->parseFullQualifiedClassNamePostfix();
4714
                break;
4715
            default:
4716
                $postfix = $this->parseMethodOrPropertyPostfix(
4717
                    $this->parsePostfixIdentifier()
4718
                );
4719
                break;
4720
        }
4721
4722
        $prefix->addChild($postfix);
4723
4724
        return $this->parseOptionalMemberPrimaryPrefix(
4725
            $this->parseOptionalIndexExpression($prefix)
4726
        );
4727
    }
4728
4729
    /**
4730
     * This method parses a method- or constant-postfix expression. This expression
4731
     * will contain an identifier node as nested child.
4732
     *
4733
     * @throws ParserException
4734
     *
4735
     * @return ASTNode
4736
     *
4737
     * @since 0.9.6
4738
     */
4739
    private function parseMethodOrConstantPostfix()
4740
    {
4741
        $this->tokenStack->push();
4742
4743
        $node = $this->parseIdentifier();
4744
4745
        $this->consumeComments();
4746
        if ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN) {
4747
            $postfix = $this->parseMethodPostfix($node);
4748
        } else {
4749
            $postfix = $this->builder->buildAstConstantPostfix($node->getImage());
4750
            $postfix->addChild($node);
4751
        }
4752
4753
        return $this->setNodePositionsAndReturn($postfix);
4754
    }
4755
4756
    /**
4757
     * This method parses a method- or property-postfix expression. This expression
4758
     * will contain the given node as method or property identifier.
4759
     *
4760
     * @param ASTNode $node The identifier for the parsed postfix expression node. This node
4761
     *                      will be the first child of the returned postfix node instance.
4762
     *
4763
     * @throws ParserException
4764
     *
4765
     * @return ASTNode
4766
     *
4767
     * @since 0.9.6
4768
     */
4769
    private function parseMethodOrPropertyPostfix(ASTNode $node)
4770
    {
4771
        // Strip optional comments
4772
        $this->consumeComments();
4773
4774
        switch ($this->tokenizer->peek()) {
4775
            case Tokens::T_PARENTHESIS_OPEN:
4776
                $postfix = $this->parseMethodPostfix($node);
4777
                break;
4778
            default:
4779
                $postfix = $this->parsePropertyPostfix($node);
4780
                break;
4781
        }
4782
        return $this->parseOptionalMemberPrimaryPrefix($postfix);
4783
    }
4784
4785
    /**
4786
     * Parses/Creates a property postfix node instance.
4787
     *
4788
     * @param ASTNode $node Node that represents the image of the property postfix node.
4789
     *
4790
     * @return ASTPropertyPostfix
4791
     *
4792
     * @since 0.10.2
4793
     */
4794
    private function parsePropertyPostfix(ASTNode $node)
4795
    {
4796
        $image = $this->extractPostfixImage($node);
4797
4798
        $postfix = $this->builder->buildAstPropertyPostfix($image);
4799
        $postfix->addChild($node);
4800
4801
        $postfix->configureLinesAndColumns(
4802
            $node->getStartLine(),
4803
            $node->getEndLine(),
4804
            $node->getStartColumn(),
4805
            $node->getEndColumn()
4806
        );
4807
4808
        return $postfix;
4809
    }
4810
4811
    /**
4812
     * Parses a full qualified class name postfix.
4813
     *
4814
     * @return ASTClassFqnPostfix
4815
     *
4816
     * @since 2.0.0
4817
     */
4818
    protected function parseFullQualifiedClassNamePostfix()
4819
    {
4820
        throw $this->getUnexpectedNextTokenException();
4821
    }
4822
4823
    /**
4824
     * This method will extract the image/name of the real property/variable
4825
     * that is wrapped by {@link ASTIndexExpression} nodes. If
4826
     * the given node is now wrapped by index expressions, this method will
4827
     * return the image of the entire <b>$node</b>.
4828
     *
4829
     * @param ASTNode $node The context node that may be wrapped by multiple array or string index expressions.
4830
     *
4831
     * @return string
4832
     *
4833
     * @since 1.0.0
4834
     */
4835
    protected function extractPostfixImage(ASTNode $node)
4836
    {
4837
        while ($node instanceof ASTIndexExpression) {
4838
            $node = $node->getChild(0);
4839
        }
4840
        return $node->getImage();
4841
    }
4842
4843
    /**
4844
     * Parses a method postfix node instance.
4845
     *
4846
     * @param ASTNode $node Node that represents the image of the method postfix node.
4847
     *
4848
     * @return ASTMethodPostfix
4849
     *
4850
     * @since 1.0.0
4851
     */
4852
    private function parseMethodPostfix(ASTNode $node)
4853
    {
4854
        $args  = $this->parseArguments();
4855
        $image = $this->extractPostfixImage($node);
4856
4857
        $postfix = $this->builder->buildAstMethodPostfix($image);
4858
        $postfix->addChild($node);
4859
        $postfix->addChild($args);
4860
4861
        $postfix->configureLinesAndColumns(
4862
            $node->getStartLine(),
4863
            $args->getEndLine(),
4864
            $node->getStartColumn(),
4865
            $args->getEndColumn()
4866
        );
4867
4868
        return $this->parseOptionalMemberPrimaryPrefix($postfix);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->parseOptio...PrimaryPrefix($postfix) also could return the type PDepend\Source\AST\ASTMemberPrimaryPrefix which is incompatible with the documented return type PDepend\Source\AST\ASTMethodPostfix.
Loading history...
4869
    }
4870
4871
    /**
4872
     * This method parses the arguments passed to a function- or method-call.
4873
     *
4874
     * @throws ParserException
4875
     *
4876
     * @return ASTArguments
4877
     *
4878
     * @since 0.9.6
4879
     */
4880
    protected function parseArguments()
4881
    {
4882
        $this->consumeComments();
4883
4884
        $this->tokenStack->push();
4885
4886
        return $this->parseArgumentsParenthesesContent(
4887
            $this->builder->buildAstArguments()
4888
        );
4889
    }
4890
4891
    /**
4892
     * This method parses the tokens after arguments passed to a function- or method-call.
4893
     *
4894
     * @throws ParserException
4895
     *
4896
     * @return ASTArguments
4897
     *
4898
     * @since 0.9.6
4899
     */
4900
    protected function parseArgumentsParenthesesContent(ASTArguments $arguments)
4901
    {
4902
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
4903
        $this->consumeComments();
4904
4905
        if (Tokens::T_PARENTHESIS_CLOSE !== $this->tokenizer->peek()) {
4906
            $arguments = $this->parseArgumentList($arguments);
4907
        }
4908
4909
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
4910
4911
        return $this->setNodePositionsAndReturn($arguments);
4912
    }
4913
4914
    /**
4915
     * @template T of ASTArguments
4916
     *
4917
     * @param T $arguments
4918
     *
4919
     * @return T
4920
     */
4921
    protected function parseArgumentList(ASTArguments $arguments)
4922
    {
4923
        return $this->parseExpressionList($arguments);
4924
    }
4925
4926
    /**
4927
     * This method implements the parsing for various expression types like
4928
     * variables, object/static method. All these expressions are valid in
4929
     * several php language constructs like, isset, empty, unset etc.
4930
     *
4931
     * @return ASTNode
4932
     *
4933
     * @since 0.9.12
4934
     */
4935
    protected function parseVariableOrConstantOrPrimaryPrefix()
4936
    {
4937
        $this->consumeComments();
4938
        switch ($this->tokenizer->peek()) {
4939
            case Tokens::T_DOLLAR:
4940
            case Tokens::T_VARIABLE:
4941
                return $this->parseVariableOrFunctionPostfixOrMemberPrimaryPrefix();
4942
            case Tokens::T_SELF:
4943
                return $this->parseConstantOrSelfMemberPrimaryPrefix();
4944
            case Tokens::T_PARENT:
4945
                return $this->parseConstantOrParentMemberPrimaryPrefix();
4946
            case Tokens::T_STATIC:
4947
                return $this->parseStaticVariableDeclarationOrMemberPrimaryPrefix();
4948
            case Tokens::T_STRING:
4949
            case Tokens::T_BACKSLASH:
4950
            case Tokens::T_NAMESPACE:
4951
                return $this->parseMemberPrefixOrFunctionPostfix();
4952
            case Tokens::T_PARENTHESIS_OPEN:
4953
                return $this->parseParenthesisExpression();
4954
        }
4955
4956
        throw $this->getUnexpectedNextTokenException();
4957
    }
4958
4959
    /**
4960
     * This method parses any type of variable, function postfix expressions or
4961
     * any kind of member primary prefix.
4962
     *
4963
     * This method expects that the actual token represents any kind of valid
4964
     * php variable: simple variable, compound variable or variable variable.
4965
     *
4966
     * It will parse a function postfix or member primary expression when this
4967
     * variable is followed by an object operator, double colon or opening
4968
     * parenthesis.
4969
     *
4970
     * @throws ParserException
4971
     *
4972
     * @return ASTNode
4973
     *
4974
     * @since 0.9.6
4975
     */
4976
    private function parseVariableOrFunctionPostfixOrMemberPrimaryPrefix()
4977
    {
4978
        $this->tokenStack->push();
4979
4980
        $variable = $this->parseCompoundVariableOrVariableVariableOrVariable();
4981
        $variable = $this->parseOptionalIndexExpression($variable);
4982
4983
        $this->consumeComments();
4984
        switch ($this->tokenizer->peek()) {
4985
            case Tokens::T_DOUBLE_COLON:
4986
                $result = $this->parseStaticMemberPrimaryPrefix($variable);
4987
                break;
4988
            case Tokens::T_NULLSAFE_OBJECT_OPERATOR:
4989
            case Tokens::T_OBJECT_OPERATOR:
4990
                $result = $this->parseMemberPrimaryPrefix($variable);
4991
                break;
4992
            case Tokens::T_PARENTHESIS_OPEN:
4993
                $result = $this->parseFunctionPostfix($variable);
4994
                break;
4995
            default:
4996
                $result = $variable;
4997
                break;
4998
        }
4999
        return $this->setNodePositionsAndReturn($result);
5000
    }
5001
5002
    /**
5003
     * Parses an assingment expression node.
5004
     *
5005
     * @param ASTNode $left The left part of the assignment expression that will be parsed by this method.
5006
     *
5007
     * @return ASTAssignmentExpression
5008
     *
5009
     * @since 0.9.12
5010
     */
5011
    protected function parseAssignmentExpression(ASTNode $left)
5012
    {
5013
        $token = $this->consumeToken($this->tokenizer->peek());
5014
5015
        $node = $this->builder->buildAstAssignmentExpression($token->image);
5016
        $node->addChild($left);
5017
5018
        // TODO: Change this into a mandatory expression in later versions
5019
        if (($expr = $this->parseOptionalExpression()) != null) {
5020
            $node->addChild($expr);
5021
        } else {
5022
            $expr = $left;
5023
        }
5024
5025
        $node->configureLinesAndColumns(
5026
            $left->getStartLine(),
5027
            $expr->getEndLine(),
5028
            $left->getStartColumn(),
5029
            $expr->getEndColumn()
5030
        );
5031
5032
        return $node;
5033
    }
5034
5035
    /**
5036
     * This method parses a {@link ASTStaticReference} node.
5037
     *
5038
     * @param Token $token The "static" keyword token.
5039
     *
5040
     * @throws ParserException
5041
     * @throws InvalidStateException
5042
     *
5043
     * @return ASTStaticReference
5044
     *
5045
     * @since 0.9.6
5046
     */
5047
    private function parseStaticReference(Token $token)
5048
    {
5049
        // Strip optional comments
5050
        $this->consumeComments();
5051
5052
        if ($this->classOrInterface === null) {
5053
            throw new InvalidStateException(
5054
                $token->startLine,
5055
                (string) $this->compilationUnit,
5056
                'The keyword "static" was used outside of a class/method scope.'
5057
            );
5058
        }
5059
5060
        $ref = $this->builder->buildAstStaticReference($this->classOrInterface);
5061
        $ref->configureLinesAndColumns(
5062
            $token->startLine,
5063
            $token->endLine,
5064
            $token->startColumn,
5065
            $token->endColumn
5066
        );
5067
5068
        return $ref;
5069
    }
5070
5071
    /**
5072
     * This method parses a {@link ASTSelfReference} node.
5073
     *
5074
     * @param Token $token The "self" keyword token.
5075
     *
5076
     * @throws ParserException
5077
     * @throws InvalidStateException
5078
     *
5079
     * @return ASTSelfReference
5080
     *
5081
     * @since 0.9.6
5082
     */
5083
    protected function parseSelfReference(Token $token)
5084
    {
5085
        if ($this->classOrInterface === null) {
5086
            throw new InvalidStateException(
5087
                $token->startLine,
5088
                (string) $this->compilationUnit,
5089
                'The keyword "self" was used outside of a class/method scope.'
5090
            );
5091
        }
5092
5093
        $ref = $this->builder->buildAstSelfReference($this->classOrInterface);
5094
        $ref->configureLinesAndColumns(
5095
            $token->startLine,
5096
            $token->endLine,
5097
            $token->startColumn,
5098
            $token->endColumn
5099
        );
5100
5101
        return $ref;
5102
    }
5103
5104
    /**
5105
     * Parses a simple PHP constant use and returns a corresponding node.
5106
     *
5107
     * @return ASTNode|null
5108
     *
5109
     * @since 1.0.0
5110
     */
5111
    protected function parseConstant()
5112
    {
5113
        $this->tokenStack->push();
5114
        switch ($type = $this->tokenizer->peek()) {
5115
            case Tokens::T_STRING:
5116
                // TODO: Separate node classes for magic constants
5117
            case Tokens::T_DIR:
5118
            case Tokens::T_FILE:
5119
            case Tokens::T_LINE:
5120
            case Tokens::T_NS_C:
5121
            case Tokens::T_FUNC_C:
5122
            case Tokens::T_CLASS_C:
5123
            case Tokens::T_METHOD_C:
5124
            case Tokens::T_TRAIT_C:
5125
                $token = $this->consumeToken($type);
5126
5127
                return $this->setNodePositionsAndReturn(
5128
                    $this->builder->buildAstConstant($token->image)
5129
                );
5130
        }
5131
5132
        return null;
5133
    }
5134
5135
    /**
5136
     * This method parses a {@link ASTConstant} node or an instance of
5137
     * {@link ASTSelfReference} as part of a {@link ASTMemberPrimaryPrefix} that
5138
     * contains the self reference as its first child when the self token is
5139
     * followed by a double colon token.
5140
     *
5141
     * @throws ParserException
5142
     * @throws InvalidStateException
5143
     *
5144
     * @return ASTNode
5145
     *
5146
     * @since 0.9.6
5147
     */
5148
    private function parseConstantOrSelfMemberPrimaryPrefix()
5149
    {
5150
        // Read self token and strip optional comments
5151
        $token = $this->consumeToken(Tokens::T_SELF);
5152
        $this->consumeComments();
5153
5154
        if ($this->tokenizer->peek() == Tokens::T_DOUBLE_COLON) {
5155
            return $this->parseStaticMemberPrimaryPrefix(
5156
                $this->parseSelfReference($token)
5157
            );
5158
        }
5159
5160
        return $this->builder->buildAstConstant($token->image);
5161
    }
5162
5163
    /**
5164
     * This method parses a {@link ASTParentReference} node.
5165
     *
5166
     * @param Token $token The "self" keyword token.
5167
     *
5168
     * @throws ParserException
5169
     * @throws InvalidStateException
5170
     *
5171
     * @return ASTParentReference
5172
     *
5173
     * @since 0.9.6
5174
     */
5175
    private function parseParentReference(Token $token)
5176
    {
5177
        if ($this->classOrInterface === null) {
5178
            throw new InvalidStateException(
5179
                $token->startLine,
5180
                (string) $this->compilationUnit,
5181
                'The keyword "parent" was used as type hint but the parameter ' .
5182
                'declaration is not in a class scope.'
5183
            );
5184
        }
5185
5186
        $classReference = $this->classOrInterface instanceof ASTTrait
5187
            ? $this->builder->buildAstClassReference('__PDepend_TraitRuntimeReference')
5188
            : $this->classOrInterface->getParentClassReference();
5189
5190
        if ($classReference === null) {
5191
            throw new InvalidStateException(
5192
                $token->startLine,
5193
                (string) $this->compilationUnit,
5194
                sprintf(
5195
                    'The keyword "parent" was used as type hint but the ' .
5196
                    'class "%s" does not declare a parent.',
5197
                    $this->classOrInterface->getName()
5198
                )
5199
            );
5200
        }
5201
5202
        $ref = $this->builder->buildAstParentReference($classReference);
5203
        $ref->configureLinesAndColumns(
5204
            $token->startLine,
5205
            $token->endLine,
5206
            $token->startColumn,
5207
            $token->endColumn
5208
        );
5209
5210
        return $ref;
5211
    }
5212
5213
    /**
5214
     * This method parses a {@link ASTConstant} node or an instance of
5215
     * {@link ASTParentReference} as part of a {@link ASTMemberPrimaryPrefix}
5216
     * that contains the parent reference as its first child when the self token
5217
     * is followed by a double colon token.
5218
     *
5219
     * @throws ParserException
5220
     * @throws InvalidStateException
5221
     *
5222
     * @return ASTNode
5223
     *
5224
     * @since 0.9.6
5225
     */
5226
    private function parseConstantOrParentMemberPrimaryPrefix()
5227
    {
5228
        // Consume parent token and strip optional comments
5229
        $token = $this->consumeToken(Tokens::T_PARENT);
5230
        $this->consumeComments();
5231
5232
        if ($this->tokenizer->peek() == Tokens::T_DOUBLE_COLON) {
5233
            return $this->parseStaticMemberPrimaryPrefix(
5234
                $this->parseParentReference($token)
5235
            );
5236
        }
5237
5238
        return $this->builder->buildAstConstant($token->image);
5239
    }
5240
5241
    /**
5242
     * Parses a variable or any other valid member expression that is optionally
5243
     * prefixed with PHP's reference operator.
5244
     *
5245
     * <code>
5246
     * //                  -----------
5247
     * foreach ( $array as &$this->foo ) {}
5248
     * //                  -----------
5249
     *
5250
     * //     ----------
5251
     * $foo = &$bar->baz;
5252
     * //     ----------
5253
     * </code>
5254
     *
5255
     * @return ASTNode
5256
     *
5257
     * @since 0.9.18
5258
     */
5259
    private function parseVariableOrMemberOptionalByReference()
5260
    {
5261
        $this->consumeComments();
5262
5263
        if ($this->tokenizer->peek() === Tokens::T_BITWISE_AND) {
5264
            return $this->parseVariableOrMemberByReference();
5265
        }
5266
5267
        return $this->parseVariableOrConstantOrPrimaryPrefix();
5268
    }
5269
5270
    /**
5271
     * Parses a variable or any other valid member expression that is prefixed
5272
     * with PHP's reference operator.
5273
     *
5274
     * <code>
5275
     * //                  -----------
5276
     * foreach ( $array as &$this->foo ) {}
5277
     * //                  -----------
5278
     *
5279
     * //     ----------
5280
     * $foo = &$bar->baz;
5281
     * //     ----------
5282
     * </code>
5283
     *
5284
     * @return ASTUnaryExpression
5285
     *
5286
     * @since 0.9.18
5287
     */
5288
    private function parseVariableOrMemberByReference()
5289
    {
5290
        $this->tokenStack->push();
5291
5292
        $token = $this->consumeToken(Tokens::T_BITWISE_AND);
5293
        $this->consumeComments();
5294
5295
        $expr = $this->builder->buildAstUnaryExpression($token->image);
5296
        $expr->addChild($this->parseVariableOrConstantOrPrimaryPrefix());
5297
5298
        return $this->setNodePositionsAndReturn($expr);
5299
    }
5300
5301
    /**
5302
     * This method parses a simple PHP variable.
5303
     *
5304
     * @throws UnexpectedTokenException
5305
     *
5306
     * @return ASTVariable
5307
     *
5308
     * @since 0.9.6
5309
     */
5310
    private function parseVariable()
5311
    {
5312
        $token = $this->consumeToken(Tokens::T_VARIABLE);
5313
5314
        $variable = $this->builder->buildAstVariable($token->image);
5315
        $variable->configureLinesAndColumns(
5316
            $token->startLine,
5317
            $token->endLine,
5318
            $token->startColumn,
5319
            $token->endColumn
5320
        );
5321
5322
        return $variable;
5323
    }
5324
5325
    /**
5326
     * This method parses a comma separated list of valid php variables and/or
5327
     * properties and adds them to the given node instance.
5328
     *
5329
     * @template T of AbstractASTNode
5330
     *
5331
     * @param T $node The context parent node.
5332
     * @param bool $inCall
5333
     *
5334
     * @return T The prepared entire node.
5335
     *
5336
     * @since 0.9.12
5337
     */
5338
    private function parseVariableList(ASTNode $node, $inCall = false)
5339
    {
5340
        $this->consumeComments();
5341
        while ($this->tokenizer->peek() !== Tokenizer::T_EOF) {
5342
            $node->addChild($this->parseVariableOrConstantOrPrimaryPrefix());
5343
5344
            $this->consumeComments();
5345
5346
            if ($this->tokenizer->peek() === Tokens::T_COMMA) {
5347
                $this->consumeToken(Tokens::T_COMMA);
5348
                $this->consumeComments();
5349
5350
                if ($inCall &&
5351
                    $this->allowTrailingCommaInSpecialFunctions() &&
5352
                    $this->tokenizer->peek() === Tokens::T_PARENTHESIS_CLOSE
5353
                ) {
5354
                    break;
5355
                }
5356
            } else {
5357
                break;
5358
            }
5359
        }
5360
5361
        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...
5362
    }
5363
5364
    /**
5365
     * This method is a decision point between the different variable types
5366
     * availanle in PHP. It peeks the next token and then decides whether it is
5367
     * a regular variable or when the next token is of type <b>T_DOLLAR</b> a
5368
     * compound- or variable-variable.
5369
     *
5370
     * <code>
5371
     * ----
5372
     * $foo;
5373
     * ----
5374
     *
5375
     * -----
5376
     * $$foo;
5377
     * -----
5378
     *
5379
     * ------
5380
     * ${FOO};
5381
     * ------
5382
     * </code>
5383
     *
5384
     * @throws ParserException
5385
     * @throws UnexpectedTokenException
5386
     *
5387
     * @return ASTExpression
5388
     *
5389
     * @since 0.9.6
5390
     */
5391
    protected function parseCompoundVariableOrVariableVariableOrVariable()
5392
    {
5393
        if ($this->tokenizer->peek() == Tokens::T_DOLLAR) {
5394
            return $this->parseCompoundVariableOrVariableVariable();
5395
        }
5396
        return $this->parseVariable();
5397
    }
5398
5399
    /**
5400
     * Parses a PHP compound variable or a simple literal node.
5401
     *
5402
     * @return ASTNode
5403
     *
5404
     * @since 0.9.19
5405
     */
5406
    private function parseCompoundVariableOrLiteral()
5407
    {
5408
        $this->tokenStack->push();
5409
5410
        // Read the dollar token
5411
        $token = $this->consumeToken(Tokens::T_DOLLAR);
5412
        $this->consumeComments();
5413
5414
        // Get next token type
5415
        $tokenType = $this->tokenizer->peek();
5416
5417
        switch ($tokenType) {
5418
            case Tokens::T_CURLY_BRACE_OPEN:
5419
                $variable = $this->builder->buildAstCompoundVariable($token->image);
5420
                $variable->addChild($this->parseCompoundExpression());
5421
                break;
5422
            default:
5423
                $variable = $this->builder->buildAstLiteral($token->image);
5424
                break;
5425
        }
5426
5427
        return $this->setNodePositionsAndReturn($variable);
5428
    }
5429
5430
    /**
5431
     * This method implements a decision point between compound-variables and
5432
     * variable-variable. It expects that the next token in the token-stream is
5433
     * of type <b>T_DOLLAR</b> and removes it from the stream. Then this method
5434
     * peeks the next available token when it is of type <b>T_CURLY_BRACE_OPEN</b>
5435
     * this is compound variable, otherwise it can be a variable-variable or a
5436
     * compound-variable.
5437
     *
5438
     * @throws ParserException
5439
     * @throws UnexpectedTokenException
5440
     *
5441
     * @return ASTExpression
5442
     *
5443
     * @since 0.9.6
5444
     */
5445
    private function parseCompoundVariableOrVariableVariable()
5446
    {
5447
        $this->tokenStack->push();
5448
5449
        // Read the dollar token
5450
        $token = $this->consumeToken(Tokens::T_DOLLAR);
5451
        $this->consumeComments();
5452
5453
        // Get next token type
5454
        $tokenType = $this->tokenizer->peek();
5455
5456
        // T_DOLLAR|T_VARIABLE === Variable variable,
5457
        // T_CURLY_BRACE_OPEN === Compound variable
5458
        switch ($tokenType) {
5459
            case Tokens::T_DOLLAR:
5460
            case Tokens::T_VARIABLE:
5461
                $variable = $this->builder->buildAstVariableVariable($token->image);
5462
                $variable->addChild(
5463
                    $this->parseCompoundVariableOrVariableVariableOrVariable()
5464
                );
5465
                break;
5466
            default:
5467
                $variable = $this->parseCompoundVariable($token);
5468
                break;
5469
        }
5470
5471
        return $this->setNodePositionsAndReturn($variable);
5472
    }
5473
5474
    /**
5475
     * This method parses a compound variable like:
5476
     *
5477
     * <code>
5478
     * //     ----------------
5479
     * return ${'Foo' . 'Bar'};
5480
     * //     ----------------
5481
     * </code>
5482
     *
5483
     * @param Token $token The dollar token.
5484
     *
5485
     * @return ASTCompoundVariable
5486
     *
5487
     * @since 0.10.0
5488
     */
5489
    private function parseCompoundVariable(Token $token)
5490
    {
5491
        return $this->parseBraceExpression(
5492
            $this->builder->buildAstCompoundVariable($token->image),
5493
            $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN),
5494
            Tokens::T_CURLY_BRACE_CLOSE
5495
        );
5496
    }
5497
5498
    /**
5499
     * This method parses a compound expression like:
5500
     *
5501
     * <code>
5502
     * //      ------  ------
5503
     * $foo = "{$bar}, {$baz}\n";
5504
     * //      ------  ------
5505
     * </code>
5506
     *
5507
     * or a simple literal token:
5508
     *
5509
     * <code>
5510
     * //      -
5511
     * $foo = "{{$bar}, {$baz}\n";
5512
     * //      -
5513
     * </code>
5514
     *
5515
     * @return ASTNode
5516
     *
5517
     * @since 0.9.10
5518
     */
5519
    private function parseCompoundExpressionOrLiteral()
5520
    {
5521
        $token = $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
5522
        $this->consumeComments();
5523
5524
        switch ($this->tokenizer->peek()) {
5525
            case Tokens::T_DOLLAR:
5526
            case Tokens::T_VARIABLE:
5527
                return $this->parseBraceExpression(
5528
                    $this->builder->buildAstCompoundExpression(),
5529
                    $token,
5530
                    Tokens::T_CURLY_BRACE_CLOSE
5531
                );
5532
        }
5533
5534
        $literal = $this->builder->buildAstLiteral($token->image);
5535
        $literal->configureLinesAndColumns(
5536
            $token->startLine,
5537
            $token->endLine,
5538
            $token->startColumn,
5539
            $token->endColumn
5540
        );
5541
5542
        return $literal;
5543
    }
5544
5545
    /**
5546
     * This method parses a compound expression node.
5547
     *
5548
     * <code>
5549
     * ------------------
5550
     * {'_' . foo . $bar}
5551
     * ------------------
5552
     * </code>
5553
     *
5554
     * @throws ParserException
5555
     * @throws ParserException
5556
     *
5557
     * @return ASTCompoundExpression
5558
     *
5559
     * @since 0.9.6
5560
     */
5561
    protected function parseCompoundExpression()
5562
    {
5563
        $this->consumeComments();
5564
5565
        return $this->parseBraceExpression(
5566
            $this->builder->buildAstCompoundExpression(),
5567
            $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN),
5568
            Tokens::T_CURLY_BRACE_CLOSE
5569
        );
5570
    }
5571
5572
    /**
5573
     * Parses a static identifier expression, as it is used for method and
5574
     * function names.
5575
     *
5576
     * @param int $tokenType
5577
     *
5578
     * @return ASTIdentifier
5579
     *
5580
     * @since 0.9.12
5581
     */
5582
    protected function parseIdentifier($tokenType = Tokens::T_STRING)
5583
    {
5584
        $token = $this->consumeToken($tokenType);
5585
5586
        $node = $this->builder->buildAstIdentifier($token->image);
5587
        $node->configureLinesAndColumns(
5588
            $token->startLine,
5589
            $token->endLine,
5590
            $token->startColumn,
5591
            $token->endColumn
5592
        );
5593
5594
        return $node;
5595
    }
5596
5597
    /**
5598
     * This method parses a {@link ASTLiteral} node or an
5599
     * instance of {@link ASTString} that represents a string
5600
     * in double quotes or surrounded by backticks.
5601
     *
5602
     * @throws UnexpectedTokenException
5603
     *
5604
     * @return ASTNode
5605
     */
5606
    protected function parseLiteralOrString()
5607
    {
5608
        $tokenType = $this->tokenizer->peek();
5609
5610
        switch ($tokenType) {
5611
            case Tokens::T_NULL:
5612
            case Tokens::T_TRUE:
5613
            case Tokens::T_FALSE:
5614
            case Tokens::T_DNUMBER:
5615
            case Tokens::T_CONSTANT_ENCAPSED_STRING:
5616
                $token = $this->consumeToken($tokenType);
5617
5618
                $literal = $this->builder->buildAstLiteral($token->image);
5619
                $literal->configureLinesAndColumns(
5620
                    $token->startLine,
5621
                    $token->endLine,
5622
                    $token->startColumn,
5623
                    $token->endColumn
5624
                );
5625
                return $literal;
5626
            case Tokens::T_LNUMBER:
5627
                return $this->parseIntegerNumber();
5628
            default:
5629
                return $this->parseString($tokenType);
5630
        }
5631
    }
5632
5633
    /**
5634
     * Parses an integer value.
5635
     *
5636
     * @return ASTLiteral
5637
     *
5638
     * @since 1.0.0
5639
     */
5640
    protected function parseIntegerNumber()
5641
    {
5642
        $token = $this->consumeToken(Tokens::T_LNUMBER);
5643
5644
        $literal = $this->builder->buildAstLiteral($token->image);
5645
        $literal->configureLinesAndColumns(
5646
            $token->startLine,
5647
            $token->endLine,
5648
            $token->startColumn,
5649
            $token->endColumn
5650
        );
5651
5652
        return $literal;
5653
    }
5654
5655
    /**
5656
     * Parses an array structure.
5657
     *
5658
     * @param bool $static
5659
     *
5660
     * @return ASTArray
5661
     *
5662
     * @since 1.0.0
5663
     */
5664
    protected function doParseArray($static = false)
5665
    {
5666
        $this->tokenStack->push();
5667
5668
        return $this->setNodePositionsAndReturn(
5669
            $this->parseArray(
5670
                $this->builder->buildAstArray(),
5671
                $static
5672
            )
5673
        );
5674
    }
5675
5676
    /**
5677
     * Tests if the next token is a valid array start delimiter in the supported
5678
     * PHP version.
5679
     *
5680
     * @return bool
5681
     *
5682
     * @since 1.0.0
5683
     */
5684
    abstract protected function isArrayStartDelimiter();
5685
5686
    /**
5687
     * Parses a php array declaration.
5688
     *
5689
     * @param bool $static
5690
     *
5691
     * @return ASTArray
5692
     *
5693
     * @since 1.0.0
5694
     */
5695
    abstract protected function parseArray(ASTArray $array, $static = false);
5696
5697
    /**
5698
     * Return true if [, $foo] or [$foo, , $bar] is allowed.
5699
     *
5700
     * @return bool
5701
     */
5702
    protected function canHaveCommaBetweenArrayElements()
5703
    {
5704
        return false;
5705
    }
5706
5707
    /**
5708
     * Parses all elements in an array.
5709
     *
5710
     * @param int  $endDelimiter
5711
     * @param bool $static
5712
     *
5713
     * @return ASTArray
5714
     *
5715
     * @since 1.0.0
5716
     */
5717
    protected function parseArrayElements(ASTArray $array, $endDelimiter, $static = false)
5718
    {
5719
        $consecutiveComma = null;
5720
        $openingToken = $this->tokenizer->prevToken();
5721
        $useSquaredBrackets = ($endDelimiter === Tokens::T_SQUARED_BRACKET_CLOSE);
5722
        $this->consumeComments();
5723
5724
        while ($endDelimiter !== $this->tokenizer->peek()) {
5725
            while ($this->canHaveCommaBetweenArrayElements() && Tokens::T_COMMA === $this->tokenizer->peek()) {
5726
                $this->consumeToken(Tokens::T_COMMA);
5727
                $this->consumeComments();
5728
            }
5729
5730
            $array->addChild($this->parseArrayElement($static));
5731
5732
            $this->consumeComments();
5733
5734
            if (Tokens::T_COMMA === $this->tokenizer->peek()) {
5735
                $this->consumeToken(Tokens::T_COMMA);
5736
                $this->consumeComments();
5737
            }
5738
5739
            if ($useSquaredBrackets && $this->isListUnpacking(Tokens::T_SQUARED_BRACKET_OPEN)) {
5740
                while (Tokens::T_COMMA === $this->tokenizer->peek()) {
5741
                    $consecutiveComma = $this->tokenizer->prevToken();
5742
                    $this->consumeToken(Tokens::T_COMMA);
5743
                    $this->consumeComments();
5744
                }
5745
            }
5746
        }
5747
5748
        // Once we parsed the whole array, detect if it's a destructuring list or a value,
5749
        // then check the content is consistent
5750
        $this->ensureArrayIsValid($useSquaredBrackets, $openingToken, $consecutiveComma);
5751
5752
        return $array;
5753
    }
5754
5755
    /**
5756
     * Check if the given array/list is a value and so does not have consecutive commas in it,
5757
     * or if it's a destructuring list and so check the syntax is valid in the current PHP level.
5758
     *
5759
     * @param bool       $useSquaredBrackets
5760
     * @param Token|null $openingToken
5761
     * @param Token|null $consecutiveComma
5762
     *
5763
     * @throws UnexpectedTokenException
5764
     *
5765
     * @return void
5766
     */
5767
    protected function ensureArrayIsValid($useSquaredBrackets, $openingToken, $consecutiveComma)
5768
    {
5769
        // If this array is followed by =, it's in fact a destructuring list
5770
        if ($this->tokenizer->peekNext() === Tokens::T_EQUAL) {
5771
            // If it uses [], check the PHP level allow it
5772
            if ($useSquaredBrackets) {
5773
                $this->ensureTokenIsListUnpackingOpening(Tokens::T_SQUARED_BRACKET_OPEN, $openingToken);
5774
            }
5775
        } elseif ($consecutiveComma) {
5776
            // If it's not a destructuring list, it must not contain 2 consecutive commas
5777
            throw $this->getUnexpectedTokenException($consecutiveComma);
5778
        }
5779
    }
5780
5781
    /**
5782
     * Parses a single match key.
5783
     *
5784
     * @return ASTNode
5785
     *
5786
     * @since 2.9.0
5787
     */
5788
    protected function parseMatchEntryKey()
5789
    {
5790
        $this->consumeComments();
5791
5792
        if ($this->tokenizer->peek() === Tokens::T_DEFAULT) {
5793
            $this->consumeToken(Tokens::T_DEFAULT);
5794
            $label = $this->builder->buildAstSwitchLabel('default');
5795
            $label->setDefault();
5796
5797
            return $label;
5798
        }
5799
5800
        if ($this->isKeyword($this->tokenizer->peek())) {
5801
            throw $this->getUnexpectedNextTokenException();
5802
        }
5803
5804
        return $this->parseExpression();
5805
    }
5806
5807
    /**
5808
     * Parses a single match value expression.
5809
     *
5810
     * @return ASTNode
5811
     *
5812
     * @since 2.9.0
5813
     */
5814
    protected function parseMatchEntryValue()
5815
    {
5816
        $this->consumeComments();
5817
5818
        if ($this->tokenizer->peek() === Tokens::T_THROW) {
5819
            return $this->parseThrowStatement(array(Tokens::T_COMMA, Tokens::T_CURLY_BRACE_CLOSE));
5820
        }
5821
5822
        return $this->parseExpression();
5823
    }
5824
5825
    /**
5826
     * Parses a single match entry key-expression pair.
5827
     *
5828
     * @return ASTMatchEntry
5829
     *
5830
     * @since 2.9.0
5831
     */
5832
    protected function parseMatchEntry()
5833
    {
5834
        $this->consumeComments();
5835
5836
        $this->tokenStack->push();
5837
5838
        $matchEntry = $this->builder->buildAstMatchEntry();
5839
5840
        do {
5841
            $matchEntry->addChild($this->parseMatchEntryKey());
5842
            $this->consumeComments();
5843
5844
            if ($this->tokenizer->peek() === Tokens::T_COMMA) {
5845
                $this->consumeToken(Tokens::T_COMMA);
5846
                $this->consumeComments();
5847
            }
5848
5849
            $type = $this->tokenizer->peek();
5850
        } while ($type != Tokens::T_DOUBLE_ARROW);
5851
5852
        $this->consumeComments();
5853
        $this->consumeToken(Tokens::T_DOUBLE_ARROW);
5854
        $this->consumeComments();
5855
5856
        $matchEntry->addChild($this->parseMatchEntryValue());
5857
5858
        return $this->setNodePositionsAndReturn($matchEntry);
5859
    }
5860
5861
    /**
5862
     * Parses a single array element.
5863
     *
5864
     * An array element can have a simple value, a key/value pair, a value by
5865
     * reference or a key/value pair with a referenced value.
5866
     *
5867
     * @param bool $static
5868
     *
5869
     * @return ASTArrayElement
5870
     *
5871
     * @since 1.0.0
5872
     */
5873
    protected function parseArrayElement($static = false)
5874
    {
5875
        $this->consumeComments();
5876
5877
        $this->tokenStack->push();
5878
5879
        $element = $this->builder->buildAstArrayElement();
5880
        if ($this->parseOptionalByReference()) {
5881
            if ($static) {
5882
                $tokens = $this->tokenStack->pop();
5883
5884
                throw $this->getUnexpectedTokenException(end($tokens) ?: null);
5885
            }
5886
5887
            $element->setByReference();
5888
        }
5889
5890
        $this->consumeComments();
5891
        if ($this->isKeyword($this->tokenizer->peek())) {
5892
            throw $this->getUnexpectedNextTokenException();
5893
        }
5894
5895
        $element->addChild($this->parseExpression());
5896
5897
        $this->consumeComments();
5898
        if (Tokens::T_DOUBLE_ARROW === $this->tokenizer->peek()) {
5899
            $this->consumeToken(Tokens::T_DOUBLE_ARROW);
5900
            $this->consumeComments();
5901
5902
            if ($this->parseOptionalByReference()) {
5903
                $element->setByReference();
5904
            }
5905
            $element->addChild($this->parseExpression());
5906
        }
5907
5908
        return $this->setNodePositionsAndReturn($element);
5909
    }
5910
5911
    /**
5912
     * Parses a here- or nowdoc string instance.
5913
     *
5914
     * @return ASTHeredoc
5915
     *
5916
     * @since 0.9.12
5917
     */
5918
    protected function parseHeredoc()
5919
    {
5920
        $this->tokenStack->push();
5921
        $this->consumeToken(Tokens::T_START_HEREDOC);
5922
5923
        $heredoc = $this->builder->buildAstHeredoc();
5924
        $this->parseStringExpressions($heredoc, Tokens::T_END_HEREDOC);
5925
5926
        $token = $this->consumeToken(Tokens::T_END_HEREDOC);
5927
        $heredoc->setDelimiter($token->image);
5928
5929
        return $this->setNodePositionsAndReturn($heredoc);
5930
    }
5931
5932
    /**
5933
     * Parses a simple string sequence between two tokens of the same type.
5934
     *
5935
     * @param int $tokenType The start/stop token type.
5936
     *
5937
     * @return string
5938
     *
5939
     * @since 0.9.10
5940
     */
5941
    private function parseStringSequence($tokenType)
5942
    {
5943
        $type   = $tokenType;
5944
        $string = '';
5945
5946
        do {
5947
            $string .= $this->consumeToken($type)->image;
5948
            $type    = $this->tokenizer->peek();
5949
        } while ($type !== $tokenType && $type !== Tokenizer::T_EOF);
5950
5951
        return $string . $this->consumeToken($tokenType)->image;
5952
    }
5953
5954
    /**
5955
     * This method parses a php string with all possible embedded expressions.
5956
     *
5957
     * <code>
5958
     * $string = "Manuel $Pichler <{$email}>";
5959
     *
5960
     * // ASTSTring
5961
     * // |-- ASTLiteral             -  "Manuel ")
5962
     * // |-- ASTVariable            -  $Pichler
5963
     * // |-- ASTLiteral             -  " <"
5964
     * // |-- ASTCompoundExpression  -  {...}
5965
     * // |   |-- ASTVariable        -  $email
5966
     * // |-- ASTLiteral             -  ">"
5967
     * </code>
5968
     *
5969
     * @param int $delimiterType The start/stop token type.
5970
     *
5971
     * @throws UnexpectedTokenException
5972
     *
5973
     * @return ASTString
5974
     *
5975
     * @since 0.9.10
5976
     */
5977
    private function parseString($delimiterType)
5978
    {
5979
        $token = $this->consumeToken($delimiterType);
5980
5981
        $string = $this->builder->buildAstString();
5982
        $startLine = $token->startLine;
5983
        $startColumn = $token->startColumn;
5984
5985
        $this->parseStringExpressions($string, $delimiterType);
5986
5987
        $token = $this->consumeToken($delimiterType);
5988
        $endLine = $token->endLine;
5989
        $endColumn = $token->endColumn;
5990
5991
        $string->configureLinesAndColumns(
5992
            $startLine,
5993
            $endLine,
5994
            $startColumn,
5995
            $endColumn
5996
        );
5997
5998
        return $string;
5999
    }
6000
6001
    /**
6002
     * This method parses the contents of a string or here-/now-doc node. It
6003
     * will not consume the given stop token, so it is up to the calling method
6004
     * to consume the stop token. The return value of this method is the prepared
6005
     * input string node.
6006
     *
6007
     * @param AbstractASTNode $node
6008
     * @param int             $stopToken
6009
     *
6010
     * @return ASTNode
6011
     *
6012
     * @since 0.9.12
6013
     */
6014
    private function parseStringExpressions(ASTNode $node, $stopToken)
6015
    {
6016
        while (($tokenType = $this->tokenizer->peek()) != Tokenizer::T_EOF) {
6017
            switch ($tokenType) {
6018
                case $stopToken:
6019
                    break 2;
6020
                case Tokens::T_BACKSLASH:
6021
                    $node->addChild($this->parseEscapedAstLiteralString());
6022
                    break;
6023
                case Tokens::T_DOLLAR:
6024
                    $node->addChild($this->parseCompoundVariableOrLiteral());
6025
                    break;
6026
                case Tokens::T_VARIABLE:
6027
                    $node->addChild($this->parseVariable());
6028
                    break;
6029
                case Tokens::T_CURLY_BRACE_OPEN:
6030
                    $node->addChild($this->parseCompoundExpressionOrLiteral());
6031
                    break;
6032
                default:
6033
                    $node->addChild($this->parseLiteral());
6034
                    break;
6035
            }
6036
        }
6037
        return $node;
6038
    }
6039
6040
    /**
6041
     * This method parses an escaped sequence of literal tokens.
6042
     *
6043
     * @return ASTLiteral
6044
     *
6045
     * @since 0.9.10
6046
     */
6047
    private function parseEscapedAstLiteralString()
6048
    {
6049
        $this->tokenStack->push();
6050
6051
        $image  = $this->consumeToken(Tokens::T_BACKSLASH)->image;
6052
        $escape = true;
6053
6054
        $tokenType = $this->tokenizer->peek();
6055
        while ($tokenType !== Tokenizer::T_EOF) {
6056
            if ($tokenType === Tokens::T_BACKSLASH) {
6057
                $escape = !$escape;
0 ignored issues
show
introduced by
The condition $escape is always true.
Loading history...
6058
                $image  .= $this->consumeToken(Tokens::T_BACKSLASH)->image;
6059
6060
                $tokenType = $this->tokenizer->peek();
6061
                continue;
6062
            }
6063
6064
            if ($escape) {
6065
                $image .= $this->consumeToken($tokenType)->image;
6066
                break;
6067
            }
6068
        }
6069
        return $this->setNodePositionsAndReturn(
6070
            $this->builder->buildAstLiteral($image)
6071
        );
6072
    }
6073
6074
    /**
6075
     * This method parses a simple literal and configures the position
6076
     * properties.
6077
     *
6078
     * @return ASTLiteral
6079
     *
6080
     * @since 0.9.10
6081
     */
6082
    protected function parseLiteral()
6083
    {
6084
        $token = $this->consumeToken($this->tokenizer->peek());
6085
6086
        $node = $this->builder->buildAstLiteral($token->image);
6087
        $node->configureLinesAndColumns(
6088
            $token->startLine,
6089
            $token->endLine,
6090
            $token->startColumn,
6091
            $token->endColumn
6092
        );
6093
6094
        return $node;
6095
    }
6096
6097
    /**
6098
     * This method parse a formal parameter and all the stuff that may be allowed
6099
     * before it according to the PHP level (type hint, passing by reference, property promotion).
6100
     *
6101
     * @param ASTCallable $callable the callable object (closure, function or method)
6102
     *                              requiring the given parameters list.
6103
     *
6104
     * @return ASTFormalParameter|ASTNode
6105
     */
6106
    protected function parseFormalParameterOrPrefix(ASTCallable $callable)
6107
    {
6108
        return $this->parseFormalParameterOrTypeHintOrByReference();
6109
    }
6110
6111
    /**
6112
     * Extracts all dependencies from a callable signature.
6113
     *
6114
     * @param ASTCallable $callable the callable object (closure, function or method)
6115
     *                              requiring the given parameters list.
6116
     *
6117
     * @return ASTFormalParameters
6118
     *
6119
     * @since 0.9.5
6120
     */
6121
    protected function parseFormalParameters(ASTCallable $callable)
6122
    {
6123
        $this->consumeComments();
6124
6125
        $this->tokenStack->push();
6126
6127
        $formalParameters = $this->builder->buildAstFormalParameters();
6128
6129
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
6130
        $this->consumeComments();
6131
6132
        $tokenType = $this->tokenizer->peek();
6133
6134
        // Check for function without parameters
6135
        if ($tokenType === Tokens::T_PARENTHESIS_CLOSE) {
6136
            $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
6137
6138
            return $this->setNodePositionsAndReturn($formalParameters);
6139
        }
6140
6141
        while ($tokenType !== Tokenizer::T_EOF) {
6142
            // check for trailing comma in parameter list
6143
            $this->consumeComments();
6144
            $tokenType = $this->tokenizer->peek();
6145
6146
            if ($this->allowTrailingCommaInFormalParametersList() && $tokenType === Tokens::T_PARENTHESIS_CLOSE) {
6147
                break;
6148
            }
6149
6150
            $formalParameters->addChild(
6151
                $this->parseFormalParameterOrPrefix($callable)
6152
            );
6153
6154
            $this->consumeComments();
6155
            $tokenType = $this->tokenizer->peek();
6156
6157
            // Check for following parameter
6158
            if ($tokenType !== Tokens::T_COMMA) {
6159
                break;
6160
            }
6161
6162
            $this->consumeToken(Tokens::T_COMMA);
6163
        }
6164
6165
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
6166
6167
        return $this->setNodePositionsAndReturn($formalParameters);
6168
    }
6169
6170
    /**
6171
     * use of trailing comma in formal parameters list is allowed since PHP 8.0
6172
     * example function foo(string $bar, int $baz,)
6173
     *
6174
     * @return bool
6175
     */
6176
    protected function allowTrailingCommaInFormalParametersList()
6177
    {
6178
        return false;
6179
    }
6180
6181
    /**
6182
     * This method parses a formal parameter in all it's variations.
6183
     *
6184
     * <code>
6185
     * //                ------------
6186
     * function traverse(Iterator $it) {}
6187
     * //                ------------
6188
     *
6189
     * //                ---------
6190
     * function traverse(array $ar) {}
6191
     * //                ---------
6192
     *
6193
     * //                ---
6194
     * function traverse(&$x) {}
6195
     * //                ---
6196
     * </code>
6197
     *
6198
     * @return ASTFormalParameter
6199
     *
6200
     * @since 0.9.6
6201
     */
6202
    protected function parseFormalParameterOrTypeHintOrByReference()
6203
    {
6204
        $this->consumeComments();
6205
        $tokenType = $this->tokenizer->peek();
6206
6207
        $this->tokenStack->push();
6208
6209
        return $this->setNodePositionsAndReturn(
6210
            $this->parseFormalParameterFromType($tokenType)
6211
        );
6212
    }
6213
6214
    /**
6215
     * @param int $tokenType
6216
     *
6217
     * @return ASTFormalParameter
6218
     */
6219
    private function parseFormalParameterFromType($tokenType)
6220
    {
6221
        if ($this->isTypeHint($tokenType)) {
6222
            $typeHint = $this->parseOptionalTypeHint();
6223
            if ($typeHint) {
0 ignored issues
show
introduced by
$typeHint is of type PDepend\Source\AST\ASTClassOrInterfaceReference, thus it always evaluated to true.
Loading history...
6224
                return $this->parseFormalParameterAndTypeHint($typeHint);
6225
            }
6226
        }
6227
6228
        switch ($tokenType) {
6229
            case Tokens::T_ARRAY:
6230
                return $this->parseFormalParameterAndArrayTypeHint();
6231
            case Tokens::T_SELF:
6232
                return $this->parseFormalParameterAndSelfTypeHint();
6233
            case Tokens::T_PARENT:
6234
                return $this->parseFormalParameterAndParentTypeHint();
6235
            case Tokens::T_STATIC:
6236
                return $this->parseFormalParameterAndStaticTypeHint();
6237
            case Tokens::T_BITWISE_AND:
6238
                return $this->parseFormalParameterAndByReference();
6239
            default:
6240
                return $this->parseFormalParameter();
6241
        }
6242
    }
6243
6244
    /**
6245
     * This method parses a formal parameter that has an array type hint.
6246
     *
6247
     * <code>
6248
     * //                ---------
6249
     * function traverse(array $ar) {}
6250
     * //                ---------
6251
     * </code>
6252
     *
6253
     * @return ASTFormalParameter
6254
     *
6255
     * @since 0.9.6
6256
     */
6257
    private function parseFormalParameterAndArrayTypeHint()
6258
    {
6259
        $node = $this->parseArrayType();
6260
6261
        $parameter = $this->parseFormalParameterOrByReference();
6262
        $parameter->prependChild($node);
6263
6264
        return $parameter;
6265
    }
6266
6267
    /**
6268
     * @return ASTTypeArray
6269
     */
6270
    protected function parseArrayType()
6271
    {
6272
        $token = $this->consumeToken(Tokens::T_ARRAY);
6273
6274
        $type = $this->builder->buildAstTypeArray();
6275
        $type->configureLinesAndColumns(
6276
            $token->startLine,
6277
            $token->endLine,
6278
            $token->startColumn,
6279
            $token->endColumn
6280
        );
6281
6282
        return $type;
6283
    }
6284
6285
    /**
6286
     * Parses a type hint that is valid in the supported PHP version after the next token.
6287
     *
6288
     * @return ASTType|null
6289
     *
6290
     * @since 2.9.2
6291
     */
6292
    private function parseOptionalTypeHint()
6293
    {
6294
        $this->tokenStack->push();
6295
6296
        return $this->parseTypeHint();
6297
    }
6298
6299
    /**
6300
     * This method parses a formal parameter that has a regular class type hint.
6301
     *
6302
     * <code>
6303
     * //                ------------
6304
     * function traverse(Iterator $it) {}
6305
     * //                ------------
6306
     * </code>
6307
     *
6308
     * @return ASTFormalParameter
6309
     *
6310
     * @since 0.9.6
6311
     */
6312
    private function parseFormalParameterAndTypeHint(ASTNode $typeHint)
6313
    {
6314
        $classReference = $this->setNodePositionsAndReturn($typeHint);
6315
        $parameter = $this->parseFormalParameterOrByReference();
6316
        $parameter->prependChild($classReference);
6317
6318
        return $parameter;
6319
    }
6320
6321
    /**
6322
     * This method will parse a formal parameter that has the keyword parent as
6323
     * parameter type hint.
6324
     *
6325
     * <code>
6326
     * class Foo extends Bar
6327
     * {
6328
     *     //                   ---------
6329
     *     public function test(parent $o) {}
6330
     *     //                   ---------
6331
     * }
6332
     * </code>
6333
     *
6334
     * @throws InvalidStateException
6335
     *
6336
     * @return ASTFormalParameter
6337
     *
6338
     * @since 0.9.6
6339
     */
6340
    private function parseFormalParameterAndParentTypeHint()
6341
    {
6342
        $reference = $this->parseParentType();
6343
        $parameter = $this->parseFormalParameterOrByReference();
6344
        $parameter->prependChild($reference);
6345
6346
        return $parameter;
6347
    }
6348
6349
    /**
6350
     * @return ASTParentReference
6351
     */
6352
    protected function parseParentType()
6353
    {
6354
        return $this->parseParentReference($this->consumeToken(Tokens::T_PARENT));
6355
    }
6356
6357
    /**
6358
     * This method will parse a formal parameter that has the keyword self as
6359
     * parameter type hint.
6360
     *
6361
     * <code>
6362
     * class Foo
6363
     * {
6364
     *     //                   -------
6365
     *     public function test(self $o) {}
6366
     *     //                   -------
6367
     * }
6368
     * </code>
6369
     *
6370
     * @return ASTFormalParameter
6371
     *
6372
     * @since 0.9.6
6373
     */
6374
    private function parseFormalParameterAndSelfTypeHint()
6375
    {
6376
        $self = $this->parseSelfType();
6377
6378
        $parameter = $this->parseFormalParameterOrByReference();
6379
        $parameter->addChild($self);
6380
6381
        return $parameter;
6382
    }
6383
6384
    /**
6385
     * This method will parse a formal parameter that has the keyword static as
6386
     * parameter type hint.
6387
     *
6388
     * <code>
6389
     * class Foo
6390
     * {
6391
     *     //                   -------
6392
     *     public function test(static $o) {}
6393
     *     //                   -------
6394
     * }
6395
     * </code>
6396
     *
6397
     * @return ASTFormalParameter
6398
     *
6399
     * @since 2.9.2
6400
     */
6401
    private function parseFormalParameterAndStaticTypeHint()
6402
    {
6403
        $self = $this->parseStaticType();
6404
6405
        $parameter = $this->parseFormalParameterOrByReference();
6406
        $parameter->addChild($self);
6407
6408
        return $parameter;
6409
    }
6410
6411
    /**
6412
     * @return ASTSelfReference
6413
     */
6414
    protected function parseSelfType()
6415
    {
6416
        return $this->parseSelfReference($this->consumeToken(Tokens::T_SELF));
6417
    }
6418
6419
    /**
6420
     * @return ASTStaticReference
6421
     */
6422
    protected function parseStaticType()
6423
    {
6424
        return $this->parseStaticReference($this->consumeToken(Tokens::T_STATIC));
6425
    }
6426
6427
    /**
6428
     * This method will parse a formal parameter that can optionally be passed
6429
     * by reference.
6430
     *
6431
     * <code>
6432
     * //                 ---  -------
6433
     * function foo(array &$x, $y = 42) {}
6434
     * //                 ---  -------
6435
     * </code>
6436
     *
6437
     * @return ASTFormalParameter
6438
     *
6439
     * @since 0.9.6
6440
     */
6441
    protected function parseFormalParameterOrByReference()
6442
    {
6443
        $this->consumeComments();
6444
        if ($this->tokenizer->peek() === Tokens::T_BITWISE_AND) {
6445
            return $this->parseFormalParameterAndByReference();
6446
        }
6447
        return $this->parseFormalParameter();
6448
    }
6449
6450
    /**
6451
     * This method will parse a formal parameter that is passed by reference.
6452
     *
6453
     * <code>
6454
     * //                 ---  --------
6455
     * function foo(array &$x, &$y = 42) {}
6456
     * //                 ---  --------
6457
     * </code>
6458
     *
6459
     * @return ASTFormalParameter
6460
     *
6461
     * @since 0.9.6
6462
     */
6463
    private function parseFormalParameterAndByReference()
6464
    {
6465
        $this->consumeToken(Tokens::T_BITWISE_AND);
6466
        $this->consumeComments();
6467
6468
        $parameter = $this->parseFormalParameter();
6469
        $parameter->setPassedByReference();
6470
6471
        return $parameter;
6472
    }
6473
6474
    /**
6475
     * This method will parse a formal parameter. A formal parameter is at least
6476
     * a variable name, but can also contain a default parameter value.
6477
     *
6478
     * <code>
6479
     * //               --  -------
6480
     * function foo(Bar $x, $y = 42) {}
6481
     * //               --  -------
6482
     * </code>
6483
     *
6484
     * @return ASTFormalParameter
6485
     *
6486
     * @since 0.9.6
6487
     */
6488
    protected function parseFormalParameter()
6489
    {
6490
        $parameter = $this->builder->buildAstFormalParameter();
6491
        $parameter->addChild($this->parseVariableDeclarator());
6492
6493
        return $parameter;
6494
    }
6495
6496
    /**
6497
     * Tests if the given token type is a valid formal parameter in the supported
6498
     * PHP version.
6499
     *
6500
     * @param int $tokenType
6501
     *
6502
     * @return bool
6503
     *
6504
     * @since 1.0.0
6505
     */
6506
    protected function isTypeHint($tokenType)
6507
    {
6508
        switch ($tokenType) {
6509
            case Tokens::T_STRING:
6510
            case Tokens::T_BACKSLASH:
6511
            case Tokens::T_NAMESPACE:
6512
            case Tokens::T_ARRAY:
6513
                return true;
6514
        }
6515
6516
        return false;
6517
    }
6518
6519
    /**
6520
     * Parses a type hint that is valid in the supported PHP version.
6521
     *
6522
     * @return ASTType|null
6523
     *
6524
     * @since 1.0.0
6525
     */
6526
    protected function parseTypeHint()
6527
    {
6528
        switch ($this->tokenizer->peek()) {
6529
            case Tokens::T_STRING:
6530
            case Tokens::T_BACKSLASH:
6531
            case Tokens::T_NAMESPACE:
6532
            case Tokens::T_CLASS:
6533
                return $this->builder->buildAstClassOrInterfaceReference(
6534
                    $this->parseQualifiedName()
6535
                );
6536
        }
6537
6538
        return null;
6539
    }
6540
6541
    /**
6542
     * Extracts all dependencies from a callable body.
6543
     *
6544
     * @return ASTScope
6545
     *
6546
     * @since 0.9.12
6547
     */
6548
    private function parseScope()
6549
    {
6550
        $scope = $this->builder->buildAstScope();
6551
6552
        $this->tokenStack->push();
6553
6554
        $this->consumeComments();
6555
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
6556
6557
        while (($stmt = $this->parseOptionalStatement()) !== null) {
6558
            // TODO: Remove if-statement once, we have translated functions and
6559
            //       closures into ast-nodes
6560
            if ($stmt instanceof ASTNode) {
6561
                $scope->addChild($stmt);
6562
            }
6563
        }
6564
6565
        $this->consumeComments();
6566
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
6567
6568
        return $this->setNodePositionsAndReturn($scope);
6569
    }
6570
6571
    /**
6572
     * Parse a statement.
6573
     *
6574
     * @throws UnexpectedTokenException
6575
     * @throws TokenStreamEndException
6576
     *
6577
     * @return ASTNode
6578
     *
6579
     * @since 1.0.0
6580
     */
6581
    private function parseStatement()
6582
    {
6583
        if ($stmt = $this->parseOptionalStatement()) {
6584
            return $stmt;
6585
        }
6586
6587
        throw $this->getUnexpectedNextTokenException();
6588
    }
6589
6590
    /**
6591
     * Parses an optional statement or returns <b>null</b>.
6592
     *
6593
     * @return AbstractASTClassOrInterface|ASTCallable|ASTNode|null
6594
     *
6595
     * @since 0.9.8
6596
     */
6597
    private function parseOptionalStatement()
6598
    {
6599
        switch ($this->tokenizer->peek()) {
6600
            case Tokens::T_ECHO:
6601
                return $this->parseEchoStatement();
6602
            case Tokens::T_SWITCH:
6603
                return $this->parseSwitchStatement();
6604
            case Tokens::T_TRY:
6605
                return $this->parseTryStatement();
6606
            case Tokens::T_THROW:
6607
                return $this->parseThrowStatement();
6608
            case Tokens::T_IF:
6609
                return $this->parseIfStatement();
6610
            case Tokens::T_FOR:
6611
                return $this->parseForStatement();
6612
            case Tokens::T_FOREACH:
6613
                return $this->parseForeachStatement();
6614
            case Tokens::T_DO:
6615
                return $this->parseDoWhileStatement();
6616
            case Tokens::T_WHILE:
6617
                return $this->parseWhileStatement();
6618
            case Tokens::T_RETURN:
6619
                return $this->parseReturnStatement();
6620
            case Tokens::T_BREAK:
6621
                return $this->parseBreakStatement();
6622
            case Tokens::T_CONTINUE:
6623
                return $this->parseContinueStatement();
6624
            case Tokens::T_GOTO:
6625
                return $this->parseGotoStatement();
6626
            case Tokens::T_GLOBAL:
6627
                return $this->parseGlobalStatement();
6628
            case Tokens::T_UNSET:
6629
                return $this->parseUnsetStatement();
6630
            case Tokens::T_ENUM:
6631
            case Tokens::T_STRING:
6632
                $token = $this->tokenizer->currentToken();
6633
                $nextType = $this->tokenizer->peekNext();
6634
6635
                if ($token && $token->image === 'enum' && $nextType === Tokens::T_STRING) {
6636
                    $package = $this->getNamespaceOrPackage();
6637
                    $package->addType($enum = $this->parseEnumDeclaration());
6638
6639
                    $this->builder->restoreEnum($enum);
6640
                    $this->compilationUnit->addChild($enum);
6641
6642
                    return $enum;
6643
                }
6644
6645
                if ($nextType === Tokens::T_COLON) {
6646
                    return $this->parseLabelStatement();
6647
                }
6648
                break;
6649
            case Tokens::T_CONST:
6650
                return $this->parseConstantDefinition();
6651
            case Tokens::T_FN:
6652
                return $this->parseLambdaFunctionDeclaration();
6653
            case Tokens::T_FUNCTION:
6654
                return $this->parseFunctionOrClosureDeclaration();
6655
            case Tokens::T_COMMENT:
6656
                return $this->parseCommentWithOptionalInlineClassOrInterfaceReference();
6657
            case Tokens::T_DOC_COMMENT:
6658
                return $this->builder->buildAstComment(
6659
                    $this->consumeToken(Tokens::T_DOC_COMMENT)->image
6660
                );
6661
            case Tokens::T_CURLY_BRACE_OPEN:
6662
                return $this->parseRegularScope();
6663
            case Tokens::T_DECLARE:
6664
                return $this->parseDeclareStatement();
6665
            case Tokens::T_ELSE:
6666
            case Tokens::T_ENDIF:
6667
            case Tokens::T_ELSEIF:
6668
            case Tokens::T_ENDFOR:
6669
            case Tokens::T_ENDWHILE:
6670
            case Tokens::T_ENDSWITCH:
6671
            case Tokens::T_ENDDECLARE:
6672
            case Tokens::T_ENDFOREACH:
6673
            case Tokens::T_CURLY_BRACE_CLOSE:
6674
                return null;
6675
            case Tokens::T_CLOSE_TAG:
6676
                if ($this->parseNonePhpCode() === Tokenizer::T_EOF) {
6677
                    return null;
6678
                }
6679
6680
                return $this->parseOptionalStatement();
6681
            case Tokens::T_TRAIT:
6682
                $package = $this->getNamespaceOrPackage();
6683
                $package->addType($trait = $this->parseTraitDeclaration());
6684
6685
                $this->builder->restoreTrait($trait);
6686
                $this->compilationUnit->addChild($trait);
6687
6688
                return $trait;
6689
            case Tokens::T_INTERFACE:
6690
                $package = $this->getNamespaceOrPackage();
6691
                $package->addType($interface = $this->parseInterfaceDeclaration());
6692
6693
                $this->builder->restoreInterface($interface);
6694
                $this->compilationUnit->addChild($interface);
6695
6696
                return $interface;
6697
            case Tokens::T_CLASS:
6698
            case Tokens::T_FINAL:
6699
            case Tokens::T_ABSTRACT:
6700
            case Tokens::T_READONLY:
6701
                $package = $this->getNamespaceOrPackage();
6702
                $package->addType($class = $this->parseClassDeclaration());
6703
6704
                $this->builder->restoreClass($class);
6705
                $this->compilationUnit->addChild($class);
6706
6707
                return $class;
6708
            case Tokens::T_YIELD:
6709
                return $this->parseYield(true);
6710
        }
6711
6712
        $this->tokenStack->push();
6713
        $stmt = $this->builder->buildAstStatement();
6714
6715
        if (($expr = $this->parseOptionalExpression()) != null) {
6716
            $stmt->addChild($expr);
6717
        }
6718
6719
        if ($this->echoing && $this->tokenizer->peek() === Tokens::T_COMMA) {
6720
            $this->consumeToken(Tokens::T_COMMA);
6721
            $this->parseOptionalStatement();
6722
        } else {
6723
            $this->parseStatementTermination();
6724
        }
6725
6726
        return $this->setNodePositionsAndReturn($stmt);
6727
    }
6728
6729
    /**
6730
     * Parses a sequence of none php code tokens and returns the token type of
6731
     * the next token.
6732
     *
6733
     * @return int
6734
     *
6735
     * @since 0.9.12
6736
     */
6737
    private function parseNonePhpCode()
6738
    {
6739
        $this->consumeToken(Tokens::T_CLOSE_TAG);
6740
6741
        $this->tokenStack->push();
6742
        while (($tokenType = $this->tokenizer->peek()) !== Tokenizer::T_EOF) {
6743
            switch ($tokenType) {
6744
                case Tokens::T_OPEN_TAG:
6745
                case Tokens::T_OPEN_TAG_WITH_ECHO:
6746
                    $this->consumeToken($tokenType);
6747
                    $tokenType = $this->tokenizer->peek();
6748
                    break 2;
6749
                default:
6750
                    $this->consumeToken($tokenType);
6751
                    break;
6752
            }
6753
        }
6754
        $this->tokenStack->pop();
6755
6756
        return $tokenType;
6757
    }
6758
6759
    /**
6760
     * Parses a comment and optionally an embedded class or interface type
6761
     * annotation.
6762
     *
6763
     * @return ASTComment
6764
     *
6765
     * @since 0.9.8
6766
     */
6767
    private function parseCommentWithOptionalInlineClassOrInterfaceReference()
6768
    {
6769
        $token = $this->consumeToken(Tokens::T_COMMENT);
6770
6771
        $comment = $this->builder->buildAstComment($token->image);
6772
        if (preg_match(self::REGEXP_INLINE_TYPE, $token->image, $match)) {
6773
            $image = $this->useSymbolTable->lookup($match[1]) ?: $match[1];
6774
6775
            $comment->addChild(
6776
                $this->builder->buildAstClassOrInterfaceReference($image)
6777
            );
6778
        }
6779
6780
        $comment->configureLinesAndColumns(
6781
            $token->startLine,
6782
            $token->endLine,
6783
            $token->startColumn,
6784
            $token->endColumn
6785
        );
6786
        return $comment;
6787
    }
6788
6789
    /**
6790
     * Parses an optional set of bound closure variables.
6791
     *
6792
     * @template T of ASTClosure
6793
     *
6794
     * @param T $closure The context closure instance.
6795
     *
6796
     * @return T
6797
     *
6798
     * @since 1.0.0
6799
     */
6800
    protected function parseOptionalBoundVariables(
6801
        ASTClosure $closure
6802
    ) {
6803
        $this->consumeComments();
6804
6805
        if (Tokens::T_USE === $this->tokenizer->peek()) {
6806
            return $this->parseBoundVariables($closure);
6807
        }
6808
6809
        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...
6810
    }
6811
6812
    /**
6813
     * Parses a list of bound closure variables.
6814
     *
6815
     * @template T of ASTClosure
6816
     *
6817
     * @param T $closure The parent closure instance.
6818
     *
6819
     * @return T
6820
     *
6821
     * @since 0.9.5
6822
     */
6823
    private function parseBoundVariables(ASTClosure $closure)
6824
    {
6825
        $this->consumeToken(Tokens::T_USE);
6826
6827
        $this->consumeComments();
6828
        $this->consumeToken(Tokens::T_PARENTHESIS_OPEN);
6829
6830
        while ($this->tokenizer->peek() !== Tokenizer::T_EOF) {
6831
            $this->consumeComments();
6832
6833
            if ($this->allowTrailingCommaInClosureUseList() &&
6834
                $this->tokenizer->peek() === Tokens::T_PARENTHESIS_CLOSE) {
6835
                break;
6836
            }
6837
6838
            if ($this->tokenizer->peek() === Tokens::T_BITWISE_AND) {
6839
                $this->consumeToken(Tokens::T_BITWISE_AND);
6840
                $this->consumeComments();
6841
            }
6842
6843
            $this->consumeToken(Tokens::T_VARIABLE);
6844
            $this->consumeComments();
6845
6846
            if ($this->tokenizer->peek() === Tokens::T_COMMA) {
6847
                $this->consumeToken(Tokens::T_COMMA);
6848
6849
                continue;
6850
            }
6851
6852
            break;
6853
        }
6854
6855
        $this->consumeComments();
6856
        $this->consumeToken(Tokens::T_PARENTHESIS_CLOSE);
6857
6858
        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...
6859
    }
6860
6861
    /**
6862
     * Trailing commas is allowed in closure use list from PHP 8.0
6863
     *
6864
     * @return bool
6865
     */
6866
    protected function allowTrailingCommaInClosureUseList()
6867
    {
6868
        return false;
6869
    }
6870
6871
    /**
6872
     * Parses a php class/method name chain.
6873
     *
6874
     * <code>
6875
     * PDepend\Source\Parser::parse();
6876
     * </code>
6877
     *
6878
     * @throws NoActiveScopeException
6879
     *
6880
     * @return string
6881
     *
6882
     * @link   http://php.net/manual/en/language.namespaces.importing.php
6883
     */
6884
    protected function parseQualifiedName()
6885
    {
6886
        $fragments = $this->parseQualifiedNameRaw();
6887
6888
        // Check for fully qualified name
6889
        if ($fragments[0] === '\\') {
6890
            return join('', $fragments);
6891
        }
6892
6893
        if ($this->isScalarOrCallableTypeHint($fragments[0])) {
6894
            return $fragments[0];
6895
        }
6896
6897
        // Search for a use alias
6898
        $mapsTo = $this->useSymbolTable->lookup($fragments[0]);
6899
6900
        if ($mapsTo !== null) {
6901
            // Remove alias and add real namespace
6902
            array_shift($fragments);
6903
            array_unshift($fragments, $mapsTo);
6904
        } elseif ($this->namespaceName !== null
6905
            && $this->namespacePrefixReplaced === false
6906
        ) {
6907
            // Prepend current namespace
6908
            array_unshift($fragments, $this->namespaceName, '\\');
6909
        }
6910
6911
        return join('', $fragments);
6912
    }
6913
6914
    /**
6915
     * This method parses a qualified PHP 5.3 class, interface and namespace
6916
     * identifier and returns the collected tokens as a string array.
6917
     *
6918
     * @return array<string>
6919
     *
6920
     * @since 0.9.5
6921
     */
6922
    protected function parseQualifiedNameRaw()
6923
    {
6924
        // Reset namespace prefix flag
6925
        $this->namespacePrefixReplaced = false;
6926
6927
        // Consume comments and fetch first token type
6928
        $this->consumeComments();
6929
        $tokenType = $this->tokenizer->peek();
6930
6931
        $qualifiedName = array();
6932
6933
        if ($tokenType === Tokens::T_NAMESPACE) {
6934
            // Consume namespace keyword
6935
            $this->consumeToken(Tokens::T_NAMESPACE);
6936
            $this->consumeComments();
6937
6938
            // Add current namespace as first token
6939
            $qualifiedName = array((string) $this->namespaceName);
6940
6941
            // Set prefixed flag to true
6942
            $this->namespacePrefixReplaced = true;
6943
        } elseif ($this->isClassName($tokenType)) {
6944
            $qualifiedName[] = $this->parseClassName();
6945
6946
            $this->consumeComments();
6947
            $tokenType = $this->tokenizer->peek();
6948
6949
            // Stop here for simple identifier
6950
            if ($tokenType !== Tokens::T_BACKSLASH) {
6951
                return $qualifiedName;
6952
            }
6953
        }
6954
6955
        do {
6956
            // Next token must be a namespace separator
6957
            $this->consumeToken(Tokens::T_BACKSLASH);
6958
            $this->consumeComments();
6959
6960
            // Append to qualified name
6961
            $qualifiedName[] = '\\';
6962
6963
            if ($nextElement = $this->parseQualifiedNameElement($qualifiedName)) {
6964
                $qualifiedName[] = $nextElement;
6965
            }
6966
6967
            $this->consumeComments();
6968
6969
            // Get next token type
6970
            $tokenType = $this->tokenizer->peek();
6971
        } while ($tokenType === Tokens::T_BACKSLASH);
6972
6973
        return $qualifiedName;
6974
    }
6975
6976
    /**
6977
     * Determines if the given image is a PHP 7 type hint.
6978
     *
6979
     * @param string $image
6980
     *
6981
     * @return bool
6982
     */
6983
    protected function isScalarOrCallableTypeHint($image)
6984
    {
6985
        // Scalar & callable type hints were not present in PHP 5
6986
        return false;
6987
    }
6988
6989
    /**
6990
     * @param array<string> $previousElements
6991
     *
6992
     * @return string
6993
     */
6994
    protected function parseQualifiedNameElement(array $previousElements)
6995
    {
6996
        return $this->parseClassName();
6997
    }
6998
6999
    /**
7000
     * This method parses a PHP 5.3 namespace declaration.
7001
     *
7002
     * @throws NoActiveScopeException
7003
     *
7004
     * @return void
7005
     *
7006
     * @since 0.9.5
7007
     */
7008
    private function parseNamespaceDeclaration()
7009
    {
7010
        // Consume namespace keyword and strip optional comments
7011
        $this->consumeToken(Tokens::T_NAMESPACE);
7012
        $this->consumeComments();
7013
7014
        $tokenType = $this->tokenizer->peek();
7015
7016
        // Search for a namespace identifier
7017
        if ($this->isClassName($tokenType)) {
7018
            // Reset namespace property
7019
            $this->namespaceName = null;
7020
7021
            $qualifiedName = $this->parseQualifiedName();
7022
7023
            $this->consumeComments();
7024
            if ($this->tokenizer->peek() === Tokens::T_CURLY_BRACE_OPEN) {
7025
                $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
7026
            } else {
7027
                $this->consumeToken(Tokens::T_SEMICOLON);
7028
            }
7029
7030
            // Create a package for this namespace
7031
            $this->namespaceName = $qualifiedName;
7032
7033
            $this->useSymbolTable->resetScope();
7034
        } elseif ($tokenType === Tokens::T_BACKSLASH) {
7035
            // Same namespace reference, something like:
7036
            //   new namespace\Foo();
7037
            // or:
7038
            //   $x = namespace\foo::bar();
7039
7040
            // Now parse a qualified name
7041
            $this->parseQualifiedNameRaw();
7042
        } else {
7043
            // Consume opening curly brace
7044
            $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
7045
7046
            // Create a package for this namespace
7047
            $this->namespaceName = '';
7048
7049
            $this->useSymbolTable->resetScope();
7050
        }
7051
7052
        $this->reset();
7053
    }
7054
7055
    /**
7056
     * This method parses a list of PHP 5.3 use declarations and adds a mapping
7057
     * between short name and full qualified name to the use symbol table.
7058
     *
7059
     * <code>
7060
     * use \foo\bar as fb,
7061
     *     \foobar\Bar;
7062
     * </code>
7063
     *
7064
     * @return void
7065
     *
7066
     * @since 0.9.5
7067
     */
7068
    protected function parseUseDeclarations()
7069
    {
7070
        // Consume use keyword
7071
        $this->consumeToken(Tokens::T_USE);
7072
        $this->consumeComments();
7073
7074
        // Parse all use declarations
7075
        $this->parseUseDeclaration();
7076
        $this->consumeComments();
7077
7078
        // Consume closing semicolon
7079
        $this->consumeToken(Tokens::T_SEMICOLON);
7080
7081
        // Reset any previous state
7082
        $this->reset();
7083
    }
7084
7085
    /**
7086
     * This method parses a single use declaration and adds a mapping between
7087
     * short name and full qualified name to the use symbol table.
7088
     *
7089
     * @throws NoActiveScopeException
7090
     *
7091
     * @return void
7092
     *
7093
     * @since 0.9.5
7094
     */
7095
    protected function parseUseDeclaration()
7096
    {
7097
        $fragments = $this->parseQualifiedNameRaw();
7098
        $this->consumeComments();
7099
7100
        // Add leading backslash, because aliases must be full qualified
7101
        // http://php.net/manual/en/language.namespaces.importing.php
7102
        if ($fragments[0] !== '\\') {
7103
            array_unshift($fragments, '\\');
7104
        }
7105
7106
        $this->parseUseDeclarationForVersion($fragments);
7107
7108
        // Check for a following use declaration
7109
        if ($this->tokenizer->peek() === Tokens::T_COMMA) {
7110
            // Consume comma token and comments
7111
            $this->consumeToken(Tokens::T_COMMA);
7112
            $this->consumeComments();
7113
7114
            $this->parseUseDeclaration();
7115
        }
7116
    }
7117
7118
    /**
7119
     * @param array<string> $fragments
7120
     *
7121
     * @return void
7122
     */
7123
    protected function parseUseDeclarationForVersion(array $fragments)
7124
    {
7125
        $image = $this->parseNamespaceImage($fragments);
7126
7127
        // Add mapping between image and qualified name to symbol table
7128
        $this->useSymbolTable->add($image, join('', $fragments));
7129
    }
7130
7131
    /**
7132
     * @param array<string> $fragments
7133
     *
7134
     * @return string
7135
     */
7136
    protected function parseNamespaceImage(array $fragments)
7137
    {
7138
        if ($this->tokenizer->peek() === Tokens::T_AS) {
7139
            $this->consumeToken(Tokens::T_AS);
7140
            $this->consumeComments();
7141
7142
            $image = $this->consumeToken(Tokens::T_STRING)->image;
7143
            $this->consumeComments();
7144
        } else {
7145
            $image = end($fragments);
7146
        }
7147
7148
        return $image;
7149
    }
7150
7151
    /**
7152
     * Parses a single constant definition with one or more constant declarators.
7153
     *
7154
     * <code>
7155
     * class Foo
7156
     * {
7157
     * //  ------------------------
7158
     *     const FOO = 42, BAR = 23;
7159
     * //  ------------------------
7160
     * }
7161
     * </code>
7162
     *
7163
     * @return ASTConstantDefinition
7164
     *
7165
     * @since 0.9.6
7166
     */
7167
    protected function parseConstantDefinition()
7168
    {
7169
        $this->tokenStack->push();
7170
7171
        $token = $this->consumeToken(Tokens::T_CONST);
7172
7173
        $definition = $this->builder->buildAstConstantDefinition($token->image);
7174
        $definition->setComment($this->docComment);
7175
7176
        do {
7177
            $definition->addChild($this->parseConstantDeclarator());
7178
7179
            $this->consumeComments();
7180
            $tokenType = $this->tokenizer->peek();
7181
7182
            if ($tokenType === Tokens::T_SEMICOLON) {
7183
                break;
7184
            }
7185
7186
            $this->consumeToken(Tokens::T_COMMA);
7187
        } while ($tokenType !== Tokenizer::T_EOF);
7188
7189
        $definition = $this->setNodePositionsAndReturn($definition);
7190
7191
        $this->consumeToken(Tokens::T_SEMICOLON);
7192
7193
        return $definition;
7194
    }
7195
7196
    /**
7197
     * Constant cannot be typed before PHP 8.3.
7198
     *
7199
     * @return ASTConstantDeclarator
7200
     *
7201
     * @since  1.16.0
7202
     */
7203
    protected function parseTypedConstantDeclarator()
7204
    {
7205
        throw $this->getUnexpectedNextTokenException();
7206
    }
7207
7208
    /**
7209
     * Parses a single constant declarator.
7210
     *
7211
     * <code>
7212
     * class Foo
7213
     * {
7214
     *     //    --------
7215
     *     const BAR = 42;
7216
     *     //    --------
7217
     * }
7218
     * </code>
7219
     *
7220
     * Or in a comma separated constant defintion:
7221
     *
7222
     * <code>
7223
     * class Foo
7224
     * {
7225
     *     //    --------
7226
     *     const BAR = 42,
7227
     *     //    --------
7228
     *
7229
     *     //    --------------
7230
     *     const BAZ = 'Foobar',
7231
     *     //    --------------
7232
     *
7233
     *     //    ----------
7234
     *     const FOO = 3.14;
7235
     *     //    ----------
7236
     * }
7237
     * </code>
7238
     *
7239
     * @return ASTConstantDeclarator
7240
     *
7241
     * @since 0.9.6
7242
     */
7243
    protected function parseConstantDeclarator()
7244
    {
7245
        // Remove leading comments and create a new token stack
7246
        $this->consumeComments();
7247
        $this->tokenStack->push();
7248
7249
        $nextToken = $this->tokenizer->peekNext();
7250
7251
        if ($this->isConstantName($nextToken)) {
7252
            return $this->parseTypedConstantDeclarator();
7253
        }
7254
7255
        $tokenType = $this->tokenizer->peek();
7256
7257
        if (!$this->isConstantName($tokenType)) {
7258
            throw $this->getUnexpectedNextTokenException();
7259
        }
7260
7261
        $token = $this->consumeToken($tokenType);
7262
7263
        $this->consumeComments();
7264
        $this->consumeToken(Tokens::T_EQUAL);
7265
7266
        $declarator = $this->builder->buildAstConstantDeclarator($token->image);
7267
        $declarator->setValue($this->parseConstantDeclaratorValue());
7268
7269
        return $this->setNodePositionsAndReturn($declarator);
7270
    }
7271
7272
    /**
7273
     * Parses the value of a php constant. By default this can be only static
7274
     * values that were allowed in the oldest supported PHP version.
7275
     *
7276
     * @return ASTValue
7277
     *
7278
     * @since 2.2.x
7279
     */
7280
    protected function parseConstantDeclaratorValue()
7281
    {
7282
        return $this->parseStaticValue();
7283
    }
7284
7285
    /**
7286
     * This method parses a static variable declaration list, a member primary
7287
     * prefix invoked in the static context of a class or it parses a static
7288
     * closure declaration.
7289
     *
7290
     * Static variable:
7291
     * <code>
7292
     * function foo() {
7293
     * //  ------------------------------
7294
     *     static $foo, $bar, $baz = null;
7295
     * //  ------------------------------
7296
     * }
7297
     * </code>
7298
     *
7299
     * Static method invocation:
7300
     * <code>
7301
     * class Foo {
7302
     *     public function baz() {
7303
     * //      ----------------
7304
     *         static::foobar();
7305
     * //      ----------------
7306
     *     }
7307
     *     public function foobar() {}
7308
     * }
7309
     *
7310
     * class Bar extends Foo {
7311
     *     public function foobar() {}
7312
     * }
7313
     * </code>
7314
     *
7315
     * Static closure declaration:
7316
     * <code>
7317
     * $closure = static function($x, $y) {
7318
     *     return ($x * $y);
7319
     * };
7320
     * </code>
7321
     *
7322
     * @throws ParserException
7323
     * @throws UnexpectedTokenException
7324
     *
7325
     * @return ASTNode
7326
     *
7327
     * @since 0.9.6
7328
     */
7329
    private function parseStaticVariableDeclarationOrMemberPrimaryPrefix()
7330
    {
7331
        $this->tokenStack->push();
7332
7333
        // Consume static token and strip optional comments
7334
        $token = $this->consumeToken(Tokens::T_STATIC);
7335
        $this->consumeComments();
7336
7337
        // Fetch next token type
7338
        $tokenType = $this->tokenizer->peek();
7339
7340
        if ($tokenType === Tokens::T_PARENTHESIS_OPEN
7341
            || $tokenType === Tokens::T_DOUBLE_COLON
7342
        ) {
7343
            return $this->setNodePositionsAndReturn(
7344
                $this->parseStaticMemberPrimaryPrefix(
7345
                    $this->parseStaticReference($token)
7346
                )
7347
            );
7348
        } elseif ($tokenType === Tokens::T_FUNCTION) {
7349
            $closure = $this->parseClosureDeclaration();
7350
            $closure->setStatic(true);
7351
7352
            return $this->setNodePositionsAndReturn($closure);
7353
        } elseif ($tokenType === Tokens::T_FN) {
7354
            $closure = $this->parseLambdaFunctionDeclaration();
7355
            $closure->setStatic(true);
7356
7357
            return $this->setNodePositionsAndReturn($closure);
7358
        }
7359
7360
        return $this->setNodePositionsAndReturn(
7361
            $this->parseStaticVariableDeclaration($token)
7362
        );
7363
    }
7364
7365
    /**
7366
     * This method will parse a static variable declaration.
7367
     *
7368
     * <code>
7369
     * function foo()
7370
     * {
7371
     *     // First declaration
7372
     *     static $foo;
7373
     *     // Second declaration
7374
     *     static $bar = array();
7375
     *     // Third declaration
7376
     *     static $baz    = array(),
7377
     *            $foobar = null,
7378
     *            $barbaz;
7379
     * }
7380
     * </code>
7381
     *
7382
     * @param Token $token Token with the "static" keyword.
7383
     *
7384
     * @return ASTStaticVariableDeclaration
7385
     *
7386
     * @since 0.9.6
7387
     */
7388
    private function parseStaticVariableDeclaration(Token $token)
7389
    {
7390
        $staticDeclaration = $this->builder->buildAstStaticVariableDeclaration(
7391
            $token->image
7392
        );
7393
7394
        // Strip optional comments
7395
        $this->consumeComments();
7396
7397
        // Fetch next token type
7398
        $tokenType = $this->tokenizer->peek();
7399
7400
        while ($tokenType !== Tokenizer::T_EOF) {
7401
            $staticDeclaration->addChild($this->parseVariableDeclarator());
7402
7403
            $this->consumeComments();
7404
7405
            // Semicolon terminates static declaration
7406
            $tokenType = $this->tokenizer->peek();
7407
7408
            if ($tokenType === Tokens::T_SEMICOLON) {
7409
                break;
7410
            }
7411
7412
            // We are here, so there must be a next declarator
7413
            $this->consumeToken(Tokens::T_COMMA);
7414
        }
7415
7416
        return $staticDeclaration;
7417
    }
7418
7419
    /**
7420
     * This method will parse a variable declarator.
7421
     *
7422
     * <code>
7423
     * // Parameter declarator
7424
     * function foo($x = 23) {
7425
     * }
7426
     * // Property declarator
7427
     * class Foo{
7428
     *     protected $bar = 42;
7429
     * }
7430
     * // Static declarator
7431
     * function baz() {
7432
     *     static $foo;
7433
     * }
7434
     * </code>
7435
     *
7436
     * @return ASTVariableDeclarator
7437
     *
7438
     * @since 0.9.6
7439
     */
7440
    protected function parseVariableDeclarator()
7441
    {
7442
        $this->tokenStack->push();
7443
7444
        $name = $this->consumeToken(Tokens::T_VARIABLE)->image;
7445
        $this->consumeComments();
7446
7447
        $declarator = $this->builder->buildAstVariableDeclarator($name);
7448
7449
        if ($this->tokenizer->peek() === Tokens::T_EQUAL) {
7450
            $this->consumeToken(Tokens::T_EQUAL);
7451
            $declarator->setValue($this->parseVariableDefaultValue());
7452
        }
7453
7454
        return $this->setNodePositionsAndReturn($declarator);
7455
    }
7456
7457
    /**
7458
     * Parse a default value after a parameter, static variable or constant declaration.
7459
     *
7460
     * @return ASTValue
7461
     *
7462
     * @since 2.11.0
7463
     */
7464
    protected function parseVariableDefaultValue()
7465
    {
7466
        return $this->parseStaticValueOrStaticArray();
7467
    }
7468
7469
    /**
7470
     * This method will parse a static value or a static array as it is
7471
     * used as default value for a parameter or property declaration.
7472
     *
7473
     * @return ASTValue
7474
     *
7475
     * @since 0.9.6
7476
     */
7477
    protected function parseStaticValueOrStaticArray()
7478
    {
7479
        $this->consumeComments();
7480
7481
        if ($this->isArrayStartDelimiter()) {
7482
            // TODO: Use default value as value!
7483
            $defaultValue = $this->doParseArray(true);
0 ignored issues
show
Unused Code introduced by
The assignment to $defaultValue is dead and can be removed.
Loading history...
7484
7485
            $value = new ASTValue();
7486
            $value->setValue(array());
7487
7488
            return $value;
7489
        }
7490
7491
        return $this->parseStaticValue();
7492
    }
7493
7494
    /**
7495
     * This method will parse a static default value as it is used for a
7496
     * parameter, property or constant declaration.
7497
     *
7498
     * @return ASTValue
7499
     *
7500
     * @since 0.9.5
7501
     */
7502
    protected function parseStaticValue()
7503
    {
7504
        $defaultValue = new ASTValue();
7505
7506
        $this->consumeComments();
7507
7508
        // By default all parameters positive signed
7509
        $signed = 1;
7510
7511
        $tokenType = $this->tokenizer->peek();
7512
7513
        while ($tokenType !== Tokenizer::T_EOF) {
7514
            switch ($tokenType) {
7515
                case Tokens::T_COMMA:
7516
                case Tokens::T_SEMICOLON:
7517
                case Tokens::T_PARENTHESIS_CLOSE:
7518
                    if ($defaultValue->isValueAvailable() === true) {
7519
                        return $defaultValue;
7520
                    }
7521
7522
                    throw new MissingValueException($this->tokenizer);
7523
                case Tokens::T_NULL:
7524
                    $this->consumeToken(Tokens::T_NULL);
7525
                    $defaultValue->setValue(null);
7526
                    break;
7527
                case Tokens::T_TRUE:
7528
                    $this->consumeToken(Tokens::T_TRUE);
7529
                    $defaultValue->setValue(true);
7530
                    break;
7531
                case Tokens::T_FALSE:
7532
                    $this->consumeToken(Tokens::T_FALSE);
7533
                    $defaultValue->setValue(false);
7534
                    break;
7535
                case Tokens::T_LNUMBER:
7536
                    $defaultValue->setValue($signed * $this->parseIntegerNumberImage(
7537
                        $this->parseNumber(Tokens::T_LNUMBER)
7538
                    ));
7539
                    break;
7540
                case Tokens::T_DNUMBER:
7541
                    $defaultValue->setValue($signed * (double) $this->getNumberFromImage(
7542
                        $this->parseNumber(Tokens::T_DNUMBER)
7543
                    ));
7544
                    break;
7545
                case Tokens::T_CONSTANT_ENCAPSED_STRING:
7546
                    $token = $this->consumeToken(Tokens::T_CONSTANT_ENCAPSED_STRING);
7547
                    $defaultValue->setValue(substr($token->image, 1, -1));
7548
                    break;
7549
                case Tokens::T_DOUBLE_COLON:
7550
                    $this->consumeToken(Tokens::T_DOUBLE_COLON);
7551
                    break;
7552
                case Tokens::T_CLASS_FQN:
7553
                    $this->consumeToken(Tokens::T_CLASS_FQN);
7554
                    break;
7555
                case Tokens::T_PLUS:
7556
                    $this->consumeToken(Tokens::T_PLUS);
7557
                    break;
7558
                case Tokens::T_ELLIPSIS:
7559
                    $this->checkEllipsisInExpressionSupport();
7560
                    $this->consumeToken(Tokens::T_ELLIPSIS);
7561
                    break;
7562
                case Tokens::T_MINUS:
7563
                    $this->consumeToken(Tokens::T_MINUS);
7564
                    $signed *= -1;
7565
                    break;
7566
                case Tokens::T_DOUBLE_QUOTE:
7567
                    $defaultValue->setValue($this->parseStringSequence($tokenType));
7568
                    break;
7569
                case Tokens::T_STATIC:
7570
                case Tokens::T_SELF:
7571
                case Tokens::T_PARENT:
7572
                    $node = $this->parseStandAloneExpressionTypeReference($tokenType);
7573
7574
                    if ($this->tokenizer->peek() === Tokens::T_DOUBLE_COLON) {
7575
                        $node->addChild($this->parseStaticMemberPrimaryPrefix($node));
7576
                    }
7577
7578
                    $defaultValue->setValue($node);
7579
                    break;
7580
                case Tokens::T_STRING:
7581
                case Tokens::T_BACKSLASH:
7582
                    $node = $this->builder->buildAstClassOrInterfaceReference(
7583
                        $this->parseQualifiedName()
7584
                    );
7585
7586
                    if ($this->tokenizer->peek() === Tokens::T_DOUBLE_COLON) {
7587
                        $node->addChild($this->parseStaticMemberPrimaryPrefix($node));
7588
                    }
7589
7590
                    $defaultValue->setValue($node);
7591
                    break;
7592
                case Tokens::T_DIR:
7593
                case Tokens::T_FILE:
7594
                case Tokens::T_LINE:
7595
                case Tokens::T_NS_C:
7596
                case Tokens::T_FUNC_C:
7597
                case Tokens::T_CLASS_C:
7598
                case Tokens::T_METHOD_C:
7599
                case Tokens::T_SQUARED_BRACKET_OPEN:
7600
                case Tokens::T_SQUARED_BRACKET_CLOSE:
7601
                    // There is a default value but we don't handle it at the moment.
7602
                    $defaultValue->setValue(null);
7603
                    $this->consumeToken($tokenType);
7604
                    break;
7605
                case Tokens::T_START_HEREDOC:
7606
                    $defaultValue->setValue(
7607
                        $this->parseHeredoc()->getChild(0)->getImage()
7608
                    );
7609
                    break;
7610
                default:
7611
                    return $this->parseStaticValueVersionSpecific($defaultValue);
7612
            }
7613
7614
            $this->consumeComments();
7615
7616
            $tokenType = $this->tokenizer->peek();
7617
        }
7618
7619
        // We should never reach this, so throw an exception
7620
        throw new TokenStreamEndException($this->tokenizer);
7621
    }
7622
7623
    /**
7624
     * Parses additional static values that are valid in the supported php version.
7625
     *
7626
     * @throws UnexpectedTokenException
7627
     *
7628
     * @return ASTValue
7629
     */
7630
    protected function parseStaticValueVersionSpecific(ASTValue $value)
7631
    {
7632
        throw $this->getUnexpectedNextTokenException();
7633
    }
7634
7635
    /**
7636
     * Parses fn operator of lambda function for syntax fn() => available since PHP 7.4.
7637
     *
7638
     * @throws UnexpectedTokenException
7639
     *
7640
     * @return ASTClosure
7641
     */
7642
    protected function parseLambdaFunctionDeclaration()
7643
    {
7644
        throw $this->getUnexpectedNextTokenException();
7645
    }
7646
7647
    /**
7648
     * Checks if the given expression is a read/write variable as defined in
7649
     * the PHP zend_language_parser.y definition.
7650
     *
7651
     * @param ASTNode $expr The context node instance.
7652
     *
7653
     * @return bool
7654
     *
7655
     * @since 0.10.0
7656
     */
7657
    private function isReadWriteVariable($expr)
7658
    {
7659
        return $expr instanceof ASTVariable
7660
            || $expr instanceof ASTFunctionPostfix
7661
            || $expr instanceof ASTVariableVariable
7662
            || $expr instanceof ASTCompoundVariable
7663
            || $expr instanceof ASTMemberPrimaryPrefix;
7664
    }
7665
7666
    /**
7667
     * This method creates a qualified class or interface name based on the
7668
     * current parser state. By default method uses the current namespace scope
7669
     * as prefix for the given local name. And it will fallback to a previously
7670
     * parsed package annotation, when no namespace declaration was parsed.
7671
     *
7672
     * @param string $localName The local class or interface name.
7673
     *
7674
     * @return string
7675
     */
7676
    private function createQualifiedTypeName($localName)
7677
    {
7678
        return ltrim($this->getNamespaceOrPackageName() . '\\' . $localName, '\\');
7679
    }
7680
7681
    /**
7682
     * Returns the name of a declared names. When the parsed code is not namespaced
7683
     * this method will return the name from the @package annotation.
7684
     *
7685
     * @return string
7686
     *
7687
     * @since 0.9.8
7688
     */
7689
    private function getNamespaceOrPackageName()
7690
    {
7691
        if ($this->namespaceName === null) {
7692
            return $this->packageName;
7693
        }
7694
        return $this->namespaceName;
7695
    }
7696
7697
    /**
7698
     * Returns the currently active package or namespace.
7699
     *
7700
     * @return ASTNamespace
7701
     *
7702
     * @since 1.0.0
7703
     */
7704
    private function getNamespaceOrPackage()
7705
    {
7706
        $namespace = $this->builder->buildNamespace($this->getNamespaceOrPackageName());
7707
        $namespace->setPackageAnnotation(null === $this->namespaceName);
7708
7709
        return $namespace;
7710
    }
7711
7712
    /**
7713
     * Extracts the @package information from the given comment.
7714
     *
7715
     * @param string $comment
7716
     *
7717
     * @return string|null
7718
     */
7719
    private function parsePackageAnnotation($comment)
7720
    {
7721
        if (getenv('DISMISS_PACKAGES')) {
7722
            $this->packageName = null;
7723
            $this->globalPackageName = null;
7724
7725
            return null;
7726
        }
7727
7728
        $package = Builder::DEFAULT_NAMESPACE;
7729
        if (preg_match('#\*\s*@package\s+(\S+)#', $comment, $match)) {
7730
            $package = trim($match[1]);
7731
            if (preg_match('#\*\s*@subpackage\s+(\S+)#', $comment, $match)) {
7732
                $package .= '\\' . trim($match[1]);
7733
            }
7734
        }
7735
7736
        // Check for doc level comment
7737
        if ($this->globalPackageName === Builder::DEFAULT_NAMESPACE
7738
            && $this->isFileComment() === true
7739
        ) {
7740
            $this->globalPackageName = $package;
7741
7742
            $this->compilationUnit->setComment($comment);
7743
        }
7744
7745
        return $package;
7746
    }
7747
7748
    /**
7749
     * Checks that the current token could be used as file comment.
7750
     *
7751
     * This method checks that the previous token is an open tag and the following
7752
     * token is not a class, a interface, final, abstract or a function.
7753
     *
7754
     * @return bool
7755
     */
7756
    protected function isFileComment()
7757
    {
7758
        if ($this->tokenizer->prev() !== Tokens::T_OPEN_TAG) {
7759
            return false;
7760
        }
7761
7762
        $notExpectedTags = array(
7763
            Tokens::T_CLASS,
7764
            Tokens::T_FINAL,
7765
            Tokens::T_TRAIT,
7766
            Tokens::T_ABSTRACT,
7767
            Tokens::T_FUNCTION,
7768
            Tokens::T_INTERFACE
7769
        );
7770
7771
        return !in_array($this->tokenizer->peek(), $notExpectedTags, true);
7772
    }
7773
7774
    /**
7775
     * Returns the class names of all <b>throws</b> annotations with in the
7776
     * given comment block.
7777
     *
7778
     * @param string $comment The context doc comment block.
7779
     *
7780
     * @return array<int, string>
7781
     */
7782
    private function parseThrowsAnnotations($comment)
7783
    {
7784
        $throws = array();
7785
7786
        if (preg_match_all(self::REGEXP_THROWS_TYPE, $comment, $matches) > 0) {
7787
            foreach ($matches[1] as $match) {
7788
                $throws[] = $this->useSymbolTable->lookup($match) ?: $match;
7789
            }
7790
        }
7791
7792
        return $throws;
7793
    }
7794
7795
    /**
7796
     * This method parses the given doc comment text for a return annotation and
7797
     * it returns the found return type.
7798
     *
7799
     * @param string $comment A doc comment text.
7800
     *
7801
     * @return string|null
7802
     */
7803
    private function parseReturnAnnotation($comment)
7804
    {
7805
        if (0 === preg_match(self::REGEXP_RETURN_TYPE, $comment, $match)) {
7806
            return null;
7807
        }
7808
7809
        foreach (explode('|', end($match) ?: '') as $image) {
7810
            $image = $this->useSymbolTable->lookup($image) ?: $image;
7811
7812
            if (Type::isScalarType($image)) {
7813
                continue;
7814
            }
7815
7816
            return $image;
7817
        }
7818
7819
        return null;
7820
    }
7821
7822
    /**
7823
     * This method parses the given doc comment text for a var annotation and
7824
     * it returns the found property types.
7825
     *
7826
     * @param string $comment A doc comment text.
7827
     *
7828
     * @return array<string>
7829
     */
7830
    private function parseVarAnnotation($comment)
7831
    {
7832
        if (preg_match(self::REGEXP_VAR_TYPE, (string) $comment, $match) > 0) {
7833
            $useSymbolTable = $this->useSymbolTable;
7834
7835
            return array_map(
7836
                function ($image) use ($useSymbolTable) {
7837
                    return $useSymbolTable->lookup($image) ?: $image;
7838
                },
7839
                array_map('trim', explode('|', end($match) ?: ''))
7840
            );
7841
        }
7842
7843
        return array();
7844
    }
7845
7846
    /**
7847
     * This method will extract the type information of a property from it's
7848
     * doc comment information. The returned value will be <b>null</b> when no
7849
     * type information exists.
7850
     *
7851
     * @return ASTType|null
7852
     *
7853
     * @since 0.9.6
7854
     */
7855
    private function parseFieldDeclarationType()
7856
    {
7857
        // Skip, if ignore annotations is set
7858
        if ($this->ignoreAnnotations === true) {
7859
            return null;
7860
        }
7861
7862
        $reference = $this->parseFieldDeclarationClassOrInterfaceReference();
7863
7864
        if ($reference !== null) {
7865
            return $reference;
7866
        }
7867
7868
        $annotations = $this->parseVarAnnotation($this->docComment);
7869
7870
        foreach ($annotations as $annotation) {
7871
            if (Type::isPrimitiveType($annotation) === true) {
7872
                return $this->builder->buildAstScalarType(
7873
                    Type::getPrimitiveType($annotation)
7874
                );
7875
            }
7876
7877
            if (Type::isArrayType($annotation) === true) {
7878
                return $this->builder->buildAstTypeArray();
7879
            }
7880
        }
7881
7882
        return null;
7883
    }
7884
7885
    /**
7886
     * Extracts non scalar types from a field doc comment and creates a
7887
     * matching type instance.
7888
     *
7889
     * @return ASTClassOrInterfaceReference|null
7890
     *
7891
     * @since 0.9.6
7892
     */
7893
    private function parseFieldDeclarationClassOrInterfaceReference()
7894
    {
7895
        $annotations = $this->parseVarAnnotation($this->docComment);
7896
7897
        foreach ($annotations as $annotation) {
7898
            if (Type::isScalarType($annotation) === false) {
7899
                return $this->builder->buildAstClassOrInterfaceReference(
7900
                    $annotation
7901
                );
7902
            }
7903
        }
7904
7905
        return null;
7906
    }
7907
7908
    /**
7909
     * This method parses a yield-statement node.
7910
     *
7911
     * @param bool $standalone Either yield is the statement (true), or nested in
7912
     *                         an expression (false).
7913
     *
7914
     * @return ASTYieldStatement
7915
     */
7916
    private function parseYield($standalone)
7917
    {
7918
        $this->tokenStack->push();
7919
7920
        $token = $this->consumeToken(Tokens::T_YIELD);
7921
        $this->consumeComments();
7922
7923
        $yield = $this->builder->buildAstYieldStatement($token->image);
7924
7925
        $node = $this->parseOptionalExpression();
7926
        if ($node) {
7927
            $yield->addChild($node);
7928
7929
            if ($this->tokenizer->peek() === Tokens::T_DOUBLE_ARROW) {
7930
                $this->consumeToken(Tokens::T_DOUBLE_ARROW);
7931
7932
                $yield->addChild($this->parseOptionalExpression());
7933
            }
7934
        }
7935
7936
        $this->consumeComments();
7937
7938
        if ($standalone) {
7939
            $this->parseStatementTermination();
7940
        }
7941
7942
        return $this->setNodePositionsAndReturn($yield);
7943
    }
7944
7945
    /**
7946
     * Extracts documented <b>throws</b> and <b>return</b> types and sets them
7947
     * to the given <b>$callable</b> instance.
7948
     *
7949
     * @return void
7950
     */
7951
    private function prepareCallable(AbstractASTCallable $callable)
7952
    {
7953
        // Skip, if ignore annotations is set
7954
        if ($this->ignoreAnnotations === true) {
7955
            return;
7956
        }
7957
7958
        // Get all @throws Types
7959
        $comment = $callable->getComment();
7960
        $throws = $comment === null ? array() : $this->parseThrowsAnnotations($comment);
7961
7962
        foreach ($throws as $qualifiedName) {
7963
            $callable->addExceptionClassReference(
7964
                $this->builder->buildAstClassOrInterfaceReference($qualifiedName)
7965
            );
7966
        }
7967
7968
        // Stop here if return class already exists.
7969
        if ($callable->hasReturnClass()) {
7970
            return;
7971
        }
7972
7973
        // Get return annotation
7974
        $qualifiedName = $comment === null ? null : $this->parseReturnAnnotation($comment);
7975
7976
        if ($qualifiedName !== null) {
7977
            $callable->setReturnClassReference(
7978
                $this->builder->buildAstClassOrInterfaceReference($qualifiedName)
7979
            );
7980
        }
7981
    }
7982
7983
    /**
7984
     * This method will consume the next token in the token stream. It will
7985
     * throw an exception if the type of this token is not identical with
7986
     * <b>$tokenType</b>.
7987
     *
7988
     * @param int $tokenType The next expected token type.
7989
     *
7990
     * @throws TokenStreamEndException
7991
     * @throws UnexpectedTokenException
7992
     *
7993
     * @return Token
7994
     */
7995
    protected function consumeToken($tokenType)
7996
    {
7997
        assert($tokenType > 0);
7998
7999
        switch ($this->tokenizer->peek()) {
8000
            case $tokenType:
8001
                $next = $this->tokenizer->next();
8002
                assert($next instanceof Token);
8003
                return $this->tokenStack->add($next);
8004
        }
8005
8006
        throw $this->getUnexpectedNextTokenException();
8007
    }
8008
8009
    /**
8010
     * This method will consume all comment tokens from the token stream.
8011
     *
8012
     * @return void
8013
     */
8014
    protected function consumeComments()
8015
    {
8016
        $type = $this->tokenizer->peek();
8017
        while ($type == Tokens::T_COMMENT || $type == Tokens::T_DOC_COMMENT) {
8018
            $token = $this->consumeToken($type);
8019
            $type  = $this->tokenizer->peek();
8020
8021
            if (Tokens::T_COMMENT === $token->type) {
8022
                continue;
8023
            }
8024
8025
            $this->docComment = $token->image;
8026
            if (preg_match('(\s+@package\s+[^\s]+\s+)', $token->image)) {
8027
                $this->packageName = $this->parsePackageAnnotation($token->image);
8028
            }
8029
        }
8030
    }
8031
8032
    /**
8033
     * Return the appropriate exception for either UnexpectedTokenException
8034
     * configured with the next token, or TokenStreamEndException if there
8035
     * is no more token.
8036
     *
8037
     * @return TokenStreamEndException|UnexpectedTokenException
8038
     */
8039
    protected function getUnexpectedNextTokenException()
8040
    {
8041
        return $this->getUnexpectedTokenException($this->tokenizer->next());
0 ignored issues
show
Bug introduced by
It seems like $this->tokenizer->next() can also be of type integer; however, parameter $token of PDepend\Source\Language\...xpectedTokenException() does only seem to accept PDepend\Source\Tokenizer...rce\Tokenizer\Tokenizer, 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

8041
        return $this->getUnexpectedTokenException(/** @scrutinizer ignore-type */ $this->tokenizer->next());
Loading history...
8042
    }
8043
8044
    /**
8045
     * Return the appropriate exception for either UnexpectedTokenException
8046
     * configured with the given token, or TokenStreamEndException if given
8047
     * null.
8048
     *
8049
     * @param Token|Tokenizer::T_*|null $token
8050
     *
8051
     * @return TokenStreamEndException|UnexpectedTokenException
8052
     */
8053
    protected function getUnexpectedTokenException($token)
8054
    {
8055
        if ($token instanceof Token) {
8056
            return new UnexpectedTokenException($token, $this->tokenizer->getSourceFile());
8057
        }
8058
8059
        return new TokenStreamEndException($this->tokenizer);
8060
    }
8061
8062
    /**
8063
     * Throws an UnexpectedTokenException
8064
     *
8065
     * @throws UnexpectedTokenException
8066
     *
8067
     * @return never
8068
     *
8069
     * @since 2.2.5
8070
     * @deprecated 3.0.0 Use throw $this->getUnexpectedTokenException($token) instead
8071
     * @codeCoverageIgnore
8072
     */
8073
    protected function throwUnexpectedTokenException(Token $token = null)
8074
    {
8075
        throw $this->getUnexpectedTokenException($token);
8076
    }
8077
8078
    /**
8079
     * @return void
8080
     */
8081
    protected function checkEllipsisInExpressionSupport()
8082
    {
8083
        throw $this->getUnexpectedNextTokenException();
8084
    }
8085
8086
    /**
8087
     * Parses throw expression syntax. available since PHP 8.0. Ex.:
8088
     *  $callable = fn() => throw new Exception();
8089
     *  $value = $nullableValue ?? throw new InvalidArgumentException();
8090
     *  $value = $falsableValue ?: throw new InvalidArgumentException();
8091
     *
8092
     * @throws UnexpectedTokenException
8093
     *
8094
     * @return ASTThrowStatement
8095
     */
8096
    protected function parseThrowExpression()
8097
    {
8098
        throw $this->getUnexpectedNextTokenException();
8099
    }
8100
8101
    /**
8102
     * Parses enum declaration. available since PHP 8.1. Ex.:
8103
     *  enum Suit: string { case HEARTS = 'hearts'; }
8104
     *
8105
     * @throws UnexpectedTokenException
8106
     *
8107
     * @return ASTEnum
8108
     */
8109
    protected function parseEnumDeclaration()
8110
    {
8111
        throw $this->getUnexpectedNextTokenException();
8112
    }
8113
8114
    /**
8115
     * Parses enum declaration signature. available since PHP 8.1. Ex.:
8116
     *  enum Suit: string
8117
     *
8118
     * @return ASTEnum
8119
     */
8120
    protected function parseEnumSignature()
8121
    {
8122
        $this->tokenStack->add($this->requireNextToken());
8123
        $this->consumeComments();
8124
8125
        if ($this->tokenizer->peek() !== Tokens::T_STRING) {
8126
            throw $this->getUnexpectedNextTokenException();
8127
        }
8128
8129
        $name = $this->tokenizer->currentToken()->image;
8130
        $this->consumeToken(Tokens::T_STRING);
8131
        $this->consumeComments();
8132
        $type = null;
8133
8134
        if ($this->tokenizer->peek() === Tokens::T_COLON) {
8135
            $this->consumeToken(Tokens::T_COLON);
8136
            $type = $this->parseTypeHint();
8137
8138
            if (!($type instanceof ASTScalarType) ||
0 ignored issues
show
introduced by
$type is never a sub-type of PDepend\Source\AST\ASTScalarType.
Loading history...
8139
                !in_array($type->getImage(), array('int', 'string'), true)
8140
            ) {
8141
                throw new TokenException(
8142
                    "Enum backing type must be 'int' or 'string'"
8143
                );
8144
            }
8145
        }
8146
8147
        $enum = $this->builder->buildEnum($name, $type);
8148
        $enum->setCompilationUnit($this->compilationUnit);
8149
        $enum->setModifiers($this->modifiers);
8150
        $enum->setComment($this->docComment);
8151
        $enum->setId($this->idBuilder->forClassOrInterface($enum));
8152
        $enum->setUserDefined();
8153
8154
        $this->consumeComments();
8155
        $tokenType = $this->tokenizer->peek();
8156
8157
        if ($tokenType === Tokens::T_IMPLEMENTS) {
8158
            $this->consumeToken(Tokens::T_IMPLEMENTS);
8159
            $this->parseInterfaceList($enum);
8160
        }
8161
8162
        return $enum;
8163
    }
8164
8165
    /**
8166
     * Peek the next token if it's of the given type, add it to current tokenStack, and return it if so.
8167
     *
8168
     * @param string $type
8169
     * @psalm-param Tokens::T_* $type
8170
     *
8171
     * @return Token|null
8172
     */
8173
    protected function addTokenToStackIfType($type)
8174
    {
8175
        if ($this->tokenizer->peek() === $type) {
0 ignored issues
show
introduced by
The condition $this->tokenizer->peek() === $type is always false.
Loading history...
8176
            $next = $this->tokenizer->next();
8177
            assert($next instanceof Token);
8178
            $this->tokenStack->add($next);
8179
8180
            return $next;
8181
        }
8182
8183
        return null;
8184
    }
8185
8186
    /**
8187
     * Return the next token if it exists, else throw a TokenStreamEndException.
8188
     *
8189
     * @return Token
8190
     */
8191
    protected function requireNextToken()
8192
    {
8193
        $next = $this->tokenizer->next();
8194
8195
        if ($next instanceof Token) {
8196
            return $next;
8197
        }
8198
8199
        throw new TokenStreamEndException($this->tokenizer);
8200
    }
8201
8202
    /**
8203
     * @param string $numberRepresentation integer number as it appears in the code, `0xfe4`, `1_000_000`
8204
     *
8205
     * @return int
8206
     */
8207
    private function parseIntegerNumberImage($numberRepresentation)
8208
    {
8209
        $numberRepresentation = trim($numberRepresentation);
8210
8211
        if (!preg_match(static::REGEXP_INTEGER, $numberRepresentation)) {
8212
            throw new InvalidArgumentException("Invalid number $numberRepresentation");
8213
        }
8214
8215
        return (int) $this->getNumberFromImage($numberRepresentation);
8216
    }
8217
8218
    /**
8219
     * @param string $numberRepresentation
8220
     *
8221
     * @return string|float|int
8222
     */
8223
    private function getNumberFromImage($numberRepresentation)
8224
    {
8225
        $numberRepresentation = str_replace('_', '', $numberRepresentation);
8226
8227
        switch (substr($numberRepresentation, 0, 2)) {
8228
            case '0x':
8229
            case '0X':
8230
                return hexdec(substr($numberRepresentation, 2));
8231
8232
            case '0b':
8233
            case '0B':
8234
                return bindec(substr($numberRepresentation, 2));
8235
8236
            default:
8237
                if (preg_match('/^0+[oO]?(\d+)$/', $numberRepresentation, $match)) {
8238
                    return octdec($match[1]);
8239
                }
8240
8241
                return $numberRepresentation;
8242
        }
8243
    }
8244
8245
    /**
8246
     * @return ASTEnumCase
8247
     */
8248
    private function parseEnumCase()
8249
    {
8250
        $this->tokenStack->add($this->requireNextToken());
8251
        $this->tokenStack->push();
8252
        $this->consumeComments();
8253
        $caseName = $this->tokenizer->currentToken()->image;
8254
8255
        if (!preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $caseName)) {
8256
            throw $this->getUnexpectedNextTokenException();
8257
        }
8258
8259
        $this->tokenStack->add($this->requireNextToken());
8260
        $this->consumeComments();
8261
        $case = $this->builder->buildEnumCase($caseName, $this->parseEnumCaseValue());
8262
        $this->consumeComments();
8263
        $this->consumeToken(Tokens::T_SEMICOLON);
8264
8265
        return $this->setNodePositionsAndReturn($case);
8266
    }
8267
8268
    /**
8269
     * @return AbstractASTNode|null
8270
     */
8271
    private function parseEnumCaseValue()
8272
    {
8273
        if ($this->tokenizer->peek() !== Tokens::T_EQUAL) {
8274
            return null;
8275
        }
8276
8277
        $this->consumeToken(Tokens::T_EQUAL);
8278
        $this->consumeComments();
8279
8280
        $expression = $this->parseOptionalExpression();
8281
8282
        if (!$expression instanceof AbstractASTNode) {
8283
            throw new MissingValueException($this->tokenizer);
8284
        }
8285
8286
        return $expression;
8287
    }
8288
8289
    /**
8290
     * @param string $type
8291
     * @psalm-param Tokens::T_* $type
8292
     *
8293
     * @return string
8294
     */
8295
    private function parseNumber($type)
8296
    {
8297
        $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

8297
        $token = $this->consumeToken(/** @scrutinizer ignore-type */ $type);
Loading history...
8298
        $number = (string) $token->image;
8299
8300
        while ($next = $this->addTokenToStackIfType(Tokens::T_STRING)) {
8301
            $number .= $next->image;
8302
        }
8303
8304
        return $number;
8305
    }
8306
}
8307