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 ( d83336...d7c119 )
by Gytis
01:52
created

FqcnMethodSniff   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 230
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 125
dl 0
loc 230
rs 3.44
c 0
b 0
f 0
wmc 62

6 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 10 3
A register() 0 6 1
A containsType() 0 5 5
D processSigType() 0 80 25
C hasUselessDocBlock() 0 60 17
B processMethod() 0 45 11

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