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.

DocTypeInspector   B
last analyzed

Complexity

Total Complexity 52

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 93
dl 0
loc 185
ccs 99
cts 99
cp 1
rs 7.44
c 0
b 0
f 0
wmc 52

6 Methods

Rating   Name   Duplication   Size   Complexity  
C reportMandatoryTypes() 0 37 15
A reportInvalidTypes() 0 14 5
B reportSuggestedTypes() 0 16 7
A reportRemovableTypes() 0 19 6
A reportReplaceableTypes() 0 29 5
C reportMissingOrWrongTypes() 0 48 14

How to fix   Complexity   

Complex Class

Complex classes like DocTypeInspector 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 DocTypeInspector, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Gskema\TypeSniff\Inspection;
4
5
use Gskema\TypeSniff\Core\Type\Common\ArrayType;
6
use Gskema\TypeSniff\Core\Type\Common\UndefinedType;
7
use Gskema\TypeSniff\Core\Type\Common\VoidType;
8
use Gskema\TypeSniff\Core\Type\Declaration\NullableType;
9
use Gskema\TypeSniff\Core\Type\DocBlock\NullType;
10
use Gskema\TypeSniff\Core\Type\TypeComparator;
11
use Gskema\TypeSniff\Core\Type\TypeConverter;
12
use Gskema\TypeSniff\Core\Type\TypeHelper;
13
use Gskema\TypeSniff\Inspection\Subject\AbstractTypeSubject;
14
use Gskema\TypeSniff\Inspection\Subject\ParamTypeSubject;
15
use Gskema\TypeSniff\Inspection\Subject\PropTypeSubject;
16
use Gskema\TypeSniff\Inspection\Subject\ReturnTypeSubject;
17
18
class DocTypeInspector
19
{
20 16
    public static function reportMandatoryTypes(AbstractTypeSubject $subject, bool $allowMissing = false): void
21
    {
22 16
        $hasDocBlock = $subject->hasDefinedDocBlock();
23 16
        $hasDocTypeTag = null !== $subject->getDocType();
24 16
        $hasArrayShape = $subject->hasAttribute('ArrayShape');
25
26
        // e.g. $arg1 = [], C1 = [], $prop1 = [], ?array $arg1
27
        if (
28 16
            (!$hasDocTypeTag && !$hasArrayShape)
29 16
            && ($subject->getValueType() instanceof ArrayType || TypeHelper::containsType($subject->getFnType(), ArrayType::class))
30
        ) {
31 5
            $isNullable = $subject->getFnType() instanceof NullableType;
32 5
            $subject->addFnTypeWarning(sprintf(
33 5
                '%s typed array type hint for :subject:, .e.g.: "string[]%s" or "SomeClass[]%s". Correct array depth must be specified.',
34 5
                $subject->hasDefinedDocBlock() ? 'Add' : 'Create PHPDoc with',
35 5
                $isNullable ? '|null' : '',
36 5
                $isNullable ? '|null' : ''
37
            ));
38
39 5
            return; // exit
40
        }
41
42
        // Above: reports for doc types that must be specified (typed array type), cannot be missing.
43
        // Below: reports for missing doc types, tags. This may be OK in case fn type is specified.
44 16
        if ($allowMissing) {
45 2
            return;
46
        }
47
48 16
        if ($subject instanceof ParamTypeSubject) {
49
            // doc block must not be missing any @param tag
50 9
            if ($hasDocBlock && !$hasDocTypeTag && !$hasArrayShape) {
51 9
                $subject->addFnTypeWarning('Missing PHPDoc tag for :subject:');
52
            }
53 16
        } elseif ($subject instanceof PropTypeSubject) {
54
            // properties: ask to add fn type first
55 12
            if ($subject->getDocType() instanceof UndefinedType) {
56 1
                $subject->addDocTypeWarning('Add type hint to @var tag for :subject:');
57
            }
58
        }
59 16
    }
60
61 16
    public static function reportRemovableTypes(AbstractTypeSubject $subject): void
62
    {
63 16
        if (!$subject->hasDefinedDocType()) {
64 15
            return;
65
        }
66
67
        // @return void in not needed
68
        if (
69 12
            $subject instanceof ReturnTypeSubject
70 12
            && $subject->getFnType() instanceof VoidType
71 12
            && $subject->getDocType() instanceof VoidType
72
        ) {
73 1
            $subject->addDocTypeWarning('Remove @return void tag, not necessary');
74
        }
75
76
        // e.g. double|float, array|int[] -> float, int[]
77 12
        if ($redundantTypes = TypeComparator::getRedundantDocTypes($subject->getDocType())) {
78 4
            $subject->addDocTypeWarning(
79 4
                sprintf('Remove redundant :subject: type hints "%s"', TypeHelper::listRawTypes($redundantTypes))
80
            );
81
        }
82 12
    }
83
84 16
    public static function reportReplaceableTypes(AbstractTypeSubject $subject): void
85
    {
86 16
        if (!$subject->hasDefinedDocType()) {
87 15
            return;
88
        }
89
90 12
        $docType = $subject->getDocType();
91
92
        // e.g. @param array $arg1 -> @param int[] $arg1
93 12
        if (TypeHelper::containsType($docType, NullableType::class)) {
94 1
            $subject->addDocTypeWarning(sprintf(
95 1
                'Replace nullable type "%s" with compound type with null "%s" for :subject:.',
96 1
                $docType->toString(),
97 1
                str_replace('?', '', $docType->toString()) . '|null' // @TODO Not ideal
98
            ));
99
        }
100
101
        // e.g. @param array $arg1 -> @param int[] $arg1
102 12
        if (TypeHelper::containsType($docType, ArrayType::class)) {
103 5
            $subject->addDocTypeWarning(
104 5
                'Replace array type with typed array type in PHPDoc for :subject:, .e.g.: "string[]" or "SomeClass[]". Use mixed[] for generic arrays. Correct array depth must be specified.'
105
            );
106
        }
107
108
        // e.g. array[] -> mixed[][]
109 12
        if ($fakeType = TypeHelper::getFakeTypedArrayType($docType)) {
110 4
            $subject->addDocTypeWarning(sprintf(
111 4
                'Use a more specific type in typed array hint "%s" for :subject:. Correct array depth must be specified.',
112 4
                $fakeType->toString()
113
            ));
114
        }
115 12
    }
116
117 12
    public static function reportInvalidTypes(AbstractTypeSubject $subject): void
118
    {
119 12
        if (!$subject->hasDefinedDocType()) {
120 10
            return;
121
        }
122
123
        // @TODO true/void/false/$this/ cannot be param tags
124
125
        // e.g. @param null $arg1 -> @param int|null $arg1
126 10
        if ($subject->getDocType() instanceof NullType) {
127 3
            if ($subject instanceof ReturnTypeSubject) {
128 2
                $subject->addDocTypeWarning('Use void :subject: type declaration or change type to compound, e.g. SomeClass|null');
129 3
            } elseif ($subject instanceof ParamTypeSubject) {
130 3
                $subject->addDocTypeWarning('Change type hint for :subject: to compound, e.g. SomeClass|null');
131
            }
132
            // having @var null for const, prop is allowed
133
        }
134 10
    }
135
136 12
    public static function reportSuggestedTypes(AbstractTypeSubject $subject): void
137
    {
138 12
        if (!$subject->hasDefinedDocBlock() || $subject->hasDefinedDocType() || $subject->hasAttribute('ArrayShape')) {
139 12
            return;
140
        }
141
142
        // e.g. ?int -> int|null
143 7
        $exampleDocType = TypeConverter::toExampleDocType($subject->getFnType());
144 7
        if (null !== $exampleDocType) {
145 3
            $subject->addDocTypeWarning(sprintf('Add type hint in PHPDoc tag for :subject:, e.g. "%s"', $exampleDocType->toString()));
146 6
        } elseif ($subject instanceof ReturnTypeSubject) {
147 6
            if (!($subject->getFnType() instanceof VoidType)) {
148 6
                $subject->addFnTypeWarning('Missing PHPDoc tag or void type declaration for :subject:');
149
            }
150
        } else {
151 1
            $subject->addDocTypeWarning('Add type hint in PHPDoc tag for :subject:');
152
        }
153 7
    }
154
155 16
    public static function reportMissingOrWrongTypes(AbstractTypeSubject $subject): void
156
    {
157
        // e.g. $param1 = null, mixed|null -> do not report
158 16
        if ($subject instanceof ParamTypeSubject && !$subject->hasDefinedFnType()) {
159 6
            return;
160
        }
161
162 16
        $hasArrayShape = $subject->hasAttribute('ArrayShape');
163
        if (
164 16
            $hasArrayShape
165 16
            && $subject->hasDefinedFnType()
166 16
            && !TypeHelper::containsType($subject->getFnType(), ArrayType::class)
167
        ) {
168 1
            $subject->addFnTypeWarning('Type declaration of :subject: not compatible with ArrayShape attribute');
169
        }
170
171 16
        if (!$subject->hasDefinedDocType()) {
172 15
            return;
173
        }
174
175
        // e.g. ?int, int|string -> ?int, int|null (wrong: string, missing: null)
176 12
        $isProp = $subject instanceof PropTypeSubject;
177 12
        [$wrongDocTypes, $missingDocTypes] = TypeComparator::compare(
178 12
            $subject->getDocType(),
179 12
            $subject->getFnType(),
180 12
            $subject->getValueType(),
181 12
            $isProp
182
        );
183
184 12
        if ($isProp && !$subject->hasDefinedFnType()) {
185 9
            $wrongDocTypes = []; // not reported because props have dynamic values
186
        }
187
188
        // wrong types are not reported for dynamic assignments, e.g. class props.
189 12
        if ($wrongDocTypes) {
190 8
            $subject->addDocTypeWarning(sprintf(
191 8
                'Type %s "%s" %s not compatible with :subject: value type',
192 8
                isset($wrongDocTypes[1]) ? 'hints' : 'hint',
193 8
                TypeHelper::listRawTypes($wrongDocTypes),
194 8
                isset($wrongDocTypes[1]) ? 'are' : 'is'
195
            ));
196
        }
197
198 12
        if ($missingDocTypes) {
199 6
            $subject->addDocTypeWarning(sprintf(
200 6
                'Missing "%s" %s in :subject: type hint',
201 6
                TypeHelper::listRawTypes($missingDocTypes),
202 6
                isset($missingDocTypes[1]) ? 'types' : 'type'
203
            ));
204
        }
205 12
    }
206
}
207