GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 17dafe...3e29f4 )
by Gytis
02:42 queued 11s
created

FqcnMethodSniff   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 293
Duplicated Lines 0 %

Test Coverage

Coverage 97.95%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 154
c 7
b 0
f 0
dl 0
loc 293
ccs 143
cts 146
cp 0.9795
rs 3.12
wmc 66

6 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 10 3
A configure() 0 11 2
A register() 0 6 1
F processSigType() 0 123 32
C hasUselessDocBlock() 0 49 15
C processMethod() 0 52 13

How to fix   Complexity   

Complex Class

Complex classes like FqcnMethodSniff often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FqcnMethodSniff, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Gskema\TypeSniff\Sniffs\CodeElement;
4
5
use Gskema\TypeSniff\Core\Type\TypeComparator;
6
use Gskema\TypeSniff\Core\Type\TypeHelper;
7
use PHP_CodeSniffer\Files\File;
8
use Gskema\TypeSniff\Core\CodeElement\Element\AbstractFqcnMethodElement;
9
use Gskema\TypeSniff\Core\CodeElement\Element\ClassMethodElement;
10
use Gskema\TypeSniff\Core\CodeElement\Element\CodeElementInterface;
11
use Gskema\TypeSniff\Core\CodeElement\Element\InterfaceMethodElement;
12
use Gskema\TypeSniff\Core\DocBlock\DocBlock;
13
use Gskema\TypeSniff\Core\DocBlock\UndefinedDocBlock;
14
use Gskema\TypeSniff\Core\Type\Common\ArrayType;
15
use Gskema\TypeSniff\Core\Type\Common\UndefinedType;
16
use Gskema\TypeSniff\Core\Type\Common\VoidType;
17
use Gskema\TypeSniff\Core\Type\Declaration\NullableType;
18
use Gskema\TypeSniff\Core\Type\DocBlock\NullType;
19
use Gskema\TypeSniff\Core\Type\DocBlock\TypedArrayType;
20
use Gskema\TypeSniff\Core\Type\TypeConverter;
21
use Gskema\TypeSniff\Core\Type\TypeInterface;
22
23
class FqcnMethodSniff implements CodeElementSniffInterface
24
{
25
    /** @var string[] */
26
    protected $baseUsefulTags = [
27
        '@deprecated',
28
        '@throws',
29
        '@dataProvider',
30
        '@see',
31
        '@todo',
32
        '@inheritDoc'
33
    ];
34
35
    /** @var string[] */
36
    protected $usefulTags = [];
37
38
    /**
39
     * @inheritDoc
40
     */
41 5
    public function configure(array $config): void
42
    {
43 5
        $rawTags = array_merge($this->baseUsefulTags, $config['usefulTags'] ?? []);
44
45 5
        $usefulTags = [];
46 5
        foreach ($rawTags as $rawTag) {
47 5
            $usefulTags[] = strtolower(ltrim($rawTag, '@'));
48
        }
49 5
        $usefulTags = array_unique($usefulTags);
50
51 5
        $this->usefulTags = $usefulTags;
52 5
    }
53
54
    /**
55
     * @inheritDoc
56
     */
57 5
    public function register(): array
58
    {
59
        return [
60 5
            ClassMethodElement::class,
61
            // TraitMethodElement::class, // can be used to implement interface, not possible to know if it is extended
62
            InterfaceMethodElement::class,
63
        ];
64
    }
65
66
    /**
67
     * @inheritDoc
68
     * @param AbstractFqcnMethodElement $method
69
     */
70 4
    public function process(File $file, CodeElementInterface $method): void
71
    {
72 4
        $warningCountBefore = $file->getWarningCount();
73
74
        // @TODO Assert description
75 4
        $this->processMethod($file, $method);
76
77 4
        $hasNewWarnings = $file->getWarningCount() > $warningCountBefore;
78 4
        if (!$hasNewWarnings && $this->hasUselessDocBlock($method)) {
79 2
            $file->addWarningOnLine('Useless PHPDoc', $method->getLine(), 'FqcnMethodSniff');
80
        }
81 4
    }
82
83 4
    protected function processMethod(File $file, AbstractFqcnMethodElement $method): void
84
    {
85 4
        $fnSig = $method->getSignature();
86 4
        $docBlock = $method->getDocBlock();
87 4
        $isMagicMethod = '__' === substr($fnSig->getName(), 0, 2);
88 4
        $isConstructMethod = '__construct' === $fnSig->getName();
89 4
        $hasInheritDocTag = $docBlock->hasTag('inheritdoc');
90
91
        // @inheritDoc
92
        // __construct can be detected as extended and magic, but we want to inspect it anyway
93 4
        if (!$isConstructMethod) {
94 4
            if ($hasInheritDocTag || $isMagicMethod) {
95 1
                return;
96 4
            } elseif ($method->isExtended()) {
97 1
                $file->addWarningOnLine('Missing @inheritDoc tag. Remove duplicated parent PHPDoc content.', $method->getLine(), 'FqcnMethodSniff');
98 1
                return;
99
            }
100
        }
101
102
        // @param
103 4
        foreach ($fnSig->getParams() as $fnParam) {
104 4
            $paramName = $fnParam->getName();
105 4
            $tag = $docBlock->getParamTag($paramName);
106
107 4
            $subject = sprintf('parameter $%s', $paramName);
108
109 4
            $fnType = $fnParam->getType();
110 4
            $fnTypeLine = $fnParam->getLine();
111 4
            $docType = $tag ? $tag->getType() : null;
112 4
            $docTypeLine = $tag ? $tag->getLine() : $fnTypeLine;
113 4
            $valueType = $fnParam->getValueType();
114
115 4
            $this->processSigType($file, $docBlock, $subject, $fnType, $fnTypeLine, $docType, $docTypeLine, $valueType);
116
        }
117
118
        // @return
119 4
        if (!$isConstructMethod) {
120 4
            $docType = $docBlock->getReturnTag();
121 4
            $this->processSigType(
122 4
                $file,
123
                $docBlock,
124 4
                'return value',
125 4
                $fnSig->getReturnType(),
126 4
                $fnSig->getReturnLine(),
127 4
                $docType ? $docType->getType() : null,
128 4
                $docType ? $docType->getLine() : $fnSig->getLine(),
129 4
                new UndefinedType()
130
            );
131
        } else {
132 2
            foreach ($docBlock->getDescriptionLines() as $lineNum => $descLine) {
133 1
                if (preg_match('#^\w+\s+constructor\.?$#', $descLine)) {
134 1
                    $file->addWarningOnLine('Useless description.', $lineNum, 'FqcnMethodSniff');
135
                }
136
            }
137
        }
138 4
    }
139
140 4
    protected function processSigType(
141
        File $file,
142
        DocBlock $docBlock,
143
        string $subject,
144
        TypeInterface $fnType,
145
        int $fnTypeLine,
146
        ?TypeInterface $docType,
147
        int $docTypeLine,
148
        ?TypeInterface $valueType
149
    ): void {
150 4
        $isReturnType = 'return value' === $subject;
151 4
        $docTypeDefined = !($docType instanceof UndefinedType);
152 4
        $fnTypeDefined = !($fnType instanceof UndefinedType);
153
154
        /** @var string[][] $warnings */
155 4
        $warnings = [];
156
157
        // func1(string $arg1 = null) -> ?string
158 4
        if ($valueType instanceof NullType && $fnTypeDefined && !($fnType instanceof NullableType)) {
159 1
            $warnings[$fnTypeLine][] = sprintf(
160 1
                'Change :subject: type declaration to nullable, e.g. %s. Remove default null value if this argument is required.',
161 1
                (new NullableType($fnType))->toString()
162
            );
163
        }
164
165 4
        if ($docBlock instanceof UndefinedDocBlock) {
166
            // Require docType for undefined type or array type
167 3
            if ($fnType instanceof UndefinedType) {
168 2
                $warnings[$fnTypeLine][] = 'Add type declaration for :subject: or create PHPDoc with type hint';
169 2
            } elseif (TypeHelper::containsType($fnType, ArrayType::class)) {
170 3
                $warnings[$fnTypeLine][] = 'Create PHPDoc with typed array type hint for :subject:, .e.g.: "string[]" or "SomeClass[]"';
171
            }
172 4
        } elseif (null === $docType) {
173
            // Require docTag unless void return type
174 4
            if ($isReturnType) {
175 4
                if (!($fnType instanceof VoidType)) {
176 4
                    $warnings[$fnTypeLine][] = 'Missing PHPDoc tag or void type declaration for :subject:';
177
                }
178
            } else {
179 4
                $warnings[$fnTypeLine][] = 'Missing PHPDoc tag for :subject:';
180
            }
181
        } else {
182 4
            if ($docTypeDefined) {
183
                // Require typed array type
184
                // Require composite with null instead of null
185
                // @TODO true/void/false/$this/ cannot be param tags
186
187 4
                $docHasTypedArray = TypeHelper::containsType($docType, TypedArrayType::class);
188 4
                $docHasArray = TypeHelper::containsType($docType, ArrayType::class);
189
190 4
                if (!$docHasTypedArray && $docHasArray) {
191 2
                    $warnings[$docTypeLine][] = 'Replace array type with typed array type in PHPDoc for :subject:. Use mixed[] for generic arrays. Correct array depth must be specified.';
192
                }
193
194 4
                if ($redundantTypes = TypeComparator::getRedundantDocTypes($docType)) {
195 2
                    $warnings[$docTypeLine][] = sprintf('Remove redundant :subject: type hints "%s"', TypeHelper::listRawTypes($redundantTypes));
196
                }
197
198 4
                if ($docHasTypedArray && $fakeType = TypeHelper::getFakeTypedArrayType($docType)) {
199 1
                    $msg = sprintf(
200 1
                        'Use a more specific type in typed array hint "%s" for :subject:. Correct array depth must be specified.',
201 1
                        $fakeType->toString()
202
                    );
203 1
                    $warnings[$docTypeLine][] = $msg;
204
                }
205
206 4
                if ($docType instanceof NullType) {
207 1
                    if ($isReturnType) {
208
                        $warnings[$docTypeLine][] = 'Use void :subject :type declaration or change type to compound, e.g. SomeClass|null';
209
                    } else {
210 4
                        $warnings[$docTypeLine][] = 'Change type hint for :subject: to compound, e.g. SomeClass|null';
211
                    }
212
                }
213
            } else {
214
                // Require docType (example from fnType)
215 1
                $exampleDocType = TypeConverter::toExampleDocType($fnType);
216 1
                if (null !== $exampleDocType) {
217 1
                    $warnings[$docTypeLine][] = sprintf('Add type hint in PHPDoc tag for :subject:, e.g. "%s"', $exampleDocType->toString());
218
                } else {
219 1
                    $warnings[$docTypeLine][] = 'Add type hint in PHPDoc tag for :subject:';
220
                }
221
            }
222
223 4
            if (!$fnTypeDefined) {
224
                // Require fnType if possible (check, suggest from docType)
225 3
                if ($suggestedFnType = TypeConverter::toExampleFnType($docType)) {
226 2
                    $warnings[$fnTypeLine][] = sprintf('Add type declaration for :subject:, e.g.: "%s"', $suggestedFnType->toString());
227
                }
228
            }
229
230 4
            if ($docTypeDefined && $fnTypeDefined) {
231
                // Require to add missing types to docType,
232 4
                if ($fnType instanceof VoidType && $docType instanceof VoidType) {
233 1
                    $warnings[$docTypeLine][] = 'Remove @return void tag, not necessary';
234
                }
235
236
                /** @var TypeInterface[] $wrongDocTypes */
237
                /** @var TypeInterface[] $missingDocTypes */
238 4
                [$wrongDocTypes, $missingDocTypes] = TypeComparator::compare($docType, $fnType, $valueType);
239
240 4
                if ($wrongDocTypes) {
241 3
                    $warnings[$docTypeLine][] = sprintf(
242 3
                        'Type %s "%s" %s not compatible with :subject: type declaration',
243 3
                        isset($wrongDocTypes[1]) ? 'hints' : 'hint',
244 3
                        TypeHelper::listRawTypes($wrongDocTypes),
245 3
                        isset($wrongDocTypes[1]) ? 'are' : 'is'
246
                    );
247
                }
248
249 4
                if ($missingDocTypes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $missingDocTypes of type Gskema\TypeSniff\Core\Type\TypeInterface[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
250 3
                    $warnings[$docTypeLine][] = sprintf(
251 3
                        'Missing "%s" %s in :subject: type hint',
252 3
                        TypeHelper::listRawTypes($missingDocTypes),
253 3
                        isset($missingDocTypes[1]) ? 'types' : 'type'
254
                    );
255
                }
256
            }
257
        }
258
259 4
        foreach ($warnings as $line => $lineWarnings) {
260 4
            foreach ($lineWarnings as $warningTpl) {
261 4
                $warning = str_replace(':subject:', $subject, $warningTpl);
262 4
                $file->addWarningOnLine($warning, $line, 'FqcnMethodSniff');
263
            }
264
        }
265 4
    }
266
267 4
    protected function hasUselessDocBlock(AbstractFqcnMethodElement $method): bool
268
    {
269 4
        $fnSig = $method->getSignature();
270 4
        $docBlock = $method->getDocBlock();
271
272 4
        $usefulTagNames = array_diff($this->usefulTags, ['param', 'return']);
273
274 4
        $docReturnTag = $docBlock->getReturnTag();
275
276 4
        if ($docBlock instanceof UndefinedDocBlock
277 4
            || $docBlock->hasDescription()
278 4
            || $docBlock->hasOneOfTags($usefulTagNames)
279 4
            || ($docReturnTag && $docReturnTag->hasDescription())
280
        ) {
281 2
            return false;
282
        }
283
284 4
        foreach ($fnSig->getParams() as $fnParam) {
285 4
            $paramTag = $docBlock->getParamTag($fnParam->getName());
286 4
            if (null === $paramTag) {
287
                continue;
288
            }
289
290 4
            if ($paramTag->hasDescription()) {
291 1
                return false;
292
            }
293
294 3
            $fnType = $fnParam->getType();
295 3
            $rawFnType = $fnType instanceof NullableType
296 2
                ? $fnType->toDocString()
297 3
                : $fnType->toString();
298 3
            if ($paramTag->getType()->toString() !== $rawFnType) {
299 2
                return false;
300
            }
301
        }
302
303 3
        $returnTag  = $docBlock->getReturnTag();
304 3
        $returnType = $fnSig->getReturnType();
305
306 3
        if ($returnTag && $returnType) {
307 2
            $rawReturnType = $returnType instanceof NullableType
308
                ? $returnType->toDocString()
309 2
                : $returnType->toString();
310 2
            if ($returnTag->getType()->toString() !== $rawReturnType) {
311 2
                return false;
312
            }
313
        }
314
315 2
        return true;
316
    }
317
}
318