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
Branch 0.14.0 (b23fa7)
by Gytis
12:27 queued 07:51
created

FqcnMethodSniff::process()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
nc 2
nop 3
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Gskema\TypeSniff\Sniffs\CodeElement;
4
5
use Gskema\TypeSniff\Core\CodeElement\Element\ClassElement;
6
use Gskema\TypeSniff\Core\DocBlock\Tag\VarTag;
7
use Gskema\TypeSniff\Core\Type\Common\UndefinedType;
8
use Gskema\TypeSniff\Core\Type\DocBlock\NullType;
9
use Gskema\TypeSniff\Core\Type\TypeHelper;
10
use Gskema\TypeSniff\Inspection\FnTypeInspector;
11
use Gskema\TypeSniff\Inspection\DocTypeInspector;
12
use Gskema\TypeSniff\Inspection\Subject\AbstractTypeSubject;
13
use Gskema\TypeSniff\Inspection\Subject\ParamTypeSubject;
14
use Gskema\TypeSniff\Inspection\Subject\ReturnTypeSubject;
15
use PHP_CodeSniffer\Files\File;
16
use Gskema\TypeSniff\Core\CodeElement\Element\AbstractFqcnMethodElement;
17
use Gskema\TypeSniff\Core\CodeElement\Element\ClassMethodElement;
18
use Gskema\TypeSniff\Core\CodeElement\Element\CodeElementInterface;
19
use Gskema\TypeSniff\Core\CodeElement\Element\InterfaceMethodElement;
20
use Gskema\TypeSniff\Core\DocBlock\UndefinedDocBlock;
21
use Gskema\TypeSniff\Core\Type\Declaration\NullableType;
22
23
/**
24
 * @see FqcnMethodSniffTest
25
 */
26
class FqcnMethodSniff implements CodeElementSniffInterface
27
{
28
    protected const CODE = 'FqcnMethodSniff';
29
30
    /** @var string[] */
31
    protected $invalidTags = [];
32
33
    /** @var bool */
34
    protected $reportMissingTags = true;
35
36
    /** @var bool */
37
    protected $reportNullableBasicGetterDocType = true;
38
39
    /** @var bool */
40
    protected $reportNullableBasicGetterFnType = true;
41
42
    /**
43
     * @inheritDoc
44
     */
45 6
    public function configure(array $config): void
46
    {
47
        // TagInterface uses lowercase tags names, no @ symbol in front
48 6
        $invalidTags = [];
49 6
        foreach ($config['invalidTags'] ?? [] as $rawTag) {
50 1
            $invalidTags[] = strtolower(ltrim($rawTag, '@'));
51
        }
52 6
        $invalidTags = array_unique($invalidTags);
53
54 6
        $this->invalidTags = $invalidTags;
55 6
        $this->reportMissingTags = $config['reportMissingTags'] ?? true;
56 6
        $this->reportNullableBasicGetterDocType = $config['reportNullableBasicGetterDocType'] ?? true;
57 6
        $this->reportNullableBasicGetterFnType = $config['reportNullableBasicGetterFnType'] ?? true;
58 6
    }
59
60
    /**
61
     * @inheritDoc
62
     */
63 6
    public function register(): array
64
    {
65
        return [
66 6
            ClassMethodElement::class,
67
            // TraitMethodElement::class, // can be used to implement interface, not possible to know if it is extended
68
            InterfaceMethodElement::class,
69
        ];
70
    }
71
72
    /**
73
     * @inheritDoc
74
     * @param AbstractFqcnMethodElement $method
75
     */
76 5
    public function process(File $file, CodeElementInterface $method, CodeElementInterface $parentElement): void
77
    {
78 5
        $warningCountBefore = $file->getWarningCount();
79
80 5
        static::reportInvalidTags($file, $method, $this->invalidTags);
81 5
        $this->processMethod($file, $method, $parentElement);
82
83 5
        $hasNewWarnings = $file->getWarningCount() > $warningCountBefore;
84 5
        if (!$hasNewWarnings && $this->hasUselessDocBlock($method)) {
85 3
            $file->addWarningOnLine('Useless PHPDoc', $method->getLine(), static::CODE);
86
        }
87 5
    }
88
89 5
    protected function processMethod(File $file, AbstractFqcnMethodElement $method, CodeElementInterface $parent): void
90
    {
91 5
        $fnSig = $method->getSignature();
92 5
        $docBlock = $method->getDocBlock();
93 5
        $isMagicMethod = '__' === substr($fnSig->getName(), 0, 2);
94 5
        $isConstructMethod = '__construct' === $fnSig->getName();
95 5
        $hasInheritDocTag = $docBlock->hasTag('inheritdoc');
96
97
        // @inheritDoc
98
        // __construct can be detected as extended and magic, but we want to inspect it anyway
99 5
        if (!$isConstructMethod) {
100 5
            if ($hasInheritDocTag || $isMagicMethod) {
101 1
                return;
102 5
            } elseif ($method->getMetadata()->isExtended()) {
103 1
                $file->addWarningOnLine('Missing @inheritDoc tag. Remove duplicated parent PHPDoc content.', $method->getLine(), static::CODE);
104 1
                return;
105
            }
106
        }
107
108
        // @param
109 5
        foreach ($fnSig->getParams() as $fnParam) {
110 5
            $paramTag = $docBlock->getParamTag($fnParam->getName());
111 5
            $subject = ParamTypeSubject::fromParam($fnParam, $paramTag, $docBlock);
112 5
            $this->processSigType($file, $subject);
113
        }
114
115
        // @return
116 5
        if (!$isConstructMethod) {
117 5
            $returnTag = $docBlock->getReturnTag();
118 5
            $subject = ReturnTypeSubject::fromSignature($fnSig, $returnTag, $docBlock);
119 5
            $this->processSigType($file, $subject);
120
121 5
            if ($method instanceof ClassMethodElement && $parent instanceof ClassElement) {
122 5
                ($this->reportNullableBasicGetterDocType || $this->reportNullableBasicGetterFnType) // skip if both disabled
123 5
                && static::reportNullableBasicGetter($file, $subject, $method, $parent);
0 ignored issues
show
Bug Best Practice introduced by
The method Gskema\TypeSniff\Sniffs\...rtNullableBasicGetter() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

123
                && static::/** @scrutinizer ignore-call */ reportNullableBasicGetter($file, $subject, $method, $parent);
Loading history...
124
            }
125
        } else {
126 2
            foreach ($docBlock->getDescriptionLines() as $lineNum => $descLine) {
127 1
                if (preg_match('#^\w+\s+constructor\.?$#', $descLine)) {
128 1
                    $file->addWarningOnLine('Useless description.', $lineNum, static::CODE);
129
                }
130
            }
131
        }
132 5
    }
133
134 5
    protected function processSigType(File $file, AbstractTypeSubject $subject): void
135
    {
136 5
        FnTypeInspector::reportMandatoryTypes($subject);
137 5
        FnTypeInspector::reportSuggestedTypes($subject);
138 5
        FnTypeInspector::reportReplaceableTypes($subject);
139
140 5
        if ($this->reportMissingTags || $subject->hasDocTypeTag()) {
141 5
            DocTypeInspector::reportMandatoryTypes($subject);
142 5
            DocTypeInspector::reportSuggestedTypes($subject);
143 5
            DocTypeInspector::reportReplaceableTypes($subject);
144
145 5
            DocTypeInspector::reportRemovableTypes($subject);
146 5
            DocTypeInspector::reportInvalidTypes($subject);
147 5
            DocTypeInspector::reportMissingOrWrongTypes($subject);
148
        } else {
149 1
            DocTypeInspector::reportMandatoryTypes($subject, true);
150
        }
151
152 5
        $subject->writeWarningsTo($file, static::CODE);
153 5
    }
154
155 10
    protected function hasUselessDocBlock(AbstractFqcnMethodElement $method): bool
156
    {
157 10
        $fnSig = $method->getSignature();
158 10
        $docBlock = $method->getDocBlock();
159
160 10
        $docReturnTag = $docBlock->getReturnTag();
161
162 10
        if ($docBlock instanceof UndefinedDocBlock
163 10
            || $docBlock->hasDescription()
164 9
            || ($docReturnTag && $docReturnTag->hasDescription())
165
            // check if other "useful" tags are present
166 10
            || array_diff($docBlock->getTagNames(), ['param', 'return'])
167
        ) {
168 4
            return false;
169
        }
170
171 9
        foreach ($fnSig->getParams() as $fnParam) {
172 9
            $paramTag = $docBlock->getParamTag($fnParam->getName());
173 9
            if (null === $paramTag) {
174 1
                return false; // missing, needs to be fixed
175
            }
176
177 8
            if ($paramTag->hasDescription()) {
178 3
                return false;
179
            }
180
181 5
            $fnType = $fnParam->getType();
182 5
            $rawFnType = $fnType instanceof NullableType
183 1
                ? $fnType->toDocString()
184 5
                : $fnType->toString();
185 5
            if ($paramTag->getType()->toString() !== $rawFnType) {
186 3
                return false;
187
            }
188
        }
189
190 4
        $returnTag  = $docBlock->getReturnTag();
191 4
        $returnType = $fnSig->getReturnType();
192
193 4
        if ($returnTag && $returnType) {
194 3
            $rawReturnType = $returnType instanceof NullableType
195 1
                ? $returnType->toDocString()
196 3
                : $returnType->toString();
197 3
            if ($returnTag->getType()->toString() !== $rawReturnType) {
198 1
                return false;
199
            }
200
        }
201
202 4
        return true;
203
    }
204
205
    /**
206
     * @param File                      $file
207
     * @param AbstractFqcnMethodElement $method
208
     * @param static[]                  $invalidTags
209
     */
210 5
    protected static function reportInvalidTags(File $file, AbstractFqcnMethodElement $method, array $invalidTags): void
211
    {
212 5
        foreach ($method->getDocBlock()->getTags() as $tag) {
213 5
            foreach ($invalidTags as $invalidTagName) {
214 1
                if ($tag->getName() === $invalidTagName) {
215 1
                    $file->addWarningOnLine('Useless tag', $tag->getLine(), static::CODE);
216
                }
217
            }
218
        }
219 5
    }
220
221 5
    protected function reportNullableBasicGetter(
222
        File $file,
223
        ReturnTypeSubject $subject,
224
        ClassMethodElement $method,
225
        ClassElement $class
226
    ): void {
227 5
        $propName = $method->getMetadata()->getBasicGetterPropName();
228 5
        if (null === $propName) {
229 5
            return;
230
        }
231
232 1
        $prop = $class->getProperty($propName);
233 1
        if (null === $prop) {
234
            return;
235
        }
236
237
        /** @var VarTag|null $varTag */
238 1
        $varTag = $prop->getDocBlock()->getTagsByName('var')[0] ?? null;
239 1
        if (null === $varTag) {
240
            return;
241
        }
242
243 1
        $propDocType = $varTag->getType();
244 1
        $isPropNullable = TypeHelper::containsType($varTag->getType(), NullType::class);
245 1
        if (!$isPropNullable) {
246
            return;
247
        }
248
249 1
        $returnDocType = $subject->getDocType();
250 1
        $isGetterDocTypeNullable = TypeHelper::containsType($returnDocType, NullType::class);
251 1
        if (!$isGetterDocTypeNullable && $subject->hasDefinedDocBlock()) {
252 1
            $this->reportNullableBasicGetterDocType && $subject->addDocTypeWarning(sprintf(
253 1
                'Returned property $%s is nullable, add null return doc type, e.g. %s',
254
                $propName,
255 1
                ($returnDocType->toString() ?? $propDocType).'|null'
0 ignored issues
show
Bug introduced by
Are you sure $returnDocType->toString() ?? $propDocType of type Gskema\TypeSniff\Core\Type\TypeInterface|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

255
                (/** @scrutinizer ignore-type */ $returnDocType->toString() ?? $propDocType).'|null'
Loading history...
256
            ));
257
        }
258
259
        // Only report in fn type is defined. Doc type and fn type is synced by other sniffs.
260 1
        $returnFnType = $subject->getFnType();
261 1
        if (!($returnFnType instanceof UndefinedType) && !($returnFnType instanceof NullableType)) {
262 1
            $this->reportNullableBasicGetterFnType && $subject->addFnTypeWarning(sprintf(
263 1
                'Returned property $%s is nullable, use nullable return type declaration, e.g. ?%s',
264
                $propName,
265 1
                $returnFnType->toString()
266
            ));
267
        }
268
269 1
        $subject->writeWarningsTo($file, static::CODE);
270 1
    }
271
}
272