Failed Conditions
Pull Request — new-parser-ast-metadata (#2)
by
unknown
01:43
created

PHPStanTypeParser::resolveIdentifierNode()   C

Complexity

Conditions 12
Paths 11

Size

Total Lines 33
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 12.0737

Importance

Changes 0
Metric Value
eloc 26
dl 0
loc 33
ccs 23
cts 25
cp 0.92
rs 6.9666
c 0
b 0
f 0
cc 12
nc 11
nop 2
crap 12.0737

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Annotations\TypeParser;
6
7
use Doctrine\Annotations\Metadata\Type\BooleanType;
8
use Doctrine\Annotations\Metadata\Type\FloatType;
9
use Doctrine\Annotations\Metadata\Type\IntegerType;
10
use Doctrine\Annotations\Metadata\Type\IntersectionType;
11
use Doctrine\Annotations\Metadata\Type\ListType;
12
use Doctrine\Annotations\Metadata\Type\MapType;
13
use Doctrine\Annotations\Metadata\Type\MixedType;
14
use Doctrine\Annotations\Metadata\Type\NullType;
15
use Doctrine\Annotations\Metadata\Type\ObjectType;
16
use Doctrine\Annotations\Metadata\Type\StringType;
17
use Doctrine\Annotations\Metadata\Type\Type;
18
use Doctrine\Annotations\Metadata\Type\UnionType;
19
use Doctrine\Annotations\Parser\Ast\Reference;
20
use Doctrine\Annotations\Parser\Reference\ReferenceResolver;
21
use Doctrine\Annotations\Parser\Scope;
22
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
23
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
24
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
25
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
26
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
27
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
28
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
29
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
30
use PHPStan\PhpDocParser\Lexer\Lexer;
31
use PHPStan\PhpDocParser\Parser\PhpDocParser;
32
use PHPStan\PhpDocParser\Parser\TokenIterator;
33
use function array_map;
34
use function assert;
35
use function count;
36
use function ltrim;
37
use function strcasecmp;
38
use function strtolower;
39
40
final class PHPStanTypeParser implements TypeParser
41
{
42
    /** @var Lexer */
43
    private $lexer;
44
45
    /** @var PhpDocParser */
46
    private $phpDocParser;
47
48
    /** @var ReferenceResolver */
49
    private $referenceResolver;
50
51 23
    public function __construct(Lexer $lexer, PhpDocParser $phpDocParser, ReferenceResolver $referenceResolver)
52
    {
53 23
        $this->lexer             = $lexer;
54 23
        $this->phpDocParser      = $phpDocParser;
55 23
        $this->referenceResolver = $referenceResolver;
56 23
    }
57
58 23
    public function parsePropertyType(string $docBlock, Scope $scope) : Type
59
    {
60 23
        $tags = $this->parse($docBlock)->getVarTagValues();
61
62 23
        assert(count($tags) <= 1, 'multiple @var tags not allowed');
63
64 23
        if (count($tags) === 0) {
65 3
            return new MixedType();
66
        }
67
68 20
        return $this->resolveType($tags[0]->type, $scope);
69
    }
70
71 23
    private function parse(string $docBlock) : PhpDocNode
72
    {
73 23
        return $this->phpDocParser->parse(new TokenIterator($this->lexer->tokenize($docBlock)));
74
    }
75
76 20
    private function resolveType(TypeNode $typeNode, Scope $scope) : Type
77
    {
78 20
        if ($typeNode instanceof IdentifierTypeNode && strcasecmp($typeNode->name, 'null') === 0) {
79 8
            return new NullType();
80
        }
81
82 20
        if ($typeNode instanceof NullableTypeNode) {
83 1
            return new UnionType($this->resolveType($typeNode->type, $scope), new NullType());
84
        }
85
86 20
        if ($typeNode instanceof UnionTypeNode) {
87 9
            return new UnionType(
88 9
                ...array_map(
89
                    function (TypeNode $type) use ($scope) : Type {
90 9
                        return $this->resolveType($type, $scope);
91 9
                    },
92 9
                    $typeNode->types
93
                )
94
            );
95
        }
96
97 20
        if ($typeNode instanceof IntersectionTypeNode) {
98 2
            return new IntersectionType(
99 2
                ...array_map(
100
                    function (TypeNode $type) use ($scope) : Type {
101 2
                        return $this->resolveType($type, $scope);
102 2
                    },
103 2
                    $typeNode->types
104
                )
105
            );
106
        }
107
108 20
        if ($typeNode instanceof ArrayTypeNode) {
109 5
            return $this->resolveArrayTypeNode($typeNode, $scope);
110
        }
111
112 20
        if ($typeNode instanceof GenericTypeNode) {
113 4
            return $this->resolveGenericNode($typeNode, $scope);
114
        }
115
116 20
        assert($typeNode instanceof IdentifierTypeNode, 'Unsupported node type');
117
118 20
        return $this->resolveIdentifierNode($typeNode, $scope);
119
    }
120
121 4
    private function resolveGenericNode(GenericTypeNode $typeNode, Scope $scope) : Type
122
    {
123 4
        assert(
124 4
            strcasecmp($typeNode->type->name, 'array') === 0
125 4
            || strcasecmp($typeNode->type->name, 'iterable') === 0
126
        );
127
128 4
        if (count($typeNode->genericTypes) === 1) {
129 2
            return new ListType($this->resolveType($typeNode->genericTypes[0], $scope));
130
        }
131
132 2
        if (count($typeNode->genericTypes) === 2) {
133 2
            assert($typeNode->genericTypes[0] instanceof IdentifierTypeNode);
134
135 2
            return new MapType(
136 2
                $this->resolveIdentifierNode($typeNode->genericTypes[0], $scope),
0 ignored issues
show
Bug introduced by
It seems like $this->resolveIdentifier...enericTypes[0], $scope) can also be of type Doctrine\Annotations\Metadata\Type\ListType and Doctrine\Annotations\Metadata\Type\ObjectType and Doctrine\Annotations\Metadata\Type\MixedType; however, parameter $keyType of Doctrine\Annotations\Met...\MapType::__construct() does only seem to accept Doctrine\Annotations\Metadata\Type\ScalarType, maybe add an additional type check? ( Ignorable by Annotation )

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

136
                /** @scrutinizer ignore-type */ $this->resolveIdentifierNode($typeNode->genericTypes[0], $scope),
Loading history...
137 2
                $this->resolveType($typeNode->genericTypes[1], $scope)
138
            );
139
        }
140
141
        assert(false, '>2 generic type args');
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return Doctrine\Annotations\Metadata\Type\Type. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
142
    }
143
144 5
    private function resolveArrayTypeNode(ArrayTypeNode $typeNode, Scope $scope) : Type
145
    {
146 5
        return new ListType($this->resolveType($typeNode->type, $scope));
147
    }
148
149 20
    private function resolveIdentifierNode(TypeNode $typeNode, Scope $scope) : Type
150
    {
151 20
        assert($typeNode instanceof IdentifierTypeNode);
152
153 20
        $canonicalName = strtolower($typeNode->name);
154
155 20
        switch ($canonicalName) {
156 20
            case 'bool':
157 20
            case 'boolean':
158
                return new BooleanType();
159 20
            case 'int' :
160 9
            case 'integer' :
161 14
                return new IntegerType();
162 9
            case 'float':
163 9
            case 'double':
164 8
            case 'real':
165 2
                return new FloatType();
166 7
            case 'string':
167 3
                return new StringType();
168 4
            case 'array':
169
                return new ListType(new MixedType());
170
            case 'mixed':
171
                return new MixedType(); // TODO not really a scalar
172 4
        }
173 4
174 4
        $fullyQualified = $typeNode->name[0] === '\\';
175 4
        return new ObjectType(
176 4
            $this->referenceResolver->resolve(
177 4
                new Reference(
178
                    $fullyQualified ? ltrim($typeNode->name, '\\') : $typeNode->name,
179 4
                    $fullyQualified
180
                ),
181
                $scope
182
            )
183
        );
184
    }
185
}
186