Passed
Push — master ( 6c036d...1275a4 )
by Théo
01:42
created

UseStmtCollection::findStatementForNode()   B

Complexity

Conditions 8
Paths 3

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 21
nc 3
nop 2
dl 0
loc 38
rs 8.4444
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\ParentNodeAppender;
20
use IteratorAggregate;
21
use PhpParser\Node;
22
use PhpParser\Node\Expr\ConstFetch;
23
use PhpParser\Node\Expr\FuncCall;
24
use PhpParser\Node\Name;
25
use PhpParser\Node\Stmt\ClassLike;
26
use PhpParser\Node\Stmt\Function_;
27
use PhpParser\Node\Stmt\Use_;
28
use PhpParser\Node\Stmt\UseUse;
29
use function array_key_exists;
30
use function count;
31
use function implode;
32
33
/**
34
 * Utility class collecting all the use statements for the scoped files allowing to easily find the use which a node
35
 * may use.
36
 *
37
 * @private
38
 */
39
final class UseStmtCollection implements IteratorAggregate
40
{
41
    private $hashes = [];
42
43
    /**
44
     * @var Use_[][]
45
     */
46
    private $nodes = [
47
        null => [],
48
    ];
49
50
    public function add(?Name $namespaceName, Use_ $use): void
51
    {
52
        $this->nodes[(string) $namespaceName][] = $use;
53
    }
54
55
    /**
56
     * Finds the statements matching the given name.
57
     *
58
     * $name = 'Foo';
59
     *
60
     * use X;
61
     * use Bar\Foo;
62
     * use Y;
63
     *
64
     * will return the use statement for `Bar\Foo`.
65
     *
66
     * @param Name|null $namespaceName
67
     * @param Name      $node
68
     *
69
     * @return null|Name
70
     */
71
    public function findStatementForNode(?Name $namespaceName, Name $node): ?Name
72
    {
73
        $name = strtolower($node->getFirst());
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
            return null;
85
        }
86
87
        $isFunctionName = $this->isFunctionName($node, $parentNode);
88
        $isConstantName = $this->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
     * @inheritdoc
114
     */
115
    public function getIterator(): iterable
116
    {
117
        return new ArrayIterator($this->nodes);
118
    }
119
120
    private function find(array $useStatements, bool $isFunctionName, bool $isConstantName, string $name): ?Name
121
    {
122
        foreach ($useStatements as $use_) {
123
            foreach ($use_->uses as $useStatement) {
124
                if (false === ($useStatement instanceof UseUse)) {
125
                    continue;
126
                }
127
128
                if ($name === $useStatement->getAlias()->toLowerString()) {
129
                    if ($isFunctionName) {
130
                        if (Use_::TYPE_FUNCTION === $use_->type) {
131
                            return UseStmtManipulator::getOriginalName($useStatement);
132
                        }
133
134
                        continue;
135
                    }
136
137
                    if ($isConstantName) {
138
                        if (Use_::TYPE_CONSTANT === $use_->type) {
139
                            return UseStmtManipulator::getOriginalName($useStatement);
140
                        }
141
142
                        continue;
143
                    }
144
145
                    // Match the alias
146
                    return UseStmtManipulator::getOriginalName($useStatement);
147
                }
148
            }
149
        }
150
151
        return null;
152
    }
153
154
    private function isFunctionName(Name $node, ?Node $parentNode): bool
155
    {
156
        if (null === $parentNode || 1 !== count($node->parts)) {
157
            return false;
158
        }
159
160
        if ($parentNode instanceof FuncCall) {
161
            return true;
162
        }
163
164
        if (false === ($parentNode instanceof Function_)) {
165
            return false;
166
        }
167
        /* @var Function_ $parentNode */
168
169
        return $node instanceof NamedIdentifier && $node->getOriginalNode() === $parentNode->name;
170
    }
171
172
    private function isConstantName(Name $node, ?Node $parentNode): bool
173
    {
174
        return $parentNode instanceof ConstFetch && 1 === count($node->parts);
175
    }
176
}
177