Completed
Push — master ( 8418b3...91ec6e )
by
unknown
13s queued 11s
created

PDepend/Source/Language/PHP/PHPParserVersion70.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
 * @since 2.3
42
 */
43
44
namespace PDepend\Source\Language\PHP;
45
46
use PDepend\Source\AST\ASTAllocationExpression;
47
use PDepend\Source\AST\ASTExpression;
48
use PDepend\Source\AST\ASTNode;
49
use PDepend\Source\Tokenizer\Tokens;
50
51
/**
52
 * Concrete parser implementation that supports features up to PHP version 7.0.
53
 *
54
 * TODO:
55
 * - Tokens: trait, callable, insteadof
56
 *   - allowed as
57
 *     - method
58
 *     - constant
59
 *   - not allowed as
60
 *     - class
61
 *     - interface
62
 *     - trait
63
 *
64
 * @copyright 2008-2017 Manuel Pichler. All rights reserved.
65
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
66
 * @since 2.3
67
 */
68
abstract class PHPParserVersion70 extends PHPParserVersion56
69
{
70
    /**
71
     * @param integer $tokenType
72
     * @return boolean
73
     */
74 32
    protected function isConstantName($tokenType)
75
    {
76
        switch ($tokenType) {
77 32
            case Tokens::T_CALLABLE:
78 32
            case Tokens::T_TRAIT:
79 32
            case Tokens::T_EXTENDS:
80 32
            case Tokens::T_IMPLEMENTS:
81 32
            case Tokens::T_STATIC:
82 32
            case Tokens::T_ABSTRACT:
83 32
            case Tokens::T_FINAL:
84 32
            case Tokens::T_PUBLIC:
85 32
            case Tokens::T_PROTECTED:
86 32
            case Tokens::T_PRIVATE:
87 32
            case Tokens::T_CONST:
88 32
            case Tokens::T_ENDDECLARE:
89 32
            case Tokens::T_ENDFOR:
90 32
            case Tokens::T_ENDFOREACH:
91 32
            case Tokens::T_ENDIF:
92 32
            case Tokens::T_ENDWHILE:
93 32
            case Tokens::T_LOGICAL_AND:
94 32
            case Tokens::T_GLOBAL:
95 32
            case Tokens::T_GOTO:
96 32
            case Tokens::T_INSTANCEOF:
97 32
            case Tokens::T_INSTEADOF:
98 32
            case Tokens::T_INTERFACE:
99 32
            case Tokens::T_NAMESPACE:
100 32
            case Tokens::T_NEW:
101 32
            case Tokens::T_LOGICAL_OR:
102 32
            case Tokens::T_LOGICAL_XOR:
103 32
            case Tokens::T_TRY:
104 32
            case Tokens::T_USE:
105 32
            case Tokens::T_VAR:
106 32
            case Tokens::T_EXIT:
107 32
            case Tokens::T_LIST:
108 32
            case Tokens::T_CLONE:
109 32
            case Tokens::T_INCLUDE:
110 32
            case Tokens::T_INCLUDE_ONCE:
111 32
            case Tokens::T_THROW:
112 32
            case Tokens::T_ARRAY:
113 32
            case Tokens::T_PRINT:
114 32
            case Tokens::T_ECHO:
115 32
            case Tokens::T_REQUIRE:
116 32
            case Tokens::T_REQUIRE_ONCE:
117 32
            case Tokens::T_RETURN:
118 32
            case Tokens::T_ELSE:
119 32
            case Tokens::T_ELSEIF:
120 32
            case Tokens::T_DEFAULT:
121 32
            case Tokens::T_BREAK:
122 32
            case Tokens::T_CONTINUE:
123 32
            case Tokens::T_SWITCH:
124 32
            case Tokens::T_YIELD:
125 32
            case Tokens::T_FUNCTION:
126 32
            case Tokens::T_IF:
127 32
            case Tokens::T_ENDSWITCH:
128 32
            case Tokens::T_FINALLY:
129 32
            case Tokens::T_FOR:
130 32
            case Tokens::T_FOREACH:
131 32
            case Tokens::T_DECLARE:
132 32
            case Tokens::T_CASE:
133 32
            case Tokens::T_DO:
134 32
            case Tokens::T_WHILE:
135 32
            case Tokens::T_AS:
136 32
            case Tokens::T_CATCH:
137
            //case Tokens::T_DIE:
138 32
            case Tokens::T_SELF:
139 32
            case Tokens::T_PARENT:
140 12
                return true;
141
        }
142 26
        return parent::isConstantName($tokenType);
143
    }
144
145
    /**
146
     * @param integer $tokenType
147
     * @return bool
148
     */
149 26
    protected function isMethodName($tokenType)
150
    {
151
        switch ($tokenType) {
152 26
            case Tokens::T_CLASS:
153 2
                return true;
154
        }
155 26
        return $this->isConstantName($tokenType);
156
    }
157
158
    /**
159
     * @return \PDepend\Source\AST\ASTNode
160
     */
161 6 View Code Duplication
    protected function parsePostfixIdentifier()
162
    {
163 6
        $tokenType = $this->tokenizer->peek();
164 6
        switch (true) {
165 6
            case ($this->isConstantName($tokenType)):
166 4
                $node = $this->parseLiteral();
167 4
                break;
168 2
            default:
169 2
                $node = parent::parsePostfixIdentifier();
170 2
                break;
171 2
        }
172 6
        return $this->parseOptionalIndexExpression($node);
173
    }
174
175
    /**
176
     * @param \PDepend\Source\AST\AbstractASTCallable $callable
177
     * @return \PDepend\Source\AST\AbstractASTCallable
178
     */
179 68 View Code Duplication
    protected function parseCallableDeclarationAddition($callable)
180
    {
181 68
        $this->consumeComments();
182 68
        if (Tokens::T_COLON != $this->tokenizer->peek()) {
183 54
            return $callable;
184
        }
185
186 28
        $this->consumeToken(Tokens::T_COLON);
187
188 28
        $type = $this->parseReturnTypeHint();
189 28
        $callable->addChild($type);
0 ignored issues
show
It seems like $type defined by $this->parseReturnTypeHint() on line 188 can also be of type false or null; however, PDepend\Source\AST\AbstractASTCallable::addChild() does only seem to accept object<PDepend\Source\AST\ASTNode>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
190
191 28
        return $callable;
192
    }
193
194
    /**
195
     * @return \PDepend\Source\AST\ASTType
196
     */
197 28
    protected function parseReturnTypeHint()
198
    {
199 28
        $this->consumeComments();
200
201 28
        switch ($tokenType = $this->tokenizer->peek()) {
202 28
            case Tokens::T_ARRAY:
203 4
                $type = $this->parseArrayType();
204 4
                break;
205 24
            case Tokens::T_SELF:
206
                $type = $this->parseSelfType();
207
                break;
208 24
            case Tokens::T_PARENT:
209
                $type = $this->parseParentType();
210
                break;
211 24
            default:
212 24
                $type = $this->parseTypeHint();
213 24
                break;
214 28
        }
215 28
        return $type;
216
    }
217
218
    /**
219
     * Parses a type hint that is valid in the supported PHP version.
220
     *
221
     * @return \PDepend\Source\AST\ASTNode
222
     * @since 2.3
223
     */
224 32
    protected function parseTypeHint()
225
    {
226 32
        switch ($this->tokenizer->peek()) {
227 32
            case Tokens::T_STRING:
228 32
            case Tokens::T_BACKSLASH:
229 32
            case Tokens::T_NAMESPACE:
230 28
                $name = $this->parseQualifiedName();
231
232 28
                if ($this->isScalarOrCallableTypeHint($name)) {
233 24
                    $type = $this->parseScalarOrCallableTypeHint($name);
234 24
                } else {
235 4
                    $type = $this->builder->buildAstClassOrInterfaceReference($name);
236
                }
237 28
                break;
238 4
            default:
239 4
                $type = parent::parseTypeHint();
240 4
                break;
241 32
        }
242 32
        return $type;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $type; (PDepend\Source\AST\ASTSc...InterfaceReference|null) is incompatible with the return type of the parent method PDepend\Source\Language\...ersion54::parseTypeHint of type PDepend\Source\AST\ASTCl...rce\AST\ASTTypeCallable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
243
    }
244
245
    /**
246
     * Parses any expression that is surrounded by an opening and a closing
247
     * parenthesis
248
     *
249
     * @return \PDepend\Source\AST\ASTExpression
250
     */
251 6
    protected function parseParenthesisExpression()
252
    {
253 6
        $this->tokenStack->push();
254 6
        $this->consumeComments();
255
256 6
        $expr = $this->builder->buildAstExpression();
257 6
        $expr = $this->parseBraceExpression(
258 6
            $expr,
259 6
            $this->consumeToken(Tokens::T_PARENTHESIS_OPEN),
260
            Tokens::T_PARENTHESIS_CLOSE
261 6
        );
262
        
263 6
        while ($this->tokenizer->peek() === Tokens::T_PARENTHESIS_OPEN) {
264 4
            $function = $this->builder->buildAstFunctionPostfix($expr->getImage());
265 4
            $function->addChild($expr);
266 4
            $function->addChild($this->parseArguments());
267 4
            $expr = $function;
268 4
        }
269
        
270 6
        return $this->setNodePositionsAndReturn($expr);
271
    }
272
273
    /**
274
     * Tests if the given image is a PHP 7 type hint.
275
     *
276
     * @param string $image
277
     * @return boolean
278
     */
279 38
    protected function isScalarOrCallableTypeHint($image)
280
    {
281 38
        switch (strtolower($image)) {
282 38
            case 'int':
283 38
            case 'bool':
284 38
            case 'float':
285 38
            case 'string':
286 38
            case 'callable':
287 24
                return true;
288 14
        }
289
290 14
        return false;
291
    }
292
293
    /**
294
     * Parses a scalar type hint or a callable type hint.
295
     *
296
     * @param string $image
297
     * @return \PDepend\Source\AST\ASTType
298
     */
299 24
    protected function parseScalarOrCallableTypeHint($image)
300
    {
301 24
        switch (strtolower($image)) {
302 24
            case 'int':
303 24
            case 'bool':
304 24
            case 'float':
305 24
            case 'string':
306 24
                return $this->builder->buildAstScalarType($image);
307
            case 'callable':
308
                return $this->builder->buildAstTypeCallable();
309
        }
310
311
        return false;
312
    }
313
314
    /**
315
     * Parse the type reference used in an allocation expression.
316
     *
317
     * @param \PDepend\Source\AST\ASTAllocationExpression $allocation
318
     * @return \PDepend\Source\AST\ASTNode
319
     * @since 2.3
320
     */
321 12
    protected function parseAllocationExpressionTypeReference(ASTAllocationExpression $allocation)
322
    {
323 12
        if ($newAllocation = $this->parseAnonymousClassDeclaration($allocation)) {
324 8
            return $newAllocation;
325
        }
326 6
        return parent::parseAllocationExpressionTypeReference($allocation);
327
    }
328
329
    /**
330
     * Attempts to the next sequence of tokens as an anonymous class and adds it to the allocation expression
331
     *
332
     * @param \PDepend\Source\AST\ASTAllocationExpression $allocation
333
     *
334
     * @return null|\PDepend\Source\AST\ASTAnonymousClass
335
     */
336 12
    protected function parseAnonymousClassDeclaration(ASTAllocationExpression $allocation)
337
    {
338 12
        $this->consumeComments();
339 12
        if (Tokens::T_CLASS !== $this->tokenizer->peek()) {
340 6
            return null;
341
        }
342
343 8
        $classOrInterface = $this->classOrInterface;
344
345 8
        $this->tokenStack->push();
346
347 8
        $this->consumeToken(Tokens::T_CLASS);
348 8
        $this->consumeComments();
349
350 8
        $class = $this->builder->buildAnonymousClass();
351 8
        $class->setName(
352 8
            sprintf(
353 8
                'class@anonymous%s0x%s',
354 8
                $this->compilationUnit->getFileName(),
355 8
                uniqid('')
356 8
            )
357 8
        );
358 8
        $class->setCompilationUnit($this->compilationUnit);
359 8
        $class->setUserDefined();
360
361 8
        if ($this->isNextTokenArguments()) {
362 8
            $class->addChild($this->parseArguments());
363 8
        }
364
365 8
        $this->consumeComments();
366 8
        $tokenType = $this->tokenizer->peek();
367
368 8 View Code Duplication
        if ($tokenType === Tokens::T_EXTENDS) {
369
            $class = $this->parseClassExtends($class);
370
371
            $this->consumeComments();
372
            $tokenType = $this->tokenizer->peek();
373
        }
374
375 8
        if ($tokenType === Tokens::T_IMPLEMENTS) {
376
            $this->consumeToken(Tokens::T_IMPLEMENTS);
377
            $this->parseInterfaceList($class);
378
        }
379
380 8
        $allocation->addChild(
381 8
            $this->setNodePositionsAndReturn(
382 8
                $this->parseTypeBody($class),
383
                $tokens
384 8
            )
385 8
        );
386 8
        $class->setTokens($tokens);
387
388 8
        $this->classOrInterface = $classOrInterface;
389
390 8
        return $allocation;
391
    }
392
393
    /**
394
     * @param \PDepend\Source\AST\ASTNode $node
395
     * @return \PDepend\Source\AST\ASTNode
396
     */
397 32
    protected function parseOptionalMemberPrimaryPrefix(ASTNode $node)
398
    {
399 32
        $this->consumeComments();
400 32
        if (Tokens::T_DOUBLE_COLON === $this->tokenizer->peek()) {
401 2
            return $this->parseStaticMemberPrimaryPrefix($node);
402
        }
403 32
        if ($this->tokenizer->peek() === Tokens::T_OBJECT_OPERATOR) {
404
            return $this->parseMemberPrimaryPrefix($node);
405
        }
406 32
        return $node;
407
    }
408
409
    /**
410
     * @param \PDepend\Source\AST\ASTExpression $expr
411
     * @return \PDepend\Source\AST\ASTExpression
412
     */
413 6
    protected function parseParenthesisExpressionOrPrimaryPrefixForVersion(ASTExpression $expr)
414
    {
415 6
        $this->consumeComments();
416 6
        if (Tokens::T_DOUBLE_COLON === $this->tokenizer->peek()) {
417 2
            return $this->parseStaticMemberPrimaryPrefix($expr->getChild(0));
418
        }
419 6
        if ($this->tokenizer->peek() === Tokens::T_OBJECT_OPERATOR) {
420
            $node = count($expr->getChildren()) === 0 ? $expr : $expr->getChild(0);
421
            return $this->parseMemberPrimaryPrefix($node);
422
        }
423 6
        return $expr;
424
    }
425
426
    /**
427
     * This method will be called when the base parser cannot handle an expression
428
     * in the base version. In this method you can implement version specific
429
     * expressions.
430
     *
431
     * @return \PDepend\Source\AST\ASTNode
432
     * @throws \PDepend\Source\Parser\UnexpectedTokenException
433
     * @since 2.3
434
     */
435 8
    protected function parseOptionalExpressionForVersion()
436
    {
437 8
        if ($expression = $this->parseExpressionVersion70()) {
438 8
            return $expression;
439
        }
440
        return parent::parseOptionalExpressionForVersion();
441
    }
442
443
    /**
444
     * In this method we implement parsing of PHP 7.0 specific expressions.
445
     *
446
     * @return \PDepend\Source\AST\ASTNode
447
     * @since 2.3
448
     */
449 8 View Code Duplication
    protected function parseExpressionVersion70()
450
    {
451 8
        $this->consumeComments();
452 8
        $nextTokenType = $this->tokenizer->peek();
453
454
        switch ($nextTokenType) {
455 8
            case Tokens::T_SPACESHIP:
456 8
            case Tokens::T_COALESCE:
457 8
                $token = $this->consumeToken($nextTokenType);
458
459 8
                $expr = $this->builder->buildAstExpression($token->image);
460 8
                $expr->configureLinesAndColumns(
461 8
                    $token->startLine,
462 8
                    $token->endLine,
463 8
                    $token->startColumn,
464 8
                    $token->endColumn
465 8
                );
466
467 8
                return $expr;
468
        }
469
    }
470
471
    /**
472
     * This method will parse a formal parameter. A formal parameter is at least
473
     * a variable name, but can also contain a default parameter value.
474
     *
475
     * <code>
476
     * //               --  -------
477
     * function foo(Bar $x, $y = 42) {}
478
     * //               --  -------
479
     * </code>
480
     *
481
     * @return \PDepend\Source\AST\ASTFormalParameter
482
     * @since 2.0.7
483
     */
484 16 View Code Duplication
    protected function parseFormalParameter()
485
    {
486 16
        $parameter = $this->builder->buildAstFormalParameter();
487
488 16
        if (Tokens::T_ELLIPSIS === $this->tokenizer->peek()) {
489
            $this->consumeToken(Tokens::T_ELLIPSIS);
490
            $this->consumeComments();
491
492
            $parameter->setVariableArgList();
493
        }
494
495 16
        $parameter->addChild($this->parseVariableDeclarator());
496
497 16
        return $parameter;
498
    }
499
500
    /**
501
     * @param array $fragments
502
     * @return void
503
     */
504 2
    protected function parseUseDeclarationForVersion(array $fragments)
505
    {
506 2
        if (Tokens::T_CURLY_BRACE_OPEN === $this->tokenizer->peek()) {
507 2
            $this->parseUseDeclarationVersion70($fragments);
508 2
            return;
509
        }
510
        parent::parseUseDeclarationForVersion($fragments);
511
    }
512
513
    /**
514
     * @param array $fragments
515
     * @return void
516
     */
517 2
    protected function parseUseDeclarationVersion70(array $fragments)
518
    {
519 2
        $namespacePrefixReplaced = $this->namespacePrefixReplaced;
520
521 2
        $this->consumeToken(Tokens::T_CURLY_BRACE_OPEN);
522 2
        $this->consumeComments();
523
524
        do {
525 2
            $nextToken = $this->tokenizer->peek();
526
            switch ($nextToken) {
527 2
                case Tokens::T_CONST:
528 2
                case Tokens::T_FUNCTION:
529 2
                    $this->consumeToken($nextToken);
530 2
            }
531
532 2
            $subFragments = $this->parseQualifiedNameRaw();
533 2
            $this->consumeComments();
534
535 2
            $image = $this->parseNamespaceImage($subFragments);
536
537 2
            if (Tokens::T_COMMA != $this->tokenizer->peek()) {
538 2
                break;
539
            }
540
541 2
            $this->consumeToken(Tokens::T_COMMA);
542 2
            $this->consumeComments();
543
544
            // Add mapping between image and qualified name to symbol table
545 2
            $this->useSymbolTable->add($image, join('', array_merge($fragments, $subFragments)));
546 2
        } while (true);
547
548 2
        $this->useSymbolTable->add($image, join('', array_merge($fragments, $subFragments)));
549
550 2
        $this->consumeToken(Tokens::T_CURLY_BRACE_CLOSE);
551 2
        $this->consumeComments();
552
553 2
        $this->namespacePrefixReplaced = $namespacePrefixReplaced;
554 2
    }
555
556
    /**
557
     * @param array $previousElements
558
     * @return string|null
559
     */
560 8
    protected function parseQualifiedNameElement(array $previousElements)
561
    {
562 8
        if (Tokens::T_CURLY_BRACE_OPEN !== $this->tokenizer->peek()) {
563 8
            return parent::parseQualifiedNameElement($previousElements);
564
        }
565 2
        if (count($previousElements) >= 2 && '\\' === end($previousElements)) {
566 2
            return null;
567
        }
568
        throw $this->getUnexpectedTokenException($this->tokenizer->next());
569
    }
570
}
571