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

nette / php-generator / 22836119803

09 Mar 2026 02:40AM UTC coverage: 94.264% (+0.2%) from 94.029%
22836119803

push

github

dg
added CLAUDE.md

1890 of 2005 relevant lines covered (94.26%)

0.94 hits per line

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

98.53
/src/PhpGenerator/Extractor.php
1
<?php declare(strict_types=1);
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Nette\PhpGenerator;
9

10
use Nette;
11
use PhpParser;
12
use PhpParser\Modifiers;
13
use PhpParser\Node;
14
use PhpParser\NodeFinder;
15
use PhpParser\ParserFactory;
16
use function addcslashes, array_map, assert, class_exists, end, in_array, is_array, is_string, rtrim, str_contains, str_repeat, str_replace, str_starts_with, strlen, substr, substr_replace, usort;
17

18

19
/**
20
 * Extracts information from PHP code.
21
 * @internal
22
 */
23
final class Extractor
24
{
25
        private string $code;
26

27
        /** @var array<Node> */
28
        private array $statements;
29
        private PhpParser\PrettyPrinterAbstract $printer;
30

31

32
        public function __construct(string $code)
1✔
33
        {
34
                if (!class_exists(ParserFactory::class)) {
1✔
35
                        throw new Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser' 4.7 or newer.");
×
36
                }
37

38
                $this->printer = new PhpParser\PrettyPrinter\Standard;
1✔
39
                $this->parseCode($code);
1✔
40
        }
1✔
41

42

43
        private function parseCode(string $code): void
1✔
44
        {
45
                if (!str_starts_with($code, '<?php')) {
1✔
46
                        throw new Nette\InvalidStateException('The input string is not a PHP code.');
1✔
47
                }
48

49
                $this->code = Nette\Utils\Strings::unixNewLines($code);
1✔
50
                $parser = (new ParserFactory)->createForNewestSupportedVersion();
1✔
51
                $stmts = $parser->parse($this->code);
1✔
52
                assert($stmts !== null);
53

54
                $traverser = new PhpParser\NodeTraverser;
1✔
55
                $traverser->addVisitor(new PhpParser\NodeVisitor\ParentConnectingVisitor);
1✔
56
                $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['preserveOriginalNames' => true]));
1✔
57
                $this->statements = $traverser->traverse($stmts);
1✔
58
        }
1✔
59

60

61
        /** @return array<string, string> */
62
        public function extractMethodBodies(string $className): array
1✔
63
        {
64
                $nodeFinder = new NodeFinder;
1✔
65
                $classNode = $nodeFinder->findFirst(
1✔
66
                        $this->statements,
1✔
67
                        fn(Node $node) => $node instanceof Node\Stmt\ClassLike && $node->namespacedName !== null && $node->namespacedName->toString() === $className,
1✔
68
                );
69
                if (!$classNode) {
1✔
70
                        return [];
1✔
71
                }
72

73
                $res = [];
1✔
74
                foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\ClassMethod::class) as $methodNode) {
1✔
75
                        if ($methodNode->stmts) {
1✔
76
                                $res[$methodNode->name->toString()] = $this->getReformattedContents($methodNode->stmts, 2);
1✔
77
                        }
78
                }
79

80
                return $res;
1✔
81
        }
82

83

84
        /** @return array<string, array<string, array{string, bool}>> */
85
        public function extractPropertyHookBodies(string $className): array
1✔
86
        {
87
                if (!class_exists(Node\PropertyHook::class)) {
1✔
88
                        return [];
×
89
                }
90

91
                $nodeFinder = new NodeFinder;
1✔
92
                $classNode = $nodeFinder->findFirst(
1✔
93
                        $this->statements,
1✔
94
                        fn(Node $node) => $node instanceof Node\Stmt\ClassLike && $node->namespacedName !== null && $node->namespacedName->toString() === $className,
1✔
95
                );
96
                if (!$classNode) {
1✔
97
                        return [];
1✔
98
                }
99

100
                $res = [];
1✔
101
                foreach ($nodeFinder->findInstanceOf($classNode, Node\Stmt\Property::class) as $propertyNode) {
1✔
102
                        foreach ($propertyNode->props as $propNode) {
1✔
103
                                $propName = $propNode->name->toString();
1✔
104
                                foreach ($propertyNode->hooks as $hookNode) {
1✔
105
                                        $body = $hookNode->body;
1✔
106
                                        if ($body !== null) {
1✔
107
                                                $contents = $this->getReformattedContents(is_array($body) ? $body : [$body], 3);
1✔
108
                                                $res[$propName][$hookNode->name->toString()] = [$contents, !is_array($body)];
1✔
109
                                        }
110
                                }
111
                        }
112
                }
113
                return $res;
1✔
114
        }
115

116

117
        public function extractFunctionBody(string $name): string
1✔
118
        {
119
                $functionNode = (new NodeFinder)->findFirst(
1✔
120
                        $this->statements,
1✔
121
                        fn(Node $node) => $node instanceof Node\Stmt\Function_ && $node->namespacedName !== null && $node->namespacedName->toString() === $name,
1✔
122
                );
123
                assert($functionNode instanceof Node\Stmt\Function_);
124

125
                return $this->getReformattedContents($functionNode->stmts, 1);
1✔
126
        }
127

128

129
        /** @param  Node[]  $nodes */
130
        private function getReformattedContents(array $nodes, int $level): string
1✔
131
        {
132
                if (!$nodes) {
1✔
133
                        return '';
1✔
134
                }
135
                $body = $this->getNodeContents(...$nodes);
1✔
136
                $body = $this->performReplacements($body, $this->prepareReplacements($nodes, $level));
1✔
137
                return Helpers::unindent($body, $level);
1✔
138
        }
139

140

141
        /**
142
         * @param  Node[]  $nodes
143
         * @return array<array{int, int, string}>
144
         */
145
        private function prepareReplacements(array $nodes, int $level): array
1✔
146
        {
147
                $start = $this->getNodeStartPos($nodes[0]);
1✔
148
                $replacements = [];
1✔
149
                $indent = "\n" . str_repeat("\t", $level);
1✔
150
                (new NodeFinder)->find($nodes, function (Node $node) use (&$replacements, $start, $level, $indent) {
1✔
151
                        if ($node instanceof Node\Name\FullyQualified) {
1✔
152
                                if ($node->getAttribute('originalName') instanceof Node\Name) {
1✔
153
                                        $of = match (true) {
1✔
154
                                                $node->getAttribute('parent') instanceof Node\Expr\ConstFetch => PhpNamespace::NameConstant,
1✔
155
                                                $node->getAttribute('parent') instanceof Node\Expr\FuncCall => PhpNamespace::NameFunction,
1✔
156
                                                default => PhpNamespace::NameNormal,
1✔
157
                                        };
158
                                        $replacements[] = [
1✔
159
                                                $node->getStartFilePos() - $start,
1✔
160
                                                $node->getEndFilePos() - $start,
1✔
161
                                                Helpers::tagName($node->toCodeString(), $of),
1✔
162
                                        ];
163
                                }
164

165
                        } elseif (
166
                                $node instanceof Node\Scalar\String_
1✔
167
                                && in_array($node->getAttribute('kind'), [Node\Scalar\String_::KIND_SINGLE_QUOTED, Node\Scalar\String_::KIND_DOUBLE_QUOTED], strict: true)
1✔
168
                                && str_contains($node->getAttribute('rawValue'), "\n")
1✔
169
                        ) { // multi-line strings -> single line
170
                                $replacements[] = [
1✔
171
                                        $node->getStartFilePos() - $start,
1✔
172
                                        $node->getEndFilePos() - $start,
1✔
173
                                        '"' . addcslashes($node->value, "\x00..\x1F\"") . '"',
1✔
174
                                ];
175

176
                        } elseif (
177
                                $node instanceof Node\Scalar\String_
1✔
178
                                && in_array($node->getAttribute('kind'), [Node\Scalar\String_::KIND_NOWDOC, Node\Scalar\String_::KIND_HEREDOC], strict: true)
1✔
179
                                && Helpers::unindent($node->getAttribute('docIndentation'), $level) === $node->getAttribute('docIndentation')
1✔
180
                        ) { // fix indentation of NOWDOW/HEREDOC
181
                                $replacements[] = [
1✔
182
                                        $node->getStartFilePos() - $start,
1✔
183
                                        $node->getEndFilePos() - $start,
1✔
184
                                        str_replace("\n", $indent, $this->getNodeContents($node)),
1✔
185
                                ];
186

187
                        } elseif (
188
                                $node instanceof Node\Scalar\Encapsed
1✔
189
                                && $node->getAttribute('kind') === Node\Scalar\String_::KIND_DOUBLE_QUOTED
1✔
190
                        ) { // multi-line strings -> single line
191
                                foreach ($node->parts as $part) {
1✔
192
                                        if ($part instanceof Node\Scalar\EncapsedStringPart) {
1✔
193
                                                $replacements[] = [
1✔
194
                                                        $part->getStartFilePos() - $start,
1✔
195
                                                        $part->getEndFilePos() - $start,
1✔
196
                                                        addcslashes($part->value, "\x00..\x1F\""),
1✔
197
                                                ];
198
                                        }
199
                                }
200
                        } elseif (
201
                                $node instanceof Node\Scalar\Encapsed && $node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC
1✔
202
                                && Helpers::unindent($node->getAttribute('docIndentation'), $level) === $node->getAttribute('docIndentation')
1✔
203
                        ) { // fix indentation of HEREDOC
204
                                $replacements[] = [
1✔
205
                                        $tmp = $node->getStartFilePos() - $start + strlen($node->getAttribute('docLabel')) + 3, // <<<
1✔
206
                                        $tmp,
1✔
207
                                        $indent,
1✔
208
                                ];
209
                                $replacements[] = [
1✔
210
                                        $tmp = $node->getEndFilePos() - $start - strlen($node->getAttribute('docLabel')),
1✔
211
                                        $tmp,
1✔
212
                                        $indent,
1✔
213
                                ];
214
                                foreach ($node->parts as $part) {
1✔
215
                                        if ($part instanceof Node\Scalar\EncapsedStringPart) {
1✔
216
                                                $replacements[] = [
1✔
217
                                                        $part->getStartFilePos() - $start,
1✔
218
                                                        $part->getEndFilePos() - $start,
1✔
219
                                                        str_replace("\n", $indent, $this->getNodeContents($part)),
1✔
220
                                                ];
221
                                        }
222
                                }
223
                        }
224
                });
1✔
225
                return $replacements;
1✔
226
        }
227

228

229
        /** @param  array<array{int, int, string}>  $replacements */
230
        private function performReplacements(string $s, array $replacements): string
1✔
231
        {
232
                usort($replacements, fn($a, $b) => $b[0] <=> $a[0]);
1✔
233

234
                foreach ($replacements as [$start, $end, $replacement]) {
1✔
235
                        $s = substr_replace($s, $replacement, $start, $end - $start + 1);
1✔
236
                }
237

238
                return $s;
1✔
239
        }
240

241

242
        public function extractAll(): PhpFile
243
        {
244
                $phpFile = new PhpFile;
1✔
245

246
                if (
247
                        ($firstStmt = $this->statements[0] ?? null)
1✔
248
                        && ($firstStmt = $firstStmt instanceof Node\Stmt\Declare_ ? $this->statements[1] ?? null : $firstStmt)
1✔
249
                        && !$firstStmt instanceof Node\Stmt\ClassLike
1✔
250
                        && !$firstStmt instanceof Node\Stmt\Function_
1✔
251
                ) {
252
                        $comments = $firstStmt->getComments();
1✔
253
                        foreach ($comments as $i => $comment) {
1✔
254
                                if ($comment instanceof PhpParser\Comment\Doc) {
1✔
255
                                        $phpFile->setComment(Helpers::unformatDocComment($comment->getReformattedText()));
1✔
256
                                        break;
1✔
257
                                }
258
                        }
259
                }
260

261
                $namespaces = ['' => $this->statements];
1✔
262
                foreach ($this->statements as $node) {
1✔
263
                        if ($node instanceof Node\Stmt\Declare_
1✔
264
                                && $node->declares[0]->key->name === 'strict_types'
1✔
265
                                && $node->declares[0]->value instanceof Node\Scalar\LNumber
1✔
266
                        ) {
267
                                $phpFile->setStrictTypes((bool) $node->declares[0]->value->value);
1✔
268

269
                        } elseif ($node instanceof Node\Stmt\Namespace_) {
1✔
270
                                $namespaces[$node->name?->toString() ?? ''] = $node->stmts;
1✔
271
                        }
272
                }
273

274
                foreach ($namespaces as $name => $nodes) {
1✔
275
                        foreach ($nodes as $node) {
1✔
276
                                match (true) {
277
                                        $node instanceof Node\Stmt\Use_ => $this->addUseToNamespace($phpFile->addNamespace($name), $node),
1✔
278
                                        $node instanceof Node\Stmt\ClassLike => $this->addClassLikeToFile($phpFile, $node),
1✔
279
                                        $node instanceof Node\Stmt\Function_ => $this->addFunctionToFile($phpFile, $node),
1✔
280
                                        default => null,
1✔
281
                                };
282
                        }
283
                }
284

285
                return $phpFile;
1✔
286
        }
287

288

289
        private function addUseToNamespace(PhpNamespace $namespace, Node\Stmt\Use_ $node): void
1✔
290
        {
291
                $of = [
1✔
292
                        $node::TYPE_NORMAL => PhpNamespace::NameNormal,
1✔
293
                        $node::TYPE_FUNCTION => PhpNamespace::NameFunction,
1✔
294
                        $node::TYPE_CONSTANT => PhpNamespace::NameConstant,
1✔
295
                ][$node->type];
1✔
296
                foreach ($node->uses as $use) {
1✔
297
                        $namespace->addUse($use->name->toString(), $use->alias?->toString(), $of);
1✔
298
                }
299
        }
1✔
300

301

302
        private function addClassLikeToFile(PhpFile $phpFile, Node\Stmt\ClassLike $node): ClassLike
1✔
303
        {
304
                assert($node->namespacedName !== null);
305
                if ($node instanceof Node\Stmt\Class_) {
1✔
306
                        $class = $phpFile->addClass($node->namespacedName->toString());
1✔
307
                        $class->setFinal($node->isFinal());
1✔
308
                        $class->setAbstract($node->isAbstract());
1✔
309
                        $class->setReadOnly($node->isReadonly());
1✔
310
                        if ($node->extends) {
1✔
311
                                $class->setExtends($node->extends->toString());
1✔
312
                        }
313
                        foreach ($node->implements as $item) {
1✔
314
                                $class->addImplement($item->toString());
1✔
315
                        }
316
                } elseif ($node instanceof Node\Stmt\Interface_) {
1✔
317
                        $class = $phpFile->addInterface($node->namespacedName->toString());
1✔
318
                        foreach ($node->extends as $item) {
1✔
319
                                $class->addExtend($item->toString());
1✔
320
                        }
321
                } elseif ($node instanceof Node\Stmt\Trait_) {
1✔
322
                        $class = $phpFile->addTrait($node->namespacedName->toString());
1✔
323

324
                } elseif ($node instanceof Node\Stmt\Enum_) {
1✔
325
                        $class = $phpFile->addEnum($node->namespacedName->toString());
1✔
326
                        $class->setType($node->scalarType?->toString());
1✔
327
                        foreach ($node->implements as $item) {
1✔
328
                                $class->addImplement($item->toString());
1✔
329
                        }
330
                } else {
331
                        throw new Nette\ShouldNotHappenException;
×
332
                }
333

334
                $this->addCommentAndAttributes($class, $node);
1✔
335
                $this->addClassMembers($class, $node);
1✔
336
                return $class;
1✔
337
        }
338

339

340
        private function addClassMembers(ClassLike $class, Node\Stmt\ClassLike $node): void
1✔
341
        {
342
                foreach ($node->stmts as $stmt) {
1✔
343
                        match (true) {
344
                                $stmt instanceof Node\Stmt\TraitUse => $this->addTraitToClass($class, $stmt),
1✔
345
                                $stmt instanceof Node\Stmt\Property => $this->addPropertyToClass($class, $stmt),
1✔
346
                                $stmt instanceof Node\Stmt\ClassMethod => $this->addMethodToClass($class, $stmt),
1✔
347
                                $stmt instanceof Node\Stmt\ClassConst => $this->addConstantToClass($class, $stmt),
1✔
348
                                $stmt instanceof Node\Stmt\EnumCase => $this->addEnumCaseToClass($class, $stmt),
1✔
349
                                default => null,
×
350
                        };
351
                }
352
        }
1✔
353

354

355
        private function addTraitToClass(ClassLike $class, Node\Stmt\TraitUse $node): void
1✔
356
        {
357
                foreach ($node->traits as $item) {
1✔
358
                        $trait = $class->addTrait($item->toString());
1✔
359
                }
360
                assert($trait instanceof TraitUse);
361

362
                foreach ($node->adaptations as $item) {
1✔
363
                        $trait->addResolution(rtrim($this->getReformattedContents([$item], 0), ';'));
1✔
364
                }
365

366
                $this->addCommentAndAttributes($trait, $node);
1✔
367
        }
1✔
368

369

370
        private function addPropertyToClass(ClassLike $class, Node\Stmt\Property $node): void
1✔
371
        {
372
                foreach ($node->props as $item) {
1✔
373
                        $prop = $class->addProperty($item->name->toString());
1✔
374
                        $prop->setStatic($node->isStatic());
1✔
375
                        $prop->setVisibility($this->toVisibility($node->flags), $this->toSetterVisibility($node->flags));
1✔
376
                        $prop->setType($node->type ? $this->toPhp($node->type) : null);
1✔
377
                        if ($item->default) {
1✔
378
                                $prop->setValue($this->toValue($item->default));
1✔
379
                        }
380

381
                        $prop->setReadOnly($node->isReadonly() || ($class instanceof ClassType && $class->isReadOnly()));
1✔
382
                        $this->addCommentAndAttributes($prop, $node);
1✔
383

384
                        $prop->setAbstract((bool) ($node->flags & Modifiers::ABSTRACT));
1✔
385
                        $prop->setFinal((bool) ($node->flags & Modifiers::FINAL));
1✔
386
                        $this->addHooksToProperty($prop, $node);
1✔
387
                }
388
        }
1✔
389

390

391
        private function addHooksToProperty(Property|PromotedParameter $prop, Node\Stmt\Property|Node\Param $node): void
1✔
392
        {
393
                if (!class_exists(Node\PropertyHook::class)) {
1✔
394
                        return;
×
395
                }
396

397
                foreach ($node->hooks as $hookNode) {
1✔
398
                        /** @var 'set'|'get' $hookType */
399
                        $hookType = $hookNode->name->toString();
1✔
400
                        $hook = $prop->addHook($hookType);
1✔
401
                        $hook->setFinal((bool) ($hookNode->flags & Modifiers::FINAL));
1✔
402
                        $this->setupFunction($hook, $hookNode);
1✔
403
                        if ($hookNode->body === null) {
1✔
404
                                $hook->setAbstract();
1✔
405
                        } elseif (!is_array($hookNode->body)) {
1✔
406
                                $hook->setBody($this->getReformattedContents([$hookNode->body], 1), short: true);
1✔
407
                        }
408
                }
409
        }
1✔
410

411

412
        private function addMethodToClass(ClassLike $class, Node\Stmt\ClassMethod $node): void
1✔
413
        {
414
                $method = $class->addMethod($node->name->toString());
1✔
415
                $method->setAbstract($node->isAbstract());
1✔
416
                $method->setFinal($node->isFinal());
1✔
417
                $method->setStatic($node->isStatic());
1✔
418
                $method->setVisibility($this->toVisibility($node->flags));
1✔
419
                $this->setupFunction($method, $node);
1✔
420
                if ($method->getName() === Method::Constructor && $class instanceof ClassType && $class->isReadOnly()) {
1✔
421
                        array_map(fn($param) => $param instanceof PromotedParameter ? $param->setReadOnly() : $param, $method->getParameters());
1✔
422
                }
423
        }
1✔
424

425

426
        private function addConstantToClass(ClassLike $class, Node\Stmt\ClassConst $node): void
1✔
427
        {
428
                foreach ($node->consts as $item) {
1✔
429
                        $const = $class->addConstant($item->name->toString(), $this->toValue($item->value));
1✔
430
                        $const->setVisibility($this->toVisibility($node->flags));
1✔
431
                        $const->setFinal($node->isFinal());
1✔
432
                        $this->addCommentAndAttributes($const, $node);
1✔
433
                }
434
        }
1✔
435

436

437
        private function addEnumCaseToClass(EnumType $class, Node\Stmt\EnumCase $node): void
1✔
438
        {
439
                $value = match (true) {
1✔
440
                        $node->expr === null => null,
1✔
441
                        $node->expr instanceof Node\Scalar\LNumber, $node->expr instanceof Node\Scalar\String_ => $node->expr->value,
1✔
442
                        default => $this->toValue($node->expr),
1✔
443
                };
444
                $case = $class->addCase($node->name->toString(), $value);
1✔
445
                $this->addCommentAndAttributes($case, $node);
1✔
446
        }
1✔
447

448

449
        private function addFunctionToFile(PhpFile $phpFile, Node\Stmt\Function_ $node): void
1✔
450
        {
451
                assert($node->namespacedName !== null);
452
                $function = $phpFile->addFunction($node->namespacedName->toString());
1✔
453
                $this->setupFunction($function, $node);
1✔
454
        }
1✔
455

456

457
        private function addCommentAndAttributes(
1✔
458
                ClassLike|Constant|Property|GlobalFunction|Method|Parameter|EnumCase|TraitUse|PropertyHook $element,
459
                Node $node,
460
        ): void
461
        {
462
                if ($node->getDocComment()) {
1✔
463
                        $comment = $node->getDocComment()->getReformattedText();
1✔
464
                        $comment = Helpers::unformatDocComment($comment);
1✔
465
                        $element->setComment($comment);
1✔
466
                        $node->setDocComment(new PhpParser\Comment\Doc(''));
1✔
467
                }
468

469
                foreach ($node->attrGroups ?? [] as $group) {
1✔
470
                        foreach ($group->attrs as $attribute) {
1✔
471
                                $args = [];
1✔
472
                                foreach ($attribute->args as $arg) {
1✔
473
                                        if ($arg->name) {
1✔
474
                                                $args[$arg->name->toString()] = $this->toValue($arg->value);
1✔
475
                                        } else {
476
                                                $args[] = $this->toValue($arg->value);
1✔
477
                                        }
478
                                }
479

480
                                $element->addAttribute($attribute->name->toString(), $args);
1✔
481
                        }
482
                }
483
        }
1✔
484

485

486
        private function setupFunction(GlobalFunction|Method|PropertyHook $function, Node\FunctionLike $node): void
1✔
487
        {
488
                $function->setReturnReference($node->returnsByRef());
1✔
489
                if (!$function instanceof PropertyHook) {
1✔
490
                        $function->setReturnType($node->getReturnType() ? $this->toPhp($node->getReturnType()) : null);
1✔
491
                }
492

493
                foreach ($node->getParams() as $item) {
1✔
494
                        assert($item->var instanceof Node\Expr\Variable && is_string($item->var->name));
495
                        $getVisibility = $this->toVisibility($item->flags);
1✔
496
                        $setVisibility = $this->toSetterVisibility($item->flags);
1✔
497
                        $final = (bool) ($item->flags & Modifiers::FINAL);
1✔
498
                        if ($getVisibility || $setVisibility || $final) {
1✔
499
                                assert($function instanceof Method);
500
                                $param = $function->addPromotedParameter($item->var->name)
1✔
501
                                        ->setVisibility($getVisibility, $setVisibility)
1✔
502
                                        ->setReadOnly($item->isReadonly())
1✔
503
                                        ->setFinal($final);
1✔
504
                                $this->addHooksToProperty($param, $item);
1✔
505
                        } else {
506
                                $param = $function->addParameter($item->var->name);
1✔
507
                        }
508
                        $param->setType($item->type ? $this->toPhp($item->type) : null);
1✔
509
                        $param->setReference($item->byRef);
1✔
510
                        if (!$function instanceof PropertyHook) {
1✔
511
                                $function->setVariadic($item->variadic);
1✔
512
                        }
513
                        if ($item->default) {
1✔
514
                                $param->setDefaultValue($this->toValue($item->default));
1✔
515
                        }
516

517
                        $this->addCommentAndAttributes($param, $item);
1✔
518
                }
519

520
                $this->addCommentAndAttributes($function, $node);
1✔
521
                if ($node->getStmts()) {
1✔
522
                        $indent = $function instanceof GlobalFunction ? 1 : 2;
1✔
523
                        $function->setBody($this->getReformattedContents($node->getStmts(), $indent));
1✔
524
                }
525
        }
1✔
526

527

528
        private function toValue(Node\Expr $node): mixed
1✔
529
        {
530
                if ($node instanceof Node\Expr\ConstFetch) {
1✔
531
                        return match ($node->name->toLowerString()) {
1✔
532
                                'null' => null,
1✔
533
                                'true' => true,
1✔
534
                                'false' => false,
1✔
535
                                default => new Literal($this->getReformattedContents([$node], 0)),
1✔
536
                        };
537
                } elseif ($node instanceof Node\Scalar\LNumber
1✔
538
                        || $node instanceof Node\Scalar\DNumber
1✔
539
                        || $node instanceof Node\Scalar\String_
1✔
540
                ) {
541
                        return $node->value;
1✔
542

543
                } elseif ($node instanceof Node\Expr\Array_) {
1✔
544
                        $res = [];
1✔
545
                        foreach ($node->items as $item) {
1✔
546
                                if ($item->unpack) {
1✔
547
                                        return new Literal($this->getReformattedContents([$node], 0));
1✔
548

549
                                } elseif ($item->key) {
1✔
550
                                        $key = $this->toValue($item->key);
1✔
551
                                        if ($key instanceof Literal) {
1✔
552
                                                return new Literal($this->getReformattedContents([$node], 0));
1✔
553
                                        }
554

555
                                        $res[$key] = $this->toValue($item->value);
1✔
556

557
                                } else {
558
                                        $res[] = $this->toValue($item->value);
1✔
559
                                }
560
                        }
561
                        return $res;
1✔
562

563
                } else {
564
                        return new Literal($this->getReformattedContents([$node], 0));
1✔
565
                }
566
        }
567

568

569
        private function toVisibility(int $flags): ?Visibility
1✔
570
        {
571
                return match (true) {
572
                        (bool) ($flags & Modifiers::PUBLIC) => Visibility::Public,
1✔
573
                        (bool) ($flags & Modifiers::PROTECTED) => Visibility::Protected,
1✔
574
                        (bool) ($flags & Modifiers::PRIVATE) => Visibility::Private,
1✔
575
                        default => null,
1✔
576
                };
577
        }
578

579

580
        private function toSetterVisibility(int $flags): ?Visibility
1✔
581
        {
582
                return match (true) {
583
                        !class_exists(Node\PropertyHook::class) => null,
1✔
584
                        (bool) ($flags & Modifiers::PUBLIC_SET) => Visibility::Public,
1✔
585
                        (bool) ($flags & Modifiers::PROTECTED_SET) => Visibility::Protected,
1✔
586
                        (bool) ($flags & Modifiers::PRIVATE_SET) => Visibility::Private,
1✔
587
                        default => null,
1✔
588
                };
589
        }
590

591

592
        private function toPhp(Node $value): string
1✔
593
        {
594
                $dolly = clone $value;
1✔
595
                $dolly->setAttribute('comments', []);
1✔
596
                return $this->printer->prettyPrint([$dolly]);
1✔
597
        }
598

599

600
        private function getNodeContents(Node ...$nodes): string
1✔
601
        {
602
                $start = $this->getNodeStartPos($nodes[0]);
1✔
603
                $last = end($nodes);
1✔
604
                assert($last !== false);
605
                return substr($this->code, $start, $last->getEndFilePos() - $start + 1);
1✔
606
        }
607

608

609
        private function getNodeStartPos(Node $node): int
1✔
610
        {
611
                return ($comments = $node->getComments())
1✔
612
                        ? $comments[0]->getStartFilePos()
1✔
613
                        : $node->getStartFilePos();
1✔
614
        }
615
}
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