4
4
5
5
use Override ;
6
6
use PhpParser \Node ;
7
+ use PhpParser \Node \Expr ;
7
8
use PhpParser \Node \Identifier ;
9
+ use PhpParser \Node \Name ;
8
10
use PhpParser \NodeVisitorAbstract ;
9
11
use PHPStan \DependencyInjection \AutowiredService ;
12
+ use function array_key_exists ;
13
+ use function array_shift ;
14
+ use function array_unshift ;
10
15
use function count ;
11
16
12
17
#[AutowiredService]
@@ -15,6 +20,11 @@ final class ClosureBindArgVisitor extends NodeVisitorAbstract
15
20
16
21
public const ATTRIBUTE_NAME = 'closureBindArg ' ;
17
22
23
+ public const SCOPE_ATTRIBUTE_NAME = 'closureBindScope ' ;
24
+
25
+ /** @var list<?Expr> */
26
+ private array $ scopeStack = [];
27
+
18
28
#[Override]
19
29
public function enterNode (Node $ node ): ?Node
20
30
{
@@ -30,7 +40,35 @@ public function enterNode(Node $node): ?Node
30
40
if (count ($ args ) > 1 ) {
31
41
$ args [0 ]->setAttribute (self ::ATTRIBUTE_NAME , true );
32
42
}
43
+
44
+ // null means default scope "static"
45
+ array_unshift ($ this ->scopeStack , $ args [2 ]->value ?? null );
46
+ }
47
+
48
+ if ($ node instanceof Name
49
+ && array_key_exists (0 , $ this ->scopeStack )
50
+ && $ node ->isSpecialClassName ()
51
+ ) {
52
+ $ node ->setAttribute (self ::SCOPE_ATTRIBUTE_NAME , $ this ->scopeStack [0 ]);
53
+ }
54
+
55
+ return null ;
56
+ }
57
+
58
+ #[Override]
59
+ public function leaveNode (Node $ node ): ?Node
60
+ {
61
+ if (
62
+ $ node instanceof Node \Expr \StaticCall
63
+ && $ node ->class instanceof Node \Name
64
+ && $ node ->class ->toLowerString () === 'closure '
65
+ && $ node ->name instanceof Identifier
66
+ && $ node ->name ->toLowerString () === 'bind '
67
+ && !$ node ->isFirstClassCallable ()
68
+ ) {
69
+ array_shift ($ this ->scopeStack );
33
70
}
71
+
34
72
return null ;
35
73
}
36
74
0 commit comments