diff --git a/config/sets/phpunit-code-quality.php b/config/sets/phpunit-code-quality.php index 7cc0e0ff..bbbc7ad9 100644 --- a/config/sets/phpunit-code-quality.php +++ b/config/sets/phpunit-code-quality.php @@ -57,6 +57,7 @@ NarrowUnusedSetUpDefinedPropertyRector::class, // specific asserts + AssertCompareOnCountableWithMethodToAssertCountRector::class, AssertComparisonToSpecificMethodRector::class, AssertNotOperatorRector::class, diff --git a/rector.php b/rector.php index be43a2aa..f2330fe9 100644 --- a/rector.php +++ b/rector.php @@ -23,14 +23,13 @@ // object types StringClassNameToClassConstantRector::class => [ - __DIR__ . '/src/Rector/Class_/TestListenerToHooksRector.php', __DIR__ . '/src/NodeAnalyzer/TestsNodeAnalyzer.php', __DIR__ . '/config', __DIR__ . '/src/NodeFinder/DataProviderClassMethodFinder.php', ], ]) ->withPhpSets() - ->withAttributesSets(all: true) + ->withAttributesSets() ->withPreparedSets( deadCode: true, codeQuality: true, diff --git a/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/AssertFuncCallToPHPUnitAssertRectorTest.php b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/AssertFuncCallToPHPUnitAssertRectorTest.php new file mode 100644 index 00000000..ced987f2 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/AssertFuncCallToPHPUnitAssertRectorTest.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/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/assert_bool_in_test.php.inc b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/assert_bool_in_test.php.inc new file mode 100644 index 00000000..4934922e --- /dev/null +++ b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/assert_bool_in_test.php.inc @@ -0,0 +1,31 @@ + +----- +assertTrue((bool) $response); + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/assert_compare_context.php.inc b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/assert_compare_context.php.inc new file mode 100644 index 00000000..c58b5144 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/assert_compare_context.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/assert_null_compare_context.php.inc b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/assert_null_compare_context.php.inc new file mode 100644 index 00000000..f2c34965 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/assert_null_compare_context.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/method_exists_context.php.inc b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/method_exists_context.php.inc new file mode 100644 index 00000000..59edebfa --- /dev/null +++ b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/method_exists_context.php.inc @@ -0,0 +1,27 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/simple_context.php.inc b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/simple_context.php.inc new file mode 100644 index 00000000..8c16d485 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/Fixture/simple_context.php.inc @@ -0,0 +1,31 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/config/configured_rule.php new file mode 100644 index 00000000..b128ff36 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([AssertFuncCallToPHPUnitAssertRector::class]); diff --git a/rules/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector.php b/rules/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector.php new file mode 100644 index 00000000..50508fa6 --- /dev/null +++ b/rules/CodeQuality/Rector/FuncCall/AssertFuncCallToPHPUnitAssertRector.php @@ -0,0 +1,172 @@ +assertSame(100, $value, "message");'), + ]); + } + + /** + * @return class-string[] + */ + public function getNodeTypes(): array + { + return [FuncCall::class]; + } + + /** + * @param FuncCall $node + */ + public function refactor(Node $node): StaticCall|MethodCall|null + { + if ($node->isFirstClassCallable()) { + return null; + } + + if (! $this->isName($node, 'assert')) { + return null; + } + + if (! $this->isTestFilePath($node) && ! $this->isBehatContext($node)) { + return null; + } + + $comparedExpr = $node->getArgs()[0] + ->value; + + if ($comparedExpr instanceof Equal) { + $methodName = AssertMethod::ASSERT_EQUALS; + $exprs = [$comparedExpr->right, $comparedExpr->left]; + + } elseif ($comparedExpr instanceof Identical) { + $methodName = AssertMethod::ASSERT_SAME; + $exprs = [$comparedExpr->right, $comparedExpr->left]; + + } elseif ($comparedExpr instanceof NotIdentical) { + if ($this->valueResolver->isNull($comparedExpr->right)) { + $methodName = 'assertNotNull'; + $exprs = [$comparedExpr->left]; + } else { + return null; + } + + } elseif ($comparedExpr instanceof Bool_) { + $methodName = 'assertTrue'; + $exprs = [$comparedExpr]; + } elseif ($comparedExpr instanceof FuncCall) { + if ($this->isName($comparedExpr, 'method_exists')) { + $methodName = 'assertTrue'; + $exprs = [$comparedExpr]; + } else { + return null; + } + } elseif ($comparedExpr instanceof Instanceof_) { + // outside TestCase + $methodName = 'assertInstanceOf'; + $exprs = []; + + if ($comparedExpr->class instanceof FullyQualified) { + $classConstFetch = new ClassConstFetch($comparedExpr->class, 'class'); + $exprs[] = $classConstFetch; + } else { + return null; + } + + $exprs[] = $comparedExpr->expr; + } else { + return null; + } + + // is there a comment message + if (isset($node->getArgs()[1])) { + $exprs[] = $node->getArgs()[1]->value; + } + + return $this->createCall($node, $methodName, $exprs); + } + + private function isBehatContext(FuncCall $funcCall): bool + { + $scope = ScopeFetcher::fetch($funcCall); + if (! $scope->getClassReflection() instanceof ClassReflection) { + return false; + } + + $className = $scope->getClassReflection() + ->getName(); + + // special case with static call + return str_ends_with($className, 'Context'); + } + + private function isTestFilePath(FuncCall $funcCall): bool + { + $scope = ScopeFetcher::fetch($funcCall); + if (! $scope->getClassReflection() instanceof ClassReflection) { + return false; + } + + $className = $scope->getClassReflection() + ->getName(); + if (str_ends_with($className, 'Test')) { + return true; + } + return str_ends_with($className, 'TestCase'); + } + + /** + * @param Expr[] $exprs + */ + private function createCall(FuncCall $funcCall, string $methodName, array $exprs): MethodCall|StaticCall + { + $args = []; + foreach ($exprs as $expr) { + $args[] = new Arg($expr); + } + + if ($this->isBehatContext($funcCall)) { + $assertFullyQualified = new FullyQualified(PHPUnitClassName::ASSERT); + return new StaticCall($assertFullyQualified, $methodName, $args); + } + + return new MethodCall(new Variable('this'), $methodName, $args); + } +} diff --git a/src/Enum/AssertMethod.php b/src/Enum/AssertMethod.php index 496d749d..cc289642 100644 --- a/src/Enum/AssertMethod.php +++ b/src/Enum/AssertMethod.php @@ -15,4 +15,14 @@ final class AssertMethod * @var string */ public const ASSERT_TRUE = 'assertTrue'; + + /** + * @var string + */ + public const ASSERT_EQUALS = 'assertEquals'; + + /** + * @var string + */ + public const ASSERT_SAME = 'assertSame'; }