From e23321b424282fdab96348456c8cd3ffb5d03f48 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Wed, 1 Apr 2020 02:05:51 +0200 Subject: [PATCH] [PHPUnit] Add AddProphecyTraitRector --- config/set/phpunit/phpunit91.yaml | 2 + docs/AllRectorsOverview.md | 33 ++++- .../src/Application/PostFileProcessor.php | 7 +- .../src/Command/CreateRectorCommand.php | 2 +- .../src/TemplateVariablesFactory.php | 3 +- .../src/ValueObject/Configuration.php | 5 + ...ructorInjectionToActionInjectionRector.php | 4 +- ...ameterFromArrayToArrayCollectionRector.php | 5 +- .../Class_/TimestampableBehaviorRector.php | 2 +- .../Rector/Class_/AddProphecyTraitRector.php | 122 ++++++++++++++++++ .../AddProphecyTraitRectorTest.php | 30 +++++ .../Fixture/fixture.php.inc | 32 +++++ .../Fixture/skip_if_already_added.php.inc | 16 +++ .../Fixture/skip_non_prophesize.php.inc | 15 +++ src/Console/Command/ScreenFileCommand.php | 2 +- .../Manipulator/ClassInsertManipulator.php | 2 +- .../Node/Manipulator/ClassManipulator.php | 2 +- .../AbstractRector/NameResolverTrait.php | 14 ++ src/Testing/Finder/RectorsFinder.php | 2 +- stubs/Prophecy/PhpUnit/ProphecyTrait.php | 14 ++ .../src/Command/DumpNodesCommand.php | 2 +- .../src/Command/DumpRectorsCommand.php | 2 +- .../src/Command/SyncTypesCommand.php | 2 +- .../Command/CheckStaticTypeMappersCommand.php | 2 +- 24 files changed, 300 insertions(+), 22 deletions(-) create mode 100644 config/set/phpunit/phpunit91.yaml create mode 100644 rules/phpunit/src/Rector/Class_/AddProphecyTraitRector.php create mode 100644 rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/AddProphecyTraitRectorTest.php create mode 100644 rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/fixture.php.inc create mode 100644 rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/skip_if_already_added.php.inc create mode 100644 rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/skip_non_prophesize.php.inc create mode 100644 stubs/Prophecy/PhpUnit/ProphecyTrait.php diff --git a/config/set/phpunit/phpunit91.yaml b/config/set/phpunit/phpunit91.yaml new file mode 100644 index 000000000000..1b9bfefc165a --- /dev/null +++ b/config/set/phpunit/phpunit91.yaml @@ -0,0 +1,2 @@ +services: + Rector\PHPUnit\Rector\Class_\AddProphecyTraitRector: null diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md index ef98f055393e..04083513a10f 100644 --- a/docs/AllRectorsOverview.md +++ b/docs/AllRectorsOverview.md @@ -1,4 +1,4 @@ -# All 481 Rectors Overview +# All 482 Rectors Overview - [Projects](#projects) - [General](#general) @@ -3612,9 +3612,10 @@ Remove temporary *Uuid relation properties Change array to ArrayCollection in setParameters method of query builder ```diff - --use Doctrine\ORM\EntityRepository; -+use Doctrine\Common\Collections\ArrayCollection;use Doctrine\ORM\EntityRepository;use Doctrine\ORM\Query\Parameter; +- ++use Doctrine\Common\Collections\ArrayCollection; + use Doctrine\ORM\EntityRepository; ++use Doctrine\ORM\Query\Parameter; class SomeRepository extends EntityRepository { @@ -5131,6 +5132,30 @@ Tests without assertion will have @doesNotPerformAssertion
+### `AddProphecyTraitRector` + +- class: [`Rector\PHPUnit\Rector\Class_\AddProphecyTraitRector`](/../master/rules/phpunit/src/Rector/Class_/AddProphecyTraitRector.php) +- [test fixtures](/../master/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture) + +Add Prophecy trait for method using $this->prophesize() + +```diff + use PHPUnit\Framework\TestCase; ++use Prophecy\PhpUnit\ProphecyTrait; + + final class ExampleTest extends TestCase + { ++ use ProphecyTrait; ++ + public function testOne(): void + { + $prophecy = $this->prophesize(\AnInterface::class); + } + } +``` + +
+ ### `AddSeeTestAnnotationRector` - class: [`Rector\PHPUnit\Rector\Class_\AddSeeTestAnnotationRector`](/../master/rules/phpunit/src/Rector/Class_/AddSeeTestAnnotationRector.php) diff --git a/packages/post-rector/src/Application/PostFileProcessor.php b/packages/post-rector/src/Application/PostFileProcessor.php index 6b4db9c1d12c..846fc2378953 100644 --- a/packages/post-rector/src/Application/PostFileProcessor.php +++ b/packages/post-rector/src/Application/PostFileProcessor.php @@ -21,7 +21,7 @@ final class PostFileProcessor */ public function __construct(array $postRectors) { - $this->sortByPriorityAndSetCommanders($postRectors); + $this->postRectors = $this->sortByPriority($postRectors); } /** @@ -41,8 +41,9 @@ public function traverse(array $nodes): array /** * @param PostRectorInterface[] $postRectors + * @return PostRectorInterface[] */ - private function sortByPriorityAndSetCommanders(array $postRectors): void + private function sortByPriority(array $postRectors): array { $postRectorsByPriority = []; @@ -56,6 +57,6 @@ private function sortByPriorityAndSetCommanders(array $postRectors): void krsort($postRectorsByPriority); - $this->postRectors = $postRectorsByPriority; + return $postRectorsByPriority; } } diff --git a/packages/rector-generator/src/Command/CreateRectorCommand.php b/packages/rector-generator/src/Command/CreateRectorCommand.php index d24ad8bb042c..7213e2803d72 100644 --- a/packages/rector-generator/src/Command/CreateRectorCommand.php +++ b/packages/rector-generator/src/Command/CreateRectorCommand.php @@ -117,7 +117,7 @@ protected function configure(): void { $this->setName(CommandNaming::classToName(self::class)); $this->setAliases(['c']); - $this->setDescription('[Dev] Create a new Rector, in a proper location, with new tests'); + $this->setDescription('[DEV] Create a new Rector, in a proper location, with new tests'); } protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/packages/rector-generator/src/TemplateVariablesFactory.php b/packages/rector-generator/src/TemplateVariablesFactory.php index 8b9765d51702..fe79841f397c 100644 --- a/packages/rector-generator/src/TemplateVariablesFactory.php +++ b/packages/rector-generator/src/TemplateVariablesFactory.php @@ -10,7 +10,6 @@ use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Name\FullyQualified; use Rector\Core\PhpParser\Printer\BetterStandardPrinter; -use Rector\Core\Util\RectorStrings; use Rector\RectorGenerator\ValueObject\Configuration; final class TemplateVariablesFactory @@ -32,7 +31,7 @@ public function createFromConfiguration(Configuration $configuration): array { $data = [ '_Package_' => $configuration->getPackage(), - '_package_' => RectorStrings::camelCaseToDashes($configuration->getPackage()), + '_package_' => $configuration->getPackageDirectory(), '_Category_' => $configuration->getCategory(), '_Description_' => $configuration->getDescription(), '_Name_' => $configuration->getName(), diff --git a/packages/rector-generator/src/ValueObject/Configuration.php b/packages/rector-generator/src/ValueObject/Configuration.php index bd492324a07c..7bab42fd0e6b 100644 --- a/packages/rector-generator/src/ValueObject/Configuration.php +++ b/packages/rector-generator/src/ValueObject/Configuration.php @@ -113,6 +113,11 @@ public function getPackage(): string public function getPackageDirectory(): string { + // special cases + if ($this->package === 'PHPUnit') { + return 'phpunit'; + } + return RectorStrings::camelCaseToDashes($this->package); } diff --git a/rules/architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php b/rules/architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php index d781511c1866..0a6dd6553650 100644 --- a/rules/architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php +++ b/rules/architecture/src/Rector/Class_/ConstructorInjectionToActionInjectionRector.php @@ -304,7 +304,9 @@ private function removeAssignsFromConstructor(ClassMethod $classMethod): void private function removeUnusedProperties(Class_ $class): void { - foreach (array_keys($this->propertyFetchToParamsToRemoveFromConstructor) as $propertyFetchName) { + $propertyFetchNames = array_keys($this->propertyFetchToParamsToRemoveFromConstructor); + + foreach ($propertyFetchNames as $propertyFetchName) { /** @var string $propertyFetchName */ $this->classManipulator->removeProperty($class, $propertyFetchName); } diff --git a/rules/doctrine-code-quality/src/Rector/Class_/ChangeQuerySetParametersMethodParameterFromArrayToArrayCollectionRector.php b/rules/doctrine-code-quality/src/Rector/Class_/ChangeQuerySetParametersMethodParameterFromArrayToArrayCollectionRector.php index 211bc43a173c..41be3fb8fcb2 100644 --- a/rules/doctrine-code-quality/src/Rector/Class_/ChangeQuerySetParametersMethodParameterFromArrayToArrayCollectionRector.php +++ b/rules/doctrine-code-quality/src/Rector/Class_/ChangeQuerySetParametersMethodParameterFromArrayToArrayCollectionRector.php @@ -83,8 +83,9 @@ public function getSomething() PHP , <<<'PHP' - -use Doctrine\Common\Collections\ArrayCollection;use Doctrine\ORM\EntityRepository;use Doctrine\ORM\Query\Parameter; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Query\Parameter; class SomeRepository extends EntityRepository { diff --git a/rules/doctrine-gedmo-to-knplabs/src/Rector/Class_/TimestampableBehaviorRector.php b/rules/doctrine-gedmo-to-knplabs/src/Rector/Class_/TimestampableBehaviorRector.php index 75f695881c92..86cf7fd00c6f 100644 --- a/rules/doctrine-gedmo-to-knplabs/src/Rector/Class_/TimestampableBehaviorRector.php +++ b/rules/doctrine-gedmo-to-knplabs/src/Rector/Class_/TimestampableBehaviorRector.php @@ -73,7 +73,7 @@ public function getNodeTypes(): array */ public function refactor(Node $node): ?Node { - if (! $this->classManipulator->hasClassTrait($node, 'Gedmo\Timestampable\Traits\TimestampableEntity')) { + if (! $this->classManipulator->hasTrait($node, 'Gedmo\Timestampable\Traits\TimestampableEntity')) { return null; } diff --git a/rules/phpunit/src/Rector/Class_/AddProphecyTraitRector.php b/rules/phpunit/src/Rector/Class_/AddProphecyTraitRector.php new file mode 100644 index 000000000000..edc6fa1f9a21 --- /dev/null +++ b/rules/phpunit/src/Rector/Class_/AddProphecyTraitRector.php @@ -0,0 +1,122 @@ +classInsertManipulator = $classInsertManipulator; + $this->classManipulator = $classManipulator; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Add Prophecy trait for method using $this->prophesize()', [ + new CodeSample( + <<<'PHP' +use PHPUnit\Framework\TestCase; + +final class ExampleTest extends TestCase +{ + public function testOne(): void + { + $prophecy = $this->prophesize(\AnInterface::class); + } +} +PHP +, + <<<'PHP' +use PHPUnit\Framework\TestCase; +use Prophecy\PhpUnit\ProphecyTrait; + +final class ExampleTest extends TestCase +{ + use ProphecyTrait; + + public function testOne(): void + { + $prophecy = $this->prophesize(\AnInterface::class); + } +} +PHP + + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if ($this->shouldSkipClass($node)) { + return null; + } + + $this->classInsertManipulator->addAsFirstTrait($node, self::PROPHECY_TRAIT); + + return $node; + } + + private function hasProphesizeMethodCall(Class_ $node): bool + { + return (bool) $this->betterNodeFinder->findFirst($node, function (Node $node) { + return $this->isMethodCall($node, 'this', 'prophesize'); + }); + } + + private function shouldSkipClass(Class_ $class): bool + { + if (! $this->isInTestClass($class)) { + return true; + } + + $hasProphesizeMethodCall = $this->hasProphesizeMethodCall($class); + if ($hasProphesizeMethodCall === false) { + return true; + } + + return $this->classManipulator->hasTrait($class, self::PROPHECY_TRAIT); + } +} diff --git a/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/AddProphecyTraitRectorTest.php b/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/AddProphecyTraitRectorTest.php new file mode 100644 index 000000000000..5655e28f7766 --- /dev/null +++ b/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/AddProphecyTraitRectorTest.php @@ -0,0 +1,30 @@ +doTestFile($file); + } + + public function provideData(): Iterator + { + return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + protected function getRectorClass(): string + { + return AddProphecyTraitRector::class; + } +} diff --git a/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/fixture.php.inc b/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..ecddb3557870 --- /dev/null +++ b/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/fixture.php.inc @@ -0,0 +1,32 @@ +prophesize(\AnInterface::class); + } +} + +?> +----- +prophesize(\AnInterface::class); + } +} + +?> diff --git a/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/skip_if_already_added.php.inc b/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/skip_if_already_added.php.inc new file mode 100644 index 000000000000..82300ec8bfff --- /dev/null +++ b/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/skip_if_already_added.php.inc @@ -0,0 +1,16 @@ +prophesize(\AnInterface::class); + } +} diff --git a/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/skip_non_prophesize.php.inc b/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/skip_non_prophesize.php.inc new file mode 100644 index 000000000000..230b84af0aa2 --- /dev/null +++ b/rules/phpunit/tests/Rector/Class_/AddProphecyTraitRector/Fixture/skip_non_prophesize.php.inc @@ -0,0 +1,15 @@ +prophesize(\AnInterface::class); + } +} diff --git a/src/Console/Command/ScreenFileCommand.php b/src/Console/Command/ScreenFileCommand.php index 3d7a03e58ea4..346bbc64048a 100644 --- a/src/Console/Command/ScreenFileCommand.php +++ b/src/Console/Command/ScreenFileCommand.php @@ -98,7 +98,7 @@ public function __construct( protected function configure(): void { $this->setName(CommandNaming::classToName(self::class)); - $this->setDescription('[Dev] Load file and print nodes meta data - super helpful to learn to build rules'); + $this->setDescription('[DEV] Load file and print nodes meta data - super helpful to learn to build rules'); $this->addArgument(self::FILE_ARGUMENT, InputArgument::REQUIRED, 'Path to file to be screened'); } diff --git a/src/PhpParser/Node/Manipulator/ClassInsertManipulator.php b/src/PhpParser/Node/Manipulator/ClassInsertManipulator.php index 1e9d2d39ef66..195f62ae7b7b 100644 --- a/src/PhpParser/Node/Manipulator/ClassInsertManipulator.php +++ b/src/PhpParser/Node/Manipulator/ClassInsertManipulator.php @@ -84,7 +84,7 @@ public function addAsFirstTrait(Class_ $class, string $traitName): void { $trait = new TraitUse([new FullyQualified($traitName)]); - $this->addStatementToClassBeforeTypes($class, $trait, TraitUse::class, Property::class); + $this->addStatementToClassBeforeTypes($class, $trait, TraitUse::class, Property::class, ClassMethod::class); } /** diff --git a/src/PhpParser/Node/Manipulator/ClassManipulator.php b/src/PhpParser/Node/Manipulator/ClassManipulator.php index 7f5a44b539a7..6f6ae5b5ab8a 100644 --- a/src/PhpParser/Node/Manipulator/ClassManipulator.php +++ b/src/PhpParser/Node/Manipulator/ClassManipulator.php @@ -196,7 +196,7 @@ public function hasPropertyName(Class_ $node, string $name): bool return false; } - public function hasClassTrait(Class_ $class, string $desiredTrait): bool + public function hasTrait(Class_ $class, string $desiredTrait): bool { foreach ($class->getTraitUses() as $traitUse) { if (! $this->nodeNameResolver->haveName($traitUse->traits, $desiredTrait)) { diff --git a/src/Rector/AbstractRector/NameResolverTrait.php b/src/Rector/AbstractRector/NameResolverTrait.php index a2e0db21594c..ceaa349e379b 100644 --- a/src/Rector/AbstractRector/NameResolverTrait.php +++ b/src/Rector/AbstractRector/NameResolverTrait.php @@ -6,6 +6,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\FuncCall; +use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\Variable; use PhpParser\Node\Identifier; use PhpParser\Node\Name; @@ -77,6 +78,19 @@ protected function isFuncCallName(Node $node, string $name): bool return $this->isName($node, $name); } + protected function isMethodCall(Node $node, string $variableName, string $methodName): bool + { + if (! $node instanceof MethodCall) { + return false; + } + + if (! $this->isName($node->var, $variableName)) { + return false; + } + + return (bool) $this->isName($node->name, $methodName); + } + protected function isVariableName(?Node $node, string $name): bool { if (! $node instanceof Variable) { diff --git a/src/Testing/Finder/RectorsFinder.php b/src/Testing/Finder/RectorsFinder.php index f1a6c477e97b..8fbfde8f31e3 100644 --- a/src/Testing/Finder/RectorsFinder.php +++ b/src/Testing/Finder/RectorsFinder.php @@ -71,7 +71,7 @@ public function findInDirectories(array $directories): array $rector = $reflectionClass->newInstanceWithoutConstructor(); if (! $rector instanceof RectorInterface) { - // lowercase letter bug in RototLoader + // lowercase letter bug in RobotLoader if (Strings::endsWith($class, 'rector')) { continue; } diff --git a/stubs/Prophecy/PhpUnit/ProphecyTrait.php b/stubs/Prophecy/PhpUnit/ProphecyTrait.php new file mode 100644 index 000000000000..da14910098d4 --- /dev/null +++ b/stubs/Prophecy/PhpUnit/ProphecyTrait.php @@ -0,0 +1,14 @@ +setName(CommandNaming::classToName(self::class)); - $this->setDescription('[Docs] Dump overview of all Nodes'); + $this->setDescription('[DOCS] Dump overview of all Nodes'); $this->addOption( 'output-format', 'o', diff --git a/utils/documentation-generator/src/Command/DumpRectorsCommand.php b/utils/documentation-generator/src/Command/DumpRectorsCommand.php index 2ef3181f609e..a11c98824711 100644 --- a/utils/documentation-generator/src/Command/DumpRectorsCommand.php +++ b/utils/documentation-generator/src/Command/DumpRectorsCommand.php @@ -44,7 +44,7 @@ public function __construct(RectorsFinder $rectorsFinder, array $dumpRectorsOutp protected function configure(): void { $this->setName(CommandNaming::classToName(self::class)); - $this->setDescription('[Docs] Dump overview of all Rectors'); + $this->setDescription('[DOCS] Dump overview of all Rectors'); $this->addOption( self::OUTPUT_FORMAT_OPTION, 'o', diff --git a/utils/phpstan-attribute-type-syncer/src/Command/SyncTypesCommand.php b/utils/phpstan-attribute-type-syncer/src/Command/SyncTypesCommand.php index 0f12c1f7edbc..736455193f90 100644 --- a/utils/phpstan-attribute-type-syncer/src/Command/SyncTypesCommand.php +++ b/utils/phpstan-attribute-type-syncer/src/Command/SyncTypesCommand.php @@ -61,7 +61,7 @@ public function __construct( protected function configure(): void { $this->setName(CommandNaming::classToName(self::class)); - $this->setDescription('[Dev] Synchronize PHPStan types to attribute aware types in Rectors'); + $this->setDescription('[DEV] Synchronize PHPStan types to attribute aware types in Rectors'); } protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/utils/phpstan-static-type-mapper-checker/src/Command/CheckStaticTypeMappersCommand.php b/utils/phpstan-static-type-mapper-checker/src/Command/CheckStaticTypeMappersCommand.php index 5a6c13f8d92f..75e316b8659c 100644 --- a/utils/phpstan-static-type-mapper-checker/src/Command/CheckStaticTypeMappersCommand.php +++ b/utils/phpstan-static-type-mapper-checker/src/Command/CheckStaticTypeMappersCommand.php @@ -49,7 +49,7 @@ public function __construct( protected function configure(): void { $this->setName(CommandNaming::classToName(self::class)); - $this->setDescription('[Dev] check PHPStan types to TypeMappers'); + $this->setDescription('[DEV] check PHPStan types to TypeMappers'); } protected function execute(InputInterface $input, OutputInterface $output): int