Completed
Push — master ( 6366df...fbe022 )
by Kirill
23s queued 19s
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
c 1
b 0
f 0
nc 15
nop 1
dl 0
loc 55
rs 6.6166

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 Spiral Framework 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\FallbackAttributeReader;
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
final 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
91
     */
92
    public function __debugInfo(): array
93
    {
94
        return [
95
            'classes'    => $this->classes,
96
            'functions'  => $this->functions,
97
            'constants'  => $this->constants,
98
            'properties' => $this->properties,
99
            'parameters' => $this->parameters,
100
        ];
101
    }
102
103
    /**
104
     * @return array<ClassName, AttributePrototypeList>
105
     */
106
    public function getClasses(): array
107
    {
108
        return $this->classes;
109
    }
110
111
    /**
112
     * @return array<FunctionEndLine, AttributePrototypeList>
113
     */
114
    public function getFunctions(): array
115
    {
116
        return $this->functions;
117
    }
118
119
    /**
120
     * @return array<ClassName, array<ConstantName, AttributePrototypeList>>
121
     */
122
    public function getConstants(): array
123
    {
124
        return $this->constants;
125
    }
126
127
    /**
128
     * @return array<ClassName, array<PropertyName, AttributePrototypeList>>
129
     */
130
    public function getProperties(): array
131
    {
132
        return $this->properties;
133
    }
134
135
    /**
136
     * @return array<FunctionEndLine, array<ParameterName, AttributePrototypeList>>
137
     */
138
    public function getParameters(): array
139
    {
140
        return $this->parameters;
141
    }
142
143
    /**
144
     * @param Node $node
145
     * @return int|null
146
     * @throws \Throwable
147
     */
148
    public function enterNode(Node $node): ?int
149
    {
150
        $this->updateContext($node);
151
152
        if ($node instanceof Node\Stmt\ClassLike) {
153
            foreach ($this->parse($node->attrGroups) as $prototype) {
154
                $this->classes[$node->namespacedName->toString()][] = $prototype;
155
            }
156
157
            return null;
158
        }
159
160
        if ($node instanceof Node\FunctionLike) {
161
            $line = $node->getEndLine();
162
163
            foreach ($this->parse($node->getAttrGroups()) as $prototype) {
164
                $this->functions[$line][] = $prototype;
165
            }
166
167
            foreach ($node->getParams() as $param) {
168
                foreach ($this->parse($param->attrGroups) as $prototype) {
169
                    assert(\is_string($param->var->name), 'Function parameter name should be an identifier');
0 ignored issues
show
Bug introduced by
The property name does not seem to exist on PhpParser\Node\Expr\Error.
Loading history...
170
171
                    $this->parameters[$line][$param->var->name][] = $prototype;
172
                }
173
            }
174
175
            return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
176
        }
177
178
        if ($node instanceof Node\Stmt\ClassConst) {
179
            $class = $this->fqn();
180
181
            foreach ($this->parse($node->attrGroups) as $prototype) {
182
                foreach ($node->consts as $const) {
183
                    $this->constants[$class][$this->name($const->name)][] = $prototype;
184
                }
185
            }
186
187
            return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
188
        }
189
190
        if ($node instanceof Property) {
191
            $class = $this->fqn();
192
193
            foreach ($this->parse($node->attrGroups) as $prototype) {
194
                foreach ($node->props as $property) {
195
                    $this->properties[$class][$this->name($property->name)][] = $prototype;
196
                }
197
            }
198
199
            return NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
200
        }
201
202
        return null;
203
    }
204
205
    /**
206
     * @param Node $node
207
     */
208
    public function leaveNode(Node $node): void
209
    {
210
        if ($node instanceof Node\Stmt\Namespace_) {
211
            $this->context[AttributeParser::CTX_NAMESPACE] = '';
212
213
            return;
214
        }
215
216
        if ($node instanceof Node\Stmt\ClassLike) {
217
            $this->context[AttributeParser::CTX_CLASS] = '';
218
            $this->context[AttributeParser::CTX_TRAIT] = '';
219
220
            return;
221
        }
222
223
        if ($node instanceof Node\FunctionLike) {
224
            $this->context[AttributeParser::CTX_FUNCTION] = '';
225
226
            return;
227
        }
228
    }
229
230
    /**
231
     * @param Node $node
232
     */
233
    private function updateContext(Node $node): void
234
    {
235
        switch (true) {
236
            case $node instanceof Node\Stmt\Namespace_:
237
                $this->context[AttributeParser::CTX_NAMESPACE] = $this->name($node->name);
238
                break;
239
240
            case $node instanceof Node\Stmt\ClassLike:
241
                $this->context[AttributeParser::CTX_CLASS] = $this->name($node->name);
242
243
            // no break
244
            case $node instanceof Node\Stmt\Trait_:
245
                $this->context[AttributeParser::CTX_TRAIT] = $this->name($node->name);
246
                break;
247
248
            case $node instanceof Node\Stmt\Function_:
249
            case $node instanceof Node\Stmt\ClassMethod:
250
                $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...
251
                break;
252
        }
253
    }
254
255
    /**
256
     * @param Node\Name|Node\Identifier|null $name
257
     * @return string
258
     */
259
    private function name($name): string
260
    {
261
        if ($name === null) {
262
            return '';
263
        }
264
265
        return $name->toString();
266
    }
267
268
    /**
269
     * @param Node\AttributeGroup[] $groups
270
     * @return \Traversable<AttributePrototype>
271
     * @throws \Throwable
272
     */
273
    private function parse(array $groups): \Traversable
274
    {
275
        return $this->parser->parseAttributes($this->file, $groups, $this->context);
276
    }
277
278
    /**
279
     * @return string
280
     */
281
    private function fqn(): string
282
    {
283
        $namespace = $this->context[AttributeParser::CTX_NAMESPACE] ?? '';
284
        $class = $this->context[AttributeParser::CTX_CLASS] ?? '';
285
286
        return \trim($namespace . '\\' . $class, '\\');
287
    }
288
}
289