From 4471d55f12922ea7e55b424c894ad6a49c300fbe Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 1 Sep 2025 16:41:39 +0200 Subject: [PATCH 1/2] [code-quality] Add AddReturnTypeToDependedRector --- config/sets/phpunit-code-quality.php | 2 + .../AddReturnTypeToDependedRectorTest.php | 28 ++++ .../Fixture/skip_non_test_methods.php.inc | 18 +++ .../Fixture/skip_silent_void.php.inc | 17 ++ .../Fixture/some_test.php.inc | 31 ++++ .../Fixture/variable_assigned.php.inc | 35 ++++ .../config/configured_rule.php | 9 ++ .../Class_/AddReturnTypeToDependedRector.php | 150 ++++++++++++++++++ 8 files changed, 290 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/AddReturnTypeToDependedRectorTest.php create mode 100644 rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/Fixture/skip_non_test_methods.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/Fixture/skip_silent_void.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/Fixture/some_test.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/Fixture/variable_assigned.php.inc create mode 100644 rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/config/configured_rule.php create mode 100644 rules/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector.php diff --git a/config/sets/phpunit-code-quality.php b/config/sets/phpunit-code-quality.php index 007215e5..e1f472a0 100644 --- a/config/sets/phpunit-code-quality.php +++ b/config/sets/phpunit-code-quality.php @@ -4,6 +4,7 @@ use Rector\Config\RectorConfig; use Rector\PHPUnit\CodeQuality\Rector\Class_\AddParamTypeFromDependsRector; +use Rector\PHPUnit\CodeQuality\Rector\Class_\AddReturnTypeToDependedRector; use Rector\PHPUnit\CodeQuality\Rector\Class_\ConstructClassMethodToSetUpTestCaseRector; use Rector\PHPUnit\CodeQuality\Rector\Class_\NarrowUnusedSetUpDefinedPropertyRector; use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector; @@ -72,6 +73,7 @@ TypeWillReturnCallableArrowFunctionRector::class, StringCastAssertStringContainsStringRector::class, AddParamTypeFromDependsRector::class, + AddReturnTypeToDependedRector::class, NarrowUnusedSetUpDefinedPropertyRector::class, diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/AddReturnTypeToDependedRectorTest.php b/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/AddReturnTypeToDependedRectorTest.php new file mode 100644 index 00000000..bdfe5714 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/AddReturnTypeToDependedRectorTest.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/Class_/AddReturnTypeToDependedRector/Fixture/skip_non_test_methods.php.inc b/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/Fixture/skip_non_test_methods.php.inc new file mode 100644 index 00000000..7b7d897f --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/Fixture/skip_non_test_methods.php.inc @@ -0,0 +1,18 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/Fixture/variable_assigned.php.inc b/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/Fixture/variable_assigned.php.inc new file mode 100644 index 00000000..87bd2a68 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/Fixture/variable_assigned.php.inc @@ -0,0 +1,35 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/config/configured_rule.php new file mode 100644 index 00000000..3363511c --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([AddReturnTypeToDependedRector::class]); diff --git a/rules/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector.php b/rules/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector.php new file mode 100644 index 00000000..26ac8d96 --- /dev/null +++ b/rules/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector.php @@ -0,0 +1,150 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if (! $this->testsNodeAnalyzer->isInTestClass($node)) { + return null; + } + + $hasChanged = false; + + foreach ($node->getMethods() as $classMethod) { + if (! $classMethod->isPublic()) { + continue; + } + + if (! $this->testsNodeAnalyzer->isTestClassMethod($classMethod)) { + continue; + } + + if (! $this->haveAllReturnsExpr($classMethod)) { + continue; + } + + // already known return type + if ($classMethod->returnType instanceof Node) { + continue; + } + + if ($this->silentVoidResolver->hasSilentVoid($classMethod)) { + continue; + } + + $returns = $this->betterNodeFinder->findReturnsScoped($classMethod); + if (count($returns) !== 1) { + continue; + } + + $soleReturnExpr = $returns[0]->expr; + + // does return a type? + $returnedExprType = $this->getType($soleReturnExpr); + $returnType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($returnedExprType, TypeKind::RETURN); + + if (! $returnType instanceof Node) { + continue; + } + + $classMethod->returnType = $returnType; + $hasChanged = true; + } + + if ($hasChanged === false) { + return null; + } + + return $node; + } + + private function haveAllReturnsExpr(ClassMethod $classMethod): bool + { + $returns = $this->betterNodeFinder->findReturnsScoped($classMethod); + foreach ($returns as $return) { + if (! $return->expr instanceof Expr) { + return false; + } + } + + return true; + } +} From 44b19d63656e02d7f1daa94240437c1f05c3ba9e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 1 Sep 2025 17:10:02 +0200 Subject: [PATCH 2/2] Update rules/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector.php Co-authored-by: Abdul Malik Ikhsan --- .../Rector/Class_/AddReturnTypeToDependedRector.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rules/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector.php b/rules/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector.php index 26ac8d96..f4894495 100644 --- a/rules/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector.php +++ b/rules/CodeQuality/Rector/Class_/AddReturnTypeToDependedRector.php @@ -13,7 +13,7 @@ use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; use Rector\Rector\AbstractRector; use Rector\StaticTypeMapper\StaticTypeMapper; -use Rector\TypeDeclaration\TypeInferer\SilentVoidResolver; +use Rector\TypeDeclaration\NodeAnalyzer\ReturnAnalyzer; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -24,7 +24,7 @@ final class AddReturnTypeToDependedRector extends AbstractRector { public function __construct( private readonly TestsNodeAnalyzer $testsNodeAnalyzer, - private readonly SilentVoidResolver $silentVoidResolver, + private readonly ReturnAnalyzer $returnAnalyzer, private readonly StaticTypeMapper $staticTypeMapper, private readonly BetterNodeFinder $betterNodeFinder ) { @@ -106,11 +106,11 @@ public function refactor(Node $node): ?Node continue; } - if ($this->silentVoidResolver->hasSilentVoid($classMethod)) { - continue; + $returns = $this->betterNodeFinder->findReturnsScoped($classMethod); + if (! $this->returnAnalyzer->hasOnlyReturnWithExpr($classMethod, $returns)) { + return null; } - $returns = $this->betterNodeFinder->findReturnsScoped($classMethod); if (count($returns) !== 1) { continue; }