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

voku / Simple-PHP-Code-Parser / 29085974904

10 Jul 2026 10:10AM UTC coverage: 85.103% (+0.3%) from 84.815%
29085974904

push

github

moellekenl
[+]: Add support for source-range information and enhance PHP model elements

- Introduced `endLine`, `startFilePos`, and `endFilePos` properties to model elements for precise source navigation.
- Added `PHPFileInfo` class for lightweight file summaries and improved AST access methods.
- Enhanced trait handling in class-like models to include trait uses and adaptations.
- Updated method and parameter models to include `is_returned_by_ref` and `is_promoted` properties.
- Improved PHPDoc context handling for better type resolution in models.

269 of 316 new or added lines in 16 files covered. (85.13%)

1 existing line in 1 file now uncovered.

1988 of 2336 relevant lines covered (85.1%)

175.85 hits per line

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

86.96
/src/voku/SimplePhpParser/Model/BasePHPElement.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace voku\SimplePhpParser\Model;
6

7
use PhpParser\Node;
8
use PhpParser\Node\Name;
9
use PhpParser\Node\Stmt\Namespace_;
10
use PhpParser\NodeAbstract;
11
use voku\SimplePhpParser\Parsers\Helper\ParserContainer;
12

13
abstract class BasePHPElement
14
{
15
    public string $name = '';
16

17
    /**
18
     * @var string[]
19
     */
20
    public array $parseError = [];
21

22
    public ?int $line = null;
23

24
    /**
25
     * Last line covered by this element, inclusive.
26
     *
27
     * This is populated from php-parser AST nodes. Reflection-backed models
28
     * populate it when the corresponding reflection API exposes an end line.
29
     */
30
    public ?int $endLine = null;
31

32
    /**
33
     * Zero-based byte offset of the first character covered by this element.
34
     *
35
     * The value is null when the active php-parser lexer does not expose file
36
     * offsets or when the element was created from reflection.
37
     */
38
    public ?int $startFilePos = null;
39

40
    /**
41
     * Zero-based byte offset of the last character covered by this element,
42
     * inclusive.
43
     *
44
     * The value is null when the active php-parser lexer does not expose file
45
     * offsets or when the element was created from reflection.
46
     */
47
    public ?int $endFilePos = null;
48

49
    public ?string $file = null;
50

51
    public ?int $pos = null;
52

53
    public ParserContainer $parserContainer;
54

55
    public function __construct(ParserContainer $parserContainer)
56
    {
57
        $this->parserContainer = $parserContainer;
709✔
58
    }
59

60
    /**
61
     * @param \Reflector $object
62
     *
63
     * @return $this
64
     */
65
    abstract public function readObjectFromReflection($object);
66

67
    /**
68
     * @param \PhpParser\NodeAbstract      $mixed_1
69
     * @param \PhpParser\NodeAbstract|null $mixed_2
70
     *
71
     * @return $this
72
     */
73
    abstract public function readObjectFromPhpNode($mixed_1, $mixed_2 = null);
74

75
    protected function getConstantFQN(NodeAbstract $node, string $nodeName): string
76
    {
77
        $parent = $node->getAttribute('parent');
314✔
78
        if (
79
            $parent instanceof Namespace_
314✔
80
            &&
81
            $parent->name instanceof Name
314✔
82
        ) {
83
            $namespace = '\\' . $parent->name->toString() . '\\';
×
84
        } else {
85
            $namespace = '';
314✔
86
        }
87

88
        return $namespace . $nodeName;
314✔
89
    }
90

91
    /**
92
     * @param \PhpParser\Node|string $node
93
     *
94
     * @return string
95
     *
96
     * @psalm-return class-string
97
     */
98
    protected static function getFQN($node): string
99
    {
100
        // init
101
        $fqn = '';
699✔
102

103
        if (
104
            $node instanceof \PhpParser\Node
699✔
105
            &&
106
            \property_exists($node, 'namespacedName')
699✔
107
        ) {
108
            if ($node->namespacedName) {
699✔
109
                $fqn = $node->namespacedName->toString();
699✔
110
            } elseif (\property_exists($node, 'name') && $node->name) {
20✔
111
                $fqn = $node->name instanceof Name ? $node->name->toString() : (string) $node->name;
×
112
            }
113
        }
114

115
        /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
116
        /** @var class-string $fqn */
117
        $fqn = $fqn;
699✔
118

119
        return $fqn;
699✔
120
    }
121

122
    protected function prepareNode(Node $node): void
123
    {
124
        // Keep the legacy getLine() fallback because we support multiple php-parser
125
        // versions and some restored compatibility code paths still rely on the alias.
126
        // @phpstan-ignore function.alreadyNarrowedType
127
        $this->line = \method_exists($node, 'getStartLine')
709✔
128
            ? $node->getStartLine()
709✔
129
            : $node->getLine();
×
130

131
        $this->endLine = self::nodePosition($node, 'getEndLine');
709✔
132
        $this->startFilePos = self::nodePosition($node, 'getStartFilePos');
709✔
133
        $this->endFilePos = self::nodePosition($node, 'getEndFilePos');
709✔
134

135
        // "pos" predates the explicit source-range properties and was never
136
        // populated. Keep it as a backwards-compatible alias for consumers
137
        // that already use it as a source position.
138
        $this->pos = $this->startFilePos;
709✔
139
    }
140

141
    private static function nodePosition(Node $node, string $method): ?int
142
    {
143
        if (!\method_exists($node, $method)) {
709✔
NEW
144
            return null;
×
145
        }
146

147
        /** @var mixed $position */
148
        $position = $node->{$method}();
709✔
149

150
        return \is_int($position) && $position >= 0 ? $position : null;
709✔
151
    }
152

153
    protected static function getPhpDocContext(Node $node): ?\phpDocumentor\Reflection\Types\Context
154
    {
155
        $context = $node->getAttribute('phpDocContext');
308✔
156

157
        return $context instanceof \phpDocumentor\Reflection\Types\Context ? $context : null;
308✔
158
    }
159

160
    /**
161
     * @phpstan-return ''|'private'|'protected'|'public'
162
     */
163
    protected static function getAsymmetricSetVisibility(object $node): string
164
    {
165
        if (\method_exists($node, 'isPublicSet') && $node->isPublicSet()) {
487✔
166
            return 'public';
×
167
        }
168

169
        if (\method_exists($node, 'isProtectedSet') && $node->isProtectedSet()) {
487✔
170
            return 'protected';
48✔
171
        }
172

173
        if (\method_exists($node, 'isPrivateSet') && $node->isPrivateSet()) {
487✔
174
            return 'private';
30✔
175
        }
176

177
        return '';
487✔
178
    }
179

180
    protected static function isPromotedParameter(\PhpParser\Node\Param $parameter): bool
181
    {
182
        return ($parameter->flags & \PhpParser\Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK) !== 0;
529✔
183
    }
184

185
    /**
186
     * @phpstan-return ''|'private'|'protected'|'public'
187
     */
188
    protected static function getVisibilityFromModifierFlags(int $flags): string
189
    {
190
        if (($flags & \PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE) !== 0) {
145✔
191
            return 'private';
10✔
192
        }
193

194
        if (($flags & \PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED) !== 0) {
135✔
195
            return 'protected';
10✔
196
        }
197

198
        if (($flags & \PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC) !== 0) {
125✔
199
            return 'public';
125✔
200
        }
201

202
        return '';
×
203
    }
204

205
    protected static function hasReadonlyModifier(int $flags): bool
206
    {
207
        return ($flags & \PhpParser\Node\Stmt\Class_::MODIFIER_READONLY) !== 0;
135✔
208
    }
209

210
    protected static function hasFinalModifier(int $flags): bool
211
    {
212
        return ($flags & \PhpParser\Node\Stmt\Class_::MODIFIER_FINAL) !== 0;
135✔
213
    }
214
}
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