Passed
Push — master ( 62af2e...d65fca )
by Théo
07:27 queued 04:04
created

NameStmtPrefixer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 3
dl 0
loc 9
ccs 4
cts 4
cp 1
crap 1
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the humbug/php-scoper package.
7
 *
8
 * Copyright (c) 2017 Théo FIDRY <[email protected]>,
9
 *                    Pádraic Brady <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Humbug\PhpScoper\PhpParser\NodeVisitor;
16
17
use Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver\FullyQualifiedNameResolver;
18
use Humbug\PhpScoper\Reflector;
19
use PhpParser\Node;
20
use PhpParser\Node\Expr\ClassConstFetch;
21
use PhpParser\Node\Expr\ConstFetch;
22
use PhpParser\Node\Expr\FuncCall;
23
use PhpParser\Node\Expr\Instanceof_;
24
use PhpParser\Node\Expr\New_;
25
use PhpParser\Node\Expr\StaticCall;
26
use PhpParser\Node\Name;
27
use PhpParser\Node\Name\FullyQualified;
28
use PhpParser\Node\NullableType;
29
use PhpParser\Node\Param;
30
use PhpParser\Node\Stmt\Catch_;
31
use PhpParser\Node\Stmt\Class_;
32
use PhpParser\Node\Stmt\ClassMethod;
33
use PhpParser\Node\Stmt\Function_;
34
use PhpParser\Node\Stmt\Interface_;
35
use PhpParser\NodeVisitorAbstract;
36
use function in_array;
37
38
/**
39
 * ```
40
 * new Foo\Bar();
41
 * ```.
42
 *
43
 * =>
44
 *
45
 * ```
46
 * new \Humbug\Foo\Bar();
47
 * ```
48
 *
49
 * @private
50
 */
51
final class NameStmtPrefixer extends NodeVisitorAbstract
52
{
53
    public const PHP_FUNCTION_KEYWORDS = [
54
        'self',
55
        'static',
56
        'parent',
57
    ];
58
59
    private $prefix;
60
    private $nameResolver;
61
    private $reflector;
62
63
    /**
64
     * @param string                     $prefix
65
     * @param FullyQualifiedNameResolver $nameResolver
66
     * @param Reflector                  $reflector
67
     */
68 371
    public function __construct(
69
        string $prefix,
70
        FullyQualifiedNameResolver $nameResolver,
71
        Reflector $reflector
72
    ) {
73 371
        $this->prefix = $prefix;
74 371
        $this->nameResolver = $nameResolver;
75 371
        $this->reflector = $reflector;
76
    }
77
78
    /**
79
     * @inheritdoc
80
     */
81 370
    public function enterNode(Node $node): Node
82
    {
83 370
        return ($node instanceof Name && AppendParentNode::hasParent($node))
84 368
            ? $this->prefixName($node)
85 370
            : $node
86
        ;
87
    }
88
89 368
    private function prefixName(Name $name): Node
90
    {
91 368
        $parentNode = AppendParentNode::getParent($name);
92
93 368
        if ($parentNode instanceof NullableType) {
94 4
            if (false === AppendParentNode::hasParent($parentNode)) {
95
                return $name;
96
            }
97
98 4
            $parentNode = AppendParentNode::getParent($parentNode);
99
        }
100
101
        if (false === (
102 368
                $parentNode instanceof ConstFetch
103 368
                || $parentNode instanceof ClassConstFetch
104 368
                || $parentNode instanceof Param
105 368
                || $parentNode instanceof FuncCall
106 368
                || $parentNode instanceof StaticCall
107 368
                || $parentNode instanceof Function_
108 368
                || $parentNode instanceof ClassMethod
109 368
                || $parentNode instanceof New_
110 368
                || $parentNode instanceof Class_
111 368
                || $parentNode instanceof Interface_
112 368
                || $parentNode instanceof Catch_
113 368
                || $parentNode instanceof Instanceof_
114
            )
115
        ) {
116 368
            return $name;
117
        }
118
119
        if (
120
            (
121 277
                $parentNode instanceof FuncCall
122 277
                || $parentNode instanceof StaticCall
123 277
                || $parentNode instanceof ClassConstFetch
124 277
                || $parentNode instanceof New_
125 277
                || $parentNode instanceof Param
126 277
                || $parentNode instanceof Catch_
127 277
                || $parentNode instanceof Instanceof_
128
            )
129 277
            && in_array((string) $name, self::PHP_FUNCTION_KEYWORDS, true)
130
        ) {
131 8
            return $name;
132
        }
133
134 275
        if ($parentNode instanceof ConstFetch && 'null' === (string) $name) {
135 2
            return $name;
136
        }
137
138 275
        $resolvedValue = $this->nameResolver->resolveName($name);
139
140 275
        $resolvedName = $resolvedValue->getName();
141
142
        // Skip if is already prefixed
143 275
        if ($this->prefix === $resolvedName->getFirst()) {
144 4
            return $resolvedName;
145
        }
146
147
        // Check if the class can be prefixed
148 275
        if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall)) {
149 188
            if ($this->reflector->isClassInternal($resolvedName->toString())) {
150 45
                return $resolvedName;
151
            }
152
        }
153
154
        // Constants have a fallback autoloading so we cannot prefix them when the name is ambiguous
155
        // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
156 247
        if ($parentNode instanceof ConstFetch) {
157 99
            if ($this->reflector->isConstantInternal($resolvedName->toString())) {
158 65
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
159
            }
160
161 34
            if (false === ($resolvedName instanceof FullyQualified)) {
162 1
                return $resolvedName;
163
            }
164
        }
165
166
        // Functions have a fallback autoloading so we cannot prefix them when the name is ambiguous
167
        // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
168 237
        if ($parentNode instanceof FuncCall) {
169 90
            if ($this->reflector->isFunctionInternal($resolvedName->toString())) {
170 57
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
171
            }
172
173 34
            if (false === ($resolvedName instanceof FullyQualified)) {
174 2
                return $resolvedName;
175
            }
176
        }
177
178 223
        if ('self' === (string) $resolvedName && $parentNode instanceof ClassMethod) {
179 3
            return $name;
180
        }
181
182 223
        return FullyQualified::concat(
183 223
            $this->prefix,
184 223
            $resolvedName->toString(),
185 223
            $resolvedName->getAttributes()
186
        );
187
    }
188
}
189