Passed
Push — main ( b860fb...534932 )
by mikhail
02:56
created

CommentDensity::printFilesAnalyzed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SavinMikhail\CommentsDensity;
6
7
use RecursiveDirectoryIterator;
8
use RecursiveIteratorIterator;
9
use SavinMikhail\CommentsDensity\Comments\CommentFactory;
10
use SavinMikhail\CommentsDensity\Comments\CommentTypeInterface;
11
use SavinMikhail\CommentsDensity\DTO\CdsDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDensity\DTO\CdsDTO 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\CommentDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDensity\DTO\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\CommentStatisticsDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDen...TO\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\ComToLocDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDensity\DTO\ComToLocDTO 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\OutputDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDensity\DTO\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\DTO\PerformanceMetricsDTO;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDen...O\PerformanceMetricsDTO 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\Reporters\ConsoleReporter;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDen...porters\ConsoleReporter 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...
18
use SavinMikhail\CommentsDensity\Reporters\HtmlReporter;
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDen...\Reporters\HtmlReporter 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...
19
use SplFileInfo;
20
use Symfony\Component\Console\Output\OutputInterface;
21
22
use function round;
23
24
final class CommentDensity
25
{
26
    private bool $exceedThreshold = false;
27
28
    public function __construct(
29
        private readonly OutputInterface $output,
30
        private readonly array $thresholds,
31
        private readonly array $exclude,
32
        private readonly array $outputConfig,
33
        private readonly CommentFactory $commentFactory,
34
        private readonly FileAnalyzer $fileAnalyzer,
0 ignored issues
show
Bug introduced by
The type SavinMikhail\CommentsDensity\FileAnalyzer 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...
35
    ) {
36
    }
37
38
    public function analyzeDirectories(array $directories): bool
39
    {
40
        $startTime = microtime(true);
41
        $comments = [];
42
        $commentStatistics = [];
43
        $totalLinesOfCode = 0;
44
        $cdsSum = 0;
45
        $filesAnalyzed = 0;
46
47
        foreach ($directories as $directory) {
48
            $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
49
            /** @var SplFileInfo $file */
50
            foreach ($iterator as $file) {
51
                if ($this->isInWhitelist($file->getRealPath())) {
52
                    continue;
53
                }
54
                $this->fileAnalyzer->analyzeFile(
55
                    $file,
56
                    $commentStatistics,
57
                    $comments,
58
                    $totalLinesOfCode,
59
                    $cdsSum
60
                );
61
                $filesAnalyzed++;
62
            }
63
        }
64
65
        $endTime = microtime(true);
66
        $executionTimeMS = round(($endTime - $startTime) * 1000, 2);
67
        $peakMemoryUsage = memory_get_peak_usage(true);
68
69
        $outputDTO = $this->createOutputDTO(
70
            $comments,
71
            $commentStatistics,
72
            $totalLinesOfCode,
73
            $cdsSum / $totalLinesOfCode,
74
            $filesAnalyzed,
75
            $executionTimeMS,
76
            $peakMemoryUsage
77
        );
78
79
        $reporter = new ConsoleReporter($this->output, $outputDTO);
80
        if (! empty($this->outputConfig) && $this->outputConfig['type'] === 'html') {
81
            $reporter = new HtmlReporter($outputDTO, __DIR__ . '/../../../' . $this->outputConfig['file']);
82
        }
83
        $reporter->report();
84
85
        return $this->exceedThreshold;
86
    }
87
88
    private function createOutputDTO(
89
        array $comments,
90
        array $commentStatistics,
91
        int $linesOfCode,
92
        float $cds,
93
        int $filesAnalyzed,
94
        float $executionTime,
95
        float $peakMemoryUsage
96
    ): OutputDTO {
97
        $performanceMetricsDTO = new PerformanceMetricsDTO(
98
            $executionTime,
99
            round($peakMemoryUsage / 1024 / 1024, 2)
100
        );
101
        return new OutputDTO(
102
            $filesAnalyzed,
103
            $this->prepareCommentStatistics($commentStatistics),
104
            $this->prepareComments($comments),
105
            $performanceMetricsDTO,
106
            $this->prepareComToLoc($commentStatistics, $linesOfCode),
107
            $this->prepareCDS($cds)
108
        );
109
    }
110
111
    private function prepareCDS(float $cds): CdsDTO
112
    {
113
        $cds = round($cds, 2);
114
        return new CdsDTO(
115
            $cds,
116
            $this->getColorForCDS($cds),
117
        );
118
    }
119
120
    private function getColorForCDS(float $cds): string
121
    {
122
        if (! isset($this->thresholds['CDS'])) {
123
            return 'white';
124
        }
125
        if ($cds >= $this->thresholds['CDS']) {
126
            return 'green';
127
        }
128
        $this->exceedThreshold = true;
129
        return 'red';
130
    }
131
132
    private function prepareComToLoc(array $commentStatistics, int $linesOfCode): ComToLocDTO
133
    {
134
        $ratio = $this->getRatio($commentStatistics, $linesOfCode);
135
        return new ComToLocDTO(
136
            $ratio,
137
            $this->getColorForRatio($ratio)
138
        );
139
    }
140
141
    private function getRatio(array $commentStatistics, int $linesOfCode): float
142
    {
143
        $totalComments = array_sum($commentStatistics);
144
        return round($totalComments / $linesOfCode, 2);
145
    }
146
147
    private function getColorForRatio(float $ratio): string
148
    {
149
        if (! isset($this->thresholds['Com/LoC'])) {
150
            return 'white';
151
        }
152
        if ($ratio >= $this->thresholds['Com/LoC']) {
153
            return 'green';
154
        }
155
        $this->exceedThreshold = true;
156
        return 'red';
157
    }
158
159
    private function prepareCommentStatistics(array $commentStatistics): array
160
    {
161
        $preparedCommentStatistics = [];
162
        foreach ($commentStatistics as $type => $count) {
163
            if ($type === 'missingDocblock') {
164
                $preparedCommentStatistics[] = new CommentStatisticsDTO(
165
                    $this->getMissingDocBlockColor(),
166
                    'missingDocblock',
167
                    $count,
168
                    $this->getMissingDocBlockStatColor($count)
169
                );
170
                continue;
171
            }
172
            $commentType = $this->commentFactory->getCommentType($type);
173
            if ($commentType) {
174
                $preparedCommentStatistics[] = new CommentStatisticsDTO(
175
                    $commentType->getColor(),
176
                    $commentType->getName(),
177
                    $count,
178
                    $commentType->getStatColor($count, $this->thresholds)
179
                );
180
            }
181
        }
182
        return  $preparedCommentStatistics;
183
    }
184
185
    private function getMissingDocBlockColor(): string
186
    {
187
        return 'red';
188
    }
189
190
    private function getMissingDocBlockStatColor(float $count): string
191
    {
192
        if (! isset($this->thresholds['missingDocBlock'])) {
193
            return 'white';
194
        }
195
        if ($count <= $this->thresholds['missingDocBlock']) {
196
            return 'green';
197
        }
198
        $this->exceedThreshold = true;
199
        return 'red';
200
    }
201
202
    private function prepareComments(array $comments): array
203
    {
204
        $preparedComments = [];
205
        foreach ($comments as $comment) {
206
            /** @var CommentTypeInterface|string $commentType */
207
            $commentType = $comment['type'];
208
            if ($commentType === 'missingDocblock') {
209
                $preparedComments[] = new CommentDTO(
210
                    'missingDocblock',
211
                    'red',
212
                    $comment['file'],
213
                    $comment['line'],
214
                    $comment['content']
215
                );
216
                continue;
217
            }
218
            if ($commentType->getAttitude() === 'good') {
219
                continue;
220
            }
221
            $preparedComments[] = new CommentDTO(
222
                $commentType->getName(),
223
                $commentType->getColor(),
224
                $comment['file'],
225
                $comment['line'],
226
                $comment['content']
227
            );
228
        }
229
        return $preparedComments;
230
    }
231
232
    public function analyzeFile(string $filename): bool
233
    {
234
        $comments = [];
235
        $commentStatistics = [];
236
        $totalLinesOfCode = 0;
237
        $cdsSum = 0;
238
239
        $this->fileAnalyzer->analyzeFile(
240
            new SplFileInfo($filename),
241
            $commentStatistics,
242
            $comments,
243
            $totalLinesOfCode,
244
            $cdsSum
245
        );
246
247
248
        return $this->exceedThreshold;
249
    }
250
251
    private function isInWhitelist(string $filePath): bool
252
    {
253
        foreach ($this->exclude as $whitelistedDir) {
254
            if (str_contains($filePath, $whitelistedDir)) {
255
                return true;
256
            }
257
        }
258
        return false;
259
    }
260
}
261