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