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.
Completed
Push — master ( 368282...c4d7dc )
by Gytis
03:56
created

FqcnMethodSniff   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 261
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 141
dl 0
loc 261
ccs 0
cts 189
cp 0
rs 3.2
c 0
b 0
f 0
wmc 65

7 Methods

Rating   Name   Duplication   Size   Complexity  
A containsType() 0 5 5
D processSigType() 0 80 25
D hasUselessDocBlock() 0 62 18
A process() 0 10 3
B processMethod() 0 45 11
A configure() 0 11 2
A register() 0 6 1

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 PHP_CodeSniffer\Files\File;
6
use Gskema\TypeSniff\Core\CodeElement\Element\AbstractFqcnMethodElement;
7
use Gskema\TypeSniff\Core\CodeElement\Element\ClassMethodElement;
8
use Gskema\TypeSniff\Core\CodeElement\Element\CodeElementInterface;
9
use Gskema\TypeSniff\Core\CodeElement\Element\InterfaceMethodElement;
10
use Gskema\TypeSniff\Core\DocBlock\DocBlock;
11
use Gskema\TypeSniff\Core\DocBlock\UndefinedDocBlock;
12
use Gskema\TypeSniff\Core\Type\Common\ArrayType;
13
use Gskema\TypeSniff\Core\Type\Common\UndefinedType;
14
use Gskema\TypeSniff\Core\Type\Common\VoidType;
15
use Gskema\TypeSniff\Core\Type\Declaration\NullableType;
16
use Gskema\TypeSniff\Core\Type\DocBlock\CompoundType;
17
use Gskema\TypeSniff\Core\Type\DocBlock\NullType;
18
use Gskema\TypeSniff\Core\Type\DocBlock\TypedArrayType;
19
use Gskema\TypeSniff\Core\Type\TypeConverter;
20
use Gskema\TypeSniff\Core\Type\TypeInterface;
21
22
class FqcnMethodSniff implements CodeElementSniffInterface
23
{
24
    /** @var string[] */
25
    protected $baseUsefulTags = [
26
        '@deprecated',
27
        '@throws',
28
        '@dataProvider',
29
        '@see',
30
        '@todo',
31
        '@inheritDoc'
32
    ];
33
34
    /** @var string[] */
35
    protected $usefulTags = [];
36
37
    /**
38
     * @inheritDoc
39
     */
40
    public function configure(array $config): void
41
    {
42
        $rawTags = array_merge($this->baseUsefulTags, $config['usefulTags'] ?? []);
43
44
        $usefulTags = [];
45
        foreach ($rawTags as $rawTag) {
46
            $usefulTags[] = strtolower(ltrim($rawTag, '@'));
47
        }
48
        $usefulTags = array_unique($usefulTags);
49
50
        $this->usefulTags = $usefulTags;
51
    }
52
53
    /**
54
     * @inheritDoc
55
     */
56
    public function register(): array
57
    {
58
        return [
59
            ClassMethodElement::class,
60
            // TraitMethodElement::class, // can be used to implement interface, not possible to know if it is extended
61
            InterfaceMethodElement::class,
62
        ];
63
    }
64
65
    /**
66
     * @inheritDoc
67
     * @param AbstractFqcnMethodElement $method
68
     */
69
    public function process(File $file, CodeElementInterface $method): void
70
    {
71
        $warningCountBefore = $file->getWarningCount();
72
73
        // @TODO Assert description
74
        $this->processMethod($file, $method);
75
76
        $hasNewWarnings = $file->getWarningCount() > $warningCountBefore;
77
        if (!$hasNewWarnings && $this->hasUselessDocBlock($method)) {
78
            $file->addWarningOnLine('Useless PHPDoc', $method->getLine(), 'FqcnMethodSniff');
79
        }
80
    }
81
82
    protected function processMethod(File $file, AbstractFqcnMethodElement $method): void
83
    {
84
        $fnSig = $method->getSignature();
85
        $docBlock = $method->getDocBlock();
86
        $isMagicMethod = '__' === substr($fnSig->getName(), 0, 2);
87
        $isConstructMethod = '__construct' === $fnSig->getName();
88
        $hasInheritDocTag = $docBlock->hasTag('inheritdoc');
89
90
        // @inheritDoc
91
        // __construct can be detected as extended and magic, but we want to inspect it anyway
92
        if (!$isConstructMethod) {
93
            if ($hasInheritDocTag || $isMagicMethod) {
94
                return;
95
            } elseif ($method->isExtended()) {
96
                $file->addWarningOnLine('Missing @inheritDoc tag', $method->getLine(), 'FqcnMethodSniff');
97
                return;
98
            }
99
        }
100
101
        // @param
102
        foreach ($fnSig->getParams() as $fnParam) {
103
            $paramName = $fnParam->getName();
104
            $tag = $docBlock->getParamTag($paramName);
105
106
            $subject = sprintf('parameter $%s', $paramName);
107
108
            $fnType = $fnParam->getType();
109
            $fnTypeLine = $fnParam->getLine();
110
            $docType = $tag ? $tag->getType() : null;
111
            $docTypeLine = $tag ? $tag->getLine() : $fnTypeLine;
112
113
            $this->processSigType($file, $docBlock, $subject, $fnType, $fnTypeLine, $docType, $docTypeLine);
114
        }
115
116
        // @return
117
        if (!$isConstructMethod) {
118
            $docType = $docBlock->getReturnTag();
119
            $this->processSigType(
120
                $file,
121
                $docBlock,
122
                'return value',
123
                $fnSig->getReturnType(),
124
                $fnSig->getReturnLine(),
125
                $docType ? $docType->getType() : null,
126
                $docType ? $docType->getLine() : $fnSig->getLine()
127
            );
128
        }
129
    }
130
131
    protected function processSigType(
132
        File $file,
133
        DocBlock $docBlock,
134
        string $subject,
135
        TypeInterface $fnType,
136
        int $fnTypeLine,
137
        ?TypeInterface $docType,
138
        int $docTypeLine
139
    ): void {
140
        // @TODO Required mixed[][] instead of array[]
141
142
        $warnings = [];
143
        if ($docBlock instanceof UndefinedDocBlock) {
144
            // doc_block:undefined, fn_type:?
145
            if ($fnType instanceof UndefinedType) {
146
                $warnings[$fnTypeLine] = 'Add type declaration for :subject: or create PHPDoc with type hint';
147
            } elseif ($this->containsType($fnType, ArrayType::class)) {
148
                $warnings[$fnTypeLine] = 'Create PHPDoc with typed array type hint for :subject:, .e.g.: "string[]" or "SomeClass[]"';
149
            }
150
        } elseif (null === $docType) {
151
            // doc_block:defined, doc_tag:missing
152
            if ('return value' === $subject) { // @TODO ??
153
                if (!($fnType instanceof VoidType)) {
154
                    $warnings[$fnTypeLine] = 'Missing PHPDoc tag or void type declaration for :subject:';
155
                }
156
            } else {
157
                $warnings[$fnTypeLine] = 'Missing PHPDoc tag for :subject:';
158
            }
159
        } elseif ($docType instanceof UndefinedType) {
160
            // doc_block:defined, doc_type:undefined
161
            $suggestedFnType = TypeConverter::toExpectedDocType($fnType);
162
            if (null !== $suggestedFnType) {
163
                $warnings[$docTypeLine] = sprintf(
164
                    'Add type hint in PHPDoc tag for :subject:, e.g. "%s"',
165
                    $suggestedFnType->toString()
166
                );
167
            } else {
168
                $warnings[$docTypeLine] = 'Add type hint in PHPDoc tag for :subject:';
169
            }
170
        } elseif ($fnType instanceof UndefinedType) {
171
            // doc_block:defined, doc_type:defined, fn_type:undefined
172
            if ($docType instanceof NullType) {
173
                $warnings[$fnTypeLine] = sprintf('Add type declaration for :subject:');
174
            } elseif ($suggestedFnType = TypeConverter::toFunctionType($docType)) {
175
                $warnings[$fnTypeLine] = sprintf('Add type declaration for :subject:, e.g.: "%s"', $suggestedFnType->toString());
176
            }
177
        } elseif ($this->containsType($fnType, ArrayType::class)) {
178
            // doc_block:defined, doc_type:defined, fn_type:array
179
            $docHasTypedArray = $this->containsType($docType, TypedArrayType::class);
180
            $docHasArray = $this->containsType($docType, ArrayType::class);
181
182
            if ($docHasTypedArray && $docHasArray) {
183
                $warnings[$docTypeLine] = 'Remove array type, typed array type is present in PHPDoc for :subject:.';
184
            } elseif (!$docHasTypedArray && $docHasArray) {
185
                $warnings[$docTypeLine] = 'Replace array type with typed array type in PHPDoc for :subject:. Use mixed[] for generic arrays.';
186
            } elseif (!$docHasTypedArray && !$docHasArray) {
187
                $warnings[$docTypeLine] = 'Add typed array type in PHPDoc for :subject:. Use mixed[] for generic arrays.';
188
            }
189
        } elseif ($fnType instanceof NullableType) {
190
            // doc_block:defined, doc_type:defined, fn_type:nullable
191
            if (!$this->containsType($docType, NullType::class)) {
192
                $warnings[$docTypeLine] = 'Add "null" type hint in PHPDoc for :subject:';
193
            }
194
        } else {
195
            // doc_block:defined, doc_type:defined, fn_type:defined
196
            $expectedDocType = TypeConverter::toExpectedDocType($fnType);
197
            $expectedDocTypes = $expectedDocType instanceof CompoundType
198
                ? $expectedDocType->getTypes()
199
                : array_filter([$expectedDocType]);
200
201
            foreach ($expectedDocTypes as $expectedDocType) {
202
                if (!$this->containsType($docType, get_class($expectedDocType))) {
203
                    $warnings[$docTypeLine] = sprintf('Add "%s" type hint in PHPDoc for :subject:', $fnType->toString());
204
                }
205
            }
206
        }
207
208
        foreach ($warnings as $line => $warningTpl) {
209
            $warning = str_replace(':subject:', $subject, $warningTpl);
210
            $file->addWarningOnLine($warning, $line, 'FqcnMethodSniff');
211
        }
212
    }
213
214
    protected function containsType(TypeInterface $type, string $typeClassName): bool
215
    {
216
        return is_a($type, $typeClassName)
217
            || ($type instanceof CompoundType && $type->containsType($typeClassName))
218
            || ($type instanceof NullableType && $type->containsType($typeClassName));
219
    }
220
221
    protected function hasUselessDocBlock(AbstractFqcnMethodElement $method): bool
222
    {
223
        $fnSig = $method->getSignature();
224
        $docBlock = $method->getDocBlock();
225
226
        $usefulTagNames = ['deprecated', 'throws', 'dataprovider', 'see', 'todo', 'inheritdoc']; // @TODO ??
227
        $usefulTagNames = array_diff($usefulTagNames, ['param', 'return']);
228
229
        $docReturnTag = $docBlock->getReturnTag();
230
231
        $hasUsefulDescription = $docBlock->hasDescription()
232
            && !preg_match('#^\w+\s+constructor\.?$#', $docBlock->getDescription());
233
234
        if ($docBlock instanceof UndefinedDocBlock
235
            || $hasUsefulDescription
236
            || $docBlock->hasOneOfTags($usefulTagNames)
237
            || ($docReturnTag && $docReturnTag->hasDescription())
238
        ) {
239
            return false;
240
        }
241
242
        $paramTags = $docBlock->getParamTags();
243
        foreach ($paramTags as $paramTag) {
244
            if ($paramTag->hasDescription()) {
245
                return false;
246
            }
247
        }
248
249
        foreach ($fnSig->getParams() as $fnParam) {
250
            $paramTag = $docBlock->getParamTag($fnParam->getName());
251
            if (null === $paramTag) {
252
                continue;
253
            }
254
255
            if ($paramTag->hasDescription()) {
256
                return false;
257
            }
258
259
            // @TODO Cleaner way?
260
            $fnType = $fnParam->getType();
261
            $rawFnType = $fnType instanceof NullableType
262
                ? $fnType->toDocString()
263
                : $fnType->toString();
264
            if ($paramTag->getType()->toString() !== $rawFnType) {
265
                return false;
266
            }
267
        }
268
269
        $returnTag  = $docBlock->getReturnTag();
270
        $returnType = $fnSig->getReturnType();
271
272
        // @TODO ??
273
        if ($returnTag && $returnType) {
274
            $rawReturnType = $returnType instanceof NullableType
275
                ? $returnType->toDocString()
276
                : $returnType->toString();
277
            if ($returnTag->getType()->toString() !== $rawReturnType) {
278
                return false;
279
            }
280
        }
281
282
        return true;
283
    }
284
}
285