PHPParserVersion70   F
last analyzed

Complexity

Total Complexity 143

Size/Duplication

Total Lines 557
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 143
eloc 263
dl 0
loc 557
rs 2
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A parseAllocationExpressionTypeReference() 0 4 2
B parseScalarOrCallableTypeHint() 0 16 8
A parseFormalParameter() 0 14 2
A parseOptionalExpressionForVersion() 0 4 2
A parseExpressionVersion70() 0 22 3
A parseReturnTypeHint() 0 5 1
A allowUseGroupDeclarationTrailingComma() 0 3 1
A isMethodName() 0 8 2
A parsePostfixIdentifier() 0 12 2
A parseQualifiedNameElement() 0 11 4
D isConstantName() 0 74 67
A parseEndReturnTypeHint() 0 11 4
A parseAnonymousClassDeclaration() 0 56 5
A parseOptionalMemberPrimaryPrefix() 0 13 3
A isTypeHint() 0 9 3
B parseTypeHint() 0 20 8
B isScalarOrCallableTypeHint() 0 14 8
A parseParenthesisExpressionOrPrimaryPrefixForVersion() 0 14 4
A parseCallableDeclarationAddition() 0 13 2
A parseUseDeclarationForVersion() 0 9 2
A parseParenthesisExpression() 0 21 2
B parseUseDeclarationVersion70() 0 45 8

How to fix   Complexity   

Complex Class

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

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

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

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
 * @since 2.3
43
 */
44
45
namespace PDepend\Source\Language\PHP;
46
47
use PDepend\Source\AST\ASTAllocationExpression;
48
use PDepend\Source\AST\ASTExpression;
49
use PDepend\Source\AST\ASTFormalParameter;
50
use PDepend\Source\AST\ASTNode;
51
use PDepend\Source\AST\ASTType;
52
use PDepend\Source\Parser\UnexpectedTokenException;
53
use PDepend\Source\Tokenizer\Tokens;
54
55
/**
56
 * Concrete parser implementation that supports features up to PHP version 7.0.
57
 *
58
 * @copyright 2008-2017 Manuel Pichler. All rights reserved.
59
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
60
 *
61
 * @since 2.3
62
 */
63
abstract class PHPParserVersion70 extends PHPParserVersion56
64
{
65
    /**
66
     * @param int $tokenType
67
     *
68
     * @return bool
69
     */
70
    protected function isConstantName($tokenType)
71
    {
72
        switch ($tokenType) {
73
            case Tokens::T_CALLABLE:
74
            case Tokens::T_TRAIT:
75
            case Tokens::T_EXTENDS:
76
            case Tokens::T_IMPLEMENTS:
77
            case Tokens::T_STATIC:
78
            case Tokens::T_ABSTRACT:
79
            case Tokens::T_FINAL:
80
            case Tokens::T_PUBLIC:
81
            case Tokens::T_PROTECTED:
82
            case Tokens::T_PRIVATE:
83
            case Tokens::T_CONST:
84
            case Tokens::T_ENDDECLARE:
85
            case Tokens::T_ENDFOR:
86
            case Tokens::T_ENDFOREACH:
87
            case Tokens::T_ENDIF:
88
            case Tokens::T_ENDWHILE:
89
            case Tokens::T_EMPTY:
90
            case Tokens::T_EVAL:
91
            case Tokens::T_LOGICAL_AND:
92
            case Tokens::T_GLOBAL:
93
            case Tokens::T_GOTO:
94
            case Tokens::T_INSTANCEOF:
95
            case Tokens::T_INSTEADOF:
96
            case Tokens::T_INTERFACE:
97
            case Tokens::T_ISSET:
98
            case Tokens::T_NAMESPACE:
99
            case Tokens::T_NEW:
100
            case Tokens::T_LOGICAL_OR:
101
            case Tokens::T_LOGICAL_XOR:
102
            case Tokens::T_TRY:
103
            case Tokens::T_USE:
104
            case Tokens::T_VAR:
105
            case Tokens::T_EXIT:
106
            case Tokens::T_LIST:
107
            case Tokens::T_CLONE:
108
            case Tokens::T_INCLUDE:
109
            case Tokens::T_INCLUDE_ONCE:
110
            case Tokens::T_THROW:
111
            case Tokens::T_ARRAY:
112
            case Tokens::T_PRINT:
113
            case Tokens::T_ECHO:
114
            case Tokens::T_REQUIRE:
115
            case Tokens::T_REQUIRE_ONCE:
116
            case Tokens::T_RETURN:
117
            case Tokens::T_ELSE:
118
            case Tokens::T_ELSEIF:
119
            case Tokens::T_DEFAULT:
120
            case Tokens::T_BREAK:
121
            case Tokens::T_CONTINUE:
122
            case Tokens::T_SWITCH:
123
            case Tokens::T_YIELD:
124
            case Tokens::T_FUNCTION:
125
            case Tokens::T_IF:
126
            case Tokens::T_ENDSWITCH:
127
            case Tokens::T_FINALLY:
128
            case Tokens::T_FOR:
129
            case Tokens::T_FOREACH:
130
            case Tokens::T_DECLARE:
131
            case Tokens::T_CASE:
132
            case Tokens::T_DO:
133
            case Tokens::T_WHILE:
134
            case Tokens::T_AS:
135
            case Tokens::T_CATCH:
136
            //case Tokens::T_DIE:
137
            case Tokens::T_SELF:
138
            case Tokens::T_PARENT:
139
            case Tokens::T_UNSET:
140
                return true;
141
        }
142
143
        return parent::isConstantName($tokenType);
144
    }
145
146
    /**
147
     * @param int $tokenType
148
     *
149
     * @return bool
150
     */
151
    protected function isMethodName($tokenType)
152
    {
153
        switch ($tokenType) {
154
            case Tokens::T_CLASS:
155
                return true;
156
        }
157
158
        return $this->isConstantName($tokenType);
159
    }
160
161
    /**
162
     * @param int $tokenType
163
     *
164
     * @return bool
165
     */
166
    protected function isTypeHint($tokenType)
167
    {
168
        switch ($tokenType) {
169
            case Tokens::T_SELF:
170
            case Tokens::T_PARENT:
171
                return true;
172
        }
173
174
        return parent::isTypeHint($tokenType);
175
    }
176
177
    /**
178
     * @return ASTNode
179
     */
180
    protected function parsePostfixIdentifier()
181
    {
182
        $tokenType = $this->tokenizer->peek();
183
        switch (true) {
184
            case ($this->isConstantName($tokenType)):
185
                $node = $this->parseLiteral();
186
                break;
187
            default:
188
                $node = parent::parsePostfixIdentifier();
189
                break;
190
        }
191
        return $this->parseOptionalIndexExpression($node);
192
    }
193
194
    protected function parseCallableDeclarationAddition($callable)
195
    {
196
        $this->consumeComments();
197
        if (Tokens::T_COLON != $this->tokenizer->peek()) {
198
            return $callable;
199
        }
200
201
        $this->consumeToken(Tokens::T_COLON);
202
203
        $type = $this->parseReturnTypeHint();
204
        $callable->addChild($type);
205
206
        return $callable;
207
    }
208
209
    /**
210
     * @return ASTType
211
     */
212
    protected function parseEndReturnTypeHint()
213
    {
214
        switch ($this->tokenizer->peek()) {
215
            case Tokens::T_ARRAY:
216
                return $this->parseArrayType();
217
            case Tokens::T_SELF:
218
                return $this->parseSelfType();
219
            case Tokens::T_PARENT:
220
                return $this->parseParentType();
221
            default:
222
                return $this->parseTypeHint();
223
        }
224
    }
225
226
    /**
227
     * @return ASTType
228
     */
229
    protected function parseReturnTypeHint()
230
    {
231
        $this->consumeComments();
232
233
        return $this->parseEndReturnTypeHint();
234
    }
235
236
    protected function parseTypeHint()
237
    {
238
        switch ($this->tokenizer->peek()) {
239
            case Tokens::T_ARRAY:
240
                return $this->parseArrayType();
241
242
            case Tokens::T_SELF:
243
                return $this->parseSelfType();
244
245
            case Tokens::T_STRING:
246
            case Tokens::T_BACKSLASH:
247
            case Tokens::T_NAMESPACE:
248
                $name = $this->parseQualifiedName();
249
250
                return $this->isScalarOrCallableTypeHint($name)
251
                    ? ($this->parseScalarOrCallableTypeHint($name) ?: null)
252
                    : $this->builder->buildAstClassOrInterfaceReference($name);
253
254
            default:
255
                return parent::parseTypeHint();
256
        }
257
    }
258
259
    /**
260
     * Parses any expression that is surrounded by an opening and a closing
261
     * parenthesis
262
     *
263
     * @return ASTExpression
264
     */
265
    protected function parseParenthesisExpression()
266
    {
267
        $this->tokenStack->push();
268
        $this->consumeComments();
269
270
        $expr = $this->builder->buildAstExpression();
271
        $expr = $this->parseBraceExpression(
272
            $expr,
273
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN),
274
            Tokens::T_PARENTHESIS_CLOSE,
275
            Tokens::T_COMMA
276
        );
277
278
        while ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN) {
279
            $function = $this->builder->buildAstFunctionPostfix($expr->getImage());
280
            $function->addChild($expr);
281
            $function->addChild($this->parseArguments());
282
            $expr = $function;
283
        }
284
285
        return $this->setNodePositionsAndReturn($expr);
286
    }
287
288
    /**
289
     * Tests if the given image is a PHP 7.0 type hint.
290
     *
291
     * @param string $image
292
     *
293
     * @return bool
294
     */
295
    protected function isScalarOrCallableTypeHint($image)
296
    {
297
        switch (strtolower($image)) {
298
            case 'int':
299
            case 'bool':
300
            case 'float':
301
            case 'string':
302
            case 'callable':
303
            case 'iterable':
304
            case 'void':
305
                return true;
306
        }
307
308
        return false;
309
    }
310
311
    /**
312
     * Parses a scalar type hint or a callable type hint.
313
     *
314
     * @param string $image
315
     *
316
     * @return ASTType|false
317
     */
318
    protected function parseScalarOrCallableTypeHint($image)
319
    {
320
        switch (strtolower($image)) {
321
            case 'int':
322
            case 'bool':
323
            case 'float':
324
            case 'string':
325
                return $this->builder->buildAstScalarType($image);
326
            case 'callable':
327
                return $this->builder->buildAstTypeCallable();
328
            case 'void':
329
            case 'iterable':
330
                throw $this->getUnexpectedTokenException($this->tokenizer->prevToken());
331
        }
332
333
        return false;
334
    }
335
336
    /**
337
     * Parse the type reference used in an allocation expression.
338
     *
339
     * @return ASTNode
340
     *
341
     * @since 2.3
342
     */
343
    protected function parseAllocationExpressionTypeReference(ASTAllocationExpression $allocation)
344
    {
345
        return $this->parseAnonymousClassDeclaration($allocation)
346
            ?: parent::parseAllocationExpressionTypeReference($allocation);
347
    }
348
349
    /**
350
     * Attempts to the next sequence of tokens as an anonymous class and adds it to the allocation expression
351
     *
352
     * @template T of ASTAllocationExpression
353
     *
354
     * @param T $allocation
0 ignored issues
show
Bug introduced by
The type PDepend\Source\Language\PHP\T was not found. Maybe you did not declare it correctly or list all dependencies?

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

filter:
    dependency_paths: ["lib/*"]

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

Loading history...
355
     *
356
     * @return T|null
357
     */
358
    protected function parseAnonymousClassDeclaration(ASTAllocationExpression $allocation)
359
    {
360
        $this->consumeComments();
361
362
        if (Tokens::T_CLASS !== $this->tokenizer->peek()) {
363
            return null;
364
        }
365
366
        $classOrInterface = $this->classOrInterface;
367
368
        $this->tokenStack->push();
369
370
        $this->consumeToken(Tokens::T_CLASS);
371
        $this->consumeComments();
372
373
        $class = $this->builder->buildAnonymousClass();
374
        $class->setName(
375
            sprintf(
376
                'class@anonymous%s0x%s',
377
                $this->compilationUnit->getFileName(),
378
                uniqid('')
379
            )
380
        );
381
        $class->setCompilationUnit($this->compilationUnit);
382
        $class->setUserDefined();
383
384
        if ($this->isNextTokenArguments()) {
385
            $class->addChild($this->parseArguments());
386
        }
387
388
        $this->consumeComments();
389
        $tokenType = $this->tokenizer->peek();
390
391
        if ($tokenType === Tokens::T_EXTENDS) {
392
            $class = $this->parseClassExtends($class);
393
394
            $this->consumeComments();
395
            $tokenType = $this->tokenizer->peek();
396
        }
397
398
        if ($tokenType === Tokens::T_IMPLEMENTS) {
399
            $this->consumeToken(Tokens::T_IMPLEMENTS);
400
            $this->parseInterfaceList($class);
401
        }
402
403
        $allocation->addChild(
404
            $this->setNodePositionsAndReturn(
405
                $this->parseTypeBody($class),
406
                $tokens
407
            )
408
        );
409
        $class->setTokens($tokens);
410
411
        $this->classOrInterface = $classOrInterface;
412
413
        return $allocation;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $allocation returns the type PDepend\Source\AST\ASTAllocationExpression which is incompatible with the documented return type PDepend\Source\Language\PHP\T|null.
Loading history...
414
    }
415
416
    protected function parseOptionalMemberPrimaryPrefix(ASTNode $node)
417
    {
418
        $this->consumeComments();
419
420
        if (Tokens::T_DOUBLE_COLON === $this->tokenizer->peek()) {
421
            return $this->parseStaticMemberPrimaryPrefix($node);
422
        }
423
424
        if ($this->isNextTokenObjectOperator()) {
425
            return $this->parseMemberPrimaryPrefix($node);
426
        }
427
428
        return $node;
429
    }
430
431
    protected function parseParenthesisExpressionOrPrimaryPrefixForVersion(ASTExpression $expr)
432
    {
433
        $this->consumeComments();
434
435
        if (Tokens::T_DOUBLE_COLON === $this->tokenizer->peek()) {
436
            return $this->parseStaticMemberPrimaryPrefix($expr->getChild(0));
437
        }
438
439
        if ($this->isNextTokenObjectOperator()) {
440
            $node = count($expr->getChildren()) === 0 ? $expr : $expr->getChild(0);
441
            return $this->parseMemberPrimaryPrefix($node);
442
        }
443
444
        return $expr;
445
    }
446
447
    /**
448
     * This method will be called when the base parser cannot handle an expression
449
     * in the base version. In this method you can implement version specific
450
     * expressions.
451
     *
452
     * @throws UnexpectedTokenException
453
     *
454
     * @return ASTNode
455
     *
456
     * @since 2.3
457
     */
458
    protected function parseOptionalExpressionForVersion()
459
    {
460
        return $this->parseExpressionVersion70()
461
            ?: parent::parseOptionalExpressionForVersion();
462
    }
463
464
    /**
465
     * In this method we implement parsing of PHP 7.0 specific expressions.
466
     *
467
     * @return ASTNode|null
468
     *
469
     * @since 2.3
470
     */
471
    protected function parseExpressionVersion70()
472
    {
473
        $this->consumeComments();
474
        $nextTokenType = $this->tokenizer->peek();
475
476
        switch ($nextTokenType) {
477
            case Tokens::T_SPACESHIP:
478
            case Tokens::T_COALESCE:
479
                $token = $this->consumeToken($nextTokenType);
480
481
                $expr = $this->builder->buildAstExpression($token->image);
482
                $expr->configureLinesAndColumns(
483
                    $token->startLine,
484
                    $token->endLine,
485
                    $token->startColumn,
486
                    $token->endColumn
487
                );
488
489
                return $expr;
490
        }
491
492
        return null;
493
    }
494
495
    /**
496
     * This method will parse a formal parameter. A formal parameter is at least
497
     * a variable name, but can also contain a default parameter value.
498
     *
499
     * <code>
500
     * //               --  -------
501
     * function foo(Bar $x, $y = 42) {}
502
     * //               --  -------
503
     * </code>
504
     *
505
     * @return ASTFormalParameter
506
     *
507
     * @since 2.0.7
508
     */
509
    protected function parseFormalParameter()
510
    {
511
        $parameter = $this->builder->buildAstFormalParameter();
512
513
        if (Tokens::T_ELLIPSIS === $this->tokenizer->peek()) {
514
            $this->consumeToken(Tokens::T_ELLIPSIS);
515
            $this->consumeComments();
516
517
            $parameter->setVariableArgList();
518
        }
519
520
        $parameter->addChild($this->parseVariableDeclarator());
521
522
        return $parameter;
523
    }
524
525
    /**
526
     * @param array<string> $fragments
527
     *
528
     * @return void
529
     */
530
    protected function parseUseDeclarationForVersion(array $fragments)
531
    {
532
        if (Tokens::T_CURLY_BRACE_OPEN === $this->tokenizer->peek()) {
533
            $this->parseUseDeclarationVersion70($fragments);
534
535
            return;
536
        }
537
538
        parent::parseUseDeclarationForVersion($fragments);
539
    }
540
541
    /**
542
     * @param array<string> $fragments
543
     *
544
     * @return void
545
     */
546
    protected function parseUseDeclarationVersion70(array $fragments)
547
    {
548
        $namespacePrefixReplaced = $this->namespacePrefixReplaced;
549
550
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
551
        $this->consumeComments();
552
553
        do {
554
            $nextToken = $this->tokenizer->peek();
555
            switch ($nextToken) {
556
                case Tokens::T_CONST:
557
                case Tokens::T_FUNCTION:
558
                    $this->consumeToken($nextToken);
559
            }
560
561
            if ($this->allowUseGroupDeclarationTrailingComma() &&
562
                Tokens::T_CURLY_BRACE_CLOSE === $this->tokenizer->peek()
563
            ) {
564
                break;
565
            }
566
567
            $subFragments = $this->parseQualifiedNameRaw();
568
            $this->consumeComments();
569
570
            $image = $this->parseNamespaceImage($subFragments);
571
572
            if (Tokens::T_COMMA !== $this->tokenizer->peek()) {
573
                break;
574
            }
575
576
            $this->consumeToken(Tokens::T_COMMA);
577
            $this->consumeComments();
578
579
            // Add mapping between image and qualified name to symbol table
580
            $this->useSymbolTable->add($image, join('', array_merge($fragments, $subFragments)));
581
        } while (true);
582
583
        if (isset($image, $subFragments)) {
584
            $this->useSymbolTable->add($image, join('', array_merge($fragments, $subFragments)));
585
        }
586
587
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
588
        $this->consumeComments();
589
590
        $this->namespacePrefixReplaced = $namespacePrefixReplaced;
591
    }
592
593
    /**
594
     * @param array<string> $previousElements
595
     *
596
     * @return string|null
597
     */
598
    protected function parseQualifiedNameElement(array $previousElements)
599
    {
600
        if (Tokens::T_CURLY_BRACE_OPEN !== $this->tokenizer->peek()) {
601
            return parent::parseQualifiedNameElement($previousElements);
602
        }
603
604
        if (count($previousElements) >= 2 && '\\' === end($previousElements)) {
605
            return null;
606
        }
607
608
        throw $this->getUnexpectedNextTokenException();
609
    }
610
611
    /**
612
     * use Foo\Bar\{TestA, TestB} is allowed since PHP 7.0
613
     * use Foo\Bar\{TestA, TestB,} but trailing comma isn't
614
     *
615
     * @return bool
616
     */
617
    protected function allowUseGroupDeclarationTrailingComma()
618
    {
619
        return false;
620
    }
621
}
622