PhpDocTypeReader::getParamTypes()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 19
rs 9.8666
cc 3
nc 3
nop 2
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\ListType;
25
use PhpDocTypeReader\Type\MixedType;
26
use PhpDocTypeReader\Type\NullType;
27
use PhpDocTypeReader\Type\ObjectType;
28
use PhpDocTypeReader\Type\StringType;
29
use PhpDocTypeReader\Type\Type;
30
use PhpDocTypeReader\Type\UnionType;
31
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
32
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
33
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
34
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
35
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
36
use PHPStan\PhpDocParser\Lexer\Lexer;
37
use PHPStan\PhpDocParser\Parser\ConstExprParser;
38
use PHPStan\PhpDocParser\Parser\PhpDocParser;
39
use PHPStan\PhpDocParser\Parser\TokenIterator;
40
use PHPStan\PhpDocParser\Parser\TypeParser;
41
42
final class PhpDocTypeReader
43
{
44
    private PhpDocParser $php_doc_parser;
45
    private Lexer $lexer;
46
47
    public function __construct(
48
        ?PhpDocParser $php_doc_parser = null,
49
        ?Lexer $lexer = null
50
    ) {
51
        if (is_null($php_doc_parser)) {
52
            $const_expr_parser = new ConstExprParser();
53
            $php_doc_parser = new PhpDocParser(
54
                new TypeParser($const_expr_parser),
55
                $const_expr_parser
56
            );
57
        }
58
        if (is_null($lexer)) {
59
            $lexer = new Lexer();
60
        }
61
        $this->php_doc_parser = $php_doc_parser;
62
        $this->lexer = $lexer;
63
    }
64
65
    public function getVarTypes(string $doc_comment, IdentifierContext $identifier_context): Type
66
    {
67
        $tokens = $this->lexer->tokenize($doc_comment);
68
        $token_iterator = new TokenIterator($tokens);
69
        $php_doc_node = $this->php_doc_parser->parse($token_iterator);
70
        $var_tag_values = $php_doc_node->getVarTagValues();
71
72
        if (count($var_tag_values) < 1) {
73
            throw new \LogicException('cannot find @var');
74
        }
75
76
        $var_tag = current($var_tag_values);
77
        return $this->getTypeFromNodeType($var_tag->type, $identifier_context);
78
    }
79
80
    /**
81
     * @return array<string, Type>
82
     */
83
    public function getParamTypes(string $doc_comment, IdentifierContext $identifier_context): array
84
    {
85
        $tokens = $this->lexer->tokenize($doc_comment);
86
        $token_iterator = new TokenIterator($tokens);
87
        $php_doc_node = $this->php_doc_parser->parse($token_iterator);
88
        $param_tag_values = $php_doc_node->getParamTagValues();
89
90
        if (count($param_tag_values) < 1) {
91
            throw new \LogicException('cannot find @param');
92
        }
93
94
        $result = [];
95
        foreach ($param_tag_values as $param_tag_value) {
96
            $result[ltrim($param_tag_value->parameterName, '$')] = $this->getTypeFromNodeType(
97
                $param_tag_value->type,
98
                $identifier_context
99
            );
100
        }
101
        return $result;
102
    }
103
104
    private function getTypeFromNodeType(TypeNode $type_node, IdentifierContext $identifier_context): Type
105
    {
106
        if ($type_node instanceof IdentifierTypeNode) {
107
            switch ($type_node->name) {
108
                case 'mixed':
109
                    return new MixedType();
110
                case 'array-key':
111
                    return new ArrayKeyType();
112
                case 'int':
113
                    return new IntType();
114
                case 'string':
115
                    return new StringType();
116
                case 'float':
117
                    return new FloatType();
118
                case 'bool':
119
                    return new BoolType();
120
                case 'null':
121
                    return new NullType();
122
                case 'array':
123
                    return new ArrayType(new MixedType());
124
                case 'list':
125
                    return new ListType(new MixedType());
126
                default:
127
                    return new ObjectType(
128
                        $this->tryGetClassNameFromIdentifier($type_node, $identifier_context)
129
                    );
130
            }
131
        }
132
        if ($type_node instanceof GenericTypeNode) {
133
            if ($type_node->type->name === 'list') {
134
                if (count($type_node->genericTypes) !== 1) {
135
                    throw new \LogicException('unsupported parameter types of list type');
136
                }
137
                $type = $this->getTypeFromNodeType($type_node->genericTypes[0], $identifier_context);
138
                return new ListType($type);
139
            }
140
            if ($type_node->type->name === 'array') {
141
                if (count($type_node->genericTypes) === 1) {
142
                    $type = $this->getTypeFromNodeType($type_node->genericTypes[0], $identifier_context);
143
                    return new ArrayType($type);
144
                } elseif (count($type_node->genericTypes) === 2) {
145
                    $key_type = $this->getTypeFromNodeType($type_node->genericTypes[0], $identifier_context);
146
                    $value_type = $this->getTypeFromNodeType($type_node->genericTypes[1], $identifier_context);
147
                    if (!($key_type instanceof ArrayKeyType)) {
148
                        throw new \LogicException('unsupported array key type');
149
                    }
150
                    return new ArrayType($value_type, $key_type);
151
                }
152
                throw new \LogicException('unsupported parameter types of array');
153
            }
154
155
            return new GenericType(
156
                new ObjectType($this->tryGetClassNameFromIdentifier($type_node->type, $identifier_context)),
157
                array_map(
158
                    fn ($type) => $this->getTypeFromNodeType($type, $identifier_context),
159
                    $type_node->genericTypes
160
                )
161
            );
162
        }
163
        if ($type_node instanceof ArrayTypeNode) {
164
            $type = $this->getTypeFromNodeType($type_node->type, $identifier_context);
165
            return new ArrayType($type);
166
        }
167
        if ($type_node instanceof UnionTypeNode) {
168
            $types = [];
169
            foreach ($type_node->types as $type) {
170
                $type = $this->getTypeFromNodeType($type, $identifier_context);
171
                if (!($type instanceof AtomicType)) {
172
                    throw new \LogicException('unsupported union type');
173
                }
174
175
                $types[] = $type;
176
            }
177
178
            return new UnionType($types);
179
        }
180
        /** @psalm-suppress ForbiddenCode */
181
        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...
182
        throw new \LogicException('unsupported type');
183
    }
184
185
186
187
    private function tryGetClassNameFromIdentifier(
188
        IdentifierTypeNode $type,
189
        IdentifierContext $identifier_context
190
    ): string {
191
        return $identifier_context->getFqnFromContext($type->name);
192
    }
193
}
194