Passed
Pull Request — master (#400)
by Théo
02:47 queued 01:01
created

NameStmtPrefixer::prefixName()   F

Complexity

Conditions 102
Paths > 20000

Size

Total Lines 251
Code Lines 155

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 102

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 102
eloc 155
c 1
b 0
f 0
nc 414822407
nop 1
dl 0
loc 251
ccs 52
cts 52
cp 1
crap 102
rs 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
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\NodeVisitor\NameResolver;
24
use PhpParser\Node;
25
use PhpParser\Node\Expr\ArrowFunction;
26
use PhpParser\Node\Expr\ClassConstFetch;
27
use PhpParser\Node\Expr\ConstFetch;
28
use PhpParser\Node\Expr\FuncCall;
29
use PhpParser\Node\Expr\Instanceof_;
30
use PhpParser\Node\Expr\New_;
31
use PhpParser\Node\Expr\StaticCall;
32
use PhpParser\Node\Expr\StaticPropertyFetch;
33
use PhpParser\Node\Name;
34
use PhpParser\Node\Name\FullyQualified;
35
use PhpParser\Node\NullableType;
36
use PhpParser\Node\Param;
37
use PhpParser\Node\Stmt\Catch_;
38
use PhpParser\Node\Stmt\Class_;
39
use PhpParser\Node\Stmt\ClassMethod;
40
use PhpParser\Node\Stmt\Function_;
41
use PhpParser\Node\Stmt\Interface_;
42
use PhpParser\Node\Stmt\Property;
43
use PhpParser\NodeVisitorAbstract;
44
use function array_merge;
45
use function count;
46
use function in_array;
47
use function xdebug_break;
48
49
/**
50
 * Prefixes names when appropriate.
51
 *
52
 * ```
53
 * new Foo\Bar();
54
 * ```.
55
 *
56
 * =>
57
 *
58
 * ```
59
 * new \Humbug\Foo\Bar();
60
 * ```
61
 *
62
 * @private
63
 */
64
final class NameStmtPrefixer extends NodeVisitorAbstract
65
{
66
    public const PHP_FUNCTION_KEYWORDS = [
67
        'self',
68 549
        'static',
69
        'parent',
70
    ];
71
72
    private string $prefix;
73
    private Whitelist $whitelist;
74 549
    private NamespaceStmtCollection $namespaceStatements;
75 549
    private UseStmtCollection $useStatements;
76 549
    private FullyQualifiedNameResolver $nameResolver;
77 549
    private NameResolver $newNameResolver;
78
    private Reflector $reflector;
79
80
    public function __construct(
81
        string $prefix,
82
        Whitelist $whitelist,
83 548
        NamespaceStmtCollection $namespaceStatements,
84
        UseStmtCollection $useStatements,
85 548
        FullyQualifiedNameResolver $nameResolver,
86 544
        NameResolver $newNameResolver,
87 548
        Reflector $reflector
88
    ) {
89
        $this->prefix = $prefix;
90
        $this->whitelist = $whitelist;
91 544
        $this->namespaceStatements = $namespaceStatements;
92
        $this->useStatements = $useStatements;
93 544
        $this->nameResolver = $nameResolver;
94
        $this->newNameResolver = $newNameResolver;
95 544
        $this->reflector = $reflector;
96 4
    }
97
98
    public function enterNode(Node $node): Node
99
    {
100 4
        return ($node instanceof Name && ParentNodeAppender::hasParent($node))
101
            ? $this->prefixName($node)
102
            : $node
103
        ;
104 544
    }
105 542
106 541
    private function prefixName(Name $name): Node
107 540
    {
108 540
        $parentNode = ParentNodeAppender::getParent($name);
109 538
110 538
        if ($parentNode instanceof NullableType) {
111 538
            if (false === ParentNodeAppender::hasParent($parentNode)) {
112 538
                return $name;
113 538
            }
114 538
115 538
            $parentNode = ParentNodeAppender::getParent($parentNode);
116 544
        }
117
118
        if (false === (
119 537
            $parentNode instanceof ArrowFunction
120
                || $parentNode instanceof Catch_
121
                || $parentNode instanceof ConstFetch
122
                || $parentNode instanceof Class_
123
                || $parentNode instanceof ClassConstFetch
124 405
                || $parentNode instanceof ClassMethod
125 349
                || $parentNode instanceof FuncCall
126 298
                || $parentNode instanceof Function_
127 236
                || $parentNode instanceof Instanceof_
128 185
                || $parentNode instanceof Interface_
129 133
                || $parentNode instanceof New_
130 131
                || $parentNode instanceof Param
131 120
                || $parentNode instanceof Property
132
                || $parentNode instanceof StaticCall
133 320
                || $parentNode instanceof StaticPropertyFetch
134
        )
135 9
        ) {
136
            return $name;
137
        }
138 403
139 4
        if (
140
            (
141
                $parentNode instanceof Catch_
142 403
                || $parentNode instanceof ClassConstFetch
143
                || $parentNode instanceof New_
144 403
                || $parentNode instanceof FuncCall
145
                || $parentNode instanceof Instanceof_
146 403
                || $parentNode instanceof Param
147 403
                || $parentNode instanceof StaticCall
148
                || $parentNode instanceof StaticPropertyFetch
149 23
            )
150
            && in_array((string) $name, self::PHP_FUNCTION_KEYWORDS, true)
151
        ) {
152
            return $name;
153 392
        }
154 257
155
        if ($parentNode instanceof ConstFetch && 'null' === (string) $name) {
156 61
            return $name;
157
        }
158
159 355
        $oldResolvedName = $this->nameResolver->resolveName($name)->getName();
160 85
161 21
        $nameType = Node\Stmt\Use_::TYPE_UNKNOWN;
162
        if (ParentNodeAppender::getParent($name) instanceof ConstFetch) {
163
            $nameType = Node\Stmt\Use_::TYPE_CONSTANT;
164 67
        }
165 19
166
        $resolvedName = $this->newNameResolver
167
            ->getNameContext()
168
            ->getResolvedName(
169
                $name,
170 48
                $nameType,
171 8
            );
172
173
        if (null === $resolvedName) {
174 46
            $resolvedName = $name;
175
        }
176 11
177
        if ((string) $oldResolvedName !== (string) $resolvedName) {
178
            $x = '';
179
        }
180
181
        // Do not prefix if there is a matching use statement.
182
        $useStatement = $this->useStatements->findStatementForNode($this->namespaceStatements->findNamespaceForNode($name), $name);
183
        
184 320
        $oldCondition = $useStatement !== null
185 76
            && !($name instanceof FullyQualified)
186 37
            && $oldResolvedName->parts !== ['Isolated', 'Symfony', 'Component', 'Finder', 'Finder']
187
            && self::array_starts_with($oldResolvedName->parts, $useStatement->parts)
188
            && !(
189 54
                $parentNode instanceof ConstFetch
190 9
                && (
191
                    $this->whitelist->isGlobalWhitelistedConstant($oldResolvedName->toString())
192
                    || $this->whitelist->isSymbolWhitelisted($oldResolvedName->toString(), true)
193
                )
194 297
            )
195 3
            && !(
196
                $useStatement->getAttribute('parent')
197
                && $useStatement->getAttribute('parent')->alias !== null
198 297
                && $this->whitelist->isSymbolWhitelisted($useStatement->toString())
199 297
            );
200 297
        
201 297
        $newCondition = $useStatement !== null
202
            && !($name instanceof FullyQualified)
203
            && $resolvedName->parts !== ['Isolated', 'Symfony', 'Component', 'Finder', 'Finder']
204
            && self::array_starts_with($resolvedName->parts, $useStatement->parts)
205
            && !(
206
                $parentNode instanceof ConstFetch
207
                && (
208
                    $this->whitelist->isGlobalWhitelistedConstant($resolvedName->toString())
209
                    || $this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)
210
                )
211
            )
212
            && !(
213
                $useStatement->getAttribute('parent')
214
                && $useStatement->getAttribute('parent')->alias !== null
215
                && $this->whitelist->isSymbolWhitelisted($useStatement->toString())
216
            );
217
218
        if ($oldCondition !== $newCondition) {
219
            $x = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $x is dead and can be removed.
Loading history...
220
        }
221
222
        if (
223
            $useStatement !== null
224
            && !($name instanceof FullyQualified)
225
            && $resolvedName->parts !== ['Isolated', 'Symfony', 'Component', 'Finder', 'Finder']
226
            && self::array_starts_with($resolvedName->parts, $useStatement->parts)
227
            && !(
228
                $parentNode instanceof ConstFetch
229
                && (
230
                    $this->whitelist->isGlobalWhitelistedConstant($resolvedName->toString())
231
                    || $this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)
232
                )
233
            )
234
            && !(
235
                $useStatement->getAttribute('parent')
236
                && $useStatement->getAttribute('parent')->alias !== null
237
                && $this->whitelist->isSymbolWhitelisted($useStatement->toString())
238
            )
239
        ) {
240
            return $name;
241
        }
242
243
        if ($this->prefix === $resolvedName->getFirst() // Skip if is already prefixed
244
            || $this->whitelist->belongsToWhitelistedNamespace((string) $resolvedName)  // Skip if the namespace node is whitelisted
245
        ) {
246
            return $resolvedName;
247
        }
248
249
        // Do not prefix if the Name is inside of the current namespace
250
        $namespace = $this->namespaceStatements->getCurrentNamespaceName();
251
        
252
        $oldCondition = (
253
                // In a namespace
254
                $namespace !== null
255
                && array_merge($namespace->parts, $name->parts) === $oldResolvedName->parts
256
            )
257
            || (
258
                // In the global scope
259
                $namespace === null
260
                && $name->parts === $oldResolvedName->parts
261
                && !($name instanceof FullyQualified)
262
                && !($parentNode instanceof ConstFetch)
263
                && !$this->whitelist->isSymbolWhitelisted($oldResolvedName->toString())
264
                && !$this->reflector->isFunctionInternal($oldResolvedName->toString())
265
                && !$this->reflector->isClassInternal($oldResolvedName->toString())
266
            );
267
        $newCondition = (
268
                // In a namespace
269
                $namespace !== null
270
                && array_merge($namespace->parts, $name->parts) === $resolvedName->parts
271
            )
272
            || (
273
                // In the global scope
274
                $namespace === null
275
                && $name->parts === $resolvedName->parts
276
                && !($name instanceof FullyQualified)
277
                && !($parentNode instanceof ConstFetch)
278
                && !$this->whitelist->isSymbolWhitelisted($resolvedName->toString())
279
                && !$this->reflector->isFunctionInternal($resolvedName->toString())
280
                && !$this->reflector->isClassInternal($resolvedName->toString())
281
            );
282
283
        if ($oldCondition !== $newCondition) {
284
            $x = '';
285
        }
286
287
        if (
288
            (
289
                // In a namespace
290
                $namespace !== null
291
                && array_merge($namespace->parts, $name->parts) === $resolvedName->parts
292
            )
293
            || (
294
                // In the global scope
295
                $namespace === null
296
                && $name->parts === $resolvedName->parts
297
                && !($name instanceof FullyQualified)
298
                && !($parentNode instanceof ConstFetch)
299
                && !$this->whitelist->isSymbolWhitelisted($resolvedName->toString())
300
                && !$this->reflector->isFunctionInternal($resolvedName->toString())
301
                && !$this->reflector->isClassInternal($resolvedName->toString())
302
            )
303
        ) {
304
            return $name;
305
        }
306
307
        // Check if the class can be prefixed
308
        if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall)
309
            && $this->reflector->isClassInternal($resolvedName->toString())
310
        ) {
311
            return $resolvedName;
312
        }
313
314
        if ($parentNode instanceof ConstFetch) {
315
            if ($this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)) {
316
                return $resolvedName;
317
            }
318
319
            if ($this->reflector->isConstantInternal($resolvedName->toString())) {
320
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
321
            }
322
323
            // Constants have an autoloading fallback so we cannot prefix them when the name is ambiguous
324
            // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
325
            if (false === ($resolvedName instanceof FullyQualified)) {
326
                return $resolvedName;
327
            }
328
329
            if ($this->whitelist->isGlobalWhitelistedConstant((string) $resolvedName)) {
330
                // Unlike classes & functions, whitelisted are not prefixed with aliases registered in scoper-autoload.php
331
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
332
            }
333
334
            // Continue
335
        }
336
337
        // Functions have a fallback autoloading so we cannot prefix them when the name is ambiguous
338
        // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
339
        if ($parentNode instanceof FuncCall) {
340
            if ($this->reflector->isFunctionInternal($resolvedName->toString())) {
341
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
342
            }
343
344
            if (false === ($resolvedName instanceof FullyQualified)) {
345
                return $resolvedName;
346
            }
347
        }
348
349
        if ($parentNode instanceof ClassMethod && $resolvedName->isSpecialClassName()) {
350
            return $name;
351
        }
352
353
        return FullyQualifiedFactory::concat(
354
            $this->prefix,
355
            $resolvedName->toString(),
356
            $resolvedName->getAttributes()
357
        );
358
    }
359
360
    private static function array_starts_with($arr, $prefix): bool
361
    {
362
        $prefixLength = count($prefix);
363
        for ($i = 0; $i < $prefixLength; ++$i) {
364
            if ($arr[$i] !== $prefix[$i]) {
365
                return false;
366
            }
367
        }
368
        return true;
369
    }
370
}
371