Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit d446d6c

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

File tree

3 files changed

+148
-65
lines changed

3 files changed

+148
-65
lines changed

‎src/Parser/CleaningVisitor.php‎

Lines changed: 131 additions & 61 deletions
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 constCONTEXT_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 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‎

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)

AltStyle によって変換されたページ (->オリジナル) /