Skip to content

Commit 1025a9c

Browse files
committed
refactored cleaning parser logic
1 parent 13d7837 commit 1025a9c

File tree

3 files changed

+140
-64
lines changed

3 files changed

+140
-64
lines changed

src/Parser/CleaningVisitor.php

Lines changed: 123 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,102 +3,165 @@
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_map;
11+
use function array_push;
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) {
44+
$params = [];
45+
foreach ($this->traverse($node->params, self::CONTEXT_DEFAULT) as $param) {
46+
$param instanceof Node\Param || throw new ShouldNotHappenException();
47+
$params[] = $param;
48+
}
49+
$node->params = $params;
2850

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

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

3963
if ($node instanceof Node\PropertyHook && is_array($node->body)) {
4064
$propertyName = $node->getAttribute('propertyName');
4165
if ($propertyName !== null) {
42-
$node->body = $this->keepVariadicsAndYields($node->body, $propertyName);
43-
return $node;
66+
$body = [];
67+
foreach ($this->traverse($node->body, self::CONTEXT_PROPERTY_HOOK, $propertyName) as $stmt) {
68+
$stmt instanceof Node\Stmt || throw new ShouldNotHappenException();
69+
$body[] = $stmt;
70+
}
71+
$node->body = $body;
72+
73+
return self::DONT_TRAVERSE_CHILDREN;
4474
}
4575
}
4676

4777
return null;
4878
}
4979

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

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

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

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) {
100+
return $this->cleanSubnodes($node);
101+
}
102+
103+
/**
104+
* @param Node[] $nodes
105+
* @param self::CONTEXT_* $context
106+
* @return Node[]
107+
*/
108+
private function traverse(
109+
array $nodes,
110+
int $context = self::CONTEXT_DEFAULT,
111+
string|null $propertyName = null,
112+
): array
113+
{
114+
$visitor = new self();
115+
$visitor->context = $context;
116+
$visitor->propertyName = $propertyName;
117+
118+
return (new NodeTraverser($visitor))->traverse($nodes);
119+
}
120+
121+
/**
122+
* @return Node[]
123+
*/
124+
private function cleanPropertyHook(Node $node): int|array
125+
{
126+
if (
127+
$node instanceof Node\Expr\PropertyFetch
128+
&& $node->var instanceof Node\Expr\Variable
129+
&& $node->var->name === 'this'
130+
&& $node->name instanceof Node\Identifier
131+
&& $node->name->toString() === $this->propertyName
132+
) {
133+
return self::DONT_TRAVERSE_CHILDREN;
134+
}
135+
136+
return $this->cleanSubnodes($node);
137+
}
138+
139+
/**
140+
* @return Node[]
141+
*/
142+
private function cleanSubnodes(Node $node): array
143+
{
144+
$subnodes = [];
145+
foreach ($node->getSubNodeNames() as $subnodeName) {
146+
$subnode = $node->$subnodeName;
147+
if (!($subnode instanceof Node) && !is_array($subnode)) {
95148
continue;
96149
}
97-
98-
$newStmts[] = new Node\Stmt\Expression(new Node\Expr\FuncCall(new Node\Name\FullyQualified('func_get_args')));
150+
array_push($subnodes, ...is_array($subnode) ? $subnode : [$subnode]);
151+
}
152+
foreach ($subnodes as $subnode) {
153+
$subnode instanceof Node || throw new ShouldNotHappenException();
99154
}
100155

101-
return $newStmts;
156+
return array_map(static function ($node) {
157+
if ($node instanceof Node\Stmt) {
158+
return $node;
159+
}
160+
if ($node instanceof Node\Expr) {
161+
return new Node\Stmt\Expression($node);
162+
}
163+
throw new ShouldNotHappenException();
164+
}, $this->traverse($subnodes, $this->context, $this->propertyName));
102165
}
103166

104167
}

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)