Failed Conditions
Push — new-parser-ast-metadata ( 6127e0...5a4a16 )
by Michael
12s
created

PHPStanTypeParser   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Test Coverage

Coverage 97.44%

Importance

Changes 0
Metric Value
wmc 29
eloc 72
dl 0
loc 142
ccs 76
cts 78
cp 0.9744
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A parsePropertyType() 0 11 2
A resolveArrayTypeNode() 0 3 1
C resolveIdentifierNode() 0 33 12
A parse() 0 3 1
A resolveGenericNode() 0 21 4
B resolveType() 0 43 8
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 30
    public function __construct(Lexer $lexer, PhpDocParser $phpDocParser, ReferenceResolver $referenceResolver)
52
    {
53 30
        $this->lexer             = $lexer;
54 30
        $this->phpDocParser      = $phpDocParser;
55 30
        $this->referenceResolver = $referenceResolver;
56 30
    }
57
58 27
    public function parsePropertyType(string $docBlock, Scope $scope) : Type
59
    {
60 27
        $tags = $this->parse($docBlock)->getVarTagValues();
61
62 27
        assert(count($tags) <= 1, 'multiple @var tags not allowed');
63
64 27
        if (count($tags) === 0) {
65 3
            return new MixedType();
66
        }
67
68 24
        return $this->resolveType($tags[0]->type, $scope);
69
    }
70
71 27
    private function parse(string $docBlock) : PhpDocNode
72
    {
73 27
        return $this->phpDocParser->parse(new TokenIterator($this->lexer->tokenize($docBlock)));
74
    }
75
76 24
    private function resolveType(TypeNode $typeNode, Scope $scope) : Type
77
    {
78 24
        if ($typeNode instanceof IdentifierTypeNode && strcasecmp($typeNode->name, 'null') === 0) {
79 8
            return new NullType();
80
        }
81
82 24
        if ($typeNode instanceof NullableTypeNode) {
83 1
            return new UnionType($this->resolveType($typeNode->type, $scope), new NullType());
84
        }
85
86 24
        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 24
        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 24
        if ($typeNode instanceof ArrayTypeNode) {
109 6
            return $this->resolveArrayTypeNode($typeNode, $scope);
110
        }
111
112 24
        if ($typeNode instanceof GenericTypeNode) {
113 5
            return $this->resolveGenericNode($typeNode, $scope);
114
        }
115
116 24
        assert($typeNode instanceof IdentifierTypeNode, 'Unsupported node type');
117
118 24
        return $this->resolveIdentifierNode($typeNode, $scope);
119
    }
120
121 5
    private function resolveGenericNode(GenericTypeNode $typeNode, Scope $scope) : Type
122
    {
123 5
        assert(
124 5
            strcasecmp($typeNode->type->name, 'array') === 0
125 5
            || strcasecmp($typeNode->type->name, 'iterable') === 0
126
        );
127
128 5
        if (count($typeNode->genericTypes) === 1) {
129 3
            return new ListType($this->resolveType($typeNode->genericTypes[0], $scope));
130
        }
131
132 3
        if (count($typeNode->genericTypes) === 2) {
133 3
            assert($typeNode->genericTypes[0] instanceof IdentifierTypeNode);
134
135 3
            return new MapType(
136 3
                $this->resolveIdentifierNode($typeNode->genericTypes[0], $scope),
137 3
                $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 6
    private function resolveArrayTypeNode(ArrayTypeNode $typeNode, Scope $scope) : Type
145
    {
146 6
        return new ListType($this->resolveType($typeNode->type, $scope));
147
    }
148
149 24
    private function resolveIdentifierNode(TypeNode $typeNode, Scope $scope) : Type
150
    {
151 24
        assert($typeNode instanceof IdentifierTypeNode);
152
153 24
        $canonicalName = strtolower($typeNode->name);
154
155 24
        switch ($canonicalName) {
156 24
            case 'bool':
157 24
            case 'boolean':
158 1
                return new BooleanType();
159 24
            case 'int':
160 13
            case 'integer':
161 15
                return new IntegerType();
162 13
            case 'float':
163 13
            case 'double':
164 12
            case 'real':
165 3
                return new FloatType();
166 11
            case 'string':
167 5
                return new StringType();
168 8
            case 'array':
169 2
                return new MapType(new UnionType(new IntegerType(), new StringType()), new MixedType());
170 7
            case 'mixed':
171 2
                return new MixedType(); // TODO not really a scalar
172
        }
173
174 6
        $fullyQualified = $typeNode->name[0] === '\\';
175 6
        return new ObjectType(
176 6
            $this->referenceResolver->resolve(
177 6
                new Reference(
178 6
                    $fullyQualified ? ltrim($typeNode->name, '\\') : $typeNode->name,
179 6
                    $fullyQualified
180
                ),
181 6
                $scope
182
            )
183
        );
184
    }
185
}
186