[DX] Add PhpUpgradeDowngradeRegisteredInSetRule PHPStan rule (#905)

* [DX] Add PhpUpgradeDowngradeRegisteredInSetRule PHPStan rule

* phpstan

* register test

* [ci-review] Rector Rectify

* phpstan

* phpstan fix

* check prefix

* Fix

* fix

* final touch: service config

* skip implements ConfigurableRectorInterface

* [ci-review] Rector Rectify

* [ci-review] Rector Rectify

* register services

* final touch: example of non-configurable service

* include configured rector

Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Abdul Malik Ikhsan 2021-09-21 18:04:34 +07:00 committed by GitHub
parent e78fadd24d
commit e09cf113d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 197 additions and 4 deletions

View File

@ -20,6 +20,7 @@ jobs:
- tests
- rules-tests
- packages-tests
- utils/phpstan-rules/tests
name: PHP ${{ matrix.php }} tests for ${{ matrix.path }}
steps:

View File

@ -93,7 +93,9 @@
"rules-tests"
],
"Rector\\Core\\Tests\\": "tests",
"Rector\\RuleDocGenerator\\": "utils/rule-doc-generator/src"
"Rector\\RuleDocGenerator\\": "utils/rule-doc-generator/src",
"Rector\\PHPStanRules\\": "utils/phpstan-rules/src",
"Rector\\PHPStanRules\\Tests\\": "utils/phpstan-rules/tests"
},
"classmap": [
"stubs",

View File

@ -3,6 +3,7 @@
declare(strict_types=1);
use Rector\Php55\Rector\Class_\ClassConstantToSelfClassRector;
use Rector\Php55\Rector\FuncCall\PregReplaceEModifierRector;
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
@ -10,4 +11,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(StringClassNameToClassConstantRector::class);
$services->set(ClassConstantToSelfClassRector::class);
$services->set(PregReplaceEModifierRector::class);
};

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
use Rector\Php71\Rector\Assign\AssignArrayToStringRector;
use Rector\Php71\Rector\BinaryOp\BinaryOpBetweenNumberAndStringRector;
use Rector\Php71\Rector\BooleanOr\IsIterableRector;
use Rector\Php71\Rector\ClassConst\PublicConstantVisibilityRector;
use Rector\Php71\Rector\FuncCall\CountOnNullRector;
use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector;
use Rector\Php71\Rector\List_\ListToArrayDestructRector;
@ -20,4 +21,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(RemoveExtraParametersRector::class);
$services->set(BinaryOpBetweenNumberAndStringRector::class);
$services->set(ListToArrayDestructRector::class);
$services->set(PublicConstantVisibilityRector::class);
};

View File

@ -9,6 +9,7 @@ use Rector\Php73\Rector\FuncCall\ArrayKeyFirstLastRector;
use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector;
use Rector\Php73\Rector\FuncCall\RegexDashEscapeRector;
use Rector\Php73\Rector\FuncCall\SensitiveDefineRector;
use Rector\Php73\Rector\FuncCall\SetCookieRector;
use Rector\Php73\Rector\FuncCall\StringifyStrNeedlesRector;
use Rector\Php73\Rector\String_\SensitiveHereNowDocRector;
use Rector\Renaming\Rector\FuncCall\RenameFunctionRector;
@ -47,4 +48,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(JsonThrowOnErrorRector::class);
$services->set(RegexDashEscapeRector::class);
$services->set(ContinueToBreakInSwitchRector::class);
$services->set(SetCookieRector::class);
};

View File

@ -3,6 +3,7 @@
declare(strict_types=1);
use Rector\Php81\Rector\Class_\MyCLabsClassToEnumRector;
use Rector\Php81\Rector\Class_\SpatieEnumClassToEnumRector;
use Rector\Php81\Rector\ClassConst\FinalizePublicClassConstantRector;
use Rector\Php81\Rector\MethodCall\MyCLabsMethodCallToEnumConstRector;
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
@ -16,4 +17,5 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->set(MyCLabsMethodCallToEnumConstRector::class);
$services->set(FinalizePublicClassConstantRector::class);
$services->set(ReadOnlyPropertyRector::class);
$services->set(SpatieEnumClassToEnumRector::class);
};

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Rector\BetterPhpDocParser\ValueObject\Type;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
@ -12,8 +11,6 @@ use Stringable;
final class SpacingAwareCallableTypeNode extends CallableTypeNode implements Stringable
{
use NodeAttributes;
public function __toString(): string
{
// keep original (Psalm?) format, see https://github.com/rectorphp/rector/issues/2841

View File

@ -12,6 +12,11 @@ includes:
- vendor/symplify/phpstan-rules/config/symfony-rules.neon
- vendor/symplify/phpstan-rules/config/test-rules.neon
services:
-
class: Rector\PHPStanRules\Rules\PhpUpgradeDowngradeRegisteredInSetRule
tags: [phpstan.rules.rule]
parameters:
level: max
@ -509,3 +514,6 @@ parameters:
- rules/Php74/Rector/LNumber/AddLiteralSeparatorToNumberRector.php
- '#^Cognitive complexity for "Rector\\CodingStyle\\Naming\\NameRenamer\:\:renameNameNode\(\)" is 13, keep it under 9$#'
- '#Register Rector\\Php71\\Rector\\Name\\ReservedObjectRector to php71 config set#'
- '#Register Rector\\Php80\\Rector\\Class_\\AnnotationToAttributeRector to php80 config set#'
- '#Register Rector\\Php80\\Rector\\Class_\\DoctrineAnnotationClassToAttributeRector to php80 config set#'

View File

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStanRules\Rules;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Analyser\Scope;
use Symplify\PHPStanRules\Rules\AbstractSymplifyRule;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Symplify\SmartFileSystem\SmartFileSystem;
/**
* @see \Rector\PHPStanRules\Tests\Rules\PhpUpgradeDowngradeRegisteredInSetRule\PhpUpgradeDowngradeRegisteredInSetRuleTest
*/
final class PhpUpgradeDowngradeRegisteredInSetRule extends AbstractSymplifyRule
{
/**
* @var string
*/
public const ERROR_MESSAGE = 'Register %s to %s config set';
/**
* @var string
* @see https://regex101.com/r/C3nz6e/1/
*/
private const PREFIX_REGEX = '#(Downgrade)?Php\d+#';
public function __construct(
private SmartFileSystem $smartFileSystem
) {
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
* @return string[]
*/
public function process(Node $node, Scope $scope): array
{
/** @var string $className */
$className = (string) $node->namespacedName;
if (! str_ends_with($className, 'Rector')) {
return [];
}
[, $prefix] = explode('\\', $className);
if (! Strings::match($prefix, self::PREFIX_REGEX)) {
return [];
}
$phpVersion = Strings::substring($prefix, -2);
$configFile = str_starts_with($prefix, 'Downgrade')
? 'downgrade-php' . $phpVersion
: 'php' . $phpVersion;
$configContent = $this->smartFileSystem->readFile(
__DIR__ . '/../../../../config/set/' . $configFile . '.php'
);
$shortClassName = (string) $node->name;
$toSearch = sprintf('$services->set(%s::class)', $shortClassName);
if (! str_contains($configContent, $toSearch)) {
return [sprintf(self::ERROR_MESSAGE, $className, $configFile)];
}
return [];
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(self::ERROR_MESSAGE, [
new CodeSample(
<<<'CODE_SAMPLE'
// config/set/php74.php
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
// config/set/php74.php
$services->set(RealToFloatTypeCastRector::class);
CODE_SAMPLE
),
]);
}
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Rector\Arguments\Rector\ClassMethod;
final class SkipSomePhpFeatureRector
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Rector\DowngradePhp80\Rector\Class_;
final class SomePhpFeature2Rector
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Rector\Php80\Rector\Class_;
final class SomePhpFeatureRector
{
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Rector\PHPStanRules\Tests\Rules\PhpUpgradeDowngradeRegisteredInSetRule;
use Iterator;
use PHPStan\Rules\Rule;
use Rector\PHPStanRules\Rules\PhpUpgradeDowngradeRegisteredInSetRule;
use Symplify\PHPStanExtensions\Testing\AbstractServiceAwareRuleTestCase;
/**
* @extends AbstractServiceAwareRuleTestCase<PhpUpgradeDowngradeRegisteredInSetRule>
*/
final class PhpUpgradeDowngradeRegisteredInSetRuleTest extends AbstractServiceAwareRuleTestCase
{
/**
* @dataProvider provideData()
* @param array<string|int> $expectedErrorMessagesWithLines
*/
public function testRule(string $filePath, array $expectedErrorMessagesWithLines): void
{
$this->analyse([$filePath], $expectedErrorMessagesWithLines);
}
/**
* @return Iterator<string[]|array<int, mixed[]>>
*/
public function provideData(): Iterator
{
yield [__DIR__ . '/Fixture/SkipSomePhpFeatureRector.php', []];
yield [__DIR__ . '/Fixture/SomePhpFeatureRector.php', [
[sprintf(PhpUpgradeDowngradeRegisteredInSetRule::ERROR_MESSAGE, 'Rector\Php80\Rector\Class_\SomePhpFeatureRector', 'php80'), 7]
]];
yield [__DIR__ . '/Fixture/SomePhpFeature2Rector.php', [
[sprintf(PhpUpgradeDowngradeRegisteredInSetRule::ERROR_MESSAGE, 'Rector\DowngradePhp80\Rector\Class_\SomePhpFeature2Rector', 'downgrade-php80'), 7]
]];
}
protected function getRule(): Rule
{
return $this->getRuleFromConfig(
PhpUpgradeDowngradeRegisteredInSetRule::class,
__DIR__ . '/config/configured_rule.neon'
);
}
}

View File

@ -0,0 +1,5 @@
services:
- Symplify\SmartFileSystem\SmartFileSystem
-
class: Rector\PHPStanRules\Rules\PhpUpgradeDowngradeRegisteredInSetRule
tags: [phpstan.rules.rule]