Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of phpunit/php-code-coverage.
4
 *
5
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace SebastianBergmann\CodeCoverage\StaticAnalysis;
11
 
12
use function assert;
13
use function implode;
14
use function rtrim;
15
use function trim;
16
use PhpParser\Node;
17
use PhpParser\Node\ComplexType;
18
use PhpParser\Node\Identifier;
19
use PhpParser\Node\IntersectionType;
20
use PhpParser\Node\Name;
21
use PhpParser\Node\NullableType;
22
use PhpParser\Node\Stmt\Class_;
23
use PhpParser\Node\Stmt\ClassMethod;
24
use PhpParser\Node\Stmt\Enum_;
25
use PhpParser\Node\Stmt\Function_;
26
use PhpParser\Node\Stmt\Interface_;
27
use PhpParser\Node\Stmt\Trait_;
28
use PhpParser\Node\UnionType;
29
use PhpParser\NodeTraverser;
30
use PhpParser\NodeVisitorAbstract;
31
use SebastianBergmann\Complexity\CyclomaticComplexityCalculatingVisitor;
32
 
33
/**
34
 * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
35
 */
36
final class CodeUnitFindingVisitor extends NodeVisitorAbstract
37
{
38
    /**
39
     * @psalm-var array<string,array{name: string, namespacedName: string, namespace: string, startLine: int, endLine: int, methods: array<string,array{methodName: string, signature: string, visibility: string, startLine: int, endLine: int, ccn: int}>}>
40
     */
41
    private $classes = [];
42
 
43
    /**
44
     * @psalm-var array<string,array{name: string, namespacedName: string, namespace: string, startLine: int, endLine: int, methods: array<string,array{methodName: string, signature: string, visibility: string, startLine: int, endLine: int, ccn: int}>}>
45
     */
46
    private $traits = [];
47
 
48
    /**
49
     * @psalm-var array<string,array{name: string, namespacedName: string, namespace: string, signature: string, startLine: int, endLine: int, ccn: int}>
50
     */
51
    private $functions = [];
52
 
53
    public function enterNode(Node $node): void
54
    {
55
        if ($node instanceof Class_) {
56
            if ($node->isAnonymous()) {
57
                return;
58
            }
59
 
60
            $this->processClass($node);
61
        }
62
 
63
        if ($node instanceof Trait_) {
64
            $this->processTrait($node);
65
        }
66
 
67
        if (!$node instanceof ClassMethod && !$node instanceof Function_) {
68
            return;
69
        }
70
 
71
        if ($node instanceof ClassMethod) {
72
            $parentNode = $node->getAttribute('parent');
73
 
74
            if ($parentNode instanceof Class_ && $parentNode->isAnonymous()) {
75
                return;
76
            }
77
 
78
            $this->processMethod($node);
79
 
80
            return;
81
        }
82
 
83
        $this->processFunction($node);
84
    }
85
 
86
    /**
87
     * @psalm-return array<string,array{name: string, namespacedName: string, namespace: string, startLine: int, endLine: int, methods: array<string,array{methodName: string, signature: string, visibility: string, startLine: int, endLine: int, ccn: int}>}>
88
     */
89
    public function classes(): array
90
    {
91
        return $this->classes;
92
    }
93
 
94
    /**
95
     * @psalm-return array<string,array{name: string, namespacedName: string, namespace: string, startLine: int, endLine: int, methods: array<string,array{methodName: string, signature: string, visibility: string, startLine: int, endLine: int, ccn: int}>}>
96
     */
97
    public function traits(): array
98
    {
99
        return $this->traits;
100
    }
101
 
102
    /**
103
     * @psalm-return array<string,array{name: string, namespacedName: string, namespace: string, signature: string, startLine: int, endLine: int, ccn: int}>
104
     */
105
    public function functions(): array
106
    {
107
        return $this->functions;
108
    }
109
 
110
    /**
111
     * @psalm-param ClassMethod|Function_ $node
112
     */
113
    private function cyclomaticComplexity(Node $node): int
114
    {
115
        assert($node instanceof ClassMethod || $node instanceof Function_);
116
 
117
        $nodes = $node->getStmts();
118
 
119
        if ($nodes === null) {
120
            return 0;
121
        }
122
 
123
        $traverser = new NodeTraverser;
124
 
125
        $cyclomaticComplexityCalculatingVisitor = new CyclomaticComplexityCalculatingVisitor;
126
 
127
        $traverser->addVisitor($cyclomaticComplexityCalculatingVisitor);
128
 
129
        /* @noinspection UnusedFunctionResultInspection */
130
        $traverser->traverse($nodes);
131
 
132
        return $cyclomaticComplexityCalculatingVisitor->cyclomaticComplexity();
133
    }
134
 
135
    /**
136
     * @psalm-param ClassMethod|Function_ $node
137
     */
138
    private function signature(Node $node): string
139
    {
140
        assert($node instanceof ClassMethod || $node instanceof Function_);
141
 
142
        $signature  = ($node->returnsByRef() ? '&' : '') . $node->name->toString() . '(';
143
        $parameters = [];
144
 
145
        foreach ($node->getParams() as $parameter) {
146
            assert(isset($parameter->var->name));
147
 
148
            $parameterAsString = '';
149
 
150
            if ($parameter->type !== null) {
151
                $parameterAsString = $this->type($parameter->type) . ' ';
152
            }
153
 
154
            $parameterAsString .= '$' . $parameter->var->name;
155
 
156
            /* @todo Handle default values */
157
 
158
            $parameters[] = $parameterAsString;
159
        }
160
 
161
        $signature .= implode(', ', $parameters) . ')';
162
 
163
        $returnType = $node->getReturnType();
164
 
165
        if ($returnType !== null) {
166
            $signature .= ': ' . $this->type($returnType);
167
        }
168
 
169
        return $signature;
170
    }
171
 
172
    /**
173
     * @psalm-param Identifier|Name|ComplexType $type
174
     */
175
    private function type(Node $type): string
176
    {
177
        assert($type instanceof Identifier || $type instanceof Name || $type instanceof ComplexType);
178
 
179
        if ($type instanceof NullableType) {
180
            return '?' . $type->type;
181
        }
182
 
183
        if ($type instanceof UnionType || $type instanceof IntersectionType) {
184
            return $this->unionOrIntersectionAsString($type);
185
        }
186
 
187
        return $type->toString();
188
    }
189
 
190
    private function visibility(ClassMethod $node): string
191
    {
192
        if ($node->isPrivate()) {
193
            return 'private';
194
        }
195
 
196
        if ($node->isProtected()) {
197
            return 'protected';
198
        }
199
 
200
        return 'public';
201
    }
202
 
203
    private function processClass(Class_ $node): void
204
    {
205
        $name           = $node->name->toString();
206
        $namespacedName = $node->namespacedName->toString();
207
 
208
        $this->classes[$namespacedName] = [
209
            'name'           => $name,
210
            'namespacedName' => $namespacedName,
211
            'namespace'      => $this->namespace($namespacedName, $name),
212
            'startLine'      => $node->getStartLine(),
213
            'endLine'        => $node->getEndLine(),
214
            'methods'        => [],
215
        ];
216
    }
217
 
218
    private function processTrait(Trait_ $node): void
219
    {
220
        $name           = $node->name->toString();
221
        $namespacedName = $node->namespacedName->toString();
222
 
223
        $this->traits[$namespacedName] = [
224
            'name'           => $name,
225
            'namespacedName' => $namespacedName,
226
            'namespace'      => $this->namespace($namespacedName, $name),
227
            'startLine'      => $node->getStartLine(),
228
            'endLine'        => $node->getEndLine(),
229
            'methods'        => [],
230
        ];
231
    }
232
 
233
    private function processMethod(ClassMethod $node): void
234
    {
235
        $parentNode = $node->getAttribute('parent');
236
 
237
        if ($parentNode instanceof Interface_) {
238
            return;
239
        }
240
 
241
        assert($parentNode instanceof Class_ || $parentNode instanceof Trait_ || $parentNode instanceof Enum_);
242
        assert(isset($parentNode->name));
243
        assert(isset($parentNode->namespacedName));
244
        assert($parentNode->namespacedName instanceof Name);
245
 
246
        $parentName           = $parentNode->name->toString();
247
        $parentNamespacedName = $parentNode->namespacedName->toString();
248
 
249
        if ($parentNode instanceof Class_) {
250
            $storage = &$this->classes;
251
        } else {
252
            $storage = &$this->traits;
253
        }
254
 
255
        if (!isset($storage[$parentNamespacedName])) {
256
            $storage[$parentNamespacedName] = [
257
                'name'           => $parentName,
258
                'namespacedName' => $parentNamespacedName,
259
                'namespace'      => $this->namespace($parentNamespacedName, $parentName),
260
                'startLine'      => $parentNode->getStartLine(),
261
                'endLine'        => $parentNode->getEndLine(),
262
                'methods'        => [],
263
            ];
264
        }
265
 
266
        $storage[$parentNamespacedName]['methods'][$node->name->toString()] = [
267
            'methodName' => $node->name->toString(),
268
            'signature'  => $this->signature($node),
269
            'visibility' => $this->visibility($node),
270
            'startLine'  => $node->getStartLine(),
271
            'endLine'    => $node->getEndLine(),
272
            'ccn'        => $this->cyclomaticComplexity($node),
273
        ];
274
    }
275
 
276
    private function processFunction(Function_ $node): void
277
    {
278
        assert(isset($node->name));
279
        assert(isset($node->namespacedName));
280
        assert($node->namespacedName instanceof Name);
281
 
282
        $name           = $node->name->toString();
283
        $namespacedName = $node->namespacedName->toString();
284
 
285
        $this->functions[$namespacedName] = [
286
            'name'           => $name,
287
            'namespacedName' => $namespacedName,
288
            'namespace'      => $this->namespace($namespacedName, $name),
289
            'signature'      => $this->signature($node),
290
            'startLine'      => $node->getStartLine(),
291
            'endLine'        => $node->getEndLine(),
292
            'ccn'            => $this->cyclomaticComplexity($node),
293
        ];
294
    }
295
 
296
    private function namespace(string $namespacedName, string $name): string
297
    {
298
        return trim(rtrim($namespacedName, $name), '\\');
299
    }
300
 
301
    /**
302
     * @psalm-param UnionType|IntersectionType $type
303
     */
304
    private function unionOrIntersectionAsString(ComplexType $type): string
305
    {
306
        if ($type instanceof UnionType) {
307
            $separator = '|';
308
        } else {
309
            $separator = '&';
310
        }
311
 
312
        $types = [];
313
 
314
        foreach ($type->types as $_type) {
315
            if ($_type instanceof Name) {
316
                $types[] = $_type->toCodeString();
317
            } else {
318
                assert($_type instanceof Identifier);
319
 
320
                $types[] = $_type->toString();
321
            }
322
        }
323
 
324
        return implode($separator, $types);
325
    }
326
}