• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

voku / Simple-PHP-Code-Parser / 24277438854

11 Apr 2026 07:15AM UTC coverage: 82.757%. Remained the same
24277438854

push

github

web-flow
Merge pull request #85 from voku/renovate/migrate-config

chore(config): migrate Renovate config

1531 of 1850 relevant lines covered (82.76%)

90.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.21
/src/voku/SimplePhpParser/Model/PHPProperty.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace voku\SimplePhpParser\Model;
6

7
use PhpParser\Comment\Doc;
8
use PhpParser\Node\Stmt\Property;
9
use ReflectionProperty;
10
use voku\SimplePhpParser\Parsers\Helper\DocFactoryProvider;
11
use voku\SimplePhpParser\Parsers\Helper\Utils;
12

13
class PHPProperty extends BasePHPElement
14
{
15
    /**
16
     * @var mixed|null
17
     */
18
    public $defaultValue;
19

20
    public ?string $phpDocRaw = null;
21

22
    public ?string $type = null;
23

24
    public ?string $typeFromDefaultValue = null;
25

26
    public ?string $typeFromPhpDoc = null;
27

28
    public ?string $typeFromPhpDocSimple = null;
29

30
    public ?string $typeFromPhpDocExtended = null;
31

32
    public ?string $typeFromPhpDocMaybeWithComment = null;
33

34
    /**
35
     * @phpstan-var ''|'private'|'protected'|'public'
36
     */
37
    public string $access = '';
38

39
    public ?bool $is_static = null;
40

41
    public ?bool $is_readonly = null;
42

43
    public ?bool $is_inheritdoc = null;
44

45
    /**
46
     * PHP 8.0+ attributes on this property.
47
     *
48
     * @var PHPAttribute[]
49
     */
50
    public array $attributes = [];
51

52
    /**
53
     * @param Property    $node
54
     * @param string|null $classStr
55
     *
56
     * @phpstan-param class-string|null $classStr
57
     *
58
     * @return $this
59
     */
60
    public function readObjectFromPhpNode($node, $classStr = null): self
61
    {
62
        $this->name = $this->getConstantFQN($node, $node->props[0]->name->name);
162✔
63

64
        $this->is_static = $node->isStatic();
162✔
65

66
        // Keep the guard for cross-version php-parser compatibility when readonly
67
        // helpers are restored or backported differently in downstream installs.
68
        if (\method_exists($node, 'isReadonly')) {
162✔
69
            $this->is_readonly = $node->isReadonly();
162✔
70
        }
71

72
        // Extract PHP 8.0+ attributes (only if not already populated by reflection)
73
        if (empty($this->attributes) && !empty($node->attrGroups)) {
162✔
74
            $this->attributes = Utils::extractAttributesFromAstNode($node->attrGroups);
8✔
75
        }
76

77
        $this->prepareNode($node);
162✔
78

79
        $docComment = $node->getDocComment();
162✔
80
        if ($docComment) {
162✔
81
            $docCommentText = $docComment->getText();
94✔
82

83
            if (\stripos($docCommentText, '@inheritdoc') !== false) {
94✔
84
                $this->is_inheritdoc = true;
×
85
            }
86

87
            $this->readPhpDoc($docComment);
94✔
88
        }
89

90
        if ($node->type !== null) {
162✔
91
            if (!$this->type) {
98✔
92
                $typeStr = Utils::typeNodeToString($node->type);
16✔
93
                if ($typeStr !== null) {
16✔
94
                    $this->type = $typeStr;
16✔
95
                }
96
            }
97

98
            if ($node->type instanceof \PhpParser\Node\NullableType) {
98✔
99
                if ($this->type && $this->type !== 'null' && \strpos($this->type, 'null|') !== 0) {
16✔
100
                    $this->type = 'null|' . $this->type;
×
101
                } elseif (!$this->type) {
16✔
102
                    $this->type = 'null|mixed';
×
103
                }
104
            }
105
        }
106

107
        if ($node->props[0]->default !== null) {
162✔
108
            $defaultValue = Utils::getPhpParserValueFromNode($node->props[0]->default, $classStr);
110✔
109
            if ($defaultValue !== Utils::GET_PHP_PARSER_VALUE_FROM_NODE_HELPER) {
110✔
110
                $this->defaultValue = $defaultValue;
110✔
111

112
                $this->typeFromDefaultValue = Utils::normalizePhpType(\gettype($this->defaultValue));
110✔
113
            }
114
        }
115

116
        if ($node->isPrivate()) {
162✔
117
            $this->access = 'private';
×
118
        } elseif ($node->isProtected()) {
162✔
119
            $this->access = 'protected';
16✔
120
        } else {
121
            $this->access = 'public';
162✔
122
        }
123

124
        return $this;
162✔
125
    }
126

127
    /**
128
     * @param ReflectionProperty $property
129
     *
130
     * @return $this
131
     */
132
    public function readObjectFromReflection($property): self
133
    {
134
        $this->name = $property->getName();
174✔
135

136
        $file = $property->getDeclaringClass()->getFileName();
174✔
137
        if ($file) {
174✔
138
            $this->file = $file;
174✔
139
        }
140

141
        $this->is_static = $property->isStatic();
174✔
142

143
        // Extract PHP 8.0+ attributes
144
        $this->attributes = Utils::extractAttributesFromReflection($property);
174✔
145

146
        if ($this->is_static) {
174✔
147
            try {
148
                if (\class_exists($property->getDeclaringClass()->getName(), true)) {
16✔
149
                    $this->defaultValue = $property->getValue();
16✔
150
                }
151
            } catch (\Exception $e) {
×
152
                // nothing
153
            }
154

155
            if ($this->defaultValue !== null) {
16✔
156
                $this->typeFromDefaultValue = Utils::normalizePhpType(\gettype($this->defaultValue));
16✔
157
            }
158
        }
159

160
        if (method_exists($property, 'isReadOnly')) {
174✔
161
            $this->is_readonly = $property->isReadOnly();
174✔
162
        }
163

164
        $docComment = $property->getDocComment();
174✔
165
        if ($docComment) {
174✔
166
            if (\stripos($docComment, '@inheritdoc') !== false) {
110✔
167
                $this->is_inheritdoc = true;
×
168
            }
169

170
            $this->readPhpDoc($docComment);
110✔
171
        }
172

173
        if (\method_exists($property, 'getType')) {
174✔
174
            $type = $property->getType();
174✔
175
            if ($type !== null) {
174✔
176
                if (\method_exists($type, 'getName')) {
94✔
177
                    $this->type = Utils::normalizePhpType($type->getName(), true);
70✔
178
                } else {
179
                    $this->type = Utils::normalizePhpType($type . '', true);
36✔
180
                }
181
                try {
182
                    if ($this->type && \class_exists($this->type, true)) {
94✔
183
                        $this->type = '\\' . \ltrim($this->type, '\\');
94✔
184
                    }
185
                } catch (\Exception $e) {
×
186
                    // nothing
187
                }
188

189
                if ($type->allowsNull()) {
94✔
190
                    if ($this->type && $this->type !== 'null' && \strpos($this->type, 'null|') !== 0) {
28✔
191
                        $this->type = 'null|' . $this->type;
28✔
192
                    } elseif (!$this->type) {
×
193
                        $this->type = 'null|mixed';
×
194
                    }
195
                }
196
            }
197
        }
198

199
        if ($property->isProtected()) {
174✔
200
            $access = 'protected';
40✔
201
        } elseif ($property->isPrivate()) {
166✔
202
            $access = 'private';
16✔
203
        } else {
204
            $access = 'public';
166✔
205
        }
206
        $this->access = $access;
174✔
207

208
        return $this;
174✔
209
    }
210

211
    /**
212
     * @return string|null
213
     */
214
    public function getType(): ?string
215
    {
216
        if ($this->typeFromPhpDocExtended) {
8✔
217
            return $this->typeFromPhpDocExtended;
8✔
218
        }
219

220
        if ($this->type) {
8✔
221
            return $this->type;
8✔
222
        }
223

224
        if ($this->typeFromPhpDocSimple) {
×
225
            return $this->typeFromPhpDocSimple;
×
226
        }
227

228
        return null;
×
229
    }
230

231
    /**
232
     * @param Doc|string $doc
233
     */
234
    private function readPhpDoc($doc): void
235
    {
236
        if ($doc instanceof Doc) {
110✔
237
            $docComment = $doc->getText();
94✔
238
        } else {
239
            $docComment = $doc;
110✔
240
        }
241
        if ($docComment === '') {
110✔
242
            return;
×
243
        }
244

245
        try {
246
            $phpDoc = DocFactoryProvider::getDocFactory()->create($docComment);
110✔
247

248
            $parsedParamTags = $phpDoc->getTagsByName('var');
110✔
249

250
            if (!empty($parsedParamTags)) {
110✔
251
                foreach ($parsedParamTags as $parsedParamTag) {
110✔
252
                    $parsedParamTagParam = (string) $parsedParamTag;
110✔
253

254
                    if ($parsedParamTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Var_) {
110✔
255
                        $type = $parsedParamTag->getType();
110✔
256

257
                        $this->typeFromPhpDoc = Utils::normalizePhpType($type . '');
110✔
258

259
                        $typeFromPhpDocMaybeWithCommentTmp = \trim($parsedParamTagParam);
110✔
260
                        if (
261
                            $typeFromPhpDocMaybeWithCommentTmp
110✔
262
                            &&
263
                            \strpos($typeFromPhpDocMaybeWithCommentTmp, '$') !== 0
110✔
264
                        ) {
265
                            $this->typeFromPhpDocMaybeWithComment = $typeFromPhpDocMaybeWithCommentTmp;
110✔
266
                        }
267

268
                        $typeTmp = Utils::parseDocTypeObject($type);
110✔
269
                        if ($typeTmp !== '') {
110✔
270
                            $this->typeFromPhpDocSimple = $typeTmp;
110✔
271
                        }
272
                    }
273

274
                    $this->phpDocRaw = $parsedParamTagParam;
110✔
275
                    $this->typeFromPhpDocExtended = Utils::modernPhpdoc($parsedParamTagParam);
110✔
276
                }
277
            }
278

279
            $parsedParamTags = $phpDoc->getTagsByName('psalm-var')
110✔
280
                               + $phpDoc->getTagsByName('phpstan-var');
110✔
281

282
            if (!empty($parsedParamTags)) {
110✔
283
                foreach ($parsedParamTags as $parsedParamTag) {
110✔
284
                    if (!$parsedParamTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Generic) {
56✔
285
                        continue;
×
286
                    }
287

288
                    $spitedData = Utils::splitTypeAndVariable($parsedParamTag);
56✔
289
                    $parsedParamTagStr = $spitedData['parsedParamTagStr'];
56✔
290

291
                    $this->typeFromPhpDocExtended = Utils::modernPhpdoc($parsedParamTagStr);
56✔
292
                }
293
            }
294
        } catch (\Exception $e) {
×
295
            $tmpErrorMessage = $this->name . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
×
296
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
×
297
        }
298

299
        try {
300
            $this->readPhpDocByTokens($docComment);
110✔
301
        } catch (\Exception $e) {
×
302
            $tmpErrorMessage = $this->name . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
×
303
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
×
304
        }
305
    }
306

307
    /**
308
     * @throws \PHPStan\PhpDocParser\Parser\ParserException
309
     */
310
    private function readPhpDocByTokens(string $docComment): void
311
    {
312
        $tokens = Utils::modernPhpdocTokens($docComment);
110✔
313

314
        $varContent = null;
110✔
315
        foreach ($tokens->getTokens() as $token) {
110✔
316
            $content = $token[0];
110✔
317

318
            if ($content === '@var' || $content === '@psalm-var' || $content === '@phpstan-var') {
110✔
319
                // reset
320
                $varContent = '';
110✔
321

322
                continue;
110✔
323
            }
324

325
            if ($varContent !== null) {
110✔
326
                $varContent .= $content;
110✔
327
            }
328
        }
329

330
        $varContent = $varContent ? \trim($varContent) : null;
110✔
331
        if ($varContent) {
110✔
332
            if (!$this->phpDocRaw) {
110✔
333
                $this->phpDocRaw = $varContent;
×
334
            }
335
            $this->typeFromPhpDocExtended = Utils::modernPhpdoc($varContent);
110✔
336
        }
337
    }
338
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc