Passed
Push — main ( 2bf89b...450136 )
by mikhail
03:21
created

Analyzer::getCommentsFromFile()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 21
ccs 0
cts 16
cp 0
rs 9.4888
c 0
b 0
f 0
cc 5
nc 5
nop 2
crap 30
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SavinMikhail\CommentsDensity\Analyzer;
6
7
use Generator;
8
use SavinMikhail\CommentsDensity\Baseline\BaselineManager;
9
use SavinMikhail\CommentsDensity\Comments\CommentFactory;
10
use SavinMikhail\CommentsDensity\Comments\CommentTypeInterface;
11
use SavinMikhail\CommentsDensity\DTO\Input\ConfigDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDensity\DTO\Input\ConfigDTO was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use SavinMikhail\CommentsDensity\DTO\Output\CommentDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDensity\DTO\Output\CommentDTO was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
use SavinMikhail\CommentsDensity\DTO\Output\CommentStatisticsDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDen...ut\CommentStatisticsDTO was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use SavinMikhail\CommentsDensity\DTO\Output\OutputDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDensity\DTO\Output\OutputDTO was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use SavinMikhail\CommentsDensity\Metrics\MetricsFacade;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDensity\Metrics\MetricsFacade was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use SavinMikhail\CommentsDensity\MissingDocblock\MissingDocBlockAnalyzer;
17
use SplFileInfo;
18
use Symfony\Component\Console\Output\OutputInterface;
19
20
use function array_merge;
21
use function array_push;
22
use function file;
23
use function file_get_contents;
24
use function in_array;
25
use function is_array;
26
use function str_contains;
27
use function substr_count;
28
use function token_get_all;
29
30
use const PHP_EOL;
31
use const T_COMMENT;
32
use const T_DOC_COMMENT;
33
34
final class Analyzer
35
{
36
    private bool $exceedThreshold = false;
37
38
    public function __construct(
39
        private readonly ConfigDTO $configDTO,
40
        private readonly CommentFactory $commentFactory,
41
        private readonly MissingDocBlockAnalyzer $missingDocBlock,
42
        private readonly MetricsFacade $metrics,
43
        private readonly OutputInterface $output,
44
        private readonly MissingDocBlockAnalyzer $docBlockAnalyzer,
45
        private readonly BaselineManager $baselineManager,
46
    ) {
47
    }
48
49
    public function analyze(Generator $files): OutputDTO
50
    {
51
        $this->metrics->startPerformanceMonitoring();
52
        $comments = [];
53
        $totalLinesOfCode = 0;
54
        $filesAnalyzed = 0;
55
56
        foreach ($files as $file) {
57
            if (!($file instanceof SplFileInfo)) {
58
                continue;
59
            }
60
            if ($this->isInWhitelist($file->getRealPath())) {
61
                continue;
62
            }
63
            if ($file->getSize() === 0) {
64
                continue;
65
            }
66
            if (!$this->isPhpFile($file) || !$file->isReadable()) {
67
                continue;
68
            }
69
70
            $commentsAndLines = $this->analyzeFile($file->getRealPath());
71
            $totalLinesOfCode += $commentsAndLines['linesOfCode'];
72
            array_push($comments, ...$commentsAndLines['comments']);
73
74
            $filesAnalyzed++;
75
        }
76
        if ($this->configDTO->useBaseline) {
77
            $comments = $this->baselineManager->filterComments($comments);
78
        }
79
80
        $commentStatistics = $this->countCommentOccurrences($comments);
81
82
        $this->metrics->stopPerformanceMonitoring();
83
84
        return $this->createOutputDTO(
85
            $comments,
86
            $commentStatistics,
87
            $totalLinesOfCode,
88
            $this
89
                ->metrics
90
                ->calculateCDS($commentStatistics),
91
            $filesAnalyzed,
92
        );
93
    }
94
95
    public function analyzeFile(
96
        string $filename,
97
    ): array {
98
        $this->output->writeln("<info>Analyzing $filename</info>");
99
100
        $code = file_get_contents($filename);
101
        $tokens = token_get_all($code);
102
103
        $comments = $this->getCommentsFromFile($tokens, $filename);
104
        if (
105
            empty($this->configDto->only)
0 ignored issues
show
Bug introduced by
The property configDto does not exist on SavinMikhail\CommentsDensity\Analyzer\Analyzer. Did you mean configDTO?
Loading history...
106
            || in_array('missingDocblock', $this->configDto->only, true)
107
        ) {
108
            $missingDocBlocks = $this
109
                ->docBlockAnalyzer
110
                ->getMissingDocblocks($code, $filename);
111
            $comments = array_merge($missingDocBlocks, $comments);
112
        }
113
114
        $linesOfCode = $this->countTotalLines($filename);
115
116
        return [
117
            'comments' => $comments,
118
            'linesOfCode' => $linesOfCode,
119
        ];
120
    }
121
122
    private function getCommentsFromFile(array $tokens, string $filename): array
123
    {
124
        $comments = [];
125
        foreach ($tokens as $token) {
126
            if (!is_array($token)) {
127
                continue;
128
            }
129
            if (!in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) {
130
                continue;
131
            }
132
            $commentType = $this->commentFactory->classifyComment($token[1]);
133
            if ($commentType) {
134
                $comments[] = [
135
                    'content' => $token[1],
136
                    'type' => $commentType,
137
                    'line' => $token[2],
138
                    'file' => $filename
139
                ];
140
            }
141
        }
142
        return $comments;
143
    }
144
145
    private function countTotalLines(string $filename): int
146
    {
147
        $fileContent = file($filename);
148
        return count($fileContent);
149
    }
150
151
    private function isPhpFile(SplFileInfo $file): bool
152
    {
153
        return $file->isFile() && $file->getExtension() === 'php';
154
    }
155
156
    private function countCommentOccurrences(array $comments): array
157
    {
158
        $lineCounts = [];
159
        foreach ($comments as $comment) {
160
            $typeName = (string)$comment['type'];
161
            if (!isset($lineCounts[$typeName])) {
162
                $lineCounts[$typeName] = [
163
                    'lines' => 0,
164
                    'count' => 0,
165
                ];
166
            }
167
            $lineCounts[$typeName]['lines'] += substr_count($comment['content'], PHP_EOL) + 1;
168
            $lineCounts[$typeName]['count']++;
169
        }
170
        return $lineCounts;
171
    }
172
173
    private function createOutputDTO(
174
        array $comments,
175
        array $commentStatistics,
176
        int $linesOfCode,
177
        float $cds,
178
        int $filesAnalyzed,
179
    ): OutputDTO {
180
        $preparedStatistics = $this->prepareCommentStatistics($commentStatistics);
181
        $preparedComments = $this->prepareComments($comments);
182
        $performanceMetrics = $this->metrics->getPerformanceMetrics();
183
        $comToLoc = $this->metrics->prepareComToLoc($commentStatistics, $linesOfCode);
184
        $cds = $this->metrics->prepareCDS($cds);
185
        if ($this->metrics->hasExceededThreshold()) {
186
            $this->exceedThreshold = true;
187
        }
188
        return new OutputDTO(
189
            $filesAnalyzed,
190
            $preparedStatistics,
191
            $preparedComments,
192
            $performanceMetrics,
193
            $comToLoc,
194
            $cds,
195
            $this->exceedThreshold
196
        );
197
    }
198
199
    private function prepareCommentStatistics(array $commentStatistics): array
200
    {
201
        $preparedStatistics = [];
202
        foreach ($commentStatistics as $type => $stat) {
203
            if ($type === 'missingDocblock') {
204
                $preparedStatistics[] = new CommentStatisticsDTO(
205
                    $this->missingDocBlock->getColor(),
206
                    $this->missingDocBlock->getName(),
207
                    $stat['lines'],
208
                    $this->missingDocBlock->getStatColor($stat['count'], $this->configDTO->thresholds),
209
                    $stat['count']
210
                );
211
                if ($this->missingDocBlock->hasExceededThreshold()) {
212
                    $this->exceedThreshold = true;
213
                }
214
                continue;
215
            }
216
            $commentType = $this->commentFactory->getCommentType($type);
217
            if ($commentType) {
218
                $preparedStatistics[] = new CommentStatisticsDTO(
219
                    $commentType->getColor(),
220
                    $commentType->getName(),
221
                    $stat['lines'],
222
                    $commentType->getStatColor($stat['count'], $this->configDTO->thresholds),
223
                    $stat['count']
224
                );
225
                if ($commentType->hasExceededThreshold()) {
226
                    $this->exceedThreshold = true;
227
                }
228
            }
229
        }
230
        return $preparedStatistics;
231
    }
232
233
    private function prepareComments(array $comments): array
234
    {
235
        $preparedComments = [];
236
        foreach ($comments as $comment) {
237
            /** @var CommentTypeInterface|string $commentType */
238
            $commentType = $comment['type'];
239
            if ($commentType === 'missingDocblock') {
240
                $preparedComments[] = new CommentDTO(
241
                    'missingDocblock',
242
                    'red',
243
                    $comment['file'],
244
                    $comment['line'],
245
                    $comment['content']
246
                );
247
                continue;
248
            }
249
            if ($commentType->getWeight() > 0) {
250
                continue;
251
            }
252
            $preparedComments[] = new CommentDTO(
253
                $commentType->getName(),
254
                $commentType->getColor(),
255
                $comment['file'],
256
                $comment['line'],
257
                $comment['content']
258
            );
259
        }
260
        return $preparedComments;
261
    }
262
263
    private function isInWhitelist(string $filePath): bool
264
    {
265
        foreach ($this->configDTO->exclude as $whitelistedDir) {
266
            if (str_contains($filePath, $whitelistedDir)) {
267
                return true;
268
            }
269
        }
270
        return false;
271
    }
272
}
273