Subversion-Projekte lars-tiefland.laravel_shop

Revision

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