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

voku / Simple-PHP-Code-Parser / 24287978923

11 Apr 2026 05:41PM UTC coverage: 81.31% (-1.6%) from 82.886%
24287978923

Pull #83

github

web-flow
Merge 29aa9bda8 into 90e1e60d3
Pull Request #83: Fix CI pipeline: phpunit.xml validation warning, php-parser v4 test skip, and comprehensive type-analysis regression coverage

247 of 288 new or added lines in 7 files covered. (85.76%)

30 existing lines in 3 files now uncovered.

1688 of 2076 relevant lines covered (81.31%)

27.68 hits per line

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

6.45
/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
    private const PHP_VERSION_8_4_0 = 80400;
16

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

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

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

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

39
    public ?bool $is_final = null;
40

41
    public ?bool $is_abstract = null;
42

43
    public ?bool $is_readonly = null;
44

45
    public ?bool $is_anonymous = null;
46

47
    public ?bool $is_cloneable = null;
48

49
    public ?bool $is_instantiable = null;
50

51
    public ?bool $is_iterable = null;
52

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

63
        if (\PHP_VERSION_ID < self::PHP_VERSION_8_3_0 && self::containsPHP83PlusSyntax($node)) {
87✔
64
            return false;
×
65
        }
66

67
        if (\PHP_VERSION_ID < self::PHP_VERSION_8_4_0 && self::containsPHP84PlusSyntax($node)) {
87✔
NEW
68
            return false;
×
69
        }
70

71
        return true;
87✔
72
    }
73

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

88
        if ($node instanceof \PhpParser\Node\UnionType) {
×
89
            foreach ($node->types as $innerType) {
×
90
                if ($innerType instanceof \PhpParser\Node\IntersectionType) {
×
91
                    return true;
×
92
                }
93
            }
94
        }
95

96
        if ($node instanceof \PhpParser\Node\Identifier) {
×
97
            $typeName = \strtolower($node->name);
×
98

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

110
        foreach ($node->getSubNodeNames() as $subNodeName) {
×
111
            $subNode = $node->{$subNodeName};
×
112

113
            if ($subNode instanceof \PhpParser\Node && self::containsPHP82PlusSyntax($subNode)) {
×
114
                return true;
×
115
            }
116

117
            if (!\is_array($subNode)) {
×
118
                continue;
×
119
            }
120

121
            foreach ($subNode as $subNodeInner) {
×
122
                if ($subNodeInner instanceof \PhpParser\Node && self::containsPHP82PlusSyntax($subNodeInner)) {
×
123
                    return true;
×
124
                }
125
            }
126
        }
127

128
        return false;
×
129
    }
130

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

144
        foreach ($node->getSubNodeNames() as $subNodeName) {
×
145
            $subNode = $node->{$subNodeName};
×
146

147
            if ($subNode instanceof \PhpParser\Node && self::containsPHP83PlusSyntax($subNode)) {
×
148
                return true;
×
149
            }
150

151
            if (!\is_array($subNode)) {
×
152
                continue;
×
153
            }
154

155
            foreach ($subNode as $subNodeInner) {
×
156
                if ($subNodeInner instanceof \PhpParser\Node && self::containsPHP83PlusSyntax($subNodeInner)) {
×
157
                    return true;
×
158
                }
159
            }
160
        }
161

162
        return false;
×
163
    }
164

165
    /**
166
     * Detect PHP 8.4-only syntax within a class-like AST such as property hooks
167
     * and asymmetric visibility modifiers.
168
     */
169
    protected static function containsPHP84PlusSyntax(\PhpParser\Node $node): bool
170
    {
171
        // Property hooks (PHP 8.4+)
NEW
172
        if ($node instanceof \PhpParser\Node\Stmt\Property && !empty($node->hooks)) {
×
NEW
173
            return true;
×
174
        }
175

176
        // Asymmetric visibility on properties (PHP 8.4+)
177
        if (
NEW
178
            $node instanceof \PhpParser\Node\Stmt\Property
×
NEW
179
            && self::getAsymmetricSetVisibility($node) !== ''
×
180
        ) {
NEW
181
            return true;
×
182
        }
183

184
        // Property hooks on promoted constructor parameters (PHP 8.4+)
NEW
185
        if ($node instanceof \PhpParser\Node\Param && !empty($node->hooks)) {
×
NEW
186
            return true;
×
187
        }
188

189
        // Asymmetric visibility on promoted constructor parameters (PHP 8.4+)
190
        if (
NEW
191
            $node instanceof \PhpParser\Node\Param
×
NEW
192
            && self::getAsymmetricSetVisibility($node) !== ''
×
193
        ) {
NEW
194
            return true;
×
195
        }
196

NEW
197
        foreach ($node->getSubNodeNames() as $subNodeName) {
×
NEW
198
            $subNode = $node->{$subNodeName};
×
199

NEW
200
            if ($subNode instanceof \PhpParser\Node && self::containsPHP84PlusSyntax($subNode)) {
×
NEW
201
                return true;
×
202
            }
203

NEW
204
            if (!\is_array($subNode)) {
×
NEW
205
                continue;
×
206
            }
207

NEW
208
            foreach ($subNode as $subNodeInner) {
×
NEW
209
                if ($subNodeInner instanceof \PhpParser\Node && self::containsPHP84PlusSyntax($subNodeInner)) {
×
NEW
210
                    return true;
×
211
                }
212
            }
213
        }
214

NEW
215
        return false;
×
216
    }
217
}
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