Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"symfony/framework-bundle": "6.1.*",
"phpecs/phpecs": "^2.2",
"tomasvotruba/class-leak": "^2.1",
"rector/rector": "^2.2",
"rector/rector": "^2.2.11",
"phpstan/extension-installer": "^1.4",
"symplify/phpstan-extensions": "^12.0",
"tomasvotruba/unused-public": "^2.1",
Expand Down
1 change: 1 addition & 0 deletions config/rector-rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ rules:
- Symplify\PHPStanRules\Rules\Rector\PreferDirectIsNameRule
- Symplify\PHPStanRules\Rules\Rector\NoOnlyNullReturnInRefactorRule
- Symplify\PHPStanRules\Rules\Rector\NoIntegerRefactorReturnRule
- Symplify\PHPStanRules\Rules\Rector\AvoidFeatureSetAttributeInRectorRule

services:
# $node->getAttribute($1) => Type|null by $1
Expand Down
2 changes: 2 additions & 0 deletions src/Enum/RuleIdentifier/RectorRuleIdentifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ final class RectorRuleIdentifier
public const NO_ONLY_NULL_RETURN_IN_REFACTOR = 'rector.noOnlyNullReturnInRefactor';

public const NO_INTEGER_REFACTOR_RETURN = 'rector.noIntegerRefactorReturn';

public const AVOID_FEATURE_SET_ATTRIBUTE_IN_RECTOR = 'rector.avoidFeatureSetAttributeInRector';
}
100 changes: 100 additions & 0 deletions src/Rules/Rector/AvoidFeatureSetAttributeInRectorRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Rules\Rector;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\NodeFinder;
use PHPStan\Analyser\Scope;
use PHPStan\Node\InClassNode;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\Constant\ConstantStringType;
use Rector\Rector\AbstractRector;
use Symplify\PHPStanRules\Enum\RuleIdentifier\RectorRuleIdentifier;
use Symplify\PHPStanRules\Helper\NamingHelper;

/**
* @see \Symplify\PHPStanRules\Tests\Rules\Rector\AvoidFeatureSetAttributeInRectorRule\AvoidFeatureSetAttributeInRectorRuleTest
*
* @implements Rule<InClassNode>
*/
final class AvoidFeatureSetAttributeInRectorRule implements Rule
{
/**
* @var string
*/
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.';

/**
* @var string[]
*/
private const ALLOWED_ATTRIBUTES = ['kind', 'origNode', 'comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos'];

public function getNodeType(): string
{
return InClassNode::class;
}

/**
* @param InClassNode $node
*/
public function processNode(Node $node, Scope $scope): array
{
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return [];
}

if (! $classReflection->is(AbstractRector::class)) {
return [];
}

$classLike = $node->getOriginalNode();

$nodeFinder = new NodeFinder();

/** @var MethodCall[] $methodCalls */
$methodCalls = $nodeFinder->findInstanceOf($classLike, MethodCall::class);

$ruleErrors = [];

foreach ($methodCalls as $methodCall) {
if (! NamingHelper::isName($methodCall->name, 'setAttribute')) {
continue;
}

$attributeName = $this->resolveAttributeKeyValue($methodCall, $scope);
if (! is_string($attributeName)) {
continue;
}

if (in_array($attributeName, self::ALLOWED_ATTRIBUTES, true)) {
continue;
}

$ruleError = RuleErrorBuilder::message(sprintf(self::ERROR_MESSAGE, $attributeName))
->identifier(RectorRuleIdentifier::AVOID_FEATURE_SET_ATTRIBUTE_IN_RECTOR)
->build();

$ruleErrors[] = $ruleError;
}

return $ruleErrors;
}

private function resolveAttributeKeyValue(MethodCall $methodCall, Scope $scope): ?string
{
$firstArg = $methodCall->getArgs()[0];
$attributeNameType = $scope->getType($firstArg->value);

if (! $attributeNameType instanceof ConstantStringType) {
return null;
}

return $attributeNameType->getValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\NoDoctrineListenerWithoutContractRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\Doctrine\NoDoctrineListenerWithoutContractRule;
Expand All @@ -31,7 +32,7 @@ public static function provideData(): Iterator
]]];
}

protected function getRule(): NoDoctrineListenerWithoutContractRule
protected function getRule(): Rule
{
return new NoDoctrineListenerWithoutContractRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Iterator;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOnServiceRepositoryEntityRule;
Expand Down Expand Up @@ -39,7 +40,7 @@ public static function provideData(): Iterator
yield [__DIR__ . '/Fixture/SkipGetRepositoryOnNormalRepository.php', []];
}

protected function getRule(): NoGetRepositoryOnServiceRepositoryEntityRule
protected function getRule(): Rule
{
$reflectionProvider = self::getContainer()->getByType(ReflectionProvider::class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\NoGetRepositoryOutsideServiceRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\Doctrine\NoGetRepositoryOutsideServiceRule;
Expand Down Expand Up @@ -37,7 +38,7 @@ public static function provideData(): Iterator
yield [__DIR__ . '/Fixture/SkipDynamicClassConstFetch.php', []];
}

protected function getRule(): NoGetRepositoryOutsideServiceRule
protected function getRule(): Rule
{
return new NoGetRepositoryOutsideServiceRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\NoParentRepositoryRule;

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

protected function getRule(): NoParentRepositoryRule
protected function getRule(): Rule
{
return new NoParentRepositoryRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\NoRepositoryCallInDataFixtureRule;

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

protected function getRule(): NoRepositoryCallInDataFixtureRule
protected function getRule(): Rule
{
return new NoRepositoryCallInDataFixtureRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\Doctrine\RequireQueryBuilderOnRepositoryRule;
Expand Down Expand Up @@ -32,7 +33,7 @@ public static function provideData(): Iterator
]];
}

protected function getRule(): RequireQueryBuilderOnRepositoryRule
protected function getRule(): Rule
{
return new RequireQueryBuilderOnRepositoryRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\Doctrine\RequireServiceRepositoryParentRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Enum\DoctrineClass;
Expand All @@ -28,7 +29,7 @@ public static function provideData(): Iterator
yield [__DIR__ . '/Fixture/SkipContractImplementingRepository.php', []];
}

protected function getRule(): RequireServiceRepositoryParentRule
protected function getRule(): Rule
{
return new RequireServiceRepositoryParentRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\Explicit\NoProtectedClassStmtRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\Explicit\NoProtectedClassStmtRule;
Expand All @@ -28,7 +29,7 @@ public static function provideData(): Iterator
yield [__DIR__ . '/Fixture/SkipAbstractWithProtected.php', []];
}

protected function getRule(): NoProtectedClassStmtRule
protected function getRule(): Rule
{
return new NoProtectedClassStmtRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoAssertFuncCallInTestsRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\PHPUnit\NoAssertFuncCallInTestsRule;
Expand All @@ -24,7 +25,7 @@ public static function provideData(): Iterator
yield [__DIR__ . '/Fixture/SkipTestOutside.php', []];
}

protected function getRule(): NoAssertFuncCallInTestsRule
protected function getRule(): Rule
{
return new NoAssertFuncCallInTestsRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoDoubleConsecutiveTestMockRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\PHPUnit\NoDoubleConsecutiveTestMockRule;
Expand All @@ -24,7 +25,7 @@ public static function provideData(): Iterator
yield [__DIR__ . '/Fixture/SkipWillReturnCallback.php', []];
}

protected function getRule(): NoDoubleConsecutiveTestMockRule
protected function getRule(): Rule
{
return new NoDoubleConsecutiveTestMockRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\PHPUnit\NoMockObjectAndRealObjectPropertyRule;
Expand All @@ -26,7 +27,7 @@ public static function provideData(): Iterator
yield [__DIR__ . '/Fixture/SkipNullableObject.php', []];
}

protected function getRule(): NoMockObjectAndRealObjectPropertyRule
protected function getRule(): Rule
{
return new NoMockObjectAndRealObjectPropertyRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\NoMockOnlyTestRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\PHPUnit\NoMockOnlyTestRule;
Expand All @@ -30,7 +31,7 @@ public static function provideData(): Iterator
yield [__DIR__ . '/Fixture/SkipConstraintValidatorTest.php', []];
}

protected function getRule(): NoMockOnlyTestRule
protected function getRule(): Rule
{
return new NoMockOnlyTestRule();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Symplify\PHPStanRules\Tests\Rules\PHPUnit\PublicStaticDataProviderRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\PHPUnit\PublicStaticDataProviderRule;
Expand All @@ -30,7 +31,7 @@ public static function provideData(): Iterator
yield [[__DIR__ . '/Fixture/SkipStaticTest.php'], []];
}

protected function getRule(): PublicStaticDataProviderRule
protected function getRule(): Rule
{
return new PublicStaticDataProviderRule();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace Symplify\PHPStanRules\Tests\Rules\Rector\AvoidFeatureSetAttributeInRectorRule;

use Iterator;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
use Symplify\PHPStanRules\Rules\Rector\AvoidFeatureSetAttributeInRectorRule;

final class AvoidFeatureSetAttributeInRectorRuleTest extends RuleTestCase
{
#[DataProvider('provideData')]
public function testRule(string $filePath, array $expectedErrorsWithLines): void
{
$this->analyse([$filePath], $expectedErrorsWithLines);
}

public static function provideData(): Iterator
{
yield [__DIR__ . '/Fixture/SetLocalAttribute.php', [[
sprintf(AvoidFeatureSetAttributeInRectorRule::ERROR_MESSAGE, 'some_attribute'), 11,
]]];

yield [__DIR__ . '/Fixture/SkipAllowedSetAttributesNode.php', []];
}

protected function getRule(): Rule
{
return new AvoidFeatureSetAttributeInRectorRule();
}
}
Loading