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

NameStmtPrefixer::prefixName()   F

Complexity

Conditions 101
Paths > 20000

Size

Total Lines 249
Code Lines 152

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 101

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 101
eloc 152
c 1
b 0
f 0
nc 207411207
nop 1
dl 0
loc 249
ccs 52
cts 52
cp 1
crap 101
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
        $resolvedName = $this->newNameResolver
162
            ->getNameContext()
163
            ->getResolvedName(
164 67
                $name,
165 19
                Node\Stmt\Use_::TYPE_UNKNOWN,
166
            );
167
168
        if (null === $resolvedName) {
169
            $resolvedName = $name;
170 48
        }
171 8
172
        if ((string) $oldResolvedName !== (string) $resolvedName) {
173
            // TODO: check those cases if relevant
174 46
            $x = '';
175
        }
176 11
177
        // Do not prefix if there is a matching use statement.
178
        $useStatement = $this->useStatements->findStatementForNode($this->namespaceStatements->findNamespaceForNode($name), $name);
179
        
180
        $oldCondition = $useStatement !== null
181
            && !($name instanceof FullyQualified)
182
            && $oldResolvedName->parts !== ['Isolated', 'Symfony', 'Component', 'Finder', 'Finder']
183
            && self::array_starts_with($oldResolvedName->parts, $useStatement->parts)
184 320
            && !(
185 76
                $parentNode instanceof ConstFetch
186 37
                && (
187
                    $this->whitelist->isGlobalWhitelistedConstant($oldResolvedName->toString())
188
                    || $this->whitelist->isSymbolWhitelisted($oldResolvedName->toString(), true)
189 54
                )
190 9
            )
191
            && !(
192
                $useStatement->getAttribute('parent')
193
                && $useStatement->getAttribute('parent')->alias !== null
194 297
                && $this->whitelist->isSymbolWhitelisted($useStatement->toString())
195 3
            );
196
        
197
        $newCondition = $useStatement !== null
198 297
            && !($name instanceof FullyQualified)
199 297
            && $resolvedName->parts !== ['Isolated', 'Symfony', 'Component', 'Finder', 'Finder']
200 297
            && self::array_starts_with($resolvedName->parts, $useStatement->parts)
201 297
            && !(
202
                $parentNode instanceof ConstFetch
203
                && (
204
                    $this->whitelist->isGlobalWhitelistedConstant($resolvedName->toString())
205
                    || $this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)
206
                )
207
            )
208
            && !(
209
                $useStatement->getAttribute('parent')
210
                && $useStatement->getAttribute('parent')->alias !== null
211
                && $this->whitelist->isSymbolWhitelisted($useStatement->toString())
212
            );
213
214
        if ($oldCondition !== $newCondition) {
215
            // TODO: check those cases if relevant
216
            $x = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $x is dead and can be removed.
Loading history...
217
        }
218
219
        if (
220
            $useStatement !== null
221
            && !($name instanceof FullyQualified)
222
            && $resolvedName->parts !== ['Isolated', 'Symfony', 'Component', 'Finder', 'Finder']
223
            && self::array_starts_with($resolvedName->parts, $useStatement->parts)
224
            && !(
225
                $parentNode instanceof ConstFetch
226
                && (
227
                    $this->whitelist->isGlobalWhitelistedConstant($resolvedName->toString())
228
                    || $this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)
229
                )
230
            )
231
            && !(
232
                $useStatement->getAttribute('parent')
233
                && $useStatement->getAttribute('parent')->alias !== null
234
                && $this->whitelist->isSymbolWhitelisted($useStatement->toString())
235
            )
236
        ) {
237
            return $name;
238
        }
239
240
        if ($this->prefix === $resolvedName->getFirst() // Skip if is already prefixed
241
            || $this->whitelist->belongsToWhitelistedNamespace((string) $resolvedName)  // Skip if the namespace node is whitelisted
242
        ) {
243
            return $resolvedName;
244
        }
245
246
        // Do not prefix if the Name is inside of the current namespace
247
        $namespace = $this->namespaceStatements->getCurrentNamespaceName();
248
        
249
        $oldCondition = (
250
                // In a namespace
251
                $namespace !== null
252
                && array_merge($namespace->parts, $name->parts) === $oldResolvedName->parts
253
            )
254
            || (
255
                // In the global scope
256
                $namespace === null
257
                && $name->parts === $oldResolvedName->parts
258
                && !($name instanceof FullyQualified)
259
                && !($parentNode instanceof ConstFetch)
260
                && !$this->whitelist->isSymbolWhitelisted($oldResolvedName->toString())
261
                && !$this->reflector->isFunctionInternal($oldResolvedName->toString())
262
                && !$this->reflector->isClassInternal($oldResolvedName->toString())
263
            );
264
        $newCondition = (
265
                // In a namespace
266
                $namespace !== null
267
                && array_merge($namespace->parts, $name->parts) === $resolvedName->parts
268
            )
269
            || (
270
                // In the global scope
271
                $namespace === null
272
                && $name->parts === $resolvedName->parts
273
                && !($name instanceof FullyQualified)
274
                && !($parentNode instanceof ConstFetch)
275
                && !$this->whitelist->isSymbolWhitelisted($resolvedName->toString())
276
                && !$this->reflector->isFunctionInternal($resolvedName->toString())
277
                && !$this->reflector->isClassInternal($resolvedName->toString())
278
            );
279
280
        if ($oldCondition !== $newCondition) {
281
            // TODO: check those cases if relevant
282
            $x = '';
283
        }
284
285
        if (
286
            (
287
                // In a namespace
288
                $namespace !== null
289
                && array_merge($namespace->parts, $name->parts) === $resolvedName->parts
290
            )
291
            || (
292
                // In the global scope
293
                $namespace === null
294
                && $name->parts === $resolvedName->parts
295
                && !($name instanceof FullyQualified)
296
                && !($parentNode instanceof ConstFetch)
297
                && !$this->whitelist->isSymbolWhitelisted($resolvedName->toString())
298
                && !$this->reflector->isFunctionInternal($resolvedName->toString())
299
                && !$this->reflector->isClassInternal($resolvedName->toString())
300
            )
301
        ) {
302
            return $name;
303
        }
304
305
        // Check if the class can be prefixed
306
        if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall)
307
            && $this->reflector->isClassInternal($resolvedName->toString())
308
        ) {
309
            return $resolvedName;
310
        }
311
312
        if ($parentNode instanceof ConstFetch) {
313
            if ($this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)) {
314
                return $resolvedName;
315
            }
316
317
            if ($this->reflector->isConstantInternal($resolvedName->toString())) {
318
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
319
            }
320
321
            // Constants have an autoloading fallback so we cannot prefix them when the name is ambiguous
322
            // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
323
            if (false === ($resolvedName instanceof FullyQualified)) {
324
                return $resolvedName;
325
            }
326
327
            if ($this->whitelist->isGlobalWhitelistedConstant((string) $resolvedName)) {
328
                // Unlike classes & functions, whitelisted are not prefixed with aliases registered in scoper-autoload.php
329
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
330
            }
331
332
            // Continue
333
        }
334
335
        // Functions have a fallback autoloading so we cannot prefix them when the name is ambiguous
336
        // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
337
        if ($parentNode instanceof FuncCall) {
338
            if ($this->reflector->isFunctionInternal($resolvedName->toString())) {
339
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
340
            }
341
342
            if (false === ($resolvedName instanceof FullyQualified)) {
343
                return $resolvedName;
344
            }
345
        }
346
347
        if ($parentNode instanceof ClassMethod && $resolvedName->isSpecialClassName()) {
348
            return $name;
349
        }
350
351
        return FullyQualifiedFactory::concat(
352
            $this->prefix,
353
            $resolvedName->toString(),
354
            $resolvedName->getAttributes()
355
        );
356
    }
357
358
    private static function array_starts_with($arr, $prefix): bool
359
    {
360
        $prefixLength = count($prefix);
361
362
        for ($i = 0; $i < $prefixLength; ++$i) {
363
            if ($arr[$i] !== $prefix[$i]) {
364
                return false;
365
            }
366
        }
367
368
        return true;
369
    }
370
}
371