Passed
Push — master ( a93c2d...7ce70b )
by Shinji
12:05 queued 10:05
created

PhpDocTypeReader   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 139
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
wmc 27
eloc 82
c 2
b 1
f 1
dl 0
loc 139
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A tryGetClassNameFromIdentifier() 0 5 1
A __construct() 0 16 3
A getVarTypes() 0 13 2
D getTypeFromNodeType() 0 68 18
A getParamTypes() 0 19 3
1
<?php
2
3
/**
4
 * This file is part of the sj-i/phpdoc-type-reader package.
5
 *
6
 * (c) sji <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace PhpDocTypeReader;
15
16
use PhpDocTypeReader\Context\IdentifierContext;
17
use PhpDocTypeReader\Type\ArrayKeyType;
18
use PhpDocTypeReader\Type\ArrayType;
19
use PhpDocTypeReader\Type\AtomicType;
20
use PhpDocTypeReader\Type\BoolType;
21
use PhpDocTypeReader\Type\FloatType;
22
use PhpDocTypeReader\Type\GenericType;
23
use PhpDocTypeReader\Type\IntType;
24
use PhpDocTypeReader\Type\MixedType;
25
use PhpDocTypeReader\Type\NullType;
26
use PhpDocTypeReader\Type\ObjectType;
27
use PhpDocTypeReader\Type\StringType;
28
use PhpDocTypeReader\Type\Type;
29
use PhpDocTypeReader\Type\UnionType;
30
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
31
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
32
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
33
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
34
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
35
use PHPStan\PhpDocParser\Lexer\Lexer;
36
use PHPStan\PhpDocParser\Parser\ConstExprParser;
37
use PHPStan\PhpDocParser\Parser\PhpDocParser;
38
use PHPStan\PhpDocParser\Parser\TokenIterator;
39
use PHPStan\PhpDocParser\Parser\TypeParser;
40
41
final class PhpDocTypeReader
42
{
43
    private PhpDocParser $php_doc_parser;
44
    private Lexer $lexer;
45
46
    public function __construct(
47
        ?PhpDocParser $php_doc_parser = null,
48
        ?Lexer $lexer = null
49
    ) {
50
        if (is_null($php_doc_parser)) {
51
            $const_expr_parser = new ConstExprParser();
52
            $php_doc_parser = new PhpDocParser(
53
                new TypeParser($const_expr_parser),
54
                $const_expr_parser
55
            );
56
        }
57
        if (is_null($lexer)) {
58
            $lexer = new Lexer();
59
        }
60
        $this->php_doc_parser = $php_doc_parser;
61
        $this->lexer = $lexer;
62
    }
63
64
    public function getVarTypes(string $doc_comment, IdentifierContext $identifier_context): Type
65
    {
66
        $tokens = $this->lexer->tokenize($doc_comment);
67
        $token_iterator = new TokenIterator($tokens);
68
        $php_doc_node = $this->php_doc_parser->parse($token_iterator);
69
        $var_tag_values = $php_doc_node->getVarTagValues();
70
71
        if (count($var_tag_values) < 1) {
72
            throw new \LogicException('cannot find @var');
73
        }
74
75
        $var_tag = current($var_tag_values);
76
        return $this->getTypeFromNodeType($var_tag->type, $identifier_context);
77
    }
78
79
    /**
80
     * @return array<string, Type>
81
     */
82
    public function getParamTypes(string $doc_comment, IdentifierContext $identifier_context): array
83
    {
84
        $tokens = $this->lexer->tokenize($doc_comment);
85
        $token_iterator = new TokenIterator($tokens);
86
        $php_doc_node = $this->php_doc_parser->parse($token_iterator);
87
        $param_tag_values = $php_doc_node->getParamTagValues();
88
89
        if (count($param_tag_values) < 1) {
90
            throw new \LogicException('cannot find @param');
91
        }
92
93
        $result = [];
94
        foreach ($param_tag_values as $param_tag_value) {
95
            $result[ltrim($param_tag_value->parameterName, '$')] = $this->getTypeFromNodeType(
96
                $param_tag_value->type,
97
                $identifier_context
98
            );
99
        }
100
        return $result;
101
    }
102
103
    private function getTypeFromNodeType(TypeNode $type_node, IdentifierContext $identifier_context): Type
104
    {
105
        if ($type_node instanceof IdentifierTypeNode) {
106
            switch ($type_node->name) {
107
                case 'mixed':
108
                    return new MixedType();
109
                case 'array-key':
110
                    return new ArrayKeyType();
111
                case 'int':
112
                    return new IntType();
113
                case 'string':
114
                    return new StringType();
115
                case 'float':
116
                    return new FloatType();
117
                case 'bool':
118
                    return new BoolType();
119
                case 'null':
120
                    return new NullType();
121
                default:
122
                    return new ObjectType(
123
                        $this->tryGetClassNameFromIdentifier($type_node, $identifier_context)
124
                    );
125
            }
126
        }
127
        if ($type_node instanceof GenericTypeNode) {
128
            if ($type_node->type->name === 'array') {
129
                if (count($type_node->genericTypes) === 1) {
130
                    $type = $this->getTypeFromNodeType($type_node->genericTypes[0], $identifier_context);
131
                    return new ArrayType($type);
132
                } elseif (count($type_node->genericTypes) === 2) {
133
                    $key_type = $this->getTypeFromNodeType($type_node->genericTypes[0], $identifier_context);
134
                    $value_type = $this->getTypeFromNodeType($type_node->genericTypes[1], $identifier_context);
135
                    if (!($key_type instanceof ArrayKeyType)) {
136
                        throw new \LogicException('unsupported array key type');
137
                    }
138
                    return new ArrayType($value_type, $key_type);
139
                }
140
                throw new \LogicException('unsupported parameter types of array');
141
            }
142
143
            return new GenericType(
144
                new ObjectType($this->tryGetClassNameFromIdentifier($type_node->type, $identifier_context)),
145
                array_map(
146
                    fn ($type) => $this->getTypeFromNodeType($type, $identifier_context),
147
                    $type_node->genericTypes
148
                )
149
            );
150
        }
151
        if ($type_node instanceof ArrayTypeNode) {
152
            $type = $this->getTypeFromNodeType($type_node->type, $identifier_context);
153
            return new ArrayType($type);
154
        }
155
        if ($type_node instanceof UnionTypeNode) {
156
            $types = [];
157
            foreach ($type_node->types as $type) {
158
                $type = $this->getTypeFromNodeType($type, $identifier_context);
159
                if (!($type instanceof AtomicType)) {
160
                    throw new \LogicException('unsupported union type');
161
                }
162
163
                $types[] = $type;
164
            }
165
166
            return new UnionType($types);
167
        }
168
        /** @psalm-suppress ForbiddenCode */
169
        var_dump($type_node);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($type_node) looks like debug code. Are you sure you do not want to remove it?
Loading history...
170
        throw new \LogicException('unsupported type');
171
    }
172
173
174
175
    private function tryGetClassNameFromIdentifier(
176
        IdentifierTypeNode $type,
177
        IdentifierContext $identifier_context
178
    ): string {
179
        return $identifier_context->getFqnFromContext($type->name);
180
    }
181
}
182