Passed
Pull Request — master (#460)
by Gena
11:10
created

NameStmtPrefixer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 6
dl 0
loc 14
ccs 5
cts 5
cp 1
crap 1
rs 10
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\Node\FullyQualifiedFactory;
18
use Humbug\PhpScoper\PhpParser\NodeVisitor\NamespaceStmt\NamespaceStmtCollection;
19
use Humbug\PhpScoper\PhpParser\NodeVisitor\Resolver\FullyQualifiedNameResolver;
20
use Humbug\PhpScoper\PhpParser\NodeVisitor\UseStmt\UseStmtCollection;
21
use Humbug\PhpScoper\Reflector;
22
use Humbug\PhpScoper\Whitelist;
23
use PhpParser\Node;
24
use PhpParser\Node\Expr\ArrowFunction;
25
use PhpParser\Node\Expr\ClassConstFetch;
26
use PhpParser\Node\Expr\ConstFetch;
27
use PhpParser\Node\Expr\FuncCall;
28
use PhpParser\Node\Expr\Instanceof_;
29
use PhpParser\Node\Expr\New_;
30
use PhpParser\Node\Expr\StaticCall;
31
use PhpParser\Node\Expr\StaticPropertyFetch;
32
use PhpParser\Node\Name;
33
use PhpParser\Node\Name\FullyQualified;
34
use PhpParser\Node\NullableType;
35
use PhpParser\Node\Param;
36
use PhpParser\Node\Stmt\Catch_;
37
use PhpParser\Node\Stmt\Class_;
38
use PhpParser\Node\Stmt\ClassMethod;
39
use PhpParser\Node\Stmt\Function_;
40
use PhpParser\Node\Stmt\Interface_;
41
use PhpParser\Node\Stmt\Property;
42
use PhpParser\NodeVisitorAbstract;
43
use function array_merge;
44
use function count;
45
use function in_array;
46
47
/**
48
 * Prefixes names when appropriate.
49
 *
50
 * ```
51
 * new Foo\Bar();
52
 * ```.
53
 *
54
 * =>
55
 *
56
 * ```
57
 * new \Humbug\Foo\Bar();
58
 * ```
59
 *
60
 * @private
61
 */
62
final class NameStmtPrefixer extends NodeVisitorAbstract
63
{
64
    public const PHP_FUNCTION_KEYWORDS = [
65
        'self',
66
        'static',
67
        'parent',
68 549
    ];
69
70
    private $prefix;
71
    private $whitelist;
72
    private $namespaceStatements;
73
    private $useStatements;
74 549
    private $nameResolver;
75 549
    private $reflector;
76 549
77 549
    public function __construct(
78
        string $prefix,
79
        Whitelist $whitelist,
80
        NamespaceStmtCollection $namespaceStatements,
81
        UseStmtCollection $useStatements,
82
        FullyQualifiedNameResolver $nameResolver,
83 548
        Reflector $reflector
84
    ) {
85 548
        $this->prefix = $prefix;
86 544
        $this->whitelist = $whitelist;
87 548
        $this->namespaceStatements = $namespaceStatements;
88
        $this->useStatements = $useStatements;
89
        $this->nameResolver = $nameResolver;
90
        $this->reflector = $reflector;
91 544
    }
92
93 544
    /**
94
     * @inheritdoc
95 544
     */
96 4
    public function enterNode(Node $node): Node
97
    {
98
        return ($node instanceof Name && ParentNodeAppender::hasParent($node))
99
            ? $this->prefixName($node)
100 4
            : $node
101
        ;
102
    }
103
104 544
    private function prefixName(Name $name): Node
105 542
    {
106 541
        $parentNode = ParentNodeAppender::getParent($name);
107 540
108 540
        if ($parentNode instanceof NullableType) {
109 538
            if (false === ParentNodeAppender::hasParent($parentNode)) {
110 538
                return $name;
111 538
            }
112 538
113 538
            $parentNode = ParentNodeAppender::getParent($parentNode);
114 538
        }
115 538
116 544
        if (false === (
117
            $parentNode instanceof ArrowFunction
118
                || $parentNode instanceof Catch_
119 537
                || $parentNode instanceof ConstFetch
120
                || $parentNode instanceof Class_
121
                || $parentNode instanceof ClassConstFetch
122
                || $parentNode instanceof ClassMethod
123
                || $parentNode instanceof FuncCall
124 405
                || $parentNode instanceof Function_
125 349
                || $parentNode instanceof Instanceof_
126 298
                || $parentNode instanceof Interface_
127 236
                || $parentNode instanceof New_
128 185
                || $parentNode instanceof Param
129 133
                || $parentNode instanceof Property
130 131
                || $parentNode instanceof StaticCall
131 120
                || $parentNode instanceof StaticPropertyFetch
132
        )
133 320
        ) {
134
            return $name;
135 9
        }
136
137
        if (
138 403
            (
139 4
                $parentNode instanceof Catch_
140
                || $parentNode instanceof ClassConstFetch
141
                || $parentNode instanceof New_
142 403
                || $parentNode instanceof FuncCall
143
                || $parentNode instanceof Instanceof_
144 403
                || $parentNode instanceof Param
145
                || $parentNode instanceof StaticCall
146 403
                || $parentNode instanceof StaticPropertyFetch
147 403
            )
148
            && in_array((string) $name, self::PHP_FUNCTION_KEYWORDS, true)
149 23
        ) {
150
            return $name;
151
        }
152
153 392
        if ($parentNode instanceof ConstFetch && 'null' === (string) $name) {
154 257
            return $name;
155
        }
156 61
157
        $resolvedName = $this->nameResolver->resolveName($name)->getName();
158
159 355
        // Do not prefix if there is a matching use statement.
160 85
        $useStatement = $this->useStatements->findStatementForNode($this->namespaceStatements->findNamespaceForNode($name), $name);
161 21
        if (
162
            $useStatement !== null and
163
            self::array_starts_with($resolvedName->parts, $useStatement->parts) and
164 67
            !($parentNode instanceof ConstFetch and ($this->whitelist->isGlobalWhitelistedConstant($resolvedName->toString()) or $this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true))) and
165 19
            !($useStatement->getAttribute('parent') and $useStatement->getAttribute('parent')->alias !== null and $this->whitelist->isSymbolWhitelisted($useStatement->toString())) and
166
            !($name instanceof FullyQualified) and
167
            $resolvedName->parts !== ['Isolated', 'Symfony', 'Component', 'Finder', 'Finder']
168
        ) {
169
            return $name;
170 48
        }
171 8
172
        if ($this->prefix === $resolvedName->getFirst() // Skip if is already prefixed
173
            || $this->whitelist->belongsToWhitelistedNamespace((string) $resolvedName)  // Skip if the namespace node is whitelisted
174 46
        ) {
175
            return $resolvedName;
176 11
        }
177
178
        // Do not prefix if the Name is inside of the current namespace
179
        $namespace = $this->namespaceStatements->getCurrentNamespaceName();
180
        if (
181
            (
182
                // In a namespace
183
                $namespace !== null and
184 320
                array_merge($namespace->parts, $name->parts) === $resolvedName->parts
185 76
            ) or
186 37
            (
187
                // In the global scope
188
                $namespace === null and
189 54
                $name->parts === $resolvedName->parts and
190 9
                !($name instanceof FullyQualified) and
191
                !($parentNode instanceof ConstFetch) and
192
                !$this->whitelist->isSymbolWhitelisted($resolvedName->toString()) and
193
                !$this->reflector->isFunctionInternal($resolvedName->toString()) and
194 297
                !$this->reflector->isClassInternal($resolvedName->toString())
195 3
            )
196
        ) {
197
            return $name;
198 297
        }
199 297
200 297
        // Check if the class can be prefixed
201 297
        if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall)
202
            && $this->reflector->isClassInternal($resolvedName->toString())
203
        ) {
204
            return $resolvedName;
205
        }
206
207
        if ($parentNode instanceof ConstFetch) {
208
            if ($this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)) {
209
                return $resolvedName;
210
            }
211
212
            if ($this->reflector->isConstantInternal($resolvedName->toString())) {
213
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
214
            }
215
216
            // Constants have an autoloading fallback so we cannot prefix them when the name is ambiguous
217
            // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
218
            if (false === ($resolvedName instanceof FullyQualified)) {
219
                return $resolvedName;
220
            }
221
222
            if ($this->whitelist->isGlobalWhitelistedConstant((string) $resolvedName)) {
223
                // Unlike classes & functions, whitelisted are not prefixed with aliases registered in scoper-autoload.php
224
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
225
            }
226
227
            // Continue
228
        }
229
230
        // Functions have a fallback autoloading so we cannot prefix them when the name is ambiguous
231
        // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
232
        if ($parentNode instanceof FuncCall) {
233
            if ($this->reflector->isFunctionInternal($resolvedName->toString())) {
234
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
235
            }
236
237
            if (false === ($resolvedName instanceof FullyQualified)) {
238
                return $resolvedName;
239
            }
240
        }
241
242
        if ('self' === (string) $resolvedName && $parentNode instanceof ClassMethod) {
243
            return $name;
244
        }
245
246
        return FullyQualifiedFactory::concat(
247
            $this->prefix,
248
            $resolvedName->toString(),
249
            $resolvedName->getAttributes()
250
        );
251
    }
252
253
    private static function array_starts_with($arr, $prefix): bool
254
    {
255
        for ($i = 0; $i < count($prefix); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
256
            if ($arr[$i] !== $prefix[$i]) {
257
                return false;
258
            }
259
        }
260
        return true;
261
    }
262
}
263