diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/AddVoidReturnTypeWhereNoReturnRectorTest.php b/rules-tests/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector/AddVoidReturnTypeWhereNoReturnRectorTest.php similarity index 86% rename from rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/AddVoidReturnTypeWhereNoReturnRectorTest.php rename to rules-tests/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector/AddVoidReturnTypeWhereNoReturnRectorTest.php index 177cc1adb2c..b2e0c1655ec 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/AddVoidReturnTypeWhereNoReturnRectorTest.php +++ b/rules-tests/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector/AddVoidReturnTypeWhereNoReturnRectorTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; +namespace Rector\Tests\TypeDeclaration\Rector\ClassLike\AddVoidReturnTypeWhereNoReturnRector; use Iterator; use PHPUnit\Framework\Attributes\DataProvider; diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/exception.php.inc b/rules-tests/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector/Fixture/exception.php.inc similarity index 64% rename from rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/exception.php.inc rename to rules-tests/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector/Fixture/exception.php.inc index b2587a9d688..3d3100756b2 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/exception.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector/Fixture/exception.php.inc @@ -1,6 +1,6 @@ +----- + diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/magic_invoke.php.inc b/rules-tests/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector/Fixture/magic_invoke.php.inc similarity index 51% rename from rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/magic_invoke.php.inc rename to rules-tests/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector/Fixture/magic_invoke.php.inc index 98a1d133b23..d26a355c68a 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/magic_invoke.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector/Fixture/magic_invoke.php.inc @@ -1,6 +1,6 @@ withRules([AddVoidReturnTypeWhereNoReturnRector::class]); diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/final_protected_method.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/final_protected_method.php.inc deleted file mode 100644 index f71c99e20c2..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/final_protected_method.php.inc +++ /dev/null @@ -1,25 +0,0 @@ - ------ - diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/skip_abstract_method.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/skip_abstract_method.php.inc deleted file mode 100644 index 55fe49bb8ca..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector/Fixture/skip_abstract_method.php.inc +++ /dev/null @@ -1,8 +0,0 @@ -withRules([ReturnTypeFromMockObjectRector::class]); diff --git a/rules/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector.php b/rules/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector.php similarity index 55% rename from rules/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector.php rename to rules/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector.php index 3c8cb16aa3c..f34dd3d8577 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/AddVoidReturnTypeWhereNoReturnRector.php +++ b/rules/TypeDeclaration/Rector/ClassLike/AddVoidReturnTypeWhereNoReturnRector.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace Rector\TypeDeclaration\Rector\ClassMethod; +namespace Rector\TypeDeclaration\Rector\ClassLike; use PhpParser\Node; use PhpParser\Node\Expr\Throw_; use PhpParser\Node\Identifier; +use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Expression; -use Rector\PHPStan\ScopeFetcher; +use PHPStan\Reflection\ClassReflection; use Rector\Rector\AbstractRector; -use Rector\Reflection\ClassModifierChecker; use Rector\RuleDoc\CodeSample\CodeSample; use Rector\RuleDoc\RuleDefinition; use Rector\TypeDeclaration\TypeInferer\SilentVoidResolver; @@ -20,14 +20,13 @@ use Rector\VersionBonding\Contract\MinPhpVersionInterface; /** - * @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector\AddVoidReturnTypeWhereNoReturnRectorTest + * @see \Rector\Tests\TypeDeclaration\Rector\ClassLike\AddVoidReturnTypeWhereNoReturnRector\AddVoidReturnTypeWhereNoReturnRectorTest */ final class AddVoidReturnTypeWhereNoReturnRector extends AbstractRector implements MinPhpVersionInterface { public function __construct( private readonly SilentVoidResolver $silentVoidResolver, private readonly ClassMethodReturnVendorLockResolver $classMethodReturnVendorLockResolver, - private readonly ClassModifierChecker $classModifierChecker, ) { } @@ -66,33 +65,49 @@ public function getValues(): void */ public function getNodeTypes(): array { - return [ClassMethod::class]; + return [ClassLike::class]; } /** - * @param ClassMethod $node + * @param ClassLike $node */ public function refactor(Node $node): ?Node { - // already has return type → skip - if ($node->returnType instanceof Node) { + $classReflection = $this->getClassReflection($node); + if (! $classReflection instanceof ClassReflection) { return null; } - if ($this->shouldSkipClassMethod($node)) { - return null; - } + $isFinalClass = $classReflection->isFinalByKeyword(); - if (! $this->silentVoidResolver->hasExclusiveVoid($node)) { - return null; + $hasChanged = false; + foreach ($node->getMethods() as $classMethod) { + // already has return type → skip + if ($classMethod->returnType instanceof Node) { + continue; + } + + if ($this->shouldSkipClassMethod($classMethod, $classReflection, $isFinalClass)) { + continue; + } + + if (! $this->silentVoidResolver->hasExclusiveVoid($classMethod)) { + continue; + } + + if ($this->classMethodReturnVendorLockResolver->isVendorLocked($classMethod)) { + continue; + } + + $classMethod->returnType = new Identifier('void'); + $hasChanged = true; } - if ($this->classMethodReturnVendorLockResolver->isVendorLocked($node)) { - return null; + if ($hasChanged) { + return $node; } - $node->returnType = new Identifier('void'); - return $node; + return null; } public function provideMinPhpVersion(): int @@ -100,55 +115,39 @@ public function provideMinPhpVersion(): int return PhpVersionFeature::VOID_TYPE; } - private function shouldSkipClassMethod(ClassMethod $classMethod): bool - { + private function shouldSkipClassMethod( + ClassMethod $classMethod, + ClassReflection $classReflection, + bool $isFinalClass + ): bool { if ($classMethod->isAbstract()) { return true; } // is not final and has only exception? possibly implemented by child - if ($this->isNotFinalAndHasExceptionOnly($classMethod)) { + if (! $isFinalClass && $this->hasExceptionOnly($classMethod)) { return true; } // possibly required by child implementation - if ($this->isNotFinalAndEmpty($classMethod)) { + if (! $isFinalClass && $classMethod->stmts === []) { return true; } if ($classMethod->isProtected()) { - return ! $this->classModifierChecker->isInsideFinalClass($classMethod); + return ! $isFinalClass; } - $scope = ScopeFetcher::fetch($classMethod); - if (! $scope->isInClass()) { - return false; - } - - $classReflection = $scope->getClassReflection(); return $classReflection->isAbstract(); } - private function isNotFinalAndHasExceptionOnly(ClassMethod $classMethod): bool + private function hasExceptionOnly(ClassMethod $classMethod): bool { if (count((array) $classMethod->stmts) !== 1) { return false; } $onlyStmt = $classMethod->stmts[0] ?? null; - if (! $onlyStmt instanceof Expression || ! $onlyStmt->expr instanceof Throw_) { - return false; - } - - return ! $this->classModifierChecker->isInsideFinalClass($classMethod); - } - - private function isNotFinalAndEmpty(ClassMethod $classMethod): bool - { - if ($classMethod->stmts !== []) { - return false; - } - - return ! $this->classModifierChecker->isInsideFinalClass($classMethod); + return $onlyStmt instanceof Expression && $onlyStmt->expr instanceof Throw_; } } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromMockObjectRector.php b/rules/TypeDeclaration/Rector/Class_/ReturnTypeFromMockObjectRector.php similarity index 63% rename from rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromMockObjectRector.php rename to rules/TypeDeclaration/Rector/Class_/ReturnTypeFromMockObjectRector.php index ee5e5cbcc87..5e9f7f8f8b3 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromMockObjectRector.php +++ b/rules/TypeDeclaration/Rector/Class_/ReturnTypeFromMockObjectRector.php @@ -2,13 +2,12 @@ declare(strict_types=1); -namespace Rector\TypeDeclaration\Rector\ClassMethod; +namespace Rector\TypeDeclaration\Rector\Class_; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Stmt\ClassMethod; -use PHPStan\Analyser\Scope; +use PhpParser\Node\Stmt\Class_; use PHPStan\Reflection\ClassReflection; use PHPStan\Type\IntersectionType; use PHPStan\Type\ObjectType; @@ -25,7 +24,7 @@ use Rector\VersionBonding\Contract\MinPhpVersionInterface; /** - * @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromMockObjectRector\ReturnTypeFromMockObjectRectorTest + * @see \Rector\Tests\TypeDeclaration\Rector\Class_\ReturnTypeFromMockObjectRector\ReturnTypeFromMockObjectRectorTest */ final class ReturnTypeFromMockObjectRector extends AbstractRector implements MinPhpVersionInterface { @@ -68,49 +67,65 @@ public function createSomeMock(): \PHPUnit\Framework\MockObject\MockObject public function getNodeTypes(): array { - return [ClassMethod::class]; + return [Class_::class]; } /** - * @param ClassMethod $node + * @param Class_ $node */ public function refactor(Node $node): ?Node { - // type is already known - if ($node->returnType instanceof Node) { + $classReflection = $this->getClassReflection($node); + if (! $classReflection instanceof ClassReflection) { return null; } - // we need exactly 1 return - $returns = $this->betterNodeFinder->findReturnsScoped($node); - if (count($returns) !== 1) { + // is phpunit test case? + if (! $classReflection->is(ClassName::TEST_CASE_CLASS)) { return null; } - if (! $this->returnAnalyzer->hasOnlyReturnWithExpr($node, $returns)) { - return null; - } + $hasChanged = false; + $scope = null; - $scope = ScopeFetcher::fetch($node); - if (! $this->isInsideTestCaseClass($scope)) { - return null; - } + foreach ($node->getMethods() as $classMethod) { + // type is already known + if ($classMethod->returnType instanceof Node) { + continue; + } - if ($this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($node, $scope)) { - return null; - } + // we need exactly 1 return + $returns = $this->betterNodeFinder->findReturnsScoped($classMethod); + if (count($returns) !== 1) { + continue; + } - /** @var Expr $expr */ - $expr = $returns[0]->expr; - $returnType = $this->nodeTypeResolver->getNativeType($expr); + if (! $this->returnAnalyzer->hasOnlyReturnWithExpr($classMethod, $returns)) { + continue; + } - if (! $this->isMockObjectType($returnType)) { - return null; + $scope ??= ScopeFetcher::fetch($node); + if ($this->classMethodReturnTypeOverrideGuard->shouldSkipClassMethod($classMethod, $scope)) { + continue; + } + + /** @var Expr $expr */ + $expr = $returns[0]->expr; + $returnType = $this->nodeTypeResolver->getNativeType($expr); + + if (! $this->isMockObjectType($returnType)) { + continue; + } + + $classMethod->returnType = new FullyQualified(ClassName::MOCK_OBJECT); + $hasChanged = true; } - $node->returnType = new FullyQualified(ClassName::MOCK_OBJECT); + if ($hasChanged) { + return $node; + } - return $node; + return null; } public function provideMinPhpVersion(): int @@ -139,15 +154,4 @@ private function isMockObjectType(Type $returnType): bool return $this->isIntersectionWithMockObjectType($returnType); } - - private function isInsideTestCaseClass(Scope $scope): bool - { - $classReflection = $scope->getClassReflection(); - if (! $classReflection instanceof ClassReflection) { - return false; - } - - // is phpunit test case? - return $classReflection->is(ClassName::TEST_CASE_CLASS); - } } diff --git a/src/Config/Level/TypeDeclarationLevel.php b/src/Config/Level/TypeDeclarationLevel.php index f4d3e7508ab..f4c421a7ea7 100644 --- a/src/Config/Level/TypeDeclarationLevel.php +++ b/src/Config/Level/TypeDeclarationLevel.php @@ -13,6 +13,7 @@ use Rector\TypeDeclaration\Rector\Class_\MergeDateTimePropertyTypeDeclarationRector; use Rector\TypeDeclaration\Rector\Class_\ObjectTypedPropertyFromJMSSerializerAttributeTypeRector; use Rector\TypeDeclaration\Rector\Class_\PropertyTypeFromStrictSetterGetterRector; +use Rector\TypeDeclaration\Rector\Class_\ReturnTypeFromMockObjectRector; use Rector\TypeDeclaration\Rector\Class_\ReturnTypeFromStrictTernaryRector; use Rector\TypeDeclaration\Rector\Class_\ScalarTypedPropertyFromJMSSerializerAttributeTypeRector; use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromContainerGetSetUpRector; @@ -20,13 +21,13 @@ use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromDocblockSetUpDefinedRector; use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromGetRepositorySetUpRector; use Rector\TypeDeclaration\Rector\Class_\TypedStaticPropertyInBehatContextRector; +use Rector\TypeDeclaration\Rector\ClassLike\AddVoidReturnTypeWhereNoReturnRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddParamFromDimFetchKeyUseRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeBasedOnPHPUnitDataProviderRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromPropertyTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeDeclarationBasedOnParentClassMethodRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeFromTryCatchTypeRector; -use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; use Rector\TypeDeclaration\Rector\ClassMethod\BoolReturnTypeFromBooleanConstAndStrictReturnsRector; use Rector\TypeDeclaration\Rector\ClassMethod\BoolReturnTypeFromBooleanConstReturnsRector; use Rector\TypeDeclaration\Rector\ClassMethod\BoolReturnTypeFromBooleanStrictReturnsRector; @@ -38,7 +39,6 @@ use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNullableTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromGetRepositoryDocblockRector; -use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromMockObjectRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnCastRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnDirectArrayRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnNewRector;