forked from phpstan/phpstan-src
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathVariadicMethodsVisitor.php
125 lines (98 loc) · 3.01 KB
/
VariadicMethodsVisitor.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<?php declare(strict_types = 1);
namespace PHPStan\Parser;
use PhpParser\Node;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\NodeVisitorAbstract;
use PHPStan\Reflection\ParametersAcceptor;
use function array_key_exists;
use function array_pop;
use function count;
use function in_array;
use function sprintf;
final class VariadicMethodsVisitor extends NodeVisitorAbstract
{
public const ATTRIBUTE_NAME = 'variadicMethods';
public const ANONYMOUS_CLASS_PREFIX = 'class@anonymous';
private ?Node $topNode = null;
private ?string $inNamespace = null;
/** @var array<string> */
private array $classStack = [];
private ?string $inMethod = null;
/** @var array<string, array<string, true>> */
public static array $cache = [];
/** @var array<string, array<string, true>> */
private array $variadicMethods = [];
public function beforeTraverse(array $nodes): ?array
{
$this->topNode = null;
$this->variadicMethods = [];
$this->inNamespace = null;
$this->classStack = [];
$this->inMethod = null;
return null;
}
public function enterNode(Node $node): ?Node
{
if ($this->topNode === null) {
$this->topNode = $node;
}
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
$this->inNamespace = $node->name->toString();
}
if ($node instanceof Node\Stmt\ClassLike) {
if (!$node->name instanceof Node\Identifier) {
$className = sprintf('%s:%s:%s', self::ANONYMOUS_CLASS_PREFIX, $node->getStartLine(), $node->getEndLine());
$this->classStack[] = $className;
} else {
$className = $node->name->name;
$this->classStack[] = $this->inNamespace !== null ? $this->inNamespace . '\\' . $className : $className;
}
}
if ($node instanceof ClassMethod) {
$this->inMethod = $node->name->name;
}
if (
$this->inMethod !== null
&& $node instanceof Node\Expr\FuncCall
&& $node->name instanceof Name
&& in_array((string) $node->name, ParametersAcceptor::VARIADIC_FUNCTIONS, true)
) {
$lastClass = $this->classStack[count($this->classStack) - 1] ?? null;
if ($lastClass !== null) {
if (
!array_key_exists($lastClass, $this->variadicMethods)
|| !array_key_exists($this->inMethod, $this->variadicMethods[$lastClass])
) {
$this->variadicMethods[$lastClass][$this->inMethod] = true;
}
}
}
return null;
}
public function leaveNode(Node $node): ?Node
{
if ($node instanceof ClassMethod) {
$this->inMethod = null;
}
if ($node instanceof Node\Stmt\ClassLike) {
array_pop($this->classStack);
}
if ($node instanceof Node\Stmt\Namespace_ && $node->name !== null) {
$this->inNamespace = null;
}
return null;
}
public function afterTraverse(array $nodes): ?array
{
if ($this->topNode !== null && $this->variadicMethods !== []) {
foreach ($this->variadicMethods as $class => $methods) {
foreach ($methods as $name => $variadic) {
self::$cache[$class][$name] = $variadic;
}
}
$this->topNode->setAttribute(self::ATTRIBUTE_NAME, $this->variadicMethods);
}
return null;
}
}