Subversion-Projekte lars-tiefland.laravel_shop

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 array_merge;
13
use function array_unique;
14
use function assert;
15
use function file_get_contents;
16
use function is_array;
17
use function max;
18
use function range;
19
use function sort;
20
use function sprintf;
21
use function substr_count;
22
use function token_get_all;
23
use function trim;
24
use PhpParser\Error;
25
use PhpParser\Lexer;
26
use PhpParser\NodeTraverser;
27
use PhpParser\NodeVisitor\NameResolver;
28
use PhpParser\NodeVisitor\ParentConnectingVisitor;
29
use PhpParser\ParserFactory;
30
use SebastianBergmann\CodeCoverage\ParserException;
31
use SebastianBergmann\LinesOfCode\LineCountingVisitor;
32
 
33
/**
34
 * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
35
 */
36
final class ParsingFileAnalyser implements FileAnalyser
37
{
38
    /**
39
     * @var array
40
     */
41
    private $classes = [];
42
 
43
    /**
44
     * @var array
45
     */
46
    private $traits = [];
47
 
48
    /**
49
     * @var array
50
     */
51
    private $functions = [];
52
 
53
    /**
54
     * @var array<string,array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int}>
55
     */
56
    private $linesOfCode = [];
57
 
58
    /**
59
     * @var array
60
     */
61
    private $ignoredLines = [];
62
 
63
    /**
64
     * @var array
65
     */
66
    private $executableLines = [];
67
 
68
    /**
69
     * @var bool
70
     */
71
    private $useAnnotationsForIgnoringCode;
72
 
73
    /**
74
     * @var bool
75
     */
76
    private $ignoreDeprecatedCode;
77
 
78
    public function __construct(bool $useAnnotationsForIgnoringCode, bool $ignoreDeprecatedCode)
79
    {
80
        $this->useAnnotationsForIgnoringCode = $useAnnotationsForIgnoringCode;
81
        $this->ignoreDeprecatedCode          = $ignoreDeprecatedCode;
82
    }
83
 
84
    public function classesIn(string $filename): array
85
    {
86
        $this->analyse($filename);
87
 
88
        return $this->classes[$filename];
89
    }
90
 
91
    public function traitsIn(string $filename): array
92
    {
93
        $this->analyse($filename);
94
 
95
        return $this->traits[$filename];
96
    }
97
 
98
    public function functionsIn(string $filename): array
99
    {
100
        $this->analyse($filename);
101
 
102
        return $this->functions[$filename];
103
    }
104
 
105
    /**
106
     * @psalm-return array{linesOfCode: int, commentLinesOfCode: int, nonCommentLinesOfCode: int}
107
     */
108
    public function linesOfCodeFor(string $filename): array
109
    {
110
        $this->analyse($filename);
111
 
112
        return $this->linesOfCode[$filename];
113
    }
114
 
115
    public function executableLinesIn(string $filename): array
116
    {
117
        $this->analyse($filename);
118
 
119
        return $this->executableLines[$filename];
120
    }
121
 
122
    public function ignoredLinesFor(string $filename): array
123
    {
124
        $this->analyse($filename);
125
 
126
        return $this->ignoredLines[$filename];
127
    }
128
 
129
    /**
130
     * @throws ParserException
131
     */
132
    private function analyse(string $filename): void
133
    {
134
        if (isset($this->classes[$filename])) {
135
            return;
136
        }
137
 
138
        $source      = file_get_contents($filename);
139
        $linesOfCode = max(substr_count($source, "\n") + 1, substr_count($source, "\r") + 1);
140
 
141
        if ($linesOfCode === 0 && !empty($source)) {
142
            $linesOfCode = 1;
143
        }
144
 
145
        $parser = (new ParserFactory)->create(
146
            ParserFactory::PREFER_PHP7,
147
            new Lexer
148
        );
149
 
150
        try {
151
            $nodes = $parser->parse($source);
152
 
153
            assert($nodes !== null);
154
 
155
            $traverser                     = new NodeTraverser;
156
            $codeUnitFindingVisitor        = new CodeUnitFindingVisitor;
157
            $lineCountingVisitor           = new LineCountingVisitor($linesOfCode);
158
            $ignoredLinesFindingVisitor    = new IgnoredLinesFindingVisitor($this->useAnnotationsForIgnoringCode, $this->ignoreDeprecatedCode);
159
            $executableLinesFindingVisitor = new ExecutableLinesFindingVisitor($source);
160
 
161
            $traverser->addVisitor(new NameResolver);
162
            $traverser->addVisitor(new ParentConnectingVisitor);
163
            $traverser->addVisitor($codeUnitFindingVisitor);
164
            $traverser->addVisitor($lineCountingVisitor);
165
            $traverser->addVisitor($ignoredLinesFindingVisitor);
166
            $traverser->addVisitor($executableLinesFindingVisitor);
167
 
168
            /* @noinspection UnusedFunctionResultInspection */
169
            $traverser->traverse($nodes);
170
            // @codeCoverageIgnoreStart
171
        } catch (Error $error) {
172
            throw new ParserException(
173
                sprintf(
174
                    'Cannot parse %s: %s',
175
                    $filename,
176
                    $error->getMessage()
177
                ),
178
                $error->getCode(),
179
                $error
180
            );
181
        }
182
        // @codeCoverageIgnoreEnd
183
 
184
        $this->classes[$filename]         = $codeUnitFindingVisitor->classes();
185
        $this->traits[$filename]          = $codeUnitFindingVisitor->traits();
186
        $this->functions[$filename]       = $codeUnitFindingVisitor->functions();
187
        $this->executableLines[$filename] = $executableLinesFindingVisitor->executableLinesGroupedByBranch();
188
        $this->ignoredLines[$filename]    = [];
189
 
190
        $this->findLinesIgnoredByLineBasedAnnotations($filename, $source, $this->useAnnotationsForIgnoringCode);
191
 
192
        $this->ignoredLines[$filename] = array_unique(
193
            array_merge(
194
                $this->ignoredLines[$filename],
195
                $ignoredLinesFindingVisitor->ignoredLines()
196
            )
197
        );
198
 
199
        sort($this->ignoredLines[$filename]);
200
 
201
        $result = $lineCountingVisitor->result();
202
 
203
        $this->linesOfCode[$filename] = [
204
            'linesOfCode'           => $result->linesOfCode(),
205
            'commentLinesOfCode'    => $result->commentLinesOfCode(),
206
            'nonCommentLinesOfCode' => $result->nonCommentLinesOfCode(),
207
        ];
208
    }
209
 
210
    private function findLinesIgnoredByLineBasedAnnotations(string $filename, string $source, bool $useAnnotationsForIgnoringCode): void
211
    {
212
        if (!$useAnnotationsForIgnoringCode) {
213
            return;
214
        }
215
 
216
        $start = false;
217
 
218
        foreach (token_get_all($source) as $token) {
219
            if (!is_array($token) ||
220
                !(T_COMMENT === $token[0] || T_DOC_COMMENT === $token[0])) {
221
                continue;
222
            }
223
 
224
            $comment = trim($token[1]);
225
 
226
            if ($comment === '// @codeCoverageIgnore' ||
227
                $comment === '//@codeCoverageIgnore') {
228
                $this->ignoredLines[$filename][] = $token[2];
229
 
230
                continue;
231
            }
232
 
233
            if ($comment === '// @codeCoverageIgnoreStart' ||
234
                $comment === '//@codeCoverageIgnoreStart') {
235
                $start = $token[2];
236
 
237
                continue;
238
            }
239
 
240
            if ($comment === '// @codeCoverageIgnoreEnd' ||
241
                $comment === '//@codeCoverageIgnoreEnd') {
242
                if (false === $start) {
243
                    $start = $token[2];
244
                }
245
 
246
                $this->ignoredLines[$filename] = array_merge(
247
                    $this->ignoredLines[$filename],
248
                    range($start, $token[2])
249
                );
250
            }
251
        }
252
    }
253
}