|
7 | 7 | use PhpParser\Node; |
8 | 8 | use PhpParser\Node\Arg; |
9 | 9 | use PhpParser\Node\Expr\ArrowFunction; |
| 10 | +use PhpParser\Node\Expr\CallLike; |
10 | 11 | use PhpParser\Node\Expr\Closure; |
| 12 | +use PhpParser\Node\Expr\FuncCall; |
11 | 13 | use PhpParser\Node\Expr\MethodCall; |
12 | 14 | use PhpParser\Node\Expr\StaticCall; |
13 | 15 | use PhpParser\Node\Identifier; |
14 | 16 | use PhpParser\Node\Param; |
15 | 17 | use PhpParser\Node\Stmt\Return_; |
16 | 18 | use PhpParser\Node\VariadicPlaceholder; |
| 19 | +use PhpParser\NodeVisitor; |
| 20 | +use PHPStan\Analyser\Scope; |
| 21 | +use Rector\PHPStan\ScopeFetcher; |
17 | 22 | use Rector\Rector\AbstractRector; |
18 | 23 | use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; |
19 | 24 | use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; |
20 | | -use Webmozart\Assert\Assert; |
21 | 25 |
|
22 | 26 | /** |
23 | 27 | * @see \Rector\Tests\CodingStyle\Rector\FunctionLike\FunctionLikeToFirstClassCallableRector\FunctionLikeToFirstClassCallableRectorTest |
@@ -49,155 +53,176 @@ public function getNodeTypes(): array |
49 | 53 | /** |
50 | 54 | * @param ArrowFunction|Closure $node |
51 | 55 | */ |
52 | | - public function refactor(Node $node): null|StaticCall|MethodCall |
| 56 | + public function refactor(Node $node): null|CallLike |
53 | 57 | { |
54 | | - $extractedMethodCall = $this->extractMethodCallFromFuncLike($node); |
| 58 | + $callLike = $this->extractCallLike($node); |
55 | 59 |
|
56 | | - if (! $extractedMethodCall instanceof MethodCall && ! $extractedMethodCall instanceof StaticCall) { |
| 60 | + if ($callLike === null) { |
57 | 61 | return null; |
58 | 62 | } |
59 | 63 |
|
60 | | - if ($extractedMethodCall instanceof MethodCall) { |
61 | | - return new MethodCall($extractedMethodCall->var, $extractedMethodCall->name, [new VariadicPlaceholder()]); |
| 64 | + if ($this->shouldSkip($node, $callLike, ScopeFetcher::fetch($node))) { |
| 65 | + return null; |
62 | 66 | } |
63 | 67 |
|
64 | | - return new StaticCall($extractedMethodCall->class, $extractedMethodCall->name, [new VariadicPlaceholder()]); |
65 | | - } |
| 68 | + $callLike->args = [new VariadicPlaceholder()]; |
66 | 69 |
|
67 | | - private function extractMethodCallFromFuncLike(Closure|ArrowFunction $node): MethodCall|StaticCall|null |
68 | | - { |
69 | | - if ($node instanceof ArrowFunction) { |
70 | | - if ( |
71 | | - ($node->expr instanceof MethodCall || $node->expr instanceof StaticCall) && |
72 | | - ! $node->expr->isFirstClassCallable() && |
73 | | - $this->notUsingNamedArgs($node->expr->getArgs()) && |
74 | | - $this->notUsingByRef($node->getParams()) && |
75 | | - $this->sameParamsForArgs($node->getParams(), $node->expr->getArgs()) && |
76 | | - $this->isNonDependantMethod($node->expr, $node->getParams()) |
77 | | - ) { |
78 | | - return $node->expr; |
79 | | - } |
| 70 | + return $callLike; |
| 71 | + } |
80 | 72 |
|
81 | | - return null; |
82 | | - } |
| 73 | + private function shouldSkip( |
| 74 | + ArrowFunction|Closure $node, |
| 75 | + FuncCall|MethodCall|StaticCall $callLike, |
| 76 | + Scope $scope |
| 77 | + ): bool { |
| 78 | + $params = $node->getParams(); |
| 79 | + $args = $callLike->getArgs(); |
83 | 80 |
|
84 | | - if (count($node->stmts) != 1 || ! $node->getStmts()[0] instanceof Return_) { |
85 | | - return null; |
| 81 | + if ( |
| 82 | + $callLike->isFirstClassCallable() |
| 83 | + || $this->isChainedCall($callLike) |
| 84 | + || $this->isUsingNamedArgs($args) |
| 85 | + || $this->isUsingByRef($params) |
| 86 | + || $this->isNotUsingSameParamsForArgs($params, $args) |
| 87 | + || $this->isDependantMethod($callLike, $params) |
| 88 | + || $this->isUsingThisInNonObjectContext($callLike, $scope) |
| 89 | + ) { |
| 90 | + return true; |
86 | 91 | } |
87 | 92 |
|
88 | | - $callLike = $node->getStmts()[0] |
89 | | - ->expr; |
| 93 | + return false; |
| 94 | + } |
90 | 95 |
|
91 | | - if (! $callLike instanceof MethodCall && ! $callLike instanceof StaticCall) { |
92 | | - return null; |
| 96 | + private function extractCallLike(Closure|ArrowFunction $node): FuncCall|MethodCall|StaticCall|null |
| 97 | + { |
| 98 | + if ($node instanceof Closure) { |
| 99 | + if (count($node->stmts) !== 1 || ! $node->stmts[0] instanceof Return_) { |
| 100 | + return null; |
| 101 | + } |
| 102 | + $callLike = $node->stmts[0]->expr; |
| 103 | + } else { |
| 104 | + $callLike = $node->expr; |
93 | 105 | } |
94 | 106 |
|
95 | 107 | if ( |
96 | | - ! $callLike->isFirstClassCallable() && |
97 | | - $this->notUsingNamedArgs($callLike->getArgs()) && |
98 | | - $this->notUsingByRef($node->getParams()) && |
99 | | - $this->sameParamsForArgs($node->getParams(), $callLike->getArgs()) && |
100 | | - $this->isNonDependantMethod($callLike, $node->getParams())) { |
101 | | - return $callLike; |
| 108 | + ! $callLike instanceof FuncCall |
| 109 | + && ! $callLike instanceof MethodCall |
| 110 | + && ! $callLike instanceof StaticCall |
| 111 | + ) { |
| 112 | + return null; |
102 | 113 | } |
103 | 114 |
|
104 | | - return null; |
| 115 | + return $callLike; |
105 | 116 | } |
106 | 117 |
|
107 | 118 | /** |
108 | | - * @param Node\Param[] $params |
109 | | - * @param Node\Arg[] $args |
| 119 | + * @param Param[] $params |
| 120 | + * @param Arg[] $args |
110 | 121 | */ |
111 | | - private function sameParamsForArgs(array $params, array $args): bool |
| 122 | + private function isNotUsingSameParamsForArgs(array $params, array $args): bool |
112 | 123 | { |
113 | | - Assert::allIsInstanceOf($args, Arg::class); |
114 | | - Assert::allIsInstanceOf($params, Param::class); |
115 | | - |
116 | 124 | if (count($args) > count($params)) { |
117 | | - return false; |
| 125 | + return true; |
118 | 126 | } |
119 | 127 |
|
120 | 128 | if (count($args) === 1 && $args[0]->unpack) { |
121 | | - return $params[0]->variadic; |
| 129 | + return ! $params[0]->variadic; |
122 | 130 | } |
123 | 131 |
|
124 | 132 | foreach ($args as $key => $arg) { |
125 | 133 | if (! $this->nodeComparator->areNodesEqual($arg->value, $params[$key]->var)) { |
126 | | - return false; |
| 134 | + return true; |
127 | 135 | } |
128 | 136 | } |
129 | 137 |
|
130 | | - return true; |
| 138 | + return false; |
131 | 139 | } |
132 | 140 |
|
133 | 141 | /** |
134 | | - * Makes sure the parameter isn't used to make the call e.g. in the var or class |
135 | | - * |
136 | 142 | * @param Param[] $params |
137 | 143 | */ |
138 | | - private function isNonDependantMethod(StaticCall|MethodCall $expr, array $params): bool |
| 144 | + private function isDependantMethod(StaticCall|MethodCall|FuncCall $expr, array $params): bool |
139 | 145 | { |
140 | | - Assert::allIsInstanceOf($params, Param::class); |
| 146 | + if ($expr instanceof FuncCall) { |
| 147 | + return false; |
| 148 | + } |
141 | 149 |
|
142 | 150 | $found = false; |
| 151 | + $parentNode = $expr instanceof MethodCall ? $expr->var : $expr->class; |
143 | 152 |
|
144 | 153 | foreach ($params as $param) { |
145 | | - if ($expr instanceof MethodCall) { |
146 | | - $this->traverseNodesWithCallable($expr->var, function (Node $node) use ($param, &$found): null { |
147 | | - if ($this->nodeComparator->areNodesEqual($node, $param->var)) { |
148 | | - $found = true; |
149 | | - } |
150 | | - |
151 | | - return null; |
152 | | - }); |
| 154 | + $this->traverseNodesWithCallable($parentNode, function (Node $node) use ($param, &$found) { |
| 155 | + if ($this->nodeComparator->areNodesEqual($node, $param->var)) { |
| 156 | + $found = true; |
| 157 | + return NodeVisitor::STOP_TRAVERSAL; |
| 158 | + } |
| 159 | + }); |
| 160 | + |
| 161 | + if ($found) { |
| 162 | + return true; |
153 | 163 | } |
| 164 | + } |
154 | 165 |
|
155 | | - if ($expr instanceof StaticCall) { |
156 | | - $this->traverseNodesWithCallable($expr->class, function (Node $node) use ($param, &$found): null { |
157 | | - if ($this->nodeComparator->areNodesEqual($node, $param->var)) { |
158 | | - $found = true; |
159 | | - } |
| 166 | + return false; |
| 167 | + } |
160 | 168 |
|
161 | | - return null; |
162 | | - }); |
163 | | - } |
| 169 | + private function isUsingThisInNonObjectContext(FuncCall|MethodCall|StaticCall $callLike, Scope $scope): bool |
| 170 | + { |
| 171 | + if (! $callLike instanceof MethodCall) { |
| 172 | + return false; |
| 173 | + } |
164 | 174 |
|
165 | | - if ($found) { |
166 | | - return false; |
167 | | - } |
| 175 | + if (in_array('this', $scope->getDefinedVariables(), true)) { |
| 176 | + return false; |
168 | 177 | } |
169 | 178 |
|
170 | | - return true; |
| 179 | + $found = false; |
| 180 | + |
| 181 | + $this->traverseNodesWithCallable($callLike, function (Node $node) use (&$found) { |
| 182 | + if ($this->isName($node, 'this')) { |
| 183 | + $found = true; |
| 184 | + return NodeVisitor::STOP_TRAVERSAL; |
| 185 | + } |
| 186 | + }); |
| 187 | + |
| 188 | + return $found; |
171 | 189 | } |
172 | 190 |
|
173 | 191 | /** |
174 | 192 | * @param Param[] $params |
175 | 193 | */ |
176 | | - private function notUsingByRef(array $params): bool |
| 194 | + private function isUsingByRef(array $params): bool |
177 | 195 | { |
178 | | - Assert::allIsInstanceOf($params, Param::class); |
179 | | - |
180 | 196 | foreach ($params as $param) { |
181 | 197 | if ($param->byRef) { |
182 | | - return false; |
| 198 | + return true; |
183 | 199 | } |
184 | 200 | } |
185 | | - |
186 | | - return true; |
| 201 | + return false; |
187 | 202 | } |
188 | 203 |
|
189 | 204 | /** |
190 | 205 | * @param Arg[] $args |
191 | 206 | */ |
192 | | - private function notUsingNamedArgs(array $args): bool |
| 207 | + private function isUsingNamedArgs(array $args): bool |
193 | 208 | { |
194 | | - Assert::allIsInstanceOf($args, Arg::class); |
195 | | - |
196 | 209 | foreach ($args as $arg) { |
197 | 210 | if ($arg->name instanceof Identifier) { |
198 | | - return false; |
| 211 | + return true; |
199 | 212 | } |
200 | 213 | } |
| 214 | + return false; |
| 215 | + } |
| 216 | + |
| 217 | + private function isChainedCall(FuncCall|MethodCall|StaticCall $callLike): bool |
| 218 | + { |
| 219 | + if (! $callLike instanceof MethodCall) { |
| 220 | + return false; |
| 221 | + } |
| 222 | + |
| 223 | + if (! $callLike->var instanceof CallLike) { |
| 224 | + return false; |
| 225 | + } |
201 | 226 |
|
202 | 227 | return true; |
203 | 228 | } |
|
0 commit comments