Passed
Push — main ( fefea7...05b26b )
by mikhail
08:12
created

Analyzer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 0
c 1
b 1
f 0
dl 0
loc 10
ccs 1
cts 1
cp 1
rs 10
cc 1
nc 1
nop 8
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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