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

voku / Simple-PHP-Code-Parser / 24218032206

09 Apr 2026 11:17PM UTC coverage: 83.27% (+0.009%) from 83.261%
24218032206

push

github

web-flow
Merge pull request #77 from voku/copilot/adapt-php-parser-compatibility

Add php-parser v5 compatibility (^4.18 || ^5)

8 of 13 new or added lines in 7 files covered. (61.54%)

42 existing lines in 4 files now uncovered.

1543 of 1853 relevant lines covered (83.27%)

43.97 hits per line

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

70.55
/src/voku/SimplePhpParser/Model/PHPTrait.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\Trait_;
9
use ReflectionClass;
10
use voku\SimplePhpParser\Parsers\Helper\Utils;
11

12
final class PHPTrait extends BasePHPClass
13
{
14
    /**
15
     * @phpstan-var class-string
16
     */
17
    public string $name;
18

19
    /**
20
     * @param Trait_ $node
21
     * @param null   $dummy
22
     *
23
     * @return $this
24
     */
25
    public function readObjectFromPhpNode($node, $dummy = null): self
26
    {
27
        $this->prepareNode($node);
15✔
28

29
        $this->name = static::getFQN($node);
15✔
30

31
        // Extract PHP 8.0+ attributes
32
        if (!empty($node->attrGroups)) {
15✔
33
            $this->attributes = Utils::extractAttributesFromAstNode($node->attrGroups);
×
34
        }
35

36
        // PHP < 8.2 raises an uncatchable E_COMPILE_ERROR for traits with constants.
37
        // Skip autoloading in that case; constants are still read from the AST below.
38
        $canAutoload = \PHP_VERSION_ID >= 80200 || empty($node->getConstants());
15✔
39
        $traitExists = false;
15✔
40
        if ($canAutoload) {
15✔
41
            try {
42
                if (\trait_exists($this->name, true)) {
15✔
43
                    $traitExists = true;
15✔
44
                }
45
            } catch (\Throwable $e) {
×
46
                // nothing
47
            }
48
        }
49
        if ($traitExists) {
15✔
50
            $reflectionClass = Utils::createClassReflectionInstance($this->name);
15✔
51
            $this->readObjectFromReflection($reflectionClass);
15✔
52
        }
53

54
        $this->collectTags($node);
15✔
55

56
        $docComment = $node->getDocComment();
15✔
57
        if ($docComment) {
15✔
58
            $this->readPhpDocProperties($docComment);
15✔
59
        }
60

61
        foreach ($node->getProperties() as $property) {
15✔
62
            $propertyNameTmp = $this->getConstantFQN($property, $property->props[0]->name->name);
12✔
63

64
            if (isset($this->properties[$propertyNameTmp])) {
12✔
65
                $this->properties[$propertyNameTmp] = $this->properties[$propertyNameTmp]->readObjectFromPhpNode($property, $this->name);
12✔
66
            } else {
67
                $this->properties[$propertyNameTmp] = (new PHPProperty($this->parserContainer))->readObjectFromPhpNode($property, $this->name);
×
68
            }
69
        }
70

71
        foreach ($node->getMethods() as $method) {
15✔
72
            $methodNameTmp = $method->name->name;
15✔
73

74
            if (isset($this->methods[$methodNameTmp])) {
15✔
75
                $this->methods[$methodNameTmp] = $this->methods[$methodNameTmp]->readObjectFromPhpNode($method, $this->name);
15✔
76
            } else {
UNCOV
77
                $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromPhpNode($method, $this->name);
2✔
78
            }
79

80
            if (!$this->methods[$methodNameTmp]->file) {
15✔
UNCOV
81
                $this->methods[$methodNameTmp]->file = $this->file;
2✔
82
            }
83
        }
84

85
        // Constants in traits (PHP 8.2+)
86
        foreach ($node->getConstants() as $constNode) {
15✔
87
            foreach ($constNode->consts as $const) {
11✔
88
                $constNameTmp = $const->name->name;
11✔
89

90
                if (isset($this->constants[$constNameTmp])) {
11✔
91
                    $this->constants[$constNameTmp] = $this->constants[$constNameTmp]->readObjectFromPhpNode($const);
9✔
92
                } else {
UNCOV
93
                    $this->constants[$constNameTmp] = (new PHPConst($this->parserContainer))->readObjectFromPhpNode($const);
2✔
94
                }
95

96
                if (!$this->constants[$constNameTmp]->file) {
11✔
UNCOV
97
                    $this->constants[$constNameTmp]->file = $this->file;
2✔
98
                }
99
            }
100
        }
101

102
        return $this;
15✔
103
    }
104

105
    /**
106
     * @param ReflectionClass $clazz
107
     *
108
     * @return $this
109
     */
110
    public function readObjectFromReflection($clazz): self
111
    {
112
        if (!$clazz->isTrait()) {
15✔
113
            return $this;
×
114
        }
115

116
        $this->name = $clazz->getName();
15✔
117

118
        if (!$this->line) {
15✔
119
            $lineTmp = $clazz->getStartLine();
×
120
            if ($lineTmp !== false) {
×
121
                $this->line = $lineTmp;
×
122
            }
123
        }
124

125
        $file = $clazz->getFileName();
15✔
126
        if ($file) {
15✔
127
            $this->file = $file;
15✔
128
        }
129

130
        $this->is_final = $clazz->isFinal();
15✔
131

132
        $this->is_abstract = $clazz->isAbstract();
15✔
133

134
        $this->is_anonymous = $clazz->isAnonymous();
15✔
135

136
        $this->is_cloneable = $clazz->isCloneable();
15✔
137

138
        $this->is_instantiable = $clazz->isInstantiable();
15✔
139

140
        $this->is_iterable = $clazz->isIterable();
15✔
141

142
        // Extract PHP 8.0+ attributes
143
        $this->attributes = Utils::extractAttributesFromReflection($clazz);
15✔
144

145
        foreach ($clazz->getProperties() as $property) {
15✔
146
            $propertyPhp = (new PHPProperty($this->parserContainer))->readObjectFromReflection($property);
12✔
147
            $this->properties[$propertyPhp->name] = $propertyPhp;
12✔
148
        }
149

150
        foreach ($clazz->getMethods() as $method) {
15✔
151
            $methodNameTmp = $method->getName();
15✔
152

153
            $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromReflection($method);
15✔
154

155
            if (!$this->methods[$methodNameTmp]->file) {
15✔
156
                $this->methods[$methodNameTmp]->file = $this->file;
×
157
            }
158
        }
159

160
        foreach ($clazz->getReflectionConstants() as $constant) {
15✔
161
            $constantNameTmp = $constant->getName();
9✔
162

163
            $this->constants[$constantNameTmp] = (new PHPConst($this->parserContainer))->readObjectFromReflection($constant);
9✔
164

165
            if (!$this->constants[$constantNameTmp]->file) {
9✔
166
                $this->constants[$constantNameTmp]->file = $this->file;
×
167
            }
168
        }
169

170
        return $this;
15✔
171
    }
172

173
    /**
174
     * @param string[] $access
175
     * @param bool     $skipMethodsWithLeadingUnderscore
176
     *
177
     * @return array
178
     *
179
     * @psalm-return array<string, array{type: null|string, typeFromPhpDocMaybeWithComment: null|string, typeFromPhpDoc: null|string, typeFromPhpDocSimple: null|string, typeFromPhpDocExtended: null|string, typeFromDefaultValue: null|string}>
180
     */
181
    public function getPropertiesInfo(
182
        array $access = ['public', 'protected', 'private'],
183
        bool $skipMethodsWithLeadingUnderscore = false
184
    ): array {
185
        // init
186
        $allInfo = [];
4✔
187

188
        foreach ($this->properties as $property) {
4✔
189
            if (!\in_array($property->access, $access, true)) {
4✔
190
                continue;
×
191
            }
192

193
            if ($skipMethodsWithLeadingUnderscore && \strpos($property->name, '_') === 0) {
4✔
194
                continue;
×
195
            }
196

197
            $types = [];
4✔
198
            $types['type'] = $property->type;
4✔
199
            $types['typeFromPhpDocMaybeWithComment'] = $property->typeFromPhpDocMaybeWithComment;
4✔
200
            $types['typeFromPhpDoc'] = $property->typeFromPhpDoc;
4✔
201
            $types['typeFromPhpDocSimple'] = $property->typeFromPhpDocSimple;
4✔
202
            $types['typeFromPhpDocExtended'] = $property->typeFromPhpDocExtended;
4✔
203
            $types['typeFromDefaultValue'] = $property->typeFromDefaultValue;
4✔
204

205
            $allInfo[$property->name] = $types;
4✔
206
        }
207

208
        return $allInfo;
4✔
209
    }
210

211
    /**
212
     * @param string[] $access
213
     * @param bool     $skipDeprecatedMethods
214
     * @param bool     $skipMethodsWithLeadingUnderscore
215
     *
216
     * @return array<mixed>
217
     *
218
     * @psalm-return array<string, array{
219
     *     fullDescription: string,
220
     *     line: null|int,
221
     *     file: null|string,
222
     *     error: string,
223
     *     is_deprecated: bool,
224
     *     is_static: null|bool,
225
     *     is_meta: bool,
226
     *     is_internal: bool,
227
     *     is_removed: bool,
228
     *     paramsTypes: array<string,
229
     *         array{
230
     *              type?: null|string,
231
     *              typeFromPhpDoc?: null|string,
232
     *              typeFromPhpDocExtended?: null|string,
233
     *              typeFromPhpDocSimple?: null|string,
234
     *              typeFromPhpDocMaybeWithComment?: null|string,
235
     *              typeFromDefaultValue?: null|string
236
     *         }
237
     *     >,
238
     *     returnTypes: array{
239
     *         type: null|string,
240
     *         typeFromPhpDoc: null|string,
241
     *         typeFromPhpDocExtended: null|string,
242
     *         typeFromPhpDocSimple: null|string,
243
     *         typeFromPhpDocMaybeWithComment: null|string
244
     *     },
245
     *     paramsPhpDocRaw: array<string, null|string>,
246
     *     returnPhpDocRaw: null|string
247
     *  }>
248
     */
249
    public function getMethodsInfo(
250
        array $access = ['public', 'protected', 'private'],
251
        bool $skipDeprecatedMethods = false,
252
        bool $skipMethodsWithLeadingUnderscore = false
253
    ): array {
254
        // init
255
        $allInfo = [];
4✔
256

257
        foreach ($this->methods as $method) {
4✔
258
            if (!\in_array($method->access, $access, true)) {
4✔
259
                continue;
×
260
            }
261

262
            if ($skipDeprecatedMethods && $method->hasDeprecatedTag) {
4✔
263
                continue;
×
264
            }
265

266
            if ($skipMethodsWithLeadingUnderscore && \strpos($method->name, '_') === 0) {
4✔
267
                continue;
×
268
            }
269

270
            $paramsTypes = [];
4✔
271
            foreach ($method->parameters as $tagParam) {
4✔
272
                $paramsTypes[$tagParam->name]['type'] = $tagParam->type;
×
273
                $paramsTypes[$tagParam->name]['typeFromPhpDocMaybeWithComment'] = $tagParam->typeFromPhpDocMaybeWithComment;
×
274
                $paramsTypes[$tagParam->name]['typeFromPhpDoc'] = $tagParam->typeFromPhpDoc;
×
275
                $paramsTypes[$tagParam->name]['typeFromPhpDocSimple'] = $tagParam->typeFromPhpDocSimple;
×
276
                $paramsTypes[$tagParam->name]['typeFromPhpDocExtended'] = $tagParam->typeFromPhpDocExtended;
×
277
                $paramsTypes[$tagParam->name]['typeFromDefaultValue'] = $tagParam->typeFromDefaultValue;
×
278
            }
279

280
            $returnTypes = [];
4✔
281
            $returnTypes['type'] = $method->returnType;
4✔
282
            $returnTypes['typeFromPhpDocMaybeWithComment'] = $method->returnTypeFromPhpDocMaybeWithComment;
4✔
283
            $returnTypes['typeFromPhpDoc'] = $method->returnTypeFromPhpDoc;
4✔
284
            $returnTypes['typeFromPhpDocSimple'] = $method->returnTypeFromPhpDocSimple;
4✔
285
            $returnTypes['typeFromPhpDocExtended'] = $method->returnTypeFromPhpDocExtended;
4✔
286

287
            $paramsPhpDocRaw = [];
4✔
288
            foreach ($method->parameters as $tagParam) {
4✔
289
                $paramsPhpDocRaw[$tagParam->name] = $tagParam->phpDocRaw;
×
290
            }
291

292
            $infoTmp = [];
4✔
293
            $infoTmp['fullDescription'] = \trim($method->summary . "\n\n" . $method->description);
4✔
294
            $infoTmp['paramsTypes'] = $paramsTypes;
4✔
295
            $infoTmp['returnTypes'] = $returnTypes;
4✔
296
            $infoTmp['paramsPhpDocRaw'] = $paramsPhpDocRaw;
4✔
297
            $infoTmp['returnPhpDocRaw'] = $method->returnPhpDocRaw;
4✔
298
            $infoTmp['line'] = $method->line ?? $this->line;
4✔
299
            $infoTmp['file'] = $method->file ?? $this->file;
4✔
300
            $infoTmp['error'] = \implode("\n", $method->parseError);
4✔
301
            foreach ($method->parameters as $parameter) {
4✔
302
                $infoTmp['error'] .= ($infoTmp['error'] ? "\n" : '') . \implode("\n", $parameter->parseError);
×
303
            }
304
            $infoTmp['is_deprecated'] = $method->hasDeprecatedTag;
4✔
305
            $infoTmp['is_static'] = $method->is_static;
4✔
306
            $infoTmp['is_meta'] = $method->hasMetaTag;
4✔
307
            $infoTmp['is_internal'] = $method->hasInternalTag;
4✔
308
            $infoTmp['is_removed'] = $method->hasRemovedTag;
4✔
309

310
            $allInfo[$method->name] = $infoTmp;
4✔
311
        }
312

313
        \asort($allInfo);
4✔
314

315
        return $allInfo;
4✔
316
    }
317

318
    /**
319
     * @param Doc|string $doc
320
     */
321
    private function readPhpDocProperties($doc): void
322
    {
323
        if ($doc instanceof Doc) {
15✔
324
            $docComment = $doc->getText();
15✔
325
        } else {
326
            $docComment = $doc;
×
327
        }
328
        if ($docComment === '') {
15✔
329
            return;
×
330
        }
331

332
        try {
333
            $phpDoc = Utils::createDocBlockInstance()->create($docComment);
15✔
334

335
            $parsedPropertyTags = $phpDoc->getTagsByName('property')
15✔
336
                               + $phpDoc->getTagsByName('property-read')
15✔
337
                               + $phpDoc->getTagsByName('property-write');
15✔
338

339
            if (!empty($parsedPropertyTags)) {
15✔
340
                foreach ($parsedPropertyTags as $parsedPropertyTag) {
15✔
341
                    if (
342
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyRead
×
343
                        ||
344
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite
×
345
                        ||
346
                        $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Property
×
347
                    ) {
348
                        $propertyPhp = new PHPProperty($this->parserContainer);
×
349

350
                        $nameTmp = $parsedPropertyTag->getVariableName();
×
351
                        if (!$nameTmp) {
×
352
                            continue;
×
353
                        }
354

355
                        $propertyPhp->name = $nameTmp;
×
356

357
                        $propertyPhp->access = 'public';
×
358

359
                        $type = $parsedPropertyTag->getType();
×
360

361
                        $propertyPhp->typeFromPhpDoc = Utils::normalizePhpType($type . '');
×
362

363
                        $typeFromPhpDocMaybeWithCommentTmp = \trim((string) $parsedPropertyTag);
×
364
                        if (
365
                            $typeFromPhpDocMaybeWithCommentTmp
×
366
                            &&
367
                            \strpos($typeFromPhpDocMaybeWithCommentTmp, '$') !== 0
×
368
                        ) {
369
                            $propertyPhp->typeFromPhpDocMaybeWithComment = $typeFromPhpDocMaybeWithCommentTmp;
×
370
                        }
371

372
                        $typeTmp = Utils::parseDocTypeObject($type);
×
373
                        if ($typeTmp !== '') {
×
374
                            $propertyPhp->typeFromPhpDocSimple = $typeTmp;
×
375
                        }
376

377
                        if ($propertyPhp->typeFromPhpDoc) {
×
378
                            $propertyPhp->typeFromPhpDocExtended = Utils::modernPhpdoc($propertyPhp->typeFromPhpDoc);
×
379
                        }
380

381
                        $this->properties[$propertyPhp->name] = $propertyPhp;
×
382
                    }
383
                }
384
            }
385
        } catch (\Exception $e) {
×
386
            $tmpErrorMessage = ($this->name ?: '?') . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true);
×
387
            $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage;
×
388
        }
389
    }
390
}
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