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 ( 75b615...368282 )
by Gytis
02:47
created

FqcnMethodSniff::hasUselessDocBlock()   D

Complexity

Conditions 18
Paths 80

Size

Total Lines 62
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 342

Importance

Changes 0
Metric Value
cc 18
eloc 37
nc 80
nop 1
dl 0
loc 62
ccs 0
cts 38
cp 0
crap 342
rs 4.8666
c 0
b 0
f 0

How to fix   Long Method    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
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 10
    public function register(): array
28
    {
29
        return [
30 10
            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
        $hasUsefulDescription = $docBlock->hasDescription()
203
            && !preg_match('#^\w+\s+constructor\.?$#', $docBlock->getDescription());
204
205
        if ($docBlock instanceof UndefinedDocBlock
206
            || $hasUsefulDescription
207
            || $docBlock->hasOneOfTags($usefulTagNames)
208
            || ($docReturnTag && $docReturnTag->hasDescription())
209
        ) {
210
            return false;
211
        }
212
213
        $paramTags = $docBlock->getParamTags();
214
        foreach ($paramTags as $paramTag) {
215
            if ($paramTag->hasDescription()) {
216
                return false;
217
            }
218
        }
219
220
        foreach ($fnSig->getParams() as $fnParam) {
221
            $paramTag = $docBlock->getParamTag($fnParam->getName());
222
            if (null === $paramTag) {
223
                continue;
224
            }
225
226
            if ($paramTag->hasDescription()) {
227
                return false;
228
            }
229
230
            // @TODO Cleaner way?
231
            $fnType = $fnParam->getType();
232
            $rawFnType = $fnType instanceof NullableType
233
                ? $fnType->toDocString()
234
                : $fnType->toString();
235
            if ($paramTag->getType()->toString() !== $rawFnType) {
236
                return false;
237
            }
238
        }
239
240
        $returnTag  = $docBlock->getReturnTag();
241
        $returnType = $fnSig->getReturnType();
242
243
        // @TODO ??
244
        if ($returnTag && $returnType) {
245
            $rawReturnType = $returnType instanceof NullableType
246
                ? $returnType->toDocString()
247
                : $returnType->toString();
248
            if ($returnTag->getType()->toString() !== $rawReturnType) {
249
                return false;
250
            }
251
        }
252
253
        return true;
254
    }
255
}
256