Passed
Push — main ( 804823...d1baf3 )
by mikhail
03:15
created

MissingDocBlockAnalyzer::isDocBlockRequired()   B

Complexity

Conditions 11
Paths 16

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 11

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 11
eloc 12
c 3
b 0
f 0
nc 16
nop 3
dl 0
loc 24
ccs 13
cts 13
cp 1
crap 11
rs 7.3166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SavinMikhail\CommentsDensity;
6
7
use function dd;
8
use function in_array;
9
use function is_array;
10
11
use const T_ARRAY;
12
use const T_CLASS;
13
use const T_CONST;
14
use const T_DOC_COMMENT;
15
use const T_FINAL;
16
use const T_FUNCTION;
17
use const T_INTERFACE;
18
use const T_PAAMAYIM_NEKUDOTAYIM;
19
use const T_PRIVATE;
20
use const T_PROTECTED;
21
use const T_PUBLIC;
22
use const T_READONLY;
23
use const T_STATIC;
24
use const T_STRING;
25
use const T_TRAIT;
26
use const T_ENUM;
27
use const T_VARIABLE;
28
use const T_WHITESPACE;
29
30
final class MissingDocBlockAnalyzer
31
{
32
    private bool $exceedThreshold = false;
33
34
    /**
35
     * Analyzes the tokens of a file for docblocks.
36
     *
37
     * @param array $tokens The tokens to analyze.
38
     * @return array The analysis results.
39
     */
40 19
    private function analyzeTokens(array $tokens, string $filename): array
41
    {
42 19
        $lastDocBlock = null;
43 19
        $missingDocBlocks = [];
44 19
        $tokenCount = count($tokens);
45
46 19
        for ($i = 0; $i < $tokenCount; $i++) {
47 19
            $token = $tokens[$i];
48
49 19
            if (! is_array($token)) {
50 19
                continue;
51
            }
52
53 19
            if ($token[0] === T_DOC_COMMENT) {
54 8
                $lastDocBlock = $token[1];
55 19
            } elseif ($this->isDocBlockRequired($token, $tokens, $i)) {
56
57 13
                if (empty($lastDocBlock)) {
58 12
                    $missingDocBlocks[] = $this->createMissingDocBlockStat($token, $filename);
59
                }
60 13
                $lastDocBlock = null;
61
            }
62
        }
63
64 19
        return $missingDocBlocks;
65
    }
66
67 15
    private function isFollowingToken(array $tokens, int $index, string $expectedToken): bool
68
    {
69 15
        for ($j = $index + 1, $count = count($tokens); $j < $count; $j++) {
70 15
            $nextToken = $tokens[$j];
71 15
            if ($nextToken[0] === T_WHITESPACE) {
72 13
                continue;
73
            }
74 15
            return $nextToken === $expectedToken;
75
        }
76
        return false;
77
    }
78
79 12
    private function isAnonymousClass(array $tokens, int $index): bool
80
    {
81 12
        return $this->isFollowingToken($tokens, $index, '{');
82
    }
83
84 6
    private function isAnonymousFunction(array $tokens, int $index): bool
85
    {
86 6
        return $this->isFollowingToken($tokens, $index, '(');
87
    }
88
89 19
    private function isDocBlockRequired(array $token, array $tokens, int $index): bool
90
    {
91 19
        if (!in_array($token[0], [T_CLASS, T_TRAIT, T_INTERFACE, T_ENUM, T_FUNCTION, T_CONST, T_VARIABLE], true)) {
92 19
            return false;
93
        }
94 19
        if ($token[0] === T_CLASS) {
95 12
            if ($this->isAnonymousClass($tokens, $index) || $this->isClosure($tokens, $index)) {
96 3
                return false;
97
            }
98
        }
99
100 19
        if ($token[0] === T_FUNCTION) {
101 6
            if ($this->isAnonymousFunction($tokens, $index) || ! $this->isFunctionDeclaration($tokens, $index)) {
102 2
                return false;
103
            }
104
        }
105
106 18
        if ($token[0] === T_CONST || $token[0] === T_VARIABLE) {
107 12
            if (! $this->isPropertyOrConstant($tokens, $index)) {
108 7
                return false;
109
            }
110
        }
111
112 13
        return true;
113
    }
114
115 12
    private function createMissingDocBlockStat(array $token, string $filename): array
116
    {
117 12
        return [
118 12
            'type' => 'missingDocblock',
119 12
            'content' => '',
120 12
            'file' => $filename,
121 12
            'line' => $token[2]
122 12
        ];
123
    }
124
125 12
    private function isPropertyOrConstant(array $tokens, int $index): bool
126
    {
127
//        dd(
128
//            $tokens[$index - 4],
129
//            $tokens[$index - 3],
130
//            $tokens[$index - 2],
131
//            $tokens[$index - 1],
132
//            $tokens[$index]
133
//        );
134 12
        return (
135 12
            $tokens[$index - 1][0] === T_WHITESPACE
136 12
            && (
137 12
                $tokens[$index - 2][0] === T_STRING
138 12
                || $tokens[$index - 2][0] === T_ARRAY
139 12
                || $tokens[$index - 2][0] === T_STATIC
140 12
                || $tokens[$index - 2][0] === T_READONLY
141 12
                || $tokens[$index - 2][0] === T_PUBLIC
142 12
                || $tokens[$index - 2][0] === T_PROTECTED
143 12
                || $tokens[$index - 2][0] === T_PRIVATE
144 12
                || $tokens[$index - 2][0] === T_FINAL
145 12
            )
146 12
            && (
147 12
                $tokens[$index - 3][0] === T_WHITESPACE
148 12
            )
149 12
            && ($tokens[$index - 4] !== '(' && $tokens[$index - 4] !== ',')
150 12
        );
151
    }
152
153 11
    protected function isClosure(array $tokens, int $index): bool
154
    {
155 11
        $tokenCount = count($tokens);
156 11
        for ($j = $index + 1; $j < $tokenCount; $j++) {
157 11
            $nextToken = $tokens[$j];
158 11
            if ($nextToken[0] === T_WHITESPACE) {
159 9
                continue;
160
            }
161
            if (
162 11
                $tokens[$index - 2][0] === T_STRING
163 11
                && $tokens[$index - 1][0] === T_PAAMAYIM_NEKUDOTAYIM
164 11
                && $tokens[$index][0] === T_CLASS
165
            ) {
166 2
                return true;
167
            }
168
        }
169 9
        return false;
170
    }
171
172 5
    private function isFunctionDeclaration(array $tokens, int $index): bool
173
    {
174 5
        $tokenCount = count($tokens);
175 5
        for ($j = $index + 1; $j < $tokenCount; $j++) {
176 5
            $nextToken = $tokens[$j];
177 5
            if (is_array($nextToken) && $nextToken[0] === T_STRING) {
178 5
                continue;
179
            }
180 5
            if ($nextToken === '(') {
181 4
                return true;
182
            }
183 5
            if ($nextToken === ';') {
184 1
                return false;
185
            }
186
        }
187
        return false;
188
    }
189
190 19
    public function getMissingDocblocks(array $tokens, string $filename): array
191
    {
192 19
        return $this->analyzeTokens($tokens, $filename);
193
    }
194
195
    public function getColor(): string
196
    {
197
        return 'red';
198
    }
199
200
    public function getStatColor(float $count, array $thresholds): string
201
    {
202
        if (! isset($thresholds['missingDocBlock'])) {
203
            return 'white';
204
        }
205
        if ($count <= $thresholds['missingDocBlock']) {
206
            return 'green';
207
        }
208
        $this->exceedThreshold = true;
209
        return 'red';
210
    }
211
212
    public function hasExceededThreshold(): bool
213
    {
214
        return $this->exceedThreshold;
215
    }
216
217
    public function getName(): string
218
    {
219
        return 'missingDocblock';
220
    }
221
}
222