Passed
Pull Request — master (#457)
by Gena
02:30 queued 23s
created

NameStmtPrefixer   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 177
Duplicated Lines 0 %

Test Coverage

Coverage 98.55%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 94
c 7
b 0
f 0
dl 0
loc 177
ccs 68
cts 69
cp 0.9855
rs 3.52
wmc 61

4 Methods

Rating   Name   Duplication   Size   Complexity  
A enterNode() 0 5 3
A __construct() 0 14 1
A array_starts_with() 0 8 3
D prefixName() 0 124 54

How to fix   Complexity   

Complex Class

Complex classes like NameStmtPrefixer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NameStmtPrefixer, and based on these observations, apply Extract Interface, too.

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 count;
44
use function in_array;
45
46
/**
47
 * Prefixes names when appropriate.
48
 *
49
 * ```
50
 * new Foo\Bar();
51
 * ```.
52
 *
53
 * =>
54
 *
55
 * ```
56
 * new \Humbug\Foo\Bar();
57
 * ```
58
 *
59
 * @private
60
 */
61
final class NameStmtPrefixer extends NodeVisitorAbstract
62
{
63
    public const PHP_FUNCTION_KEYWORDS = [
64
        'self',
65
        'static',
66
        'parent',
67
    ];
68 549
69
    private $prefix;
70
    private $whitelist;
71
    private $namespaceStatements;
72
    private $useStatements;
73
    private $nameResolver;
74 549
    private $reflector;
75 549
76 549
    public function __construct(
77 549
        string $prefix,
78
        Whitelist $whitelist,
79
        NamespaceStmtCollection $namespaceStatements,
80
        UseStmtCollection $useStatements,
81
        FullyQualifiedNameResolver $nameResolver,
82
        Reflector $reflector
83 548
    ) {
84
        $this->prefix = $prefix;
85 548
        $this->whitelist = $whitelist;
86 544
        $this->namespaceStatements = $namespaceStatements;
87 548
        $this->useStatements = $useStatements;
88
        $this->nameResolver = $nameResolver;
89
        $this->reflector = $reflector;
90
    }
91 544
92
    /**
93 544
     * @inheritdoc
94
     */
95 544
    public function enterNode(Node $node): Node
96 4
    {
97
        return ($node instanceof Name && ParentNodeAppender::hasParent($node))
98
            ? $this->prefixName($node)
99
            : $node
100 4
        ;
101
    }
102
103
    private function prefixName(Name $name): Node
104 544
    {
105 542
        $parentNode = ParentNodeAppender::getParent($name);
106 541
107 540
        if ($parentNode instanceof NullableType) {
108 540
            if (false === ParentNodeAppender::hasParent($parentNode)) {
109 538
                return $name;
110 538
            }
111 538
112 538
            $parentNode = ParentNodeAppender::getParent($parentNode);
113 538
        }
114 538
115 538
        if (false === (
116 544
            $parentNode instanceof ArrowFunction
117
                || $parentNode instanceof Catch_
118
                || $parentNode instanceof ConstFetch
119 537
                || $parentNode instanceof Class_
120
                || $parentNode instanceof ClassConstFetch
121
                || $parentNode instanceof ClassMethod
122
                || $parentNode instanceof FuncCall
123
                || $parentNode instanceof Function_
124 405
                || $parentNode instanceof Instanceof_
125 349
                || $parentNode instanceof Interface_
126 298
                || $parentNode instanceof New_
127 236
                || $parentNode instanceof Param
128 185
                || $parentNode instanceof Property
129 133
                || $parentNode instanceof StaticCall
130 131
                || $parentNode instanceof StaticPropertyFetch
131 120
        )
132
        ) {
133 320
            return $name;
134
        }
135 9
136
        if (
137
            (
138 403
                $parentNode instanceof Catch_
139 4
                || $parentNode instanceof ClassConstFetch
140
                || $parentNode instanceof New_
141
                || $parentNode instanceof FuncCall
142 403
                || $parentNode instanceof Instanceof_
143
                || $parentNode instanceof Param
144 403
                || $parentNode instanceof StaticCall
145
                || $parentNode instanceof StaticPropertyFetch
146 403
            )
147 403
            && in_array((string) $name, self::PHP_FUNCTION_KEYWORDS, true)
148
        ) {
149 23
            return $name;
150
        }
151
152
        if ($parentNode instanceof ConstFetch && 'null' === (string) $name) {
153 392
            return $name;
154 257
        }
155
156 61
        $resolvedName = $this->nameResolver->resolveName($name)->getName();
157
158
        // Do not prefix if there is a matching use statement.
159 355
        $useStatement = $this->useStatements->findStatementForNode($this->namespaceStatements->findNamespaceForNode($name), $name);
160 85
        if (
161 21
            $useStatement !== null and
162
            self::array_starts_with($resolvedName->parts, $useStatement->parts) and
163
            !($parentNode instanceof ConstFetch and ($this->whitelist->isGlobalWhitelistedConstant($resolvedName->toString()) or $this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true))) and
164 67
            !($useStatement->getAttribute('parent') and $useStatement->getAttribute('parent')->alias !== null and $this->whitelist->isSymbolWhitelisted($useStatement->toString())) and
165 19
            !($name instanceof FullyQualified) and
166
            $resolvedName->parts !== ['Isolated', 'Symfony', 'Component', 'Finder', 'Finder']
167
        ) {
168
            return $name;
169
        }
170 48
171 8
        if ($this->prefix === $resolvedName->getFirst() // Skip if is already prefixed
172
            || $this->whitelist->belongsToWhitelistedNamespace((string) $resolvedName)  // Skip if the namespace node is whitelisted
173
        ) {
174 46
            return $resolvedName;
175
        }
176 11
177
        // Check if the class can be prefixed
178
        if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall)
179
            && $this->reflector->isClassInternal($resolvedName->toString())
180
        ) {
181
            return $resolvedName;
182
        }
183
184 320
        if ($parentNode instanceof ConstFetch) {
185 76
            if ($this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)) {
186 37
                return $resolvedName;
187
            }
188
189 54
            if ($this->reflector->isConstantInternal($resolvedName->toString())) {
190 9
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
191
            }
192
193
            // Constants have an autoloading fallback so we cannot prefix them when the name is ambiguous
194 297
            // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
195 3
            if (false === ($resolvedName instanceof FullyQualified)) {
196
                return $resolvedName;
197
            }
198 297
199 297
            if ($this->whitelist->isGlobalWhitelistedConstant((string) $resolvedName)) {
200 297
                // Unlike classes & functions, whitelisted are not prefixed with aliases registered in scoper-autoload.php
201 297
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
202
            }
203
204
            // Continue
205
        }
206
207
        // Functions have a fallback autoloading so we cannot prefix them when the name is ambiguous
208
        // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
209
        if ($parentNode instanceof FuncCall) {
210
            if ($this->reflector->isFunctionInternal($resolvedName->toString())) {
211
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
212
            }
213
214
            if (false === ($resolvedName instanceof FullyQualified)) {
215
                return $resolvedName;
216
            }
217
        }
218
219
        if ($parentNode instanceof ClassMethod && $resolvedName->isSpecialClassName()) {
220
            return $name;
221
        }
222
223
        return FullyQualifiedFactory::concat(
224
            $this->prefix,
225
            $resolvedName->toString(),
226
            $resolvedName->getAttributes()
227
        );
228
    }
229
230
    private static function array_starts_with($arr, $prefix): bool
231
    {
232
        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...
233
            if ($arr[$i] !== $prefix[$i]) {
234
                return false;
235
            }
236
        }
237
        return true;
238
    }
239
}
240