Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 200 | 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 array_diff_key;
13
use function assert;
14
use function count;
15
use function current;
16
use function end;
17
use function explode;
18
use function max;
19
use function preg_match;
20
use function preg_quote;
21
use function range;
22
use function reset;
23
use function sprintf;
24
use PhpParser\Node;
25
use PhpParser\NodeVisitorAbstract;
26
 
27
/**
28
 * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
29
 */
30
final class ExecutableLinesFindingVisitor extends NodeVisitorAbstract
31
{
32
    /**
33
     * @var int
34
     */
35
    private $nextBranch = 0;
36
 
37
    /**
38
     * @var string
39
     */
40
    private $source;
41
 
42
    /**
43
     * @var array<int, int>
44
     */
45
    private $executableLinesGroupedByBranch = [];
46
 
47
    /**
48
     * @var array<int, bool>
49
     */
50
    private $unsets = [];
51
 
52
    /**
53
     * @var array<int, string>
54
     */
55
    private $commentsToCheckForUnset = [];
56
 
57
    public function __construct(string $source)
58
    {
59
        $this->source = $source;
60
    }
61
 
62
    public function enterNode(Node $node): void
63
    {
64
        foreach ($node->getComments() as $comment) {
65
            $commentLine = $comment->getStartLine();
66
 
67
            if (!isset($this->executableLinesGroupedByBranch[$commentLine])) {
68
                continue;
69
            }
70
 
71
            foreach (explode("\n", $comment->getText()) as $text) {
72
                $this->commentsToCheckForUnset[$commentLine] = $text;
73
                $commentLine++;
74
            }
75
        }
76
 
77
        if ($node instanceof Node\Scalar\String_ ||
78
            $node instanceof Node\Scalar\EncapsedStringPart) {
79
            $startLine = $node->getStartLine() + 1;
80
            $endLine   = $node->getEndLine() - 1;
81
 
82
            if ($startLine <= $endLine) {
83
                foreach (range($startLine, $endLine) as $line) {
84
                    unset($this->executableLinesGroupedByBranch[$line]);
85
                }
86
            }
87
 
88
            return;
89
        }
90
 
991 lars 91
        if ($node instanceof Node\Stmt\Interface_) {
92
            foreach (range($node->getStartLine(), $node->getEndLine()) as $line) {
93
                $this->unsets[$line] = true;
94
            }
95
 
96
            return;
97
        }
98
 
148 lars 99
        if ($node instanceof Node\Stmt\Declare_ ||
100
            $node instanceof Node\Stmt\DeclareDeclare ||
101
            $node instanceof Node\Stmt\Else_ ||
102
            $node instanceof Node\Stmt\EnumCase ||
103
            $node instanceof Node\Stmt\Finally_ ||
104
            $node instanceof Node\Stmt\Label ||
105
            $node instanceof Node\Stmt\Namespace_ ||
106
            $node instanceof Node\Stmt\Nop ||
107
            $node instanceof Node\Stmt\Switch_ ||
108
            $node instanceof Node\Stmt\TryCatch ||
109
            $node instanceof Node\Stmt\Use_ ||
110
            $node instanceof Node\Stmt\UseUse ||
200 lars 111
            $node instanceof Node\Expr\ConstFetch ||
148 lars 112
            $node instanceof Node\Expr\Match_ ||
113
            $node instanceof Node\Expr\Variable ||
114
            $node instanceof Node\ComplexType ||
115
            $node instanceof Node\Const_ ||
116
            $node instanceof Node\Identifier ||
117
            $node instanceof Node\Name ||
118
            $node instanceof Node\Param ||
119
            $node instanceof Node\Scalar) {
120
            return;
121
        }
122
 
123
        if ($node instanceof Node\Stmt\Throw_) {
124
            $this->setLineBranch($node->expr->getEndLine(), $node->expr->getEndLine(), ++$this->nextBranch);
125
 
126
            return;
127
        }
128
 
129
        if ($node instanceof Node\Stmt\Enum_ ||
130
            $node instanceof Node\Stmt\Function_ ||
131
            $node instanceof Node\Stmt\Class_ ||
132
            $node instanceof Node\Stmt\ClassMethod ||
133
            $node instanceof Node\Expr\Closure ||
134
            $node instanceof Node\Stmt\Trait_) {
135
            $isConcreteClassLike = $node instanceof Node\Stmt\Enum_ || $node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_;
136
 
137
            if (null !== $node->stmts) {
138
                foreach ($node->stmts as $stmt) {
139
                    if ($stmt instanceof Node\Stmt\Nop) {
140
                        continue;
141
                    }
142
 
143
                    foreach (range($stmt->getStartLine(), $stmt->getEndLine()) as $line) {
144
                        unset($this->executableLinesGroupedByBranch[$line]);
145
 
146
                        if (
147
                            $isConcreteClassLike &&
148
                            !$stmt instanceof Node\Stmt\ClassMethod
149
                        ) {
150
                            $this->unsets[$line] = true;
151
                        }
152
                    }
153
                }
154
            }
155
 
156
            if ($isConcreteClassLike) {
157
                return;
158
            }
159
 
160
            $hasEmptyBody = [] === $node->stmts ||
161
                null === $node->stmts ||
162
                (
163
                    1 === count($node->stmts) &&
164
                    $node->stmts[0] instanceof Node\Stmt\Nop
165
                );
166
 
167
            if ($hasEmptyBody) {
168
                if ($node->getEndLine() === $node->getStartLine()) {
169
                    return;
170
                }
171
 
172
                $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch);
173
 
174
                return;
175
            }
176
 
177
            return;
178
        }
179
 
180
        if ($node instanceof Node\Expr\ArrowFunction) {
181
            $startLine = max(
182
                $node->getStartLine() + 1,
183
                $node->expr->getStartLine()
184
            );
185
 
186
            $endLine = $node->expr->getEndLine();
187
 
188
            if ($endLine < $startLine) {
189
                return;
190
            }
191
 
192
            $this->setLineBranch($startLine, $endLine, ++$this->nextBranch);
193
 
194
            return;
195
        }
196
 
197
        if ($node instanceof Node\Expr\Ternary) {
198
            if (null !== $node->if &&
199
                $node->getStartLine() !== $node->if->getEndLine()) {
200
                $this->setLineBranch($node->if->getStartLine(), $node->if->getEndLine(), ++$this->nextBranch);
201
            }
202
 
203
            if ($node->getStartLine() !== $node->else->getEndLine()) {
204
                $this->setLineBranch($node->else->getStartLine(), $node->else->getEndLine(), ++$this->nextBranch);
205
            }
206
 
207
            return;
208
        }
209
 
210
        if ($node instanceof Node\Expr\BinaryOp\Coalesce) {
211
            if ($node->getStartLine() !== $node->getEndLine()) {
212
                $this->setLineBranch($node->getEndLine(), $node->getEndLine(), ++$this->nextBranch);
213
            }
214
 
215
            return;
216
        }
217
 
218
        if ($node instanceof Node\Stmt\If_ ||
219
            $node instanceof Node\Stmt\ElseIf_ ||
220
            $node instanceof Node\Stmt\Case_) {
221
            if (null === $node->cond) {
222
                return;
223
            }
224
 
225
            $this->setLineBranch(
226
                $node->cond->getStartLine(),
227
                $node->cond->getStartLine(),
228
                ++$this->nextBranch
229
            );
230
 
231
            return;
232
        }
233
 
234
        if ($node instanceof Node\Stmt\For_) {
235
            $startLine = null;
236
            $endLine   = null;
237
 
238
            if ([] !== $node->init) {
239
                $startLine = $node->init[0]->getStartLine();
240
 
241
                end($node->init);
242
 
243
                $endLine = current($node->init)->getEndLine();
244
 
245
                reset($node->init);
246
            }
247
 
248
            if ([] !== $node->cond) {
249
                if (null === $startLine) {
250
                    $startLine = $node->cond[0]->getStartLine();
251
                }
252
 
253
                end($node->cond);
254
 
255
                $endLine = current($node->cond)->getEndLine();
256
 
257
                reset($node->cond);
258
            }
259
 
260
            if ([] !== $node->loop) {
261
                if (null === $startLine) {
262
                    $startLine = $node->loop[0]->getStartLine();
263
                }
264
 
265
                end($node->loop);
266
 
267
                $endLine = current($node->loop)->getEndLine();
268
 
269
                reset($node->loop);
270
            }
271
 
272
            if (null === $startLine || null === $endLine) {
273
                return;
274
            }
275
 
276
            $this->setLineBranch(
277
                $startLine,
278
                $endLine,
279
                ++$this->nextBranch
280
            );
281
 
282
            return;
283
        }
284
 
285
        if ($node instanceof Node\Stmt\Foreach_) {
286
            $this->setLineBranch(
287
                $node->expr->getStartLine(),
288
                $node->valueVar->getEndLine(),
289
                ++$this->nextBranch
290
            );
291
 
292
            return;
293
        }
294
 
295
        if ($node instanceof Node\Stmt\While_ ||
296
            $node instanceof Node\Stmt\Do_) {
297
            $this->setLineBranch(
298
                $node->cond->getStartLine(),
299
                $node->cond->getEndLine(),
300
                ++$this->nextBranch
301
            );
302
 
303
            return;
304
        }
305
 
306
        if ($node instanceof Node\Stmt\Catch_) {
307
            assert([] !== $node->types);
308
            $startLine = $node->types[0]->getStartLine();
309
            end($node->types);
310
            $endLine = current($node->types)->getEndLine();
311
 
312
            $this->setLineBranch(
313
                $startLine,
314
                $endLine,
315
                ++$this->nextBranch
316
            );
317
 
318
            return;
319
        }
320
 
321
        if ($node instanceof Node\Expr\CallLike) {
322
            if (isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) {
323
                $branch = $this->executableLinesGroupedByBranch[$node->getStartLine()];
324
            } else {
325
                $branch = ++$this->nextBranch;
326
            }
327
 
328
            $this->setLineBranch($node->getStartLine(), $node->getEndLine(), $branch);
329
 
330
            return;
331
        }
332
 
333
        if (isset($this->executableLinesGroupedByBranch[$node->getStartLine()])) {
334
            return;
335
        }
336
 
337
        $this->setLineBranch($node->getStartLine(), $node->getEndLine(), ++$this->nextBranch);
338
    }
339
 
340
    public function afterTraverse(array $nodes): void
341
    {
342
        $lines = explode("\n", $this->source);
343
 
344
        foreach ($lines as $lineNumber => $line) {
345
            $lineNumber++;
346
 
347
            if (1 === preg_match('/^\s*$/', $line) ||
348
                (
349
                    isset($this->commentsToCheckForUnset[$lineNumber]) &&
350
                    1 === preg_match(sprintf('/^\s*%s\s*$/', preg_quote($this->commentsToCheckForUnset[$lineNumber], '/')), $line)
351
                )) {
352
                unset($this->executableLinesGroupedByBranch[$lineNumber]);
353
            }
354
        }
355
 
356
        $this->executableLinesGroupedByBranch = array_diff_key(
357
            $this->executableLinesGroupedByBranch,
358
            $this->unsets
359
        );
360
    }
361
 
362
    public function executableLinesGroupedByBranch(): array
363
    {
364
        return $this->executableLinesGroupedByBranch;
365
    }
366
 
367
    private function setLineBranch(int $start, int $end, int $branch): void
368
    {
369
        foreach (range($start, $end) as $line) {
370
            $this->executableLinesGroupedByBranch[$line] = $branch;
371
        }
372
    }
373
}