Skip to content

Commit 669b317

Browse files
authored
[rector] add AvoidFeatureSetAttributeInRectorRule (#252)
* [rector] add AvoidFeatureSetAttributeInRectorRule * restore interface
1 parent d45a53f commit 669b317

File tree

49 files changed

+272
-42
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+272
-42
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"symfony/framework-bundle": "6.1.*",
1717
"phpecs/phpecs": "^2.2",
1818
"tomasvotruba/class-leak": "^2.1",
19-
"rector/rector": "^2.2",
19+
"rector/rector": "^2.2.11",
2020
"phpstan/extension-installer": "^1.4",
2121
"symplify/phpstan-extensions": "^12.0",
2222
"tomasvotruba/unused-public": "^2.1",

config/rector-rules.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ rules:
88
- Symplify\PHPStanRules\Rules\Rector\PreferDirectIsNameRule
99
- Symplify\PHPStanRules\Rules\Rector\NoOnlyNullReturnInRefactorRule
1010
- Symplify\PHPStanRules\Rules\Rector\NoIntegerRefactorReturnRule
11+
- Symplify\PHPStanRules\Rules\Rector\AvoidFeatureSetAttributeInRectorRule
1112

1213
services:
1314
# $node->getAttribute($1) => Type|null by $1

src/Enum/RuleIdentifier/RectorRuleIdentifier.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ final class RectorRuleIdentifier
2121
public const NO_ONLY_NULL_RETURN_IN_REFACTOR = 'rector.noOnlyNullReturnInRefactor';
2222

2323
public const NO_INTEGER_REFACTOR_RETURN = 'rector.noIntegerRefactorReturn';
24+
25+
public const AVOID_FEATURE_SET_ATTRIBUTE_IN_RECTOR = 'rector.avoidFeatureSetAttributeInRector';
2426
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symplify\PHPStanRules\Rules\Rector;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PhpParser\NodeFinder;
10+
use PHPStan\Analyser\Scope;
11+
use PHPStan\Node\InClassNode;
12+
use PHPStan\Reflection\ClassReflection;
13+
use PHPStan\Rules\Rule;
14+
use PHPStan\Rules\RuleErrorBuilder;
15+
use PHPStan\Type\Constant\ConstantStringType;
16+
use Rector\Rector\AbstractRector;
17+
use Symplify\PHPStanRules\Enum\RuleIdentifier\RectorRuleIdentifier;
18+
use Symplify\PHPStanRules\Helper\NamingHelper;
19+
20+
/**
21+
* @see \Symplify\PHPStanRules\Tests\Rules\Rector\AvoidFeatureSetAttributeInRectorRule\AvoidFeatureSetAttributeInRectorRuleTest
22+
*
23+
* @implements Rule<InClassNode>
24+
*/
25+
final class AvoidFeatureSetAttributeInRectorRule implements Rule
26+
{
27+
/**
28+
* @var string
29+
*/
30+
public const ERROR_MESSAGE = 'Instead of using Rector rule to setAttribute("%s") to be used later, create a service extending "DecoratingNodeVisitorInterface". This ensures attribute decoration and node changes are in 2 separated steps.';
31+
32+
/**
33+
* @var string[]
34+
*/
35+
private const ALLOWED_ATTRIBUTES = ['kind', 'origNode', 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos'];
36+
37+
public function getNodeType(): string
38+
{
39+
return InClassNode::class;
40+
}
41+
42+
/**
43+
* @param InClassNode $node
44+
*/
45+
public function processNode(Node $node, Scope $scope): array
46+
{
47+
$classReflection = $scope->getClassReflection();
48+
if (! $classReflection instanceof ClassReflection) {
49+
return [];
50+
}
51+
52+
if (! $classReflection->is(AbstractRector::class)) {
53+
return [];
54+
}
55+
56+
$classLike = $node->getOriginalNode();
57+
58+
$nodeFinder = new NodeFinder();
59+
60+
/** @var MethodCall[] $methodCalls */
61+
$methodCalls = $nodeFinder->findInstanceOf($classLike, MethodCall::class);
62+
63+
$ruleErrors = [];
64+
65+
foreach ($methodCalls as $methodCall) {
66+
if (! NamingHelper::isName($methodCall->name, 'setAttribute')) {
67+
continue;
68+
}
69+
70+
$attributeName = $this->resolveAttributeKeyValue($methodCall, $scope);
71+
if (! is_string($attributeName)) {
72+
continue;
73+
}
74+
75+
if (in_array($attributeName, self::ALLOWED_ATTRIBUTES, true)) {
76+
continue;
77+
}
78+
79+
$ruleError = RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $attributeName))
80+
->identifier(RectorRuleIdentifier::AVOID_FEATURE_SET_ATTRIBUTE_IN_RECTOR)
81+
->build();
82+
83+
$ruleErrors[] = $ruleError;
84+
}
85+
86+
return $ruleErrors;
87+
}
88+
89+
private function resolveAttributeKeyValue(MethodCall $methodCall, Scope $scope): ?string
90+
{
91+
$firstArg = $methodCall->getArgs()[0];
92+
$attributeNameType = $scope->getType($firstArg->value);
93+
94+
if (! $attributeNameType instanceof ConstantStringType) {
95+
return null;
96+
}
97+
98+
return $attributeNameType->getValue();
99+
}
100+
}

tests/Rules/Doctrine/NoDoctrineListenerWithoutContractRule/NoDoctrineListenerWithoutContractRuleTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\NoDoctrineListenerWithoutContractRule;
66

77
use Iterator;
8+
use PHPStan\Rules\Rule;
89
use PHPStan\Testing\RuleTestCase;
910
use PHPUnit\Framework\Attributes\DataProvider;
1011
use Symplify\PHPStanRules\Rules\Doctrine\NoDoctrineListenerWithoutContractRule;
@@ -31,7 +32,7 @@ public static function provideData(): Iterator
3132
]]];
3233
}
3334

34-
protected function getRule(): NoDoctrineListenerWithoutContractRule
35+
protected function getRule(): Rule
3536
{
3637
return new NoDoctrineListenerWithoutContractRule();
3738
}

tests/Rules/Doctrine/NoGetRepositoryOnServiceRepositoryEntityRule/NoGetRepositoryOnServiceRepositoryEntityRuleTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Iterator;
88
use PHPStan\Reflection\ReflectionProvider;
9+
use PHPStan\Rules\Rule;
910
use PHPStan\Testing\RuleTestCase;
1011
use PHPUnit\Framework\Attributes\DataProvider;
1112
use Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOnServiceRepositoryEntityRule;
@@ -39,7 +40,7 @@ public static function provideData(): Iterator
3940
yield [__DIR__ . '/Fixture/SkipGetRepositoryOnNormalRepository.php', []];
4041
}
4142

42-
protected function getRule(): NoGetRepositoryOnServiceRepositoryEntityRule
43+
protected function getRule(): Rule
4344
{
4445
$reflectionProvider = self::getContainer()->getByType(ReflectionProvider::class);
4546

tests/Rules/Doctrine/NoGetRepositoryOutsideServiceRule/NoGetRepositoryOutsideServiceRuleTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\NoGetRepositoryOutsideServiceRule;
66

77
use Iterator;
8+
use PHPStan\Rules\Rule;
89
use PHPStan\Testing\RuleTestCase;
910
use PHPUnit\Framework\Attributes\DataProvider;
1011
use Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOutsideServiceRule;
@@ -37,7 +38,7 @@ public static function provideData(): Iterator
3738
yield [__DIR__ . '/Fixture/SkipDynamicClassConstFetch.php', []];
3839
}
3940

40-
protected function getRule(): NoGetRepositoryOutsideServiceRule
41+
protected function getRule(): Rule
4142
{
4243
return new NoGetRepositoryOutsideServiceRule();
4344
}

tests/Rules/Doctrine/NoParentRepositoryRule/NoParentRepositoryRuleTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\NoParentRepositoryRule;
66

77
use Iterator;
8+
use PHPStan\Rules\Rule;
89
use PHPStan\Testing\RuleTestCase;
910
use PHPUnit\Framework\Attributes\DataProvider;
1011
use Symplify\PHPStanRules\Rules\Doctrine\NoParentRepositoryRule;
@@ -22,7 +23,7 @@ public static function provideData(): Iterator
2223
yield [__DIR__ . '/Fixture/SomeRepository.php', [[NoParentRepositoryRule::ERROR_MESSAGE, 9]]];
2324
}
2425

25-
protected function getRule(): NoParentRepositoryRule
26+
protected function getRule(): Rule
2627
{
2728
return new NoParentRepositoryRule();
2829
}

tests/Rules/Doctrine/NoRepositoryCallInDataFixtureRule/NoRepositoryCallInDataFixtureRuleTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\NoRepositoryCallInDataFixtureRule;
66

77
use Iterator;
8+
use PHPStan\Rules\Rule;
89
use PHPStan\Testing\RuleTestCase;
910
use PHPUnit\Framework\Attributes\DataProvider;
1011
use Symplify\PHPStanRules\Rules\Doctrine\NoRepositoryCallInDataFixtureRule;
@@ -30,7 +31,7 @@ public static function provideData(): Iterator
3031
yield [__DIR__ . '/Fixture/SkipNonFixtureClass.php', []];
3132
}
3233

33-
protected function getRule(): NoRepositoryCallInDataFixtureRule
34+
protected function getRule(): Rule
3435
{
3536
return new NoRepositoryCallInDataFixtureRule();
3637
}

tests/Rules/Doctrine/RequireQueryBuilderOnRepositoryRule/RequireQueryBuilderOnRepositoryRuleTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule;
66

77
use Iterator;
8+
use PHPStan\Rules\Rule;
89
use PHPStan\Testing\RuleTestCase;
910
use PHPUnit\Framework\Attributes\DataProvider;
1011
use Symplify\PHPStanRules\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule;
@@ -32,7 +33,7 @@ public static function provideData(): Iterator
3233
]];
3334
}
3435

35-
protected function getRule(): RequireQueryBuilderOnRepositoryRule
36+
protected function getRule(): Rule
3637
{
3738
return new RequireQueryBuilderOnRepositoryRule();
3839
}

0 commit comments

Comments
 (0)