Passed
Push — master ( e5e542...80e7c7 )
by Gytis
04:34 queued 13s
created

FqcnPropSniff::hasNonNullAssignedProp()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 16
c 1
b 0
f 0
nc 5
nop 3
dl 0
loc 28
ccs 17
cts 17
cp 1
crap 5
rs 9.4222
1
<?php
2
3
namespace Gskema\TypeSniff\Sniffs\CodeElement;
4
5
use Gskema\TypeSniff\Core\CodeElement\Element\AbstractFqcnElement;
6
use Gskema\TypeSniff\Core\CodeElement\Element\AbstractFqcnMethodElement;
7
use Gskema\TypeSniff\Core\CodeElement\Element\ClassElement;
8
use Gskema\TypeSniff\Core\CodeElement\Element\ClassMethodElement;
9
use Gskema\TypeSniff\Core\CodeElement\Element\TraitElement;
10
use Gskema\TypeSniff\Core\CodeElement\Element\TraitMethodElement;
11
use Gskema\TypeSniff\Core\Type\DocBlock\NullType;
12
use Gskema\TypeSniff\Core\Type\TypeHelper;
13
use Gskema\TypeSniff\Inspection\DocTypeInspector;
14
use Gskema\TypeSniff\Inspection\Subject\PropTypeSubject;
15
use PHP_CodeSniffer\Files\File;
16
use Gskema\TypeSniff\Core\CodeElement\Element\AbstractFqcnPropElement;
17
use Gskema\TypeSniff\Core\CodeElement\Element\ClassPropElement;
18
use Gskema\TypeSniff\Core\CodeElement\Element\CodeElementInterface;
19
use Gskema\TypeSniff\Core\CodeElement\Element\TraitPropElement;
20
21
class FqcnPropSniff implements CodeElementSniffInterface
22
{
23
    protected const CODE = 'FqcnPropSniff';
24
25
    /** @var bool */
26
    protected $reportUninitializedProp = true;
27
28
    /**
29
     * @inheritDoc
30
     */
31 11
    public function configure(array $config): void
32
    {
33 11
        $this->reportUninitializedProp = $config['reportUninitializedProp'] ?? true;
34 11
    }
35
36
    /**
37
     * @inheritDoc
38
     */
39 11
    public function register(): array
40
    {
41
        return [
42 11
            ClassPropElement::class,
43
            TraitPropElement::class,
44
        ];
45
    }
46
47
    /**
48
     * @inheritDoc
49
     * @param AbstractFqcnPropElement $prop
50
     * @param AbstractFqcnElement $parentElement
51
     */
52 8
    public function process(File $file, CodeElementInterface $prop, CodeElementInterface $parentElement): void
53
    {
54 8
        $subject = PropTypeSubject::fromElement($prop);
55
56 8
        DocTypeInspector::reportMandatoryTypes($subject);
57 8
        DocTypeInspector::reportReplaceableTypes($subject);
58 8
        DocTypeInspector::reportRemovableTypes($subject);
59 8
        DocTypeInspector::reportMissingOrWrongTypes($subject);
60
61 8
        static::reportInvalidDescription($subject);
62 8
        $this->reportUninitializedProp && static::reportUninitializedProp($subject, $prop, $parentElement);
63
64 8
        $subject->writeWarningsTo($file, static::CODE);
65 8
    }
66
67 8
    protected static function reportInvalidDescription(PropTypeSubject $subject): void
68
    {
69 8
        $varTag = $subject->getDocBlock()->getTagsByName('var')[0] ?? null;
70
71 8
        if ($varTag && null !== $varTag->getParamName()) {
72 1
            $subject->addDocTypeWarning('Remove property name $'.$varTag->getParamName().' from @var tag');
73
        }
74 8
    }
75
76
    /**
77
     * @param PropTypeSubject                                           $subject
78
     * @param AbstractFqcnPropElement|ClassPropElement|TraitPropElement $prop
79
     * @param AbstractFqcnElement|ClassElement|TraitElement             $parent
80
     */
81 8
    protected static function reportUninitializedProp(
82
        PropTypeSubject $subject,
83
        AbstractFqcnPropElement $prop,
84
        AbstractFqcnElement $parent
85
    ): void {
86
        // Report nothing for PHP7.4 instead of reporting wrong
87 8
        if (version_compare(PHP_VERSION, '7.4', '>=')) {
88
            return; // @TODO Exit when prop has defined fnType
89
        }
90
91 8
        $propHasDefaultValue = $prop->getMetadata()->hasDefaultValue(); // null = not detected
0 ignored issues
show
Bug introduced by
The method getMetadata() does not exist on Gskema\TypeSniff\Core\Co...AbstractFqcnPropElement. Since it exists in all sub-types, consider adding an abstract or default implementation to Gskema\TypeSniff\Core\Co...AbstractFqcnPropElement. ( Ignorable by Annotation )

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

91
        $propHasDefaultValue = $prop->/** @scrutinizer ignore-call */ getMetadata()->hasDefaultValue(); // null = not detected
Loading history...
92
93 8
        $ownConstructor = $parent->getOwnConstructor();
0 ignored issues
show
Bug introduced by
The method getOwnConstructor() does not exist on Gskema\TypeSniff\Core\Co...ent\AbstractFqcnElement. It seems like you code against a sub-type of Gskema\TypeSniff\Core\Co...ent\AbstractFqcnElement such as Gskema\TypeSniff\Core\Co...nt\Element\TraitElement or Gskema\TypeSniff\Core\Co...nt\Element\ClassElement. ( Ignorable by Annotation )

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

93
        /** @scrutinizer ignore-call */ 
94
        $ownConstructor = $parent->getOwnConstructor();
Loading history...
94
95 8
        if ((false === $propHasDefaultValue || $prop->getDefaultValueType() instanceof NullType)
96 8
            && !TypeHelper::containsType($subject->getDocType(), NullType::class)
97 8
            && (!$ownConstructor || !static::hasNonNullAssignedProp($parent, $ownConstructor, $prop->getPropName()))
98
        ) {
99 7
            $subject->addDocTypeWarning(':Subject: not initialized by __construct(), add null doc type or set a default value');
100
        }
101 8
    }
102
103
    /**
104
     * @param ClassElement|TraitElement|AbstractFqcnElement                   $parent
105
     * @param ClassMethodElement|TraitMethodElement|AbstractFqcnMethodElement $method
106
     * @param string                                                          $propName
107
     *
108
     * @return bool|null
109
     */
110 3
    public static function hasNonNullAssignedProp(
111
        AbstractFqcnElement $parent,
112
        AbstractFqcnMethodElement $method,
113
        string $propName
114
    ): ?bool {
115
        // Not ideal because we have to descend on every prop inspection.
116
        // However early exit is used, so we don't have to descend fully each time.
117 3
        $visitedNames = [];
118 3
        $unvisitedNames = [$method->getSignature()->getName()];
119 3
        while (!empty($unvisitedNames)) {
120 3
            $callNames = [];
121 3
            foreach ($unvisitedNames as $unvisitedName) {
122 3
                $unvisitedMethod = $parent->getMethod($unvisitedName);
0 ignored issues
show
Bug introduced by
The method getMethod() does not exist on Gskema\TypeSniff\Core\Co...ent\AbstractFqcnElement. It seems like you code against a sub-type of Gskema\TypeSniff\Core\Co...ent\AbstractFqcnElement such as Gskema\TypeSniff\Core\Co...nt\Element\TraitElement or Gskema\TypeSniff\Core\Co...nt\Element\ClassElement. ( Ignorable by Annotation )

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

122
                /** @scrutinizer ignore-call */ 
123
                $unvisitedMethod = $parent->getMethod($unvisitedName);
Loading history...
123 3
                if (null === $unvisitedMethod) {
124 2
                    continue;
125
                }
126 3
                $nonNullAssignedProps = $unvisitedMethod->getMetadata()->getNonNullAssignedProps() ?? []; // never null
127 3
                if (in_array($propName, $nonNullAssignedProps)) {
128 2
                    return true;
129
                }
130 3
                $callNameChunk = $unvisitedMethod->getMetadata()->getThisMethodCalls() ?? []; // never null
131 3
                $callNames = array_merge($callNames, $callNameChunk);
132
            }
133 3
            $visitedNames = array_merge($visitedNames, $unvisitedNames);
134 3
            $unvisitedNames = array_diff($callNames, $visitedNames);
135
        }
136
137 3
        return false;
138
    }
139
}
140