Passed
Push — master ( b674ac...2bc77e )
by Kirill
04:21
created

AttributeFinderVisitor::enterNode()   C

Complexity

Conditions 13
Paths 15

Size

Total Lines 55
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 27
nc 15
nop 1
dl 0
loc 55
rs 6.6166
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of Attributes package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Attributes\Internal;
13
14
use PhpParser\Node;
15
use PhpParser\Node\Stmt\Property;
16
use PhpParser\NodeTraverser;
17
use PhpParser\NodeVisitorAbstract;
18
19
/**
20
 * @internal AttributeFinderVisitor is an internal library class, please do not use it in your code.
21
 * @psalm-internal Spiral\Attributes
22
 *
23
 * @psalm-type AttributePrototypeList = array<AttributePrototype>
24
 *
25
 * @psalm-type FunctionEndLine = positive-int
26
 *
27
 * @psalm-type ClassName = string
28
 * @psalm-type ConstantName = string
29
 * @psalm-type PropertyName = string
30
 * @psalm-type ParameterName = string
31
 */
32
class AttributeFinderVisitor extends NodeVisitorAbstract
33
{
34
    /**
35
     * @var array
36
     */
37
    private $context = [
38
        AttributeParser::CTX_FUNCTION  => '',
39
        AttributeParser::CTX_NAMESPACE => '',
40
        AttributeParser::CTX_CLASS     => '',
41
        AttributeParser::CTX_TRAIT     => '',
42
    ];
43
44
    /**
45
     * @var array<ClassName, AttributePrototypeList>
46
     */
47
    private $classes = [];
48
49
    /**
50
     * @var array<FunctionEndLine, AttributePrototypeList>
51
     */
52
    private $functions = [];
53
54
    /**
55
     * @var array<ClassName, array<ConstantName, AttributePrototypeList>>
56
     */
57
    private $constants = [];
58
59
    /**
60
     * @var array<ClassName, array<PropertyName, AttributePrototypeList>>
61
     */
62
    private $properties = [];
63
64
    /**
65
     * @var array<FunctionEndLine, array<ParameterName, AttributePrototypeList>>
66
     */
67
    private $parameters = [];
68
69
    /**
70
     * @var string
71
     */
72
    private $file;
73
74
    /**
75
     * @var AttributeParser
76
     */
77
    private $parser;
78
79
    /**
80
     * @param string $file
81
     * @param AttributeParser $parser
82
     */
83
    public function __construct(string $file, AttributeParser $parser)
84
    {
85
        $this->file = $file;
86
        $this->parser = $parser;
87
    }
88
89
    /**
90
     * @return array<ClassName, AttributePrototypeList>
91
     */
92
    public function getClasses(): array
93
    {
94
        return $this->classes;
95
    }
96
97
    /**
98
     * @return array<FunctionEndLine, AttributePrototypeList>
99
     */
100
    public function getFunctions(): array
101
    {
102
        return $this->functions;
103
    }
104
105
    /**
106
     * @return array<ClassName, array<ConstantName, AttributePrototypeList>>
107
     */
108
    public function getConstants(): array
109
    {
110
        return $this->constants;
111
    }
112
113
    /**
114
     * @return array<ClassName, array<PropertyName, AttributePrototypeList>>
115
     */
116
    public function getProperties(): array
117
    {
118
        return $this->properties;
119
    }
120
121
    /**
122
     * @return array<FunctionEndLine, array<ParameterName, AttributePrototypeList>>
123
     */
124
    public function getParameters(): array
125
    {
126
        return $this->parameters;
127
    }
128
129
    /**
130
     * @param Node\AttributeGroup[] $groups
131
     * @return \Traversable<AttributePrototype>
132
     * @throws \Throwable
133
     */
134
    private function parse(array $groups): \Traversable
135
    {
136
        return $this->parser->parseAttributes($this->file, $groups, $this->context);
137
    }
138
139
    /**
140
     * @param Node $node
141
     * @return int|null
142
     * @throws \Throwable
143
     */
144
    public function enterNode(Node $node): ?int
145
    {
146
        $this->updateContext($node);
147
148
        if ($node instanceof Node\Stmt\ClassLike) {
149
            foreach ($this->parse($node->attrGroups) as $prototype) {
150
                $this->classes[$node->namespacedName->toString()][] = $prototype;
151
            }
152
153
            return null;
154
        }
155
156
        if ($node instanceof Node\FunctionLike) {
157
            $line = $node->getEndLine();
158
159
            foreach ($this->parse($node->getAttrGroups()) as $prototype) {
160
                $this->functions[$line][] = $prototype;
161
            }
162
163
            foreach ($node->getParams() as $param) {
164
                foreach ($this->parse($param->attrGroups) as $prototype) {
165
                    assert(\is_string($param->var->name), 'Function parameter name should be an identifier');
166
167
                    $this->parameters[$line][$param->var->name][] = $prototype;
168
                }
169
            }
170
171
            return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
172
        }
173
174
        if ($node instanceof Node\Stmt\ClassConst) {
175
            $class = $this->fqn();
176
177
            foreach ($this->parse($node->attrGroups) as $prototype) {
178
                foreach ($node->consts as $const) {
179
                    $this->constants[$class][$this->name($const->name)][] = $prototype;
180
                }
181
            }
182
183
            return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
184
        }
185
186
        if ($node instanceof Property) {
187
            $class = $this->fqn();
188
189
            foreach ($this->parse($node->attrGroups) as $prototype) {
190
                foreach ($node->props as $property) {
191
                    $this->properties[$class][$this->name($property->name)][] = $prototype;
192
                }
193
            }
194
195
            return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
196
        }
197
198
        return null;
199
    }
200
201
    /**
202
     * @return string
203
     */
204
    private function fqn(): string
205
    {
206
        $namespace = $this->context[AttributeParser::CTX_NAMESPACE] ?? '';
207
        $class = $this->context[AttributeParser::CTX_CLASS] ?? '';
208
209
        return \trim($namespace . '\\' . $class, '\\');
210
    }
211
212
    /**
213
     * @param Node\Name|Node\Identifier|null $name
214
     * @return string
215
     */
216
    private function name($name): string
217
    {
218
        if ($name === null) {
219
            return '';
220
        }
221
222
        return $name->toString();
223
    }
224
225
    /**
226
     * @param Node $node
227
     */
228
    private function updateContext(Node $node): void
229
    {
230
        switch (true) {
231
            case $node instanceof Node\Stmt\Namespace_:
232
                $this->context[AttributeParser::CTX_NAMESPACE] = $this->name($node->name);
233
                break;
234
235
            case $node instanceof Node\Stmt\ClassLike:
236
                $this->context[AttributeParser::CTX_CLASS] = $this->name($node->name);
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
237
238
            case $node instanceof Node\Stmt\Trait_:
239
                $this->context[AttributeParser::CTX_TRAIT] = $this->name($node->name);
240
                break;
241
242
            case $node instanceof Node\Stmt\Function_:
243
            case $node instanceof Node\Stmt\ClassMethod:
244
                $this->context[AttributeParser::CTX_FUNCTION] = $this->name($node->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface PhpParser\Node suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
245
                break;
246
        }
247
    }
248
249
    /**
250
     * @param Node $node
251
     */
252
    public function leaveNode(Node $node): void
253
    {
254
        if ($node instanceof Node\Stmt\Namespace_) {
255
            $this->context[AttributeParser::CTX_NAMESPACE] = '';
256
            return;
257
        }
258
259
        if ($node instanceof Node\Stmt\ClassLike) {
260
            $this->context[AttributeParser::CTX_CLASS] = '';
261
            $this->context[AttributeParser::CTX_TRAIT] = '';
262
            return;
263
        }
264
265
        if ($node instanceof Node\FunctionLike) {
266
            $this->context[AttributeParser::CTX_FUNCTION] = '';
267
            return;
268
        }
269
    }
270
}