Skip to content

Commit df9dc44

Browse files
committed
refactored cleaning parser logic
1 parent f3e39df commit df9dc44

File tree

3 files changed

+148
-65
lines changed

3 files changed

+148
-65
lines changed

src/Parser/CleaningVisitor.php

+131-61
Original file line numberDiff line numberDiff line change
@@ -3,102 +3,172 @@
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+
if (!($param instanceof Node\Param)) {
50+
continue;
51+
}
2852

29-
if ($node instanceof Node\Stmt\ClassMethod && $node->stmts !== null) {
30-
$node->stmts = $this->keepVariadicsAndYields($node->stmts, null);
31-
return $node;
32-
}
53+
$params[] = $param;
54+
}
55+
$node->params = $params;
3356

34-
if ($node instanceof Node\Expr\Closure) {
35-
$node->stmts = $this->keepVariadicsAndYields($node->stmts, null);
36-
return $node;
57+
$stmts = [];
58+
foreach ($this->traverse($node->stmts, self::CONTEXT_FUNCTION_OR_METHOD) as $stmt) {
59+
if (!($stmt instanceof Node\Stmt)) {
60+
continue;
61+
}
62+
63+
$stmts[] = $stmt;
64+
}
65+
$node->stmts = $stmts;
66+
67+
return self::DONT_TRAVERSE_CHILDREN;
3768
}
3869

3970
if ($node instanceof Node\PropertyHook && is_array($node->body)) {
4071
$propertyName = $node->getAttribute('propertyName');
4172
if ($propertyName !== null) {
42-
$node->body = $this->keepVariadicsAndYields($node->body, $propertyName);
43-
return $node;
73+
$body = [];
74+
foreach ($this->traverse($node->body, self::CONTEXT_PROPERTY_HOOK, $propertyName) as $stmt) {
75+
if (!($stmt instanceof Node\Stmt)) {
76+
continue;
77+
}
78+
79+
$body[] = $stmt;
80+
}
81+
$node->body = $body;
82+
83+
return self::DONT_TRAVERSE_CHILDREN;
4484
}
4585
}
4686

4787
return null;
4888
}
4989

5090
/**
51-
* @param Node\Stmt[] $stmts
52-
* @return Node\Stmt[]
91+
* @return int|Node[]
5392
*/
54-
private function keepVariadicsAndYields(array $stmts, ?string $hookedPropertyName): array
93+
private function cleanFunctionOrMethod(Node $node): int|array
5594
{
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-
}
95+
if ($node instanceof Node\Expr\YieldFrom || $node instanceof Node\Expr\Yield_) {
96+
return self::DONT_TRAVERSE_CHILDREN;
97+
}
6398

64-
if ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) {
65-
return true;
66-
}
99+
if ($node instanceof Node\Expr\FuncCall && $node->name instanceof Node\Name
100+
&& in_array($node->name->toLowerString(), ParametersAcceptor::VARIADIC_FUNCTIONS, true)
101+
) {
102+
$node->name = new Node\Name\FullyQualified('func_get_args');
103+
return self::DONT_TRAVERSE_CHILDREN;
104+
}
67105

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-
}
106+
if ($node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) {
107+
return self::REMOVE_NODE;
108+
}
79109

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-
}
110+
return $this->cleanSubnodes($node);
111+
}
112+
113+
/**
114+
* @param Node[] $nodes
115+
* @param self::CONTEXT_* $context
116+
* @return Node[]
117+
*/
118+
private function traverse(
119+
array $nodes,
120+
int $context = self::CONTEXT_DEFAULT,
121+
string|null $propertyName = null,
122+
): array
123+
{
124+
$visitor = new self();
125+
$visitor->context = $context;
126+
$visitor->propertyName = $propertyName;
127+
128+
return (new NodeTraverser($visitor))->traverse($nodes);
129+
}
97130

98-
$newStmts[] = new Node\Stmt\Expression(new Node\Expr\FuncCall(new Node\Name\FullyQualified('func_get_args')));
131+
/**
132+
* @return int|Node[]
133+
*/
134+
private function cleanPropertyHook(Node $node): int|array
135+
{
136+
if (
137+
$node instanceof Node\Expr\PropertyFetch
138+
&& $node->var instanceof Node\Expr\Variable
139+
&& $node->var->name === 'this'
140+
&& $node->name instanceof Node\Identifier
141+
&& $node->name->toString() === $this->propertyName
142+
) {
143+
return self::DONT_TRAVERSE_CHILDREN;
99144
}
100145

101-
return $newStmts;
146+
return $this->cleanSubnodes($node);
147+
}
148+
149+
/**
150+
* @return Node[]
151+
*/
152+
private function cleanSubnodes(Node $node): array
153+
{
154+
$subnodes = [];
155+
foreach ($node->getSubNodeNames() as $subnodeName) {
156+
$subnodes = [...$subnodes, ...array_filter(
157+
is_array($node->$subnodeName) ? $node->$subnodeName : [$node->$subnodeName],
158+
static fn ($subnode) => $subnode instanceof Node,
159+
)];
160+
}
161+
162+
return array_map(static function ($node) {
163+
switch (true) {
164+
case $node instanceof Node\Stmt:
165+
return $node;
166+
case $node instanceof Node\Expr:
167+
return new Node\Stmt\Expression($node);
168+
default:
169+
throw new ShouldNotHappenException();
170+
}
171+
}, $this->traverse($subnodes, $this->context, $this->propertyName));
102172
}
103173

104174
}

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

+7-4
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

+10
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)