Skip to content

Commit 32cf692

Browse files
committed
refactored cleaning parser logic
1 parent 13d7837 commit 32cf692

File tree

3 files changed

+128
-65
lines changed

3 files changed

+128
-65
lines changed

src/Parser/CleaningVisitor.php

Lines changed: 111 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,102 +3,152 @@
33
namespace PHPStan\Parser;
44

55
use PhpParser\Node;
6-
use PhpParser\NodeFinder;
6+
use PhpParser\NodeTraverser;
77
use PhpParser\NodeVisitorAbstract;
88
use PHPStan\Reflection\ParametersAcceptor;
9+
use PHPStan\ShouldNotHappenException;
10+
use function array_filter;
11+
use function array_map;
912
use function in_array;
1013
use function is_array;
1114

1215
final class CleaningVisitor extends NodeVisitorAbstract
1316
{
1417

15-
private NodeFinder $nodeFinder;
18+
private const CONTEXT_DEFAULT = 0;
1619

17-
public function __construct()
20+
private const CONTEXT_FUNCTION_OR_METHOD = 1;
21+
22+
private const CONTEXT_PROPERTY_HOOK = 2;
23+
24+
/** @var self::CONTEXT_* */
25+
private int $context = self::CONTEXT_DEFAULT;
26+
27+
private string|null $propertyName = null;
28+
29+
/**
30+
* @return int|Node[]|null
31+
*/
32+
public function enterNode(Node $node): int|array|null
1833
{
19-
$this->nodeFinder = new NodeFinder();
34+
return match ($this->context) {
35+
self::CONTEXT_DEFAULT => $this->clean($node),
36+
self::CONTEXT_FUNCTION_OR_METHOD => $this->cleanFunctionOrMethod($node),
37+
self::CONTEXT_PROPERTY_HOOK => $this->cleanPropertyHook($node),
38+
};
2039
}
2140

22-
public function enterNode(Node $node): ?Node
41+
private function clean(Node $node): int|null
2342
{
24-
if ($node instanceof Node\Stmt\Function_) {
25-
$node->stmts = $this->keepVariadicsAndYields($node->stmts, null);
26-
return $node;
27-
}
43+
if (($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod) && $node->stmts !== null) {
44+
$params = [];
45+
foreach ($this->traverse($node->params, self::CONTEXT_DEFAULT) as $param) {
46+
$params[] = $param instanceof Node\Param ? $param : throw new ShouldNotHappenException();
47+
}
48+
$node->params = $params;
2849

29-
if ($node instanceof Node\Stmt\ClassMethod && $node->stmts !== null) {
30-
$node->stmts = $this->keepVariadicsAndYields($node->stmts, null);
31-
return $node;
32-
}
50+
$stmts = [];
51+
foreach ($this->traverse($node->stmts, self::CONTEXT_FUNCTION_OR_METHOD) as $stmt) {
52+
$stmts[] = $stmt instanceof Node\Stmt ? $stmt : throw new ShouldNotHappenException();
53+
}
54+
$node->stmts = $stmts;
3355

34-
if ($node instanceof Node\Expr\Closure) {
35-
$node->stmts = $this->keepVariadicsAndYields($node->stmts, null);
36-
return $node;
56+
return self::DONT_TRAVERSE_CHILDREN;
3757
}
3858

3959
if ($node instanceof Node\PropertyHook && is_array($node->body)) {
4060
$propertyName = $node->getAttribute('propertyName');
4161
if ($propertyName !== null) {
42-
$node->body = $this->keepVariadicsAndYields($node->body, $propertyName);
43-
return $node;
62+
$body = [];
63+
foreach ($this->traverse($node->body, self::CONTEXT_PROPERTY_HOOK, $propertyName) as $stmt) {
64+
$body[] = $stmt instanceof Node\Stmt ? $stmt : throw new ShouldNotHappenException();
65+
}
66+
$node->body = $body;
67+
68+
return self::DONT_TRAVERSE_CHILDREN;
4469
}
4570
}
4671

4772
return null;
4873
}
4974

5075
/**
51-
* @param Node\Stmt[] $stmts
52-
* @return Node\Stmt[]
76+
* @return int|Node[]
5377
*/
54-
private function keepVariadicsAndYields(array $stmts, ?string $hookedPropertyName): array
78+
private function cleanFunctionOrMethod(Node $node): int|array
5579
{
56-
$results = $this->nodeFinder->find($stmts, static function (Node $node) use ($hookedPropertyName): bool {
57-
if ($node instanceof Node\Expr\YieldFrom || $node instanceof Node\Expr\Yield_) {
58-
return true;
59-
}
60-
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name) {
61-
return in_array($node->name->toLowerString(), ParametersAcceptor::VARIADIC_FUNCTIONS, true);
62-
}
80+
if ($node instanceof Node\Expr\YieldFrom || $node instanceof Node\Expr\Yield_) {
81+
return self::DONT_TRAVERSE_CHILDREN;
82+
}
6383

64-
if ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) {
65-
return true;
66-
}
84+
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name
85+
&& in_array($node->name->toLowerString(), ParametersAcceptor::VARIADIC_FUNCTIONS, true)
86+
) {
87+
$node->name = new Node\Name\FullyQualified('func_get_args');
88+
return self::DONT_TRAVERSE_CHILDREN;
89+
}
6790

68-
if ($hookedPropertyName !== null) {
69-
if (
70-
$node instanceof Node\Expr\PropertyFetch
71-
&& $node->var instanceof Node\Expr\Variable
72-
&& $node->var->name === 'this'
73-
&& $node->name instanceof Node\Identifier
74-
&& $node->name->toString() === $hookedPropertyName
75-
) {
76-
return true;
77-
}
78-
}
91+
if ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) {
92+
return self::REMOVE_NODE;
93+
}
7994

80-
return false;
81-
});
82-
$newStmts = [];
83-
foreach ($results as $result) {
84-
if (
85-
$result instanceof Node\Expr\Yield_
86-
|| $result instanceof Node\Expr\YieldFrom
87-
|| $result instanceof Node\Expr\Closure
88-
|| $result instanceof Node\Expr\ArrowFunction
89-
|| $result instanceof Node\Expr\PropertyFetch
90-
) {
91-
$newStmts[] = new Node\Stmt\Expression($result);
92-
continue;
93-
}
94-
if (!$result instanceof Node\Expr\FuncCall) {
95-
continue;
96-
}
95+
return $this->cleanSubnodes($node);
96+
}
97+
98+
/**
99+
* @param Node[] $nodes
100+
* @param self::CONTEXT_* $context
101+
* @return Node[]
102+
*/
103+
private function traverse(
104+
array $nodes,
105+
int $context = self::CONTEXT_DEFAULT,
106+
string|null $propertyName = null,
107+
): array
108+
{
109+
$visitor = new self();
110+
$visitor->context = $context;
111+
$visitor->propertyName = $propertyName;
97112

98-
$newStmts[] = new Node\Stmt\Expression(new Node\Expr\FuncCall(new Node\Name\FullyQualified('func_get_args')));
113+
return (new NodeTraverser($visitor))->traverse($nodes);
114+
}
115+
116+
/**
117+
* @return Node[]
118+
*/
119+
private function cleanPropertyHook(Node $node): int|array
120+
{
121+
if (
122+
$node instanceof Node\Expr\PropertyFetch
123+
&& $node->var instanceof Node\Expr\Variable
124+
&& $node->var->name === 'this'
125+
&& $node->name instanceof Node\Identifier
126+
&& $node->name->toString() === $this->propertyName
127+
) {
128+
return self::DONT_TRAVERSE_CHILDREN;
129+
}
130+
131+
return $this->cleanSubnodes($node);
132+
}
133+
134+
/**
135+
* @return Node[]
136+
*/
137+
private function cleanSubnodes(Node $node): array
138+
{
139+
$subnodes = [];
140+
foreach ($node->getSubNodeNames() as $subnodeName) {
141+
$subnodes = [...$subnodes, ...array_filter(
142+
is_array($node->$subnodeName) ? $node->$subnodeName : [$node->$subnodeName],
143+
static fn ($subnode) => $subnode instanceof Node,
144+
)];
99145
}
100146

101-
return $newStmts;
147+
return array_map(static fn ($node) => match (true) {
148+
$node instanceof Node\Stmt => $node,
149+
$node instanceof Node\Expr => new Node\Stmt\Expression($node),
150+
default => throw new ShouldNotHappenException()
151+
}, $this->traverse($subnodes, $this->context, $this->propertyName));
102152
}
103153

104154
}

tests/PHPStan/Parser/data/cleaning-1-after.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public function someGenerator2()
2121
{
2222
yield from [1, 2, 3];
2323
}
24+
public function someGenerator3()
25+
{
26+
yield;
27+
}
2428
public function someVariadics()
2529
{
2630
\func_get_args();
@@ -43,9 +47,8 @@ class ContainsClosure
4347
{
4448
public function doFoo()
4549
{
46-
static function () {
47-
yield;
48-
};
49-
yield;
50+
}
51+
public function doBar()
52+
{
5053
}
5154
}

tests/PHPStan/Parser/data/cleaning-1-before.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public function someGenerator2()
3636
}
3737
}
3838

39+
public function someGenerator3()
40+
{
41+
echo yield;
42+
}
43+
3944
public function someVariadics()
4045
{
4146
if (rand(0, 1)) {
@@ -82,4 +87,9 @@ public function doFoo()
8287
};
8388
}
8489

90+
public function doBar()
91+
{
92+
$fn = fn() => yield;
93+
}
94+
8595
}

0 commit comments

Comments
 (0)