Skip to content

Commit 1521c0e

Browse files
authored
allow REMOVE_NODE (#249)
* allow REMOVE_NODE * skip nested closure
1 parent 32fc26e commit 1521c0e

File tree

4 files changed

+128
-6
lines changed

4 files changed

+128
-6
lines changed

src/Rules/Rector/NoIntegerRefactorReturnRule.php

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@
44

55
namespace Symplify\PHPStanRules\Rules\Rector;
66

7+
use PhpParser\Node\Name;
78
use PhpParser\Node;
9+
use PhpParser\Node\Expr\ClassConstFetch;
10+
use PhpParser\Node\Expr\Closure;
811
use PhpParser\Node\Identifier;
912
use PhpParser\Node\Stmt\ClassMethod;
1013
use PhpParser\Node\UnionType;
14+
use PhpParser\NodeTraverser;
15+
use PhpParser\NodeVisitor;
1116
use PHPStan\Analyser\Scope;
1217
use PHPStan\Rules\Rule;
1318
use PHPStan\Rules\RuleErrorBuilder;
1419
use Symplify\PHPStanRules\Enum\RuleIdentifier\RectorRuleIdentifier;
20+
use Symplify\PHPStanRules\NodeTraverser\SimpleCallableNodeTraverser;
1521

1622
/**
1723
* @see \Symplify\PHPStanRules\Tests\Rules\Rector\NoIntegerRefactorReturnRule\NoIntegerRefactorReturnRuleTest
@@ -23,7 +29,7 @@ final class NoIntegerRefactorReturnRule implements Rule
2329
/**
2430
* @var string
2531
*/
26-
public const ERROR_MESSAGE = 'Instead of using int in refactor(), make use of attributes and return always node or a null. Using traverser enums might lead to unexpected results';
32+
public const ERROR_MESSAGE = 'Instead of using DONT_TRAVERSE_CHILDREN* or STOP_TRAVERSAL in refactor() method, make use of attributes. Return always node, null or REMOVE_NODE. Using traverser enums might lead to unexpected results';
2733

2834
public function getNodeType(): string
2935
{
@@ -52,15 +58,61 @@ public function processNode(Node $node, Scope $scope): array
5258
continue;
5359
}
5460

55-
if ($type->name === 'int') {
56-
$ruleError = RuleErrorBuilder::message(self::ERROR_MESSAGE)
57-
->identifier(RectorRuleIdentifier::NO_INTEGER_REFACTOR_RETURN)
58-
->build();
61+
if ($type->name !== 'int') {
62+
continue;
63+
}
64+
65+
$constantNames = $this->findUsedNodeVisitorConstantNames($node);
5966

60-
return [$ruleError];
67+
$undesiredConstantNames = array_diff($constantNames, ['REMOVE_NODE']);
68+
if ($undesiredConstantNames === []) {
69+
return [];
6170
}
71+
72+
$ruleError = RuleErrorBuilder::message(self::ERROR_MESSAGE)
73+
->identifier(RectorRuleIdentifier::NO_INTEGER_REFACTOR_RETURN)
74+
->build();
75+
76+
return [$ruleError];
6277
}
6378

6479
return [];
6580
}
81+
82+
/**
83+
* @return string[]
84+
*/
85+
private function findUsedNodeVisitorConstantNames(ClassMethod $classMethod): array
86+
{
87+
$constantNames = [];
88+
89+
$simpleCallableNodeTraverser = new SimpleCallableNodeTraverser();
90+
$simpleCallableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $subNode) use (&$constantNames): int|null {
91+
// skip closure nodes as they have their own scope
92+
if ($subNode instanceof Closure) {
93+
return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
94+
}
95+
96+
if (! $subNode instanceof ClassConstFetch) {
97+
return null;
98+
}
99+
100+
if (! $subNode->class instanceof Name) {
101+
return null;
102+
}
103+
104+
if (! in_array($subNode->class->toString(), [NodeVisitor::class, NodeTraverser::class])) {
105+
return null;
106+
}
107+
108+
if (! $subNode->name instanceof Identifier) {
109+
return null;
110+
}
111+
112+
$constantNames[] = $subNode->name->toString();
113+
return null;
114+
});
115+
116+
return array_unique($constantNames);
117+
}
66118
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Rector\NoIntegerRefactorReturnRule\Fixture;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\Class_;
9+
use PhpParser\Node\Stmt\Interface_;
10+
use PhpParser\NodeFinder;
11+
use PhpParser\NodeTraverser;
12+
use PhpParser\NodeVisitor;
13+
use Rector\Rector\AbstractRector;
14+
15+
final class AllowNestedClosure extends AbstractRector
16+
{
17+
public function getNodeTypes(): array
18+
{
19+
return [Class_::class, Interface_::class];
20+
}
21+
22+
public function refactor(Node $node): \PhpParser\Node|int
23+
{
24+
if ($node instanceof Class_) {
25+
return $node;
26+
}
27+
28+
$nodeFinder = new NodeFinder();
29+
$nodeFinder->find($node, function (Node $subNode) {
30+
if ($subNode instanceof Node\Stmt\Function_) {
31+
return NodeTraverser::STOP_TRAVERSAL;
32+
}
33+
34+
return true;
35+
});
36+
37+
38+
return NodeVisitor::REMOVE_NODE;
39+
}
40+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Tests\Rules\Rector\NoIntegerRefactorReturnRule\Fixture;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\Class_;
9+
use PhpParser\Node\Stmt\Interface_;
10+
use PhpParser\NodeVisitor;
11+
use Rector\Rector\AbstractRector;
12+
13+
final class AllowRemoveNode extends AbstractRector
14+
{
15+
public function getNodeTypes(): array
16+
{
17+
return [Class_::class, Interface_::class];
18+
}
19+
20+
public function refactor(Node $node): \PhpParser\Node|int
21+
{
22+
if ($node instanceof Class_) {
23+
return $node;
24+
}
25+
26+
return NodeVisitor::REMOVE_NODE;
27+
}
28+
}

tests/Rules/Rector/NoIntegerRefactorReturnRule/NoIntegerRefactorReturnRuleTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public static function provideData(): Iterator
2323
yield [__DIR__ . '/Fixture/SimpleReturnInt.php', [[$errorMessage, 20]]];
2424

2525
yield [__DIR__ . '/Fixture/SkipBareNodeReturn.php', []];
26+
yield [__DIR__ . '/Fixture/AllowRemoveNode.php', []];
27+
yield [__DIR__ . '/Fixture/AllowNestedClosure.php', []];
2628
}
2729

2830
protected function getRule(): NoIntegerRefactorReturnRule

0 commit comments

Comments
 (0)