From 34de1afc018fb8da083433b7f1a1d856b6890efd Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Sun, 3 May 2020 14:47:06 +0200 Subject: [PATCH 1/4] README: more clear coding standard section --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 817eba359dcd..c09866bd1d6c 100644 --- a/README.md +++ b/README.md @@ -65,11 +65,9 @@ It supports all versions of PHP from 5.2 and many open-source projects: ## How to Apply Coding Standards? -The AST libraries that Rector uses aren't well-suited for coding standards, so it's better to let coding standard tools do that. +Rector uses [nikic/php-parser](https://github.com/nikic/PHP-Parser/), that build on technology called *abstract syntax tree*) technology* (AST). AST doesn't care about spaces and produces mall-formatted code. That's why your project needs to have coding standard tool and set of rules, so it can make refactored nice and shiny again. -Don't have a coding standard tool for your project? Consider adding [EasyCodingStandard](https://github.com/Symplify/EasyCodingStandard), [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) or [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). - -*Tip: If you have EasyCodingStandard, you can start your set with [`ecs-after-rector.yaml`](/ecs-after-rector.yaml).* +Don't have any coding standard tool? Add [EasyCodingStandard](https://github.com/Symplify/EasyCodingStandard) and use prepared [`ecs-after-rector.yaml`](/ecs-after-rector.yaml) set. ## Install From 61aeeaeebd3f16494f6c8148757793a262d764ce Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Sun, 3 May 2020 14:51:41 +0200 Subject: [PATCH 2/4] improve PHPStan rules for Rector --- phpunit.xml | 1 + .../KeepRectorNamespaceForRectorRule.php | 41 ++++++++----------- ...ventParentMethodVisibilityOverrideRule.php | 31 +++++--------- .../KeepRectorNamespaceForRectorRuleTest.php | 10 ++--- .../ClassLike/Source/Rector/WrongClass.php | 1 - ...ParentMethodVisibilityOverrideRuleTest.php | 17 +++++--- .../Source/ClassWithOverridingVisibility.php | 9 ---- .../ClassMethod/Source/GoodVisibility.php | 13 ++++++ 8 files changed, 59 insertions(+), 64 deletions(-) create mode 100644 utils/phpstan-extensions/tests/Rule/ClassMethod/Source/GoodVisibility.php diff --git a/phpunit.xml b/phpunit.xml index 9e8a7f538349..07cc31ff3005 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -13,6 +13,7 @@ rules/*/tests packages/*/tests tests + utils/*/tests diff --git a/utils/phpstan-extensions/src/Rule/ClassLike/KeepRectorNamespaceForRectorRule.php b/utils/phpstan-extensions/src/Rule/ClassLike/KeepRectorNamespaceForRectorRule.php index e1469d9983e5..91b3f2428b33 100644 --- a/utils/phpstan-extensions/src/Rule/ClassLike/KeepRectorNamespaceForRectorRule.php +++ b/utils/phpstan-extensions/src/Rule/ClassLike/KeepRectorNamespaceForRectorRule.php @@ -9,11 +9,17 @@ use PhpParser\Node\Stmt\ClassLike; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; -use PHPStan\Rules\RuleError; -use PHPStan\Rules\RuleErrorBuilder; +/** + * @see \Rector\PHPStanExtensions\Tests\Rule\ClassLike\KeepRectorNamespaceForRectorRuleTest + */ final class KeepRectorNamespaceForRectorRule implements Rule { + /** + * @var string + */ + public const ERROR_MESSAGE = 'Change namespace for "%s". It cannot be in "Rector" namespace, unless Rector rule.'; + public function getNodeType(): string { return ClassLike::class; @@ -21,7 +27,7 @@ public function getNodeType(): string /** * @param ClassLike $node - * @return RuleError[] + * @return string[] */ public function processNode(Node $node, Scope $scope): array { @@ -32,20 +38,23 @@ public function processNode(Node $node, Scope $scope): array /** @var string $classLikeName */ $classLikeName = $node->name->toString(); - $ruleError = $this->createRuleError($node, $scope, $classLikeName); + $errorMessage = sprintf(self::ERROR_MESSAGE, $classLikeName); - return [$ruleError]; + return [$errorMessage]; } - private function shouldSkip(Node $node, Scope $scope): bool + private function shouldSkip(ClassLike $classLike, Scope $scope): bool { $namespace = $scope->getNamespace(); if ($namespace === null) { return true; } - // skip interface and tests - if (Strings::match($namespace, '#\\\\(Contract|Exception|Tests)\\\\#')) { + // skip interface and tests, except tests here + if (Strings::match($namespace, '#\\\\(Contract|Exception|Tests)\\\\#') && ! Strings::contains( + $namespace, + 'PHPStanExtensions' + )) { return true; } @@ -53,7 +62,7 @@ private function shouldSkip(Node $node, Scope $scope): bool return true; } - $name = $node->name; + $name = $classLike->name; if ($name === null) { return true; } @@ -63,18 +72,4 @@ private function shouldSkip(Node $node, Scope $scope): bool return (bool) Strings::match($classLikeName, '#(Rector|Test|Trait)$#'); } - - private function createRuleError(Node $node, Scope $scope, string $classLikeName): RuleError - { - $message = sprintf( - 'Change namespace for "%s". It cannot be in "Rector" namespace, unless Rector rule.', - $classLikeName - ); - - $ruleErrorBuilder = RuleErrorBuilder::message($message); - $ruleErrorBuilder->line($node->getLine()); - $ruleErrorBuilder->file($scope->getFile()); - - return $ruleErrorBuilder->build(); - } } diff --git a/utils/phpstan-extensions/src/Rule/ClassMethod/PreventParentMethodVisibilityOverrideRule.php b/utils/phpstan-extensions/src/Rule/ClassMethod/PreventParentMethodVisibilityOverrideRule.php index f792523da4c9..d3a9bf8a8ad4 100644 --- a/utils/phpstan-extensions/src/Rule/ClassMethod/PreventParentMethodVisibilityOverrideRule.php +++ b/utils/phpstan-extensions/src/Rule/ClassMethod/PreventParentMethodVisibilityOverrideRule.php @@ -8,13 +8,19 @@ use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; -use PHPStan\Rules\RuleError; -use PHPStan\Rules\RuleErrorBuilder; use Rector\Core\Exception\NotImplementedException; use ReflectionMethod; +/** + * @see \Rector\PHPStanExtensions\Tests\Rule\ClassMethod\PreventParentMethodVisibilityOverrideRuleTest + */ final class PreventParentMethodVisibilityOverrideRule implements Rule { + /** + * @var string + */ + public const ERROR_MESSAGE = 'Change "%s()" method visibility to "%s" to respect parent method visibility.'; + public function getNodeType(): string { return ClassMethod::class; @@ -22,7 +28,7 @@ public function getNodeType(): string /** * @param ClassMethod $node - * @return RuleError[] + * @return string[] */ public function processNode(Node $node, Scope $scope): array { @@ -49,8 +55,8 @@ public function processNode(Node $node, Scope $scope): array $methodVisibility = $this->resolveReflectionMethodVisibilityAsStrings($parentReflectionMethod); - $ruleError = $this->createRuleError($node, $scope, $methodName, $methodVisibility); - return [$ruleError]; + $errorMessage = sprintf(self::ERROR_MESSAGE, $methodName, $methodVisibility); + return [$errorMessage]; } return []; @@ -87,19 +93,4 @@ private function resolveReflectionMethodVisibilityAsStrings(ReflectionMethod $re throw new NotImplementedException(); } - - private function createRuleError(Node $node, Scope $scope, string $methodName, string $methodVisibility): RuleError - { - $message = sprintf( - 'Change "%s()" method visibility to "%s" to respect parent method visibility.', - $methodName, - $methodVisibility - ); - - $ruleErrorBuilder = RuleErrorBuilder::message($message); - $ruleErrorBuilder->line($node->getLine()); - $ruleErrorBuilder->file($scope->getFile()); - - return $ruleErrorBuilder->build(); - } } diff --git a/utils/phpstan-extensions/tests/Rule/ClassLike/KeepRectorNamespaceForRectorRuleTest.php b/utils/phpstan-extensions/tests/Rule/ClassLike/KeepRectorNamespaceForRectorRuleTest.php index 71c1594820bc..21717554f59e 100644 --- a/utils/phpstan-extensions/tests/Rule/ClassLike/KeepRectorNamespaceForRectorRuleTest.php +++ b/utils/phpstan-extensions/tests/Rule/ClassLike/KeepRectorNamespaceForRectorRuleTest.php @@ -12,21 +12,19 @@ final class KeepRectorNamespaceForRectorRuleTest extends RuleTestCase { /** - * @param array $expectedErrorsWithLines * @dataProvider provideData() */ public function testRule(string $filePath, array $expectedErrorsWithLines): void { - $this->analyse([$filePath], [$expectedErrorsWithLines]); + $this->analyse([$filePath], $expectedErrorsWithLines); } public function provideData(): Iterator { yield [__DIR__ . '/Source/Rector/ClassInCorrectNamespaceRector.php', []]; - yield [ - __DIR__ . '/Source/Rector/WrongClass.php', - ['Change namespace for "WrongClass". It cannot be in Rector namespace, unless Rector rule.', 8], - ]; + + $errorMessage = sprintf(KeepRectorNamespaceForRectorRule::ERROR_MESSAGE, 'WrongClass', 'Rector'); + yield [__DIR__ . '/Source/Rector/WrongClass.php', [[$errorMessage, 7]]]; } protected function getRule(): Rule diff --git a/utils/phpstan-extensions/tests/Rule/ClassLike/Source/Rector/WrongClass.php b/utils/phpstan-extensions/tests/Rule/ClassLike/Source/Rector/WrongClass.php index d7a878cb4088..3eeedebed357 100644 --- a/utils/phpstan-extensions/tests/Rule/ClassLike/Source/Rector/WrongClass.php +++ b/utils/phpstan-extensions/tests/Rule/ClassLike/Source/Rector/WrongClass.php @@ -2,7 +2,6 @@ declare(strict_types=1); - namespace Rector\PHPStanExtensions\Tests\Rule\ClassLike\Source\Rector; final class WrongClass diff --git a/utils/phpstan-extensions/tests/Rule/ClassMethod/PreventParentMethodVisibilityOverrideRuleTest.php b/utils/phpstan-extensions/tests/Rule/ClassMethod/PreventParentMethodVisibilityOverrideRuleTest.php index 82d552ef5732..7202c58860a4 100644 --- a/utils/phpstan-extensions/tests/Rule/ClassMethod/PreventParentMethodVisibilityOverrideRuleTest.php +++ b/utils/phpstan-extensions/tests/Rule/ClassMethod/PreventParentMethodVisibilityOverrideRuleTest.php @@ -4,18 +4,25 @@ namespace Rector\PHPStanExtensions\Tests\Rule\ClassMethod; +use Iterator; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use Rector\PHPStanExtensions\Rule\ClassMethod\PreventParentMethodVisibilityOverrideRule; final class PreventParentMethodVisibilityOverrideRuleTest extends RuleTestCase { - public function testRule(): void + /** + * @dataProvider provideData() + */ + public function testRule(string $filePath, array $expectedErrorsWithLines): void { - $this->analyse( - [__DIR__ . '/Source/ClassWithOverridingVisibility.php'], - [['Change "run()" method visibility to "protected" to respect parent method visibility.', 10]] - ); + $this->analyse([$filePath], $expectedErrorsWithLines); + } + + public function provideData(): Iterator + { + $errorMessage = sprintf(PreventParentMethodVisibilityOverrideRule::ERROR_MESSAGE, 'run', 'protected'); + yield [__DIR__ . '/Source/ClassWithOverridingVisibility.php', [[$errorMessage, 9]]]; } protected function getRule(): Rule diff --git a/utils/phpstan-extensions/tests/Rule/ClassMethod/Source/ClassWithOverridingVisibility.php b/utils/phpstan-extensions/tests/Rule/ClassMethod/Source/ClassWithOverridingVisibility.php index 8b012f86285c..0c9cf41b2d79 100644 --- a/utils/phpstan-extensions/tests/Rule/ClassMethod/Source/ClassWithOverridingVisibility.php +++ b/utils/phpstan-extensions/tests/Rule/ClassMethod/Source/ClassWithOverridingVisibility.php @@ -2,7 +2,6 @@ declare(strict_types=1); - namespace Rector\PHPStanExtensions\Tests\Rule\ClassMethod\Source; final class ClassWithOverridingVisibility extends GoodVisibility @@ -11,11 +10,3 @@ public function run() { } } - -abstract class GoodVisibility -{ - protected function run() - { - - } -} diff --git a/utils/phpstan-extensions/tests/Rule/ClassMethod/Source/GoodVisibility.php b/utils/phpstan-extensions/tests/Rule/ClassMethod/Source/GoodVisibility.php new file mode 100644 index 000000000000..f5f14f603aec --- /dev/null +++ b/utils/phpstan-extensions/tests/Rule/ClassMethod/Source/GoodVisibility.php @@ -0,0 +1,13 @@ + Date: Sun, 3 May 2020 15:38:05 +0200 Subject: [PATCH 3/4] [Utils] Add SeeAnnotationToTestRule --- .../config/phpstan-extensions.neon | 13 ++- .../src/Rule/SeeAnnotationToTestRule.php | 97 +++++++++++++++++++ .../AbstractServiceAwareRuleTestCase.php | 35 +++++++ .../Fixture/ClassMissingDocBlockRector.php | 10 ++ .../ClassMissingSeeAnnotationRector.php | 13 +++ .../SeeAnnotationToTestRuleTest.php | 40 ++++++++ 6 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php create mode 100644 utils/phpstan-extensions/tests/AbstractServiceAwareRuleTestCase.php create mode 100644 utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingDocBlockRector.php create mode 100644 utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingSeeAnnotationRector.php create mode 100644 utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/SeeAnnotationToTestRuleTest.php diff --git a/utils/phpstan-extensions/config/phpstan-extensions.neon b/utils/phpstan-extensions/config/phpstan-extensions.neon index 65525e48d0b3..27ce597c6674 100644 --- a/utils/phpstan-extensions/config/phpstan-extensions.neon +++ b/utils/phpstan-extensions/config/phpstan-extensions.neon @@ -1,6 +1,15 @@ services: - - { class: Rector\PHPStanExtensions\Rule\ClassMethod\PreventParentMethodVisibilityOverrideRule, tags: [phpstan.rules.rule] } - - { class: Rector\PHPStanExtensions\Rule\ClassLike\KeepRectorNamespaceForRectorRule, tags: [phpstan.rules.rule] } + - + class: Rector\PHPStanExtensions\Rule\SeeAnnotationToTestRule + tags: [phpstan.rules.rule] + + - + class: Rector\PHPStanExtensions\Rule\ClassMethod\PreventParentMethodVisibilityOverrideRule + tags: [phpstan.rules.rule] + + - + class: Rector\PHPStanExtensions\Rule\ClassLike\KeepRectorNamespaceForRectorRule + tags: [phpstan.rules.rule] - Rector\PHPStanExtensions\Utils\PHPStanValueResolver diff --git a/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php b/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php new file mode 100644 index 000000000000..96449cc0a2b3 --- /dev/null +++ b/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php @@ -0,0 +1,97 @@ +fileTypeMapper = $fileTypeMapper; + $this->broker = $broker; + } + + public function getNodeType(): string + { + return Class_::class; + } + + /** + * @param Class_ $node + * @return string[] + */ + public function processNode(Node $node, Scope $scope): array + { + if ($node->name === null) { + return []; + } + + $className = (string) $node->namespacedName; + if (! $this->isClassMatch($className)) { + return []; + } + + $classReflection = $this->broker->getClass($className); + + $docComment = $node->getDocComment(); + if ($docComment === null) { + return [sprintf(self::ERROR_MESSAGE, $className)]; + } + + $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $classReflection->getName(), + null, + null, + $docComment->getText() + ); + + $seeTags = $resolvedPhpDoc->getPhpDocNode()->getTagsByName('@see'); + // @todo validate to refer a TestCase class + if ($seeTags !== []) { + return []; + } + + return [sprintf(self::ERROR_MESSAGE, $className)]; + } + + private function isClassMatch(string $className): bool + { + foreach (self::CLASS_SUFFIXES as $classSuffix) { + if (Strings::endsWith($className, $classSuffix)) { + return true; + } + } + + return false; + } +} diff --git a/utils/phpstan-extensions/tests/AbstractServiceAwareRuleTestCase.php b/utils/phpstan-extensions/tests/AbstractServiceAwareRuleTestCase.php new file mode 100644 index 000000000000..c3b6515e314f --- /dev/null +++ b/utils/phpstan-extensions/tests/AbstractServiceAwareRuleTestCase.php @@ -0,0 +1,35 @@ +createContainer([$config]); + return $container->getByType($ruleClass); + } + + /** + * @param string[] $configs + */ + private function createContainer(array $configs): Container + { + $containerFactory = new ContainerFactory(getcwd()); + // random for tests cache invalidation in case the container changes + $tempDirectory = sys_get_temp_dir() . '/_phpstan_rector/id_' . random_int(0, 1000); + + return $containerFactory->create($tempDirectory, $configs, [], []); + } +} diff --git a/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingDocBlockRector.php b/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingDocBlockRector.php new file mode 100644 index 000000000000..c4cb301678b2 --- /dev/null +++ b/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingDocBlockRector.php @@ -0,0 +1,10 @@ +analyse([$filePath], $expectedErrorsWithLines); + } + + public function provideData(): Iterator + { + $errorMessage = sprintf(SeeAnnotationToTestRule::ERROR_MESSAGE, ClassMissingDocBlockRector::class); + yield [__DIR__ . '/Fixture/ClassMissingDocBlockRector.php', [[$errorMessage, 7]]]; + + $errorMessage = sprintf(SeeAnnotationToTestRule::ERROR_MESSAGE, ClassMissingSeeAnnotationRector::class); + yield [__DIR__ . '/Fixture/ClassMissingSeeAnnotationRector.php', [[$errorMessage, 10]]]; + } + + protected function getRule(): Rule + { + return $this->getRuleFromConfig( + SeeAnnotationToTestRule::class, + __DIR__ . '/../../../config/phpstan-extensions.neon' + ); + } +} From a952cac41cfd7fe4bc62dcdd9026bf256d1c6347 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Sun, 3 May 2020 16:07:56 +0200 Subject: [PATCH 4/4] complete @see annotation, minor improvements --- phpstan.neon | 5 ++ .../NetteAssertToPHPUnitAssertRector.php | 3 + ...placeBackwardsCompatabilityClassRector.php | 3 + .../php72/src/Rector/Each/ListEachRector.php | 2 + .../Rector/Each/WhileEachToForeachRector.php | 2 + rules/psr4/config/config.yaml | 1 + .../TemplateAnnotationRector.php | 5 ++ .../ReplaceVariableByPropertyFetchRector.php | 3 + .../src/Rule/SeeAnnotationToTestRule.php | 73 +++++++++++++++---- .../Fixture/ClassMissingDocBlockRector.php | 17 ++++- .../ClassMissingSeeAnnotationRector.php | 17 ++++- .../SeeAnnotationToTestRuleTest.php | 4 +- 12 files changed, 117 insertions(+), 18 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 8c5584530577..c98a76e222df 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -252,3 +252,8 @@ parameters: - '#Class (.*?) should be written with \:\:class notation, string found#' - '#Parameter \#2 \$key of method Rector\\BetterPhpDocParser\\PhpDocNode\\AbstractTagValueNode\:\:printArrayItem\(\) expects string\|null, int\|string given#' - '#Method Rector\\Naming\\Naming\\PropertyNaming\:\:resolveShortClassName\(\) should return string but returns string\|null#' + + - + message: "#^Class \"Rector\\\\PSR4\\\\Rector\\\\Namespace_\\\\NormalizeNamespaceByPSR4ComposerAutoloadRector\" is missing @see annotation with test case class reference$#" + count: 1 + path: rules/psr4/src/Rector/Namespace_/NormalizeNamespaceByPSR4ComposerAutoloadRector.php diff --git a/rules/nette-tester-to-phpunit/src/Rector/StaticCall/NetteAssertToPHPUnitAssertRector.php b/rules/nette-tester-to-phpunit/src/Rector/StaticCall/NetteAssertToPHPUnitAssertRector.php index 13e098ad73a0..a64119ff59f4 100644 --- a/rules/nette-tester-to-phpunit/src/Rector/StaticCall/NetteAssertToPHPUnitAssertRector.php +++ b/rules/nette-tester-to-phpunit/src/Rector/StaticCall/NetteAssertToPHPUnitAssertRector.php @@ -11,6 +11,9 @@ use Rector\Core\RectorDefinition\RectorDefinition; use Rector\NetteTesterToPHPUnit\AssertManipulator; +/** + * @see \Rector\NetteTesterToPHPUnit\Tests\Rector\Class_\NetteTesterClassToPHPUnitClassRector\NetteTesterPHPUnitRectorTest + */ final class NetteAssertToPHPUnitAssertRector extends AbstractRector { /** diff --git a/rules/oxid/src/Rector/FuncCall/OxidReplaceBackwardsCompatabilityClassRector.php b/rules/oxid/src/Rector/FuncCall/OxidReplaceBackwardsCompatabilityClassRector.php index 039b86c99a23..d610476a5bd1 100644 --- a/rules/oxid/src/Rector/FuncCall/OxidReplaceBackwardsCompatabilityClassRector.php +++ b/rules/oxid/src/Rector/FuncCall/OxidReplaceBackwardsCompatabilityClassRector.php @@ -15,6 +15,9 @@ use Rector\Core\RectorDefinition\CodeSample; use Rector\Core\RectorDefinition\RectorDefinition; +/** + * @see \Rector\Oxid\Tests\Rector\FuncCall\OxidReplaceBackwardsCompatabilityClassRector\OxidReplaceBackwardsCompatabilityClassRectorTest + */ final class OxidReplaceBackwardsCompatabilityClassRector extends AbstractRector { /** diff --git a/rules/php72/src/Rector/Each/ListEachRector.php b/rules/php72/src/Rector/Each/ListEachRector.php index 4d8ff51cd5aa..94bea7a46027 100644 --- a/rules/php72/src/Rector/Each/ListEachRector.php +++ b/rules/php72/src/Rector/Each/ListEachRector.php @@ -18,6 +18,8 @@ /** * @source https://wiki.php.net/rfc/deprecations_php_7_2#each + * + * @see \Rector\Php72\Tests\Rector\Each\EachRectorTest */ final class ListEachRector extends AbstractRector { diff --git a/rules/php72/src/Rector/Each/WhileEachToForeachRector.php b/rules/php72/src/Rector/Each/WhileEachToForeachRector.php index 4fc0dca3d5d8..1d83032d33d6 100644 --- a/rules/php72/src/Rector/Each/WhileEachToForeachRector.php +++ b/rules/php72/src/Rector/Each/WhileEachToForeachRector.php @@ -18,6 +18,8 @@ /** * @source https://wiki.php.net/rfc/deprecations_php_7_2#each + * + * @see \Rector\Php72\Tests\Rector\Each\EachRectorTest */ final class WhileEachToForeachRector extends AbstractRector { diff --git a/rules/psr4/config/config.yaml b/rules/psr4/config/config.yaml index d8fb38901f0b..6300c1432bec 100644 --- a/rules/psr4/config/config.yaml +++ b/rules/psr4/config/config.yaml @@ -2,6 +2,7 @@ services: _defaults: public: true autowire: true + autoconfigure: true Rector\PSR4\: resource: '../src' diff --git a/rules/sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php b/rules/sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php index d82b682a46d5..37a160e6a3f3 100644 --- a/rules/sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php +++ b/rules/sensio/src/Rector/FrameworkExtraBundle/TemplateAnnotationRector.php @@ -23,6 +23,10 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; +/** + * @see \Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationRector\TemplateAnnotationVersion3RectorTest + * @see \Rector\Sensio\Tests\Rector\FrameworkExtraBundle\TemplateAnnotationRector\TemplateAnnotationVersion5RectorTest + */ final class TemplateAnnotationRector extends AbstractRector { /** @@ -139,6 +143,7 @@ private function classHasTemplateAnnotations(Class_ $node): bool private function getSensioTemplateTagValueNode(ClassMethod $classMethod): ?SensioTemplateTagValueNode { + /** @var PhpDocInfo|null $phpDocInfo */ $phpDocInfo = $classMethod->getAttribute(AttributeKey::PHP_DOC_INFO); if ($phpDocInfo === null) { return null; diff --git a/src/Rector/Architecture/DependencyInjection/ReplaceVariableByPropertyFetchRector.php b/src/Rector/Architecture/DependencyInjection/ReplaceVariableByPropertyFetchRector.php index 489a446c877b..04003dc54ba5 100644 --- a/src/Rector/Architecture/DependencyInjection/ReplaceVariableByPropertyFetchRector.php +++ b/src/Rector/Architecture/DependencyInjection/ReplaceVariableByPropertyFetchRector.php @@ -15,6 +15,9 @@ use Rector\Core\RectorDefinition\RectorDefinition; use Rector\NodeTypeResolver\Node\AttributeKey; +/** + * @see \Rector\Core\Tests\Rector\Architecture\DependencyInjection\ActionInjectionToConstructorInjectionRector\ActionInjectionToConstructorInjectionRectorTest + */ final class ReplaceVariableByPropertyFetchRector extends AbstractRector { /** diff --git a/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php b/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php index 96449cc0a2b3..ddc303ffa893 100644 --- a/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php +++ b/utils/phpstan-extensions/src/Rule/SeeAnnotationToTestRule.php @@ -5,13 +5,21 @@ namespace Rector\PHPStanExtensions\Rule; use Nette\Utils\Strings; +use PhpParser\Comment\Doc; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PHPStan\Analyser\Scope; use PHPStan\Broker\Broker; +use PHPStan\PhpDoc\ResolvedPhpDocBlock; +use PHPStan\Reflection\ClassReflection; use PHPStan\Rules\Rule; use PHPStan\Type\FileTypeMapper; +use Rector\Core\Contract\Rector\PhpRectorInterface; +use Rector\PostRector\Contract\Rector\PostRectorInterface; +/** + * @see \Rector\PHPStanExtensions\Tests\Rule\SeeAnnotationToTestRule\SeeAnnotationToTestRuleTest + */ final class SeeAnnotationToTestRule implements Rule { /** @@ -51,37 +59,30 @@ public function getNodeType(): string */ public function processNode(Node $node, Scope $scope): array { - if ($node->name === null) { + $classReflection = $this->matchClassReflection($node); + if ($classReflection === null) { return []; } - $className = (string) $node->namespacedName; - if (! $this->isClassMatch($className)) { + if ($this->shouldSkipClassReflection($classReflection)) { return []; } - $classReflection = $this->broker->getClass($className); - $docComment = $node->getDocComment(); if ($docComment === null) { - return [sprintf(self::ERROR_MESSAGE, $className)]; + return [sprintf(self::ERROR_MESSAGE, $classReflection->getName())]; } - $resolvedPhpDoc = $this->fileTypeMapper->getResolvedPhpDoc( - $scope->getFile(), - $classReflection->getName(), - null, - null, - $docComment->getText() - ); + $resolvedPhpDoc = $this->resolvePhpDoc($scope, $classReflection, $docComment); $seeTags = $resolvedPhpDoc->getPhpDocNode()->getTagsByName('@see'); + // @todo validate to refer a TestCase class if ($seeTags !== []) { return []; } - return [sprintf(self::ERROR_MESSAGE, $className)]; + return [sprintf(self::ERROR_MESSAGE, $classReflection->getName())]; } private function isClassMatch(string $className): bool @@ -94,4 +95,48 @@ private function isClassMatch(string $className): bool return false; } + + private function shouldSkipClassReflection(ClassReflection $classReflection): bool + { + if (! $this->isClassMatch($classReflection->getName())) { + return true; + } + + if ($classReflection->isAbstract()) { + return true; + } + + // meta-Rector + if ($classReflection->isSubclassOf(PostRectorInterface::class)) { + return true; + } + + // skip filesystem for now + return ! $classReflection->isSubclassOf(PhpRectorInterface::class); + } + + private function matchClassReflection(Class_ $node): ?ClassReflection + { + if ($node->name === null) { + return null; + } + + $className = (string) $node->namespacedName; + if (! class_exists($className)) { + return null; + } + + return $this->broker->getClass($className); + } + + private function resolvePhpDoc(Scope $scope, ClassReflection $classReflection, Doc $doc): ResolvedPhpDocBlock + { + return $this->fileTypeMapper->getResolvedPhpDoc( + $scope->getFile(), + $classReflection->getName(), + null, + null, + $doc->getText() + ); + } } diff --git a/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingDocBlockRector.php b/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingDocBlockRector.php index c4cb301678b2..008e30febcaa 100644 --- a/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingDocBlockRector.php +++ b/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingDocBlockRector.php @@ -4,7 +4,22 @@ namespace Rector\PHPStanExtensions\Tests\Rule\SeeAnnotationToTestRule\Fixture; -final class ClassMissingDocBlockRector +use PhpParser\Node; +use Rector\Core\Contract\Rector\PhpRectorInterface; +use Rector\Core\Rector\AbstractRector; +use Rector\Core\RectorDefinition\RectorDefinition; + +final class ClassMissingDocBlockRector extends AbstractRector implements PhpRectorInterface { + public function getNodeTypes(): array + { + } + + public function refactor(Node $node): ?Node + { + } + public function getDefinition(): RectorDefinition + { + } } diff --git a/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingSeeAnnotationRector.php b/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingSeeAnnotationRector.php index 6c1a5c41ecd5..02bc12d4cf5b 100644 --- a/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingSeeAnnotationRector.php +++ b/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/Fixture/ClassMissingSeeAnnotationRector.php @@ -4,10 +4,25 @@ namespace Rector\PHPStanExtensions\Tests\Rule\SeeAnnotationToTestRule\Fixture; +use PhpParser\Node; +use Rector\Core\Contract\Rector\PhpRectorInterface; +use Rector\Core\Rector\AbstractRector; +use Rector\Core\RectorDefinition\RectorDefinition; + /** * Some doc block */ -final class ClassMissingSeeAnnotationRector +final class ClassMissingSeeAnnotationRector extends AbstractRector implements PhpRectorInterface { + public function getNodeTypes(): array + { + } + + public function refactor(Node $node): ?Node + { + } + public function getDefinition(): RectorDefinition + { + } } diff --git a/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/SeeAnnotationToTestRuleTest.php b/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/SeeAnnotationToTestRuleTest.php index 11cd9fe2d828..009bacb972a9 100644 --- a/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/SeeAnnotationToTestRuleTest.php +++ b/utils/phpstan-extensions/tests/Rule/SeeAnnotationToTestRule/SeeAnnotationToTestRuleTest.php @@ -24,10 +24,10 @@ public function testRule(string $filePath, array $expectedErrorsWithLines): void public function provideData(): Iterator { $errorMessage = sprintf(SeeAnnotationToTestRule::ERROR_MESSAGE, ClassMissingDocBlockRector::class); - yield [__DIR__ . '/Fixture/ClassMissingDocBlockRector.php', [[$errorMessage, 7]]]; + yield [__DIR__ . '/Fixture/ClassMissingDocBlockRector.php', [[$errorMessage, 12]]]; $errorMessage = sprintf(SeeAnnotationToTestRule::ERROR_MESSAGE, ClassMissingSeeAnnotationRector::class); - yield [__DIR__ . '/Fixture/ClassMissingSeeAnnotationRector.php', [[$errorMessage, 10]]]; + yield [__DIR__ . '/Fixture/ClassMissingSeeAnnotationRector.php', [[$errorMessage, 15]]]; } protected function getRule(): Rule