diff --git a/packages/VendorLocker/NodeVendorLocker/ClassMethodParamVendorLockResolver.php b/packages/VendorLocker/NodeVendorLocker/ClassMethodParamVendorLockResolver.php index 063901657d6..88174862ece 100644 --- a/packages/VendorLocker/NodeVendorLocker/ClassMethodParamVendorLockResolver.php +++ b/packages/VendorLocker/NodeVendorLocker/ClassMethodParamVendorLockResolver.php @@ -21,6 +21,25 @@ public function __construct( ) { } + /** + * Includes non-vendor classes + */ + public function isSoftLocked(ClassMethod $classMethod): bool + { + if ($this->isVendorLocked($classMethod)) { + return true; + } + + $classReflection = $this->resolveClassReflection($classMethod); + if (! $classReflection instanceof ClassReflection) { + return false; + } + + /** @var string $methodName */ + $methodName = $this->nodeNameResolver->getName($classMethod); + return $this->hasClassMethodLockMatchingFileName($classReflection, $methodName, ''); + } + public function isVendorLocked(ClassMethod $classMethod): bool { if ($classMethod->isMagic()) { @@ -34,7 +53,6 @@ public function isVendorLocked(ClassMethod $classMethod): bool /** @var string $methodName */ $methodName = $this->nodeNameResolver->getName($classMethod); - if ($this->hasTraitMethodVendorLock($classReflection, $methodName)) { return true; } @@ -44,30 +62,10 @@ public function isVendorLocked(ClassMethod $classMethod): bool return true; } + /** @var string $methodName */ $methodName = $this->nodeNameResolver->getName($classMethod); - foreach ($classReflection->getAncestors() as $ancestorClassReflection) { - // skip self - if ($ancestorClassReflection === $classReflection) { - continue; - } - - // parent type - if (! $ancestorClassReflection->hasNativeMethod($methodName)) { - continue; - } - - // is file in vendor? - $fileName = $ancestorClassReflection->getFileName(); - // probably internal class - if ($fileName === false) { - continue; - } - $normalizedFileName = $this->pathNormalizer->normalizePath($fileName, '/'); - return str_contains($normalizedFileName, '/vendor/'); - } - - return false; + return $this->hasClassMethodLockMatchingFileName($classReflection, $methodName, '/vendor/'); } private function hasTraitMethodVendorLock(ClassReflection $classReflection, string $methodName): bool @@ -111,4 +109,41 @@ private function hasParentInterfaceMethod(ClassReflection $classReflection, stri return false; } + + private function hasClassMethodLockMatchingFileName( + ClassReflection $classReflection, + string $methodName, + string $filePathPartName + ): bool { + foreach ($classReflection->getAncestors() as $ancestorClassReflection) { + // skip self + if ($ancestorClassReflection === $classReflection) { + continue; + } + + // parent type + if (! $ancestorClassReflection->hasNativeMethod($methodName)) { + continue; + } + + // is file in vendor? + $fileName = $ancestorClassReflection->getFileName(); + // probably internal class + if ($fileName === false) { + continue; + } + + // not conditions? its a match + if ($filePathPartName === '') { + return true; + } + + $normalizedFileName = $this->pathNormalizer->normalizePath($fileName, '/'); + if (str_contains($normalizedFileName, $filePathPartName)) { + return true; + } + } + + return false; + } } diff --git a/rules-tests/Php80/Rector/FunctionLike/UnionTypesRector/Fixture/skip_parent_contract.php.inc b/rules-tests/Php80/Rector/FunctionLike/UnionTypesRector/Fixture/skip_parent_contract.php.inc new file mode 100644 index 00000000000..3e04663d868 --- /dev/null +++ b/rules-tests/Php80/Rector/FunctionLike/UnionTypesRector/Fixture/skip_parent_contract.php.inc @@ -0,0 +1,15 @@ +hasChanged = false; + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); $this->refactorParamTypes($node, $phpDocInfo); @@ -95,7 +99,15 @@ public function refactor(Node $node): ?Node $this->paramTagRemover->removeParamTagsIfUseless($phpDocInfo, $node); $this->returnTagRemover->removeReturnTagIfUseless($phpDocInfo, $node); - return $node; + if ($phpDocInfo->hasChanged()) { + $this->hasChanged = true; + } + + if ($this->hasChanged) { + return $node; + } + + return null; } public function provideMinPhpVersion(): int @@ -107,7 +119,8 @@ private function refactorParamTypes( ClassMethod | Function_ | Closure | ArrowFunction $functionLike, PhpDocInfo $phpDocInfo ): void { - if ($functionLike instanceof ClassMethod && $this->classMethodParamVendorLockResolver->isVendorLocked( + // skip parent class lock too, just to be safe in case of different parent docs + if ($functionLike instanceof ClassMethod && $this->classMethodParamVendorLockResolver->isSoftLocked( $functionLike )) { return; @@ -145,6 +158,7 @@ private function refactorParamTypes( } $param->type = $phpParserUnionType; + $this->hasChanged = true; } } @@ -155,6 +169,7 @@ private function changeObjectWithoutClassType(Param $param, UnionType $unionType } $param->type = new Name('object'); + $this->hasChanged = true; } private function refactorReturnType( @@ -186,6 +201,7 @@ private function refactorReturnType( } $functionLike->returnType = $phpParserUnionType; + $this->hasChanged = true; } private function filterOutDuplicatedArrayTypes(UnionType $unionType): UnionType | Type diff --git a/rules/TypeDeclaration/Rector/FunctionLike/ParamTypeDeclarationRector.php b/rules/TypeDeclaration/Rector/FunctionLike/ParamTypeDeclarationRector.php index dac6288e09d..fb1cb86d9aa 100644 --- a/rules/TypeDeclaration/Rector/FunctionLike/ParamTypeDeclarationRector.php +++ b/rules/TypeDeclaration/Rector/FunctionLike/ParamTypeDeclarationRector.php @@ -34,6 +34,8 @@ */ final class ParamTypeDeclarationRector extends AbstractRector implements MinPhpVersionInterface { + private bool $hasChanged = false; + public function __construct( private VendorLockResolver $vendorLockResolver, private ParamTypeInferer $paramTypeInferer, @@ -121,6 +123,8 @@ public function change(int $number) */ public function refactor(Node $node): ?Node { + $this->hasChanged = false; + if ($node->params === []) { return null; } @@ -129,6 +133,10 @@ public function refactor(Node $node): ?Node $this->refactorParam($param, $node); } + if ($this->hasChanged) { + return $node; + } + return null; } @@ -170,6 +178,8 @@ private function refactorParam(Param $param, ClassMethod | Function_ $functionLi $functionLikePhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($functionLike); $this->paramTagRemover->removeParamTagsIfUseless($functionLikePhpDocInfo, $functionLike); + + $this->hasChanged = true; } private function shouldSkipParam(Param $param, ClassMethod | Function_ $functionLike): bool