Passed
Branch master (c976c6)
by Théo
03:51
created

NameStmtPrefixer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 4
dl 0
loc 10
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\NodeVisitor\Resolver\FullyQualifiedNameResolver;
18
use Humbug\PhpScoper\Reflector;
19
use Humbug\PhpScoper\Whitelist;
20
use PhpParser\Node;
21
use PhpParser\Node\Expr\ClassConstFetch;
22
use PhpParser\Node\Expr\ConstFetch;
23
use PhpParser\Node\Expr\FuncCall;
24
use PhpParser\Node\Expr\Instanceof_;
25
use PhpParser\Node\Expr\New_;
26
use PhpParser\Node\Expr\StaticCall;
27
use PhpParser\Node\Expr\StaticPropertyFetch;
28
use PhpParser\Node\Name;
29
use PhpParser\Node\Name\FullyQualified;
30
use PhpParser\Node\NullableType;
31
use PhpParser\Node\Param;
32
use PhpParser\Node\Stmt\Catch_;
33
use PhpParser\Node\Stmt\Class_;
34
use PhpParser\Node\Stmt\ClassMethod;
35
use PhpParser\Node\Stmt\Function_;
36
use PhpParser\Node\Stmt\Interface_;
37
use PhpParser\NodeVisitorAbstract;
38
use function in_array;
39
40
/**
41
 * Prefixes names when appropriate.
42
 *
43
 * ```
44
 * new Foo\Bar();
45
 * ```.
46
 *
47
 * =>
48
 *
49
 * ```
50
 * new \Humbug\Foo\Bar();
51
 * ```
52
 *
53
 * @private
54
 */
55
final class NameStmtPrefixer extends NodeVisitorAbstract
56
{
57
    public const PHP_FUNCTION_KEYWORDS = [
58
        'self',
59
        'static',
60
        'parent',
61
    ];
62
63
    private $prefix;
64
    private $whitelist;
65
    private $nameResolver;
66
    private $reflector;
67
68 549
    public function __construct(
69
        string $prefix,
70
        Whitelist $whitelist,
71
        FullyQualifiedNameResolver $nameResolver,
72
        Reflector $reflector
73
    ) {
74 549
        $this->prefix = $prefix;
75 549
        $this->whitelist = $whitelist;
76 549
        $this->nameResolver = $nameResolver;
77 549
        $this->reflector = $reflector;
78
    }
79
80
    /**
81
     * @inheritdoc
82
     */
83 548
    public function enterNode(Node $node): Node
84
    {
85 548
        return ($node instanceof Name && ParentNodeAppender::hasParent($node))
86 544
            ? $this->prefixName($node)
87 548
            : $node
88
        ;
89
    }
90
91 544
    private function prefixName(Name $name): Node
92
    {
93 544
        $parentNode = ParentNodeAppender::getParent($name);
94
95 544
        if ($parentNode instanceof NullableType) {
96 4
            if (false === ParentNodeAppender::hasParent($parentNode)) {
97
                return $name;
98
            }
99
100 4
            $parentNode = ParentNodeAppender::getParent($parentNode);
101
        }
102
103
        if (false === (
104 544
                $parentNode instanceof ConstFetch
105 542
                || $parentNode instanceof ClassConstFetch
106 541
                || $parentNode instanceof StaticPropertyFetch
107 540
                || $parentNode instanceof Param
108 540
                || $parentNode instanceof FuncCall
109 538
                || $parentNode instanceof StaticCall
110 538
                || $parentNode instanceof Function_
111 538
                || $parentNode instanceof ClassMethod
112 538
                || $parentNode instanceof New_
113 538
                || $parentNode instanceof Class_
114 538
                || $parentNode instanceof Interface_
115 538
                || $parentNode instanceof Catch_
116 544
                || $parentNode instanceof Instanceof_
117
            )
118
        ) {
119 537
            return $name;
120
        }
121
122
        if (
123
            (
124 405
                $parentNode instanceof FuncCall
125 349
                || $parentNode instanceof StaticCall
126 298
                || $parentNode instanceof ClassConstFetch
127 236
                || $parentNode instanceof StaticPropertyFetch
128 185
                || $parentNode instanceof New_
129 133
                || $parentNode instanceof Param
130 131
                || $parentNode instanceof Catch_
131 120
                || $parentNode instanceof Instanceof_
132
            )
133 320
            && in_array((string) $name, self::PHP_FUNCTION_KEYWORDS, true)
134
        ) {
135 9
            return $name;
136
        }
137
138 403
        if ($parentNode instanceof ConstFetch && 'null' === (string) $name) {
139 4
            return $name;
140
        }
141
142 403
        $resolvedValue = $this->nameResolver->resolveName($name);
143
144 403
        $resolvedName = $resolvedValue->getName();
145
146 403
        if ($this->prefix === $resolvedName->getFirst() // Skip if is already prefixed
147 403
            || $this->whitelist->belongsToWhitelistedNamespace((string) $resolvedName)  // Skip if the namespace node is whitelisted
148
        ) {
149 23
            return $resolvedName;
150
        }
151
152
        // Check if the class can be prefixed
153 392
        if (false === ($parentNode instanceof ConstFetch || $parentNode instanceof FuncCall)
154 257
            && $this->reflector->isClassInternal($resolvedName->toString())
155
        ) {
156 61
            return $resolvedName;
157
        }
158
159 355
        if ($parentNode instanceof ConstFetch) {
160 85
            if ($this->whitelist->isSymbolWhitelisted($resolvedName->toString(), true)) {
161 21
                return $resolvedName;
162
            }
163
164 67
            if ($this->reflector->isConstantInternal($resolvedName->toString())) {
165 19
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
166
            }
167
168
            // Constants have an autoloading fallback so we cannot prefix them when the name is ambiguous
169
            // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
170 48
            if (false === ($resolvedName instanceof FullyQualified)) {
171 8
                return $resolvedName;
172
            }
173
174 46
            if ($this->whitelist->isGlobalWhitelistedConstant((string) $resolvedName)) {
175
                // Unlike classes & functions, whitelisted are not prefixed with aliases registered in scoper-autoload.php
176 11
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
177
            }
178
179
            // Continue
180
        }
181
182
        // Functions have a fallback autoloading so we cannot prefix them when the name is ambiguous
183
        // See https://wiki.php.net/rfc/fallback-to-root-scope-deprecation
184 320
        if ($parentNode instanceof FuncCall) {
185 76
            if ($this->reflector->isFunctionInternal($resolvedName->toString())) {
186 37
                return new FullyQualified($resolvedName->toString(), $resolvedName->getAttributes());
187
            }
188
189 54
            if (false === ($resolvedName instanceof FullyQualified)) {
190 9
                return $resolvedName;
191
            }
192
        }
193
194 297
        if ('self' === (string) $resolvedName && $parentNode instanceof ClassMethod) {
195 3
            return $name;
196
        }
197
198 297
        return FullyQualified::concat(
199 297
            $this->prefix,
200 297
            $resolvedName->toString(),
201 297
            $resolvedName->getAttributes()
202
        );
203
    }
204
}
205