Skip to content

Commit 3f60b07

Browse files
authored
[cleanup] Make FirstClassCallableRector use attributes over node traverser hacks (#7696)
* [cleanup] Make FirstClassCallableRector use attributes over node traverser hacks * handle nested arrays as well
1 parent 3ec5fda commit 3f60b07

File tree

7 files changed

+104
-10
lines changed

7 files changed

+104
-10
lines changed

rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/skip_on_class_const.php.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ final class SkipOnClassConst
66
{
77
private const CLASS_CONST = [SkipOnClassConst::class, 'size'];
88

9+
private const CLASS_CONST_NESTED = [
10+
'hey' => [SkipOnClassConst::class, 'size']
11+
];
12+
913
public function size()
1014
{
1115
}

rules-tests/Php81/Rector/Array_/FirstClassCallableRector/Fixture/skip_on_property.php.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ final class SkipOnProperty
66
{
77
private $prop = [SkipOnProperty::class, 'size'];
88

9+
private $nestedProperty = [
10+
'some_callback' => [SkipOnProperty::class, 'size']
11+
];
12+
913
public function size()
1014
{
1115
}

rules/Php81/Rector/Array_/FirstClassCallableRector.php

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@
1212
use PhpParser\Node\Expr\PropertyFetch;
1313
use PhpParser\Node\Expr\StaticCall;
1414
use PhpParser\Node\Expr\Variable;
15-
use PhpParser\Node\Stmt\ClassConst;
16-
use PhpParser\Node\Stmt\Property;
1715
use PhpParser\Node\VariadicPlaceholder;
1816
use PhpParser\NodeVisitor;
1917
use PHPStan\Analyser\Scope;
2018
use PHPStan\Reflection\ClassReflection;
2119
use PHPStan\Reflection\ReflectionProvider;
2220
use Rector\NodeCollector\NodeAnalyzer\ArrayCallableMethodMatcher;
2321
use Rector\NodeCollector\ValueObject\ArrayCallable;
22+
use Rector\NodeTypeResolver\Node\AttributeKey;
2423
use Rector\PHPStan\ScopeFetcher;
2524
use Rector\Rector\AbstractRector;
2625
use Rector\Reflection\ReflectionResolver;
@@ -32,6 +31,7 @@
3231
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
3332

3433
/**
34+
* @see RFC https://wiki.php.net/rfc/first_class_callable_syntax
3535
* @see \Rector\Tests\Php81\Rector\Array_\FirstClassCallableRector\FirstClassCallableRectorTest
3636
*/
3737
final class FirstClassCallableRector extends AbstractRector implements MinPhpVersionInterface
@@ -46,7 +46,6 @@ public function __construct(
4646

4747
public function getRuleDefinition(): RuleDefinition
4848
{
49-
// see RFC https://wiki.php.net/rfc/first_class_callable_syntax
5049
return new RuleDefinition('Upgrade array callable to first class callable', [
5150
new CodeSample(
5251
<<<'CODE_SAMPLE'
@@ -86,14 +85,14 @@ public function name()
8685
*/
8786
public function getNodeTypes(): array
8887
{
89-
return [Property::class, ClassConst::class, Array_::class, Closure::class];
88+
return [Array_::class, Closure::class];
9089
}
9190

9291
/**
93-
* @param Property|ClassConst|Array_|Closure $node
92+
* @param Array_|Closure $node
9493
* @return StaticCall|MethodCall|null|NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN
9594
*/
96-
public function refactor(Node $node): int|null|StaticCall|MethodCall
95+
public function refactor(Node $node): StaticCall|MethodCall|null|int
9796
{
9897
if ($node instanceof Closure) {
9998
if ($this->symfonyPhpClosureDetector->detect($node)) {
@@ -103,10 +102,6 @@ public function refactor(Node $node): int|null|StaticCall|MethodCall
103102
return null;
104103
}
105104

106-
if ($node instanceof Property || $node instanceof ClassConst) {
107-
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
108-
}
109-
110105
$scope = ScopeFetcher::fetch($node);
111106

112107
$arrayCallable = $this->arrayCallableMethodMatcher->match($node, $scope);
@@ -119,6 +114,14 @@ public function refactor(Node $node): int|null|StaticCall|MethodCall
119114
return null;
120115
}
121116

117+
if ($node->getAttribute(AttributeKey::IS_CLASS_CONST_VALUE)) {
118+
return null;
119+
}
120+
121+
if ($node->getAttribute(AttributeKey::IS_DEFAULT_PROPERTY_VALUE)) {
122+
return null;
123+
}
124+
122125
$args = [new VariadicPlaceholder()];
123126
if ($callerExpr instanceof ClassConstFetch) {
124127
$type = $this->getType($callerExpr->class);

src/DependencyInjection/LazyContainerFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\ContextNodeVisitor;
103103
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\GlobalVariableNodeVisitor;
104104
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\NameNodeVisitor;
105+
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\PropertyOrClassConstDefaultNodeVisitor;
105106
use Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor\StaticVariableNodeVisitor;
106107
use Rector\NodeTypeResolver\PHPStan\Scope\PHPStanNodeScopeResolver;
107108
use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider;
@@ -243,6 +244,7 @@ final class LazyContainerFactory
243244
GlobalVariableNodeVisitor::class,
244245
NameNodeVisitor::class,
245246
StaticVariableNodeVisitor::class,
247+
PropertyOrClassConstDefaultNodeVisitor::class,
246248
];
247249

248250
/**

src/NodeTypeResolver/Node/AttributeKey.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,8 @@ final class AttributeKey
286286
* @var string
287287
*/
288288
public const HAS_MERGED_COMMENTS = 'has_merged_comments';
289+
290+
public const IS_DEFAULT_PROPERTY_VALUE = 'is_default_property_value';
291+
292+
public const IS_CLASS_CONST_VALUE = 'is_default_class_const_value';
289293
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\NodeTypeResolver\PHPStan\Scope\NodeVisitor;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr;
9+
use PhpParser\Node\Stmt\ClassConst;
10+
use PhpParser\Node\Stmt\Property;
11+
use PhpParser\NodeVisitorAbstract;
12+
use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface;
13+
use Rector\NodeTypeResolver\Node\AttributeKey;
14+
use Rector\PhpParser\NodeTraverser\SimpleTraverser;
15+
16+
final class PropertyOrClassConstDefaultNodeVisitor extends NodeVisitorAbstract implements DecoratingNodeVisitorInterface
17+
{
18+
public function enterNode(Node $node): ?Node
19+
{
20+
if ($node instanceof Property) {
21+
foreach ($node->props as $propertyItem) {
22+
$default = $propertyItem->default;
23+
if (! $default instanceof Expr) {
24+
continue;
25+
}
26+
27+
SimpleTraverser::decorateWithTrueAttribute($default, AttributeKey::IS_DEFAULT_PROPERTY_VALUE);
28+
}
29+
}
30+
31+
if ($node instanceof ClassConst) {
32+
foreach ($node->consts as $const) {
33+
SimpleTraverser::decorateWithTrueAttribute($const->value, AttributeKey::IS_CLASS_CONST_VALUE);
34+
}
35+
}
36+
37+
return null;
38+
}
39+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\PhpParser\NodeTraverser;
6+
7+
use PhpParser\Node;
8+
use PhpParser\NodeTraverser;
9+
use PhpParser\NodeVisitorAbstract;
10+
use Rector\NodeTypeResolver\Node\AttributeKey;
11+
12+
final class SimpleTraverser
13+
{
14+
/**
15+
* @param Node[]|Node $nodesOrNode
16+
* @param AttributeKey::* $attributeKey
17+
*/
18+
public static function decorateWithTrueAttribute(array|Node $nodesOrNode, string $attributeKey): void
19+
{
20+
$callableNodeVisitor = new class($attributeKey) extends NodeVisitorAbstract {
21+
public function __construct(
22+
private readonly string $attributeKey
23+
) {
24+
}
25+
26+
public function enterNode(Node $node)
27+
{
28+
$node->setAttribute($this->attributeKey, true);
29+
return null;
30+
}
31+
};
32+
33+
$nodeTraverser = new NodeTraverser($callableNodeVisitor);
34+
35+
$nodes = $nodesOrNode instanceof Node ? [$nodesOrNode] : $nodesOrNode;
36+
$nodeTraverser->traverse($nodes);
37+
}
38+
}

0 commit comments

Comments
 (0)