From da5cbace5e4af71eb580b963582c019513308737 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sun, 29 Sep 2024 13:51:19 +0200 Subject: [PATCH] [CodeQuality] Add NarrowSingleWillReturnCallbackRector --- .../Fixture/single_match.php.inc | 45 +++++ ...rrowSingleWillReturnCallbackRectorTest.php | 28 +++ .../Source/SomeMockedClass.php | 7 + .../config/configured_rule.php | 10 ++ .../NarrowSingleWillReturnCallbackRector.php | 168 ++++++++++++++++++ 5 files changed, 258 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/Fixture/single_match.php.inc create mode 100644 rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/NarrowSingleWillReturnCallbackRectorTest.php create mode 100644 rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/Source/SomeMockedClass.php create mode 100644 rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/config/configured_rule.php create mode 100644 rules/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector.php diff --git a/rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/Fixture/single_match.php.inc b/rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/Fixture/single_match.php.inc new file mode 100644 index 00000000..87dfa36e --- /dev/null +++ b/rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/Fixture/single_match.php.inc @@ -0,0 +1,45 @@ +exactly(3); + + $someServiceMock = $this->createMock(SomeMockedClass::class); + $someServiceMock->expects($matcher) + ->method('prepare') + ->willReturnCallback(function (...$parameters) use ($matcher) { + match ($matcher->getInvocationCount()) { + 1 => $this->assertSame([1], $parameters), + }; + }); + } +} + +?> +----- +exactly(3); + + $someServiceMock = $this->createMock(SomeMockedClass::class); + $someServiceMock->expects($matcher) + ->method('prepare') + ->with([1]); + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/NarrowSingleWillReturnCallbackRectorTest.php b/rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/NarrowSingleWillReturnCallbackRectorTest.php new file mode 100644 index 00000000..efd5387e --- /dev/null +++ b/rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/NarrowSingleWillReturnCallbackRectorTest.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/MethodCall/NarrowSingleWillReturnCallbackRector/Source/SomeMockedClass.php b/rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/Source/SomeMockedClass.php new file mode 100644 index 00000000..c2e2ddcc --- /dev/null +++ b/rules-tests/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector/Source/SomeMockedClass.php @@ -0,0 +1,7 @@ +rule(NarrowSingleWillReturnCallbackRector::class); +}; diff --git a/rules/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector.php b/rules/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector.php new file mode 100644 index 00000000..36caba5e --- /dev/null +++ b/rules/CodeQuality/Rector/MethodCall/NarrowSingleWillReturnCallbackRector.php @@ -0,0 +1,168 @@ +exactly(1); + + $this->personServiceMock->expects($matcher) + ->willReturnCallback(function (...$parameters) use ($matcher) { + match ($matcher->getInvocationCount()) { + 1 => $this->assertSame([1], $parameters), + }; + }); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +use PHPUnit\Framework\TestCase; + +final class SomeTest extends TestCase +{ + public function run() + { + $matcher = $this->exactly(1); + + $this->personServiceMock->expects($matcher) + ->with([1], $parameters); + } +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [MethodCall::class]; + } + + /** + * @param MethodCall $node + */ + public function refactor(Node $node): MethodCall|null + { + if (! $this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + if (! $this->isName($node->name, 'willReturnCallback')) { + return null; + } + + if ($node->isFirstClassCallable()) { + return null; + } + + $firstArg = $node->getArgs()[0]; + + // skip as most likely nested array of unique values + if ($firstArg->unpack) { + return null; + } + + if (! $firstArg->value instanceof Closure) { + return null; + } + + $closure = $firstArg->value; + + // handle easy path of single stmt first + if (count($closure->stmts) !== 1) { + return null; + } + + $onlyStmts = $closure->stmts[0]; + if (! $onlyStmts instanceof Expression) { + return null; + } + + if (! $onlyStmts->expr instanceof Match_) { + return null; + } + + $match = $onlyStmts->expr; + + // has more options + if (count($match->arms) !== 1) { + return null; + } + + $onlyArm = $match->arms[0]; + if ($onlyArm->conds === null || count($onlyArm->conds) !== 1) { + return null; + } + + $onlyArmCond = $onlyArm->conds[0]; + if (! $onlyArmCond instanceof LNumber) { + return null; + } + + if ($onlyArmCond->value !== 1) { + return null; + } + + // we look for $this->assertSame(...) + if (! $onlyArm->body instanceof MethodCall) { + return null; + } + + $methodCall = $onlyArm->body; + if (! $this->isName($methodCall->var, 'this')) { + return null; + } + + if (! $this->isName($methodCall->name, 'assertSame')) { + return null; + } + + $expectedArg = $methodCall->getArgs()[0]; + + $node->name = new Identifier('with'); + $node->args = [new Arg($expectedArg->value)]; + + return $node; + } +}