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

voku / Simple-PHP-Code-Parser / 24274741178

11 Apr 2026 04:33AM UTC coverage: 82.504% (-0.4%) from 82.857%
24274741178

Pull #79

github

web-flow
Merge d654d7d88 into 3fcaa1a81
Pull Request #79: Fix PHP 8 type resolution order and safe autoloading for newer syntax

64 of 86 new or added lines in 5 files covered. (74.42%)

12 existing lines in 5 files now uncovered.

1575 of 1909 relevant lines covered (82.5%)

44.66 hits per line

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

71.43
/src/voku/SimplePhpParser/Model/BasePHPClass.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace voku\SimplePhpParser\Model;
6

7
abstract class BasePHPClass extends BasePHPElement
8
{
9
    use PHPDocElement;
10

11
    private const PHP_VERSION_8_2_0 = 80200;
12

13
    private const PHP_VERSION_8_3_0 = 80300;
14

15
    /**
16
     * @var array<string, PHPMethod>
17
     */
18
    public array $methods = [];
19

20
    /**
21
     * @var array<string, PHPProperty>
22
     */
23
    public array $properties = [];
24

25
    /**
26
     * @var array<string, PHPConst>
27
     */
28
    public array $constants = [];
29

30
    /**
31
     * PHP 8.0+ attributes on this class/interface/trait/enum.
32
     *
33
     * @var PHPAttribute[]
34
     */
35
    public array $attributes = [];
36

37
    public ?bool $is_final = null;
38

39
    public ?bool $is_abstract = null;
40

41
    public ?bool $is_readonly = null;
42

43
    public ?bool $is_anonymous = null;
44

45
    public ?bool $is_cloneable = null;
46

47
    public ?bool $is_instantiable = null;
48

49
    public ?bool $is_iterable = null;
50

51
    /**
52
     * Check if the parsed class-like node can be safely autoloaded on the
53
     * current runtime without triggering fatal syntax errors from newer PHP features.
54
     */
55
    protected static function canAutoloadFromPhpNode(\PhpParser\Node $node): bool
56
    {
57
        if (\PHP_VERSION_ID < self::PHP_VERSION_8_2_0 && self::containsPHP82PlusSyntax($node)) {
8✔
NEW
58
            return false;
×
59
        }
60

61
        if (\PHP_VERSION_ID < self::PHP_VERSION_8_3_0 && self::containsPHP83PlusSyntax($node)) {
8✔
NEW
62
            return false;
×
63
        }
64

65
        return true;
8✔
66
    }
67

68
    /**
69
     * Detect PHP 8.2-only syntax within a class-like AST such as readonly classes,
70
     * DNF types, and standalone null/true/false types.
71
     */
72
    private static function containsPHP82PlusSyntax(\PhpParser\Node $node): bool
73
    {
74
        if (
75
            $node instanceof \PhpParser\Node\Stmt\Class_
2✔
76
            &&
77
            \method_exists($node, 'isReadonly')
2✔
78
            &&
79
            $node->isReadonly()
2✔
80
        ) {
NEW
81
            return true;
×
82
        }
83

84
        if ($node instanceof \PhpParser\Node\UnionType) {
2✔
NEW
85
            foreach ($node->types as $innerType) {
×
NEW
86
                if ($innerType instanceof \PhpParser\Node\IntersectionType) {
×
NEW
87
                    return true;
×
88
                }
89
            }
90
        }
91

92
        if ($node instanceof \PhpParser\Node\Identifier) {
2✔
93
            $typeName = \strtolower($node->name);
2✔
94

95
            // Standalone null/true/false are represented as Identifier nodes too,
96
            // so they are only PHP 8.2+ when they are not part of a union type.
97
            if (
98
                ($typeName === 'null' || $typeName === 'true' || $typeName === 'false')
2✔
99
                &&
100
                !($node->getAttribute('parent') instanceof \PhpParser\Node\UnionType)
2✔
101
            ) {
NEW
102
                return true;
×
103
            }
104
        }
105

106
        foreach ($node->getSubNodeNames() as $subNodeName) {
2✔
107
            $subNode = $node->{$subNodeName};
2✔
108

109
            if ($subNode instanceof \PhpParser\Node && self::containsPHP82PlusSyntax($subNode)) {
2✔
NEW
110
                return true;
×
111
            }
112

113
            if (!\is_array($subNode)) {
2✔
114
                continue;
2✔
115
            }
116

117
            foreach ($subNode as $subNodeInner) {
2✔
118
                if ($subNodeInner instanceof \PhpParser\Node && self::containsPHP82PlusSyntax($subNodeInner)) {
2✔
NEW
119
                    return true;
×
120
                }
121
            }
122
        }
123

124
        return false;
2✔
125
    }
126

127
    /**
128
     * Detect PHP 8.3-only syntax within a class-like AST such as typed class constants.
129
     */
130
    private static function containsPHP83PlusSyntax(\PhpParser\Node $node): bool
131
    {
132
        if (
133
            $node instanceof \PhpParser\Node\Stmt\ClassConst
4✔
134
            &&
135
            \property_exists($node, 'type')
4✔
136
            &&
137
            $node->type !== null
4✔
138
        ) {
NEW
139
            return true;
×
140
        }
141

142
        foreach ($node->getSubNodeNames() as $subNodeName) {
4✔
143
            $subNode = $node->{$subNodeName};
4✔
144

145
            if ($subNode instanceof \PhpParser\Node && self::containsPHP83PlusSyntax($subNode)) {
4✔
NEW
146
                return true;
×
147
            }
148

149
            if (!\is_array($subNode)) {
4✔
150
                continue;
4✔
151
            }
152

153
            foreach ($subNode as $subNodeInner) {
4✔
154
                if ($subNodeInner instanceof \PhpParser\Node && self::containsPHP83PlusSyntax($subNodeInner)) {
4✔
NEW
155
                    return true;
×
156
                }
157
            }
158
        }
159

160
        return false;
4✔
161
    }
162
}
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