Passed
Push — master ( 1c5a50...565b8d )
by Théo
10:21
created

UseStmtCollection::isFuncCallFunctionName()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 4
nop 1
dl 0
loc 18
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\UseStmt;
16
17
use ArrayIterator;
18
use Humbug\PhpScoper\PhpParser\Node\NamedIdentifier;
19
use Humbug\PhpScoper\PhpParser\NodeVisitor\OriginalNameResolver;
20
use Humbug\PhpScoper\PhpParser\NodeVisitor\ParentNodeAppender;
21
use IteratorAggregate;
22
use PhpParser\Node;
23
use PhpParser\Node\Expr\ConstFetch;
24
use PhpParser\Node\Expr\FuncCall;
25
use PhpParser\Node\Name;
26
use PhpParser\Node\Stmt\ClassLike;
27
use PhpParser\Node\Stmt\Function_;
28
use PhpParser\Node\Stmt\Use_;
29
use PhpParser\Node\Stmt\UseUse;
30
use function array_key_exists;
31
use function count;
32
use function implode;
33
use function strtolower;
34
35
/**
36
 * Utility class collecting all the use statements for the scoped files allowing to easily find the use which a node
37
 * may use.
38
 *
39
 * @private
40
 */
41
final class UseStmtCollection implements IteratorAggregate
42
{
43
    private array $hashes = [];
44
45
    /**
46
     * @var Use_[][]
47
     */
48
    private array $nodes = [
49
        null => [],
50
    ];
51
52
    public function add(?Name $namespaceName, Use_ $use): void
53
    {
54
        $this->nodes[(string) $namespaceName][] = $use;
55
    }
56
57
    /**
58
     * Finds the statements matching the given name.
59
     *
60
     * $name = 'Foo';
61
     *
62
     * use X;
63
     * use Bar\Foo;
64
     * use Y;
65
     *
66
     * will return the use statement for `Bar\Foo`.
67
     */
68
    public function findStatementForNode(?Name $namespaceName, Name $node): ?Name
69
    {
70
        $name = self::getName($node);
71
72
        $parentNode = ParentNodeAppender::findParent($node);
73
74
        if ($parentNode instanceof ClassLike
75
            && $node instanceof NamedIdentifier
76
            && $node->getOriginalNode() === $parentNode->name
77
        ) {
78
            // The current node can either be the class like name or one of its elements, e.g. extends or implements.
79
            // In the first case, the node was original an Identifier.
80
81
            return null;
82
        }
83
84
        $isFunctionName = self::isFunctionName($node, $parentNode);
85
        $isConstantName = self::isConstantName($node, $parentNode);
86
87
        $hash = implode(
88
            ':',
89
            [
90
                $namespaceName ? $namespaceName->toString() : '',
91
                $name,
92
                $isFunctionName ? 'func' : '',
93
                $isConstantName ? 'const' : '',
94
            ]
95
        );
96
97
        if (array_key_exists($hash, $this->hashes)) {
98
            return $this->hashes[$hash];
99
        }
100
101
        return $this->hashes[$hash] = $this->find(
102
            $this->nodes[(string) $namespaceName] ?? [],
103
            $isFunctionName,
104
            $isConstantName,
105
            $name,
106
        );
107
    }
108
109
    public function getIterator(): iterable
110
    {
111
        return new ArrayIterator($this->nodes);
112
    }
113
114
    private static function getName(Name $node): string
115
    {
116
        $originalNode = OriginalNameResolver::getOriginalName($node);
117
118
        if (null === $originalNode) {
119
            return self::getNameFirstPart($node);
120
        }
121
122
        return self::getNameFirstPart($originalNode);
123
    }
124
125
    private static function getNameFirstPart(Name $node): string
126
    {
127
        return strtolower($node->getFirst());
128
    }
129
130
    private function find(array $useStatements, bool $isFunctionName, bool $isConstantName, string $name): ?Name
131
    {
132
        foreach ($useStatements as $use_) {
133
            foreach ($use_->uses as $useStatement) {
134
                if (false === ($useStatement instanceof UseUse)) {
135
                    continue;
136
                }
137
138
                $type = Use_::TYPE_UNKNOWN !== $use_->type ? $use_->type : $useStatement->type;
139
140
                if ($name === $useStatement->getAlias()->toLowerString()) {
141
                    if ($isFunctionName) {
142
                        if (Use_::TYPE_FUNCTION === $type) {
143
                            return UseStmtManipulator::getOriginalName($useStatement);
144
                        }
145
146
                        continue;
147
                    }
148
149
                    if ($isConstantName) {
150
                        if (Use_::TYPE_CONSTANT === $type) {
151
                            return UseStmtManipulator::getOriginalName($useStatement);
152
                        }
153
154
                        continue;
155
                    }
156
157
                    if (Use_::TYPE_NORMAL === $type) {
158
                        // Match the alias
159
                        return UseStmtManipulator::getOriginalName($useStatement);
160
                    }
161
                }
162
            }
163
        }
164
165
        return null;
166
    }
167
168
    private static function isFunctionName(Name $node, ?Node $parentNode): bool
169
    {
170
        if (null === $parentNode) {
171
            return false;
172
        }
173
174
        if ($parentNode instanceof FuncCall) {
175
            return self::isFuncCallFunctionName($node);
176
        }
177
178
        if (false === ($parentNode instanceof Function_)) {
179
            return false;
180
        }
181
        /* @var Function_ $parentNode */
182
183
        return $node instanceof NamedIdentifier && $node->getOriginalNode() === $parentNode->name;
184
    }
185
186
    private static function isFuncCallFunctionName(Name $name): bool
187
    {
188
        if ($name instanceof Name\FullyQualified) {
189
            $name = OriginalNameResolver::getOriginalName($name);
190
        }
191
192
        if (null === $name) {
0 ignored issues
show
introduced by
The condition null === $name is always false.
Loading history...
193
            return false;
194
        }
195
196
        // If the name has more than one part then any potentially associated
197
        // use statement will be a regular use statement.
198
        // e.g.:
199
        // ```
200
        // use Foo
201
        // echo Foo\main();
202
        // ```
203
        return 1 === count($name->parts);
204
    }
205
206
    private static function isConstantName(Name $name, ?Node $parentNode): bool
207
    {
208
        if (!($parentNode instanceof ConstFetch)) {
209
            return false;
210
        }
211
212
        if ($name instanceof Name\FullyQualified) {
213
            $name = OriginalNameResolver::getOriginalName($name);
214
        }
215
216
        if (null === $name) {
0 ignored issues
show
introduced by
The condition null === $name is always false.
Loading history...
217
            return false;
218
        }
219
220
        // If the name has more than one part then any potentially associated
221
        // use statement will be a regular use statement.
222
        // e.g.:
223
        // ```
224
        // use Foo
225
        // echo Foo\DUMMY_CONST;
226
        // ```
227
        return 1 === count($name->parts);
228
    }
229
}
230