UseStmtCollection::isFuncCallFunctionName()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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