Completed
Push — master ( 3e29f4...a0257c )
by Gytis
03:40 queued 11s
created

FqcnMethodSniff::processMethod()   B

Complexity

Conditions 9
Paths 18

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 9

Importance

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

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

120
    protected function processSigType(File $file, /** @scrutinizer ignore-unused */ DocBlock $docBlock, AbstractTypeSubject $subject): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
121
    {
122 4
        FnTypeInspector::reportMandatoryTypes($subject);
123 4
        FnTypeInspector::reportSuggestedTypes($subject);
124 4
        FnTypeInspector::reportReplaceableTypes($subject);
125
126 4
        DocTypeInspector::reportMandatoryTypes($subject);
127 4
        DocTypeInspector::reportSuggestedTypes($subject);
128 4
        DocTypeInspector::reportReplaceableTypes($subject);
129
130 4
        DocTypeInspector::reportRemovableTypes($subject);
131 4
        DocTypeInspector::reportInvalidTypes($subject);
132 4
        DocTypeInspector::reportMissingOrWrongTypes($subject);
133
134 4
        $subject->writeWarningsTo($file, static::CODE);
135 4
    }
136
137 4
    protected function hasUselessDocBlock(AbstractFqcnMethodElement $method): bool
138
    {
139 4
        $fnSig = $method->getSignature();
140 4
        $docBlock = $method->getDocBlock();
141
142 4
        $usefulTagNames = array_diff($this->usefulTags, ['param', 'return']);
143
144 4
        $docReturnTag = $docBlock->getReturnTag();
145
146 4
        if ($docBlock instanceof UndefinedDocBlock
147 4
            || $docBlock->hasDescription()
148 4
            || $docBlock->hasOneOfTags($usefulTagNames)
149 4
            || ($docReturnTag && $docReturnTag->hasDescription())
150
        ) {
151 2
            return false;
152
        }
153
154 4
        foreach ($fnSig->getParams() as $fnParam) {
155 4
            $paramTag = $docBlock->getParamTag($fnParam->getName());
156 4
            if (null === $paramTag) {
157
                continue;
158
            }
159
160 4
            if ($paramTag->hasDescription()) {
161 1
                return false;
162
            }
163
164 3
            $fnType = $fnParam->getType();
165 3
            $rawFnType = $fnType instanceof NullableType
166 1
                ? $fnType->toDocString()
167 3
                : $fnType->toString();
168 3
            if ($paramTag->getType()->toString() !== $rawFnType) {
169 3
                return false;
170
            }
171
        }
172
173 2
        $returnTag  = $docBlock->getReturnTag();
174 2
        $returnType = $fnSig->getReturnType();
175
176 2
        if ($returnTag && $returnType) {
177 1
            $rawReturnType = $returnType instanceof NullableType
178
                ? $returnType->toDocString()
179 1
                : $returnType->toString();
180 1
            if ($returnTag->getType()->toString() !== $rawReturnType) {
181 1
                return false;
182
            }
183
        }
184
185 2
        return true;
186
    }
187
}
188