diff --git a/config/sets/phpunit-code-quality.php b/config/sets/phpunit-code-quality.php index 9a168f34..d9fd8c33 100644 --- a/config/sets/phpunit-code-quality.php +++ b/config/sets/phpunit-code-quality.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Rector\Config\RectorConfig; +use Rector\PHPUnit\CodeQuality\Rector\CallLike\DirectInstanceOverMockArgRector; use Rector\PHPUnit\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector; use Rector\PHPUnit\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector; use Rector\PHPUnit\CodeQuality\Rector\Class_\ConstructClassMethodToSetUpTestCaseRector; @@ -120,6 +121,7 @@ RemoveExpectAnyFromMockRector::class, SingleMockPropertyTypeRector::class, SimplerWithIsInstanceOfRector::class, + DirectInstanceOverMockArgRector::class, FinalizeTestCaseClassRector::class, DeclareStrictTypesTestsRector::class, diff --git a/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/DirectInstanceOverMockArgRectorTest.php b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/DirectInstanceOverMockArgRectorTest.php new file mode 100644 index 00000000..8861c737 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/DirectInstanceOverMockArgRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Fixture/handle_array_item.php.inc b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Fixture/handle_array_item.php.inc new file mode 100644 index 00000000..46d27a16 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Fixture/handle_array_item.php.inc @@ -0,0 +1,37 @@ +createMock(Request::class), + ]; + } +} + +?> +----- + diff --git a/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Fixture/handle_static_call.php.inc b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Fixture/handle_static_call.php.inc new file mode 100644 index 00000000..bcb7c47f --- /dev/null +++ b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Fixture/handle_static_call.php.inc @@ -0,0 +1,37 @@ +createMock(Request::class), + ); + } +} + +?> +----- + diff --git a/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Fixture/skip_another_object.php.inc b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Fixture/skip_another_object.php.inc new file mode 100644 index 00000000..2a30c7a4 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Fixture/skip_another_object.php.inc @@ -0,0 +1,17 @@ +createMock(AnotherObject::class), + ); + } +} diff --git a/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Source/AnotherObject.php b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Source/AnotherObject.php new file mode 100644 index 00000000..8c10a994 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector/Source/AnotherObject.php @@ -0,0 +1,8 @@ +withRules([DirectInstanceOverMockArgRector::class]); diff --git a/rules-tests/PHPUnit120/Rector/CallLike/CreateStubOverCreateMockArgRector/Fixture/handle_array_item.php.inc b/rules-tests/PHPUnit120/Rector/CallLike/CreateStubOverCreateMockArgRector/Fixture/handle_array_item.php.inc new file mode 100644 index 00000000..c1341a3b --- /dev/null +++ b/rules-tests/PHPUnit120/Rector/CallLike/CreateStubOverCreateMockArgRector/Fixture/handle_array_item.php.inc @@ -0,0 +1,35 @@ +createMock(\stdClass::class), + ]; + } +} + +?> +----- +createStub(\stdClass::class), + ]; + } +} + +?> diff --git a/rules/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector.php b/rules/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector.php new file mode 100644 index 00000000..dd2e58f8 --- /dev/null +++ b/rules/CodeQuality/Rector/CallLike/DirectInstanceOverMockArgRector.php @@ -0,0 +1,165 @@ +someMethod($this->createMock(Request::class)); + } + + private function someMethod($someClass) + { + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; + +final class SomeTest extends TestCase +{ + public function test() + { + $this->someMethod(new Request()); + } + + private function someMethod($someClass) + { + } +} +CODE_SAMPLE + ), + + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [StaticCall::class, MethodCall::class, New_::class, ArrayItem::class]; + } + + /** + * @param MethodCall|StaticCall|New_|ArrayItem $node + */ + public function refactor(Node $node): MethodCall|StaticCall|New_|ArrayItem|null + { + $scope = ScopeFetcher::fetch($node); + if (! $scope->isInClass()) { + return null; + } + + $classReflection = $scope->getClassReflection(); + if (! $classReflection->is(PHPUnitClassName::TEST_CASE)) { + return null; + } + + $hasChanged = false; + + if ($node instanceof ArrayItem) { + + return $this->refactorArrayItem($node); + } + + foreach ($node->getArgs() as $arg) { + $firstArg = $this->matchCreateMockMethodCallArg($arg->value); + if (! $firstArg instanceof Arg) { + continue; + } + + $className = $this->valueResolver->getValue($firstArg->value); + if (! in_array($className, [SymfonyClass::REQUEST, SymfonyClass::REQUEST_STACK])) { + continue; + } + + $arg->value = new New_(new FullyQualified($className)); + $hasChanged = true; + } + + if ($hasChanged) { + return $node; + } + + return null; + } + + private function matchCreateMockMethodCallArg(Expr $expr): ?Arg + { + if (! $expr instanceof MethodCall) { + return null; + } + + $methodCall = $expr; + if (! $this->isName($methodCall->name, 'createMock')) { + return null; + } + + if ($methodCall->isFirstClassCallable()) { + return null; + } + + return $methodCall->getArgs()[0]; + } + + private function refactorArrayItem(ArrayItem $arrayItem): ?ArrayItem + { + $mockedCallArg = $this->matchCreateMockMethodCallArg($arrayItem->value); + if (! $mockedCallArg instanceof Arg) { + return null; + } + + $className = $this->valueResolver->getValue($mockedCallArg->value); + + if (! in_array($className, [SymfonyClass::REQUEST, SymfonyClass::REQUEST_STACK])) { + return null; + } + + $arrayItem->value = new New_(new FullyQualified($className)); + + return $arrayItem; + } +} diff --git a/rules/PHPUnit120/Rector/CallLike/CreateStubOverCreateMockArgRector.php b/rules/PHPUnit120/Rector/CallLike/CreateStubOverCreateMockArgRector.php index 86a1d513..1aac6d07 100644 --- a/rules/PHPUnit120/Rector/CallLike/CreateStubOverCreateMockArgRector.php +++ b/rules/PHPUnit120/Rector/CallLike/CreateStubOverCreateMockArgRector.php @@ -5,6 +5,7 @@ namespace Rector\PHPUnit\PHPUnit120\Rector\CallLike; use PhpParser\Node; +use PhpParser\Node\ArrayItem; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\StaticCall; @@ -25,7 +26,7 @@ final class CreateStubOverCreateMockArgRector extends AbstractRector public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( - 'Use createStub() over createMock() when used as argument and does not add any mock requirements', + 'Use createStub() over createMock() when used as argument or array value and does not add any mock requirements', [ new CodeSample( <<<'CODE_SAMPLE' @@ -69,13 +70,13 @@ private function someMethod($someClass) */ public function getNodeTypes(): array { - return [StaticCall::class, MethodCall::class, New_::class]; + return [StaticCall::class, MethodCall::class, New_::class, ArrayItem::class]; } /** - * @param MethodCall|StaticCall|New_ $node + * @param MethodCall|StaticCall|New_|ArrayItem $node */ - public function refactor(Node $node): MethodCall|StaticCall|New_|null + public function refactor(Node $node): MethodCall|StaticCall|New_|ArrayItem|null { $scope = ScopeFetcher::fetch($node); if (! $scope->isInClass()) { @@ -87,6 +88,21 @@ public function refactor(Node $node): MethodCall|StaticCall|New_|null return null; } + if ($node instanceof ArrayItem) { + if (! $node->value instanceof MethodCall) { + return null; + } + + $methodCall = $node->value; + if (! $this->isName($methodCall->name, 'createMock')) { + return null; + } + + $methodCall->name = new Identifier('createStub'); + + return $node; + } + $hasChanges = false; foreach ($node->getArgs() as $arg) { diff --git a/stubs/Symfony/Component/HttpFoundation/Request.php b/stubs/Symfony/Component/HttpFoundation/Request.php new file mode 100644 index 00000000..6d2cfca6 --- /dev/null +++ b/stubs/Symfony/Component/HttpFoundation/Request.php @@ -0,0 +1,7 @@ +