Skip to content

Commit 1b277d0

Browse files
committed
refactored cleaning parser logic
1 parent 07875ff commit 1b277d0

File tree

3 files changed

+136
-65
lines changed

3 files changed

+136
-65
lines changed

src/Parser/CleaningVisitor.php

Lines changed: 119 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,102 +3,160 @@
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+
switch ($this->context) {
35+
case self::CONTEXT_DEFAULT:
36+
return $this->clean($node);
37+
case self::CONTEXT_FUNCTION_OR_METHOD:
38+
return $this->cleanFunctionOrMethod($node);
39+
case self::CONTEXT_PROPERTY_HOOK:
40+
return $this->cleanPropertyHook($node);
41+
}
2042
}
2143

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

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

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

3962
if ($node instanceof Node\PropertyHook && is_array($node->body)) {
4063
$propertyName = $node->getAttribute('propertyName');
4164
if ($propertyName !== null) {
42-
$node->body = $this->keepVariadicsAndYields($node->body, $propertyName);
43-
return $node;
65+
$body = [];
66+
foreach ($this->traverse($node->body, self::CONTEXT_PROPERTY_HOOK, $propertyName) as $stmt) {
67+
$body[] = $stmt instanceof Node\Stmt ? $stmt : throw new ShouldNotHappenException();
68+
}
69+
$node->body = $body;
70+
71+
return self::DONT_TRAVERSE_CHILDREN;
4472
}
4573
}
4674

4775
return null;
4876
}
4977

5078
/**
51-
* @param Node\Stmt[] $stmts
52-
* @return Node\Stmt[]
79+
* @return int|Node[]
5380
*/
54-
private function keepVariadicsAndYields(array $stmts, ?string $hookedPropertyName): array
81+
private function cleanFunctionOrMethod(Node $node): int|array
5582
{
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-
}
83+
if ($node instanceof Node\Expr\YieldFrom || $node instanceof Node\Expr\Yield_) {
84+
return self::DONT_TRAVERSE_CHILDREN;
85+
}
6386

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

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-
}
94+
if ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) {
95+
return self::REMOVE_NODE;
96+
}
7997

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-
}
98+
return $this->cleanSubnodes($node);
99+
}
100+
101+
/**
102+
* @param Node[] $nodes
103+
* @param self::CONTEXT_* $context
104+
* @return Node[]
105+
*/
106+
private function traverse(
107+
array $nodes,
108+
int $context = self::CONTEXT_DEFAULT,
109+
string|null $propertyName = null,
110+
): array
111+
{
112+
$visitor = new self();
113+
$visitor->context = $context;
114+
$visitor->propertyName = $propertyName;
115+
116+
return (new NodeTraverser($visitor))->traverse($nodes);
117+
}
118+
119+
/**
120+
* @return Node[]
121+
*/
122+
private function cleanPropertyHook(Node $node): int|array
123+
{
124+
if (
125+
$node instanceof Node\Expr\PropertyFetch
126+
&& $node->var instanceof Node\Expr\Variable
127+
&& $node->var->name === 'this'
128+
&& $node->name instanceof Node\Identifier
129+
&& $node->name->toString() === $this->propertyName
130+
) {
131+
return self::DONT_TRAVERSE_CHILDREN;
132+
}
133+
134+
return $this->cleanSubnodes($node);
135+
}
97136

98-
$newStmts[] = new Node\Stmt\Expression(new Node\Expr\FuncCall(new Node\Name\FullyQualified('func_get_args')));
137+
/**
138+
* @return Node[]
139+
*/
140+
private function cleanSubnodes(Node $node): array
141+
{
142+
$subnodes = [];
143+
foreach ($node->getSubNodeNames() as $subnodeName) {
144+
$subnodes = [...$subnodes, ...array_filter(
145+
is_array($node->$subnodeName) ? $node->$subnodeName : [$node->$subnodeName],
146+
static fn ($subnode) => $subnode instanceof Node,
147+
)];
99148
}
100149

101-
return $newStmts;
150+
return array_map(static function ($node) {
151+
switch (true) {
152+
case $node instanceof Node\Stmt:
153+
return $node;
154+
case $node instanceof Node\Expr:
155+
return new Node\Stmt\Expression($node);
156+
default:
157+
throw new ShouldNotHappenException();
158+
}
159+
}, $this->traverse($subnodes, $this->context, $this->propertyName));
102160
}
103161

104162
}

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)