From b4a7e3f732738761f45280d99e800776635fd572 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 12 Sep 2025 11:10:26 +0200 Subject: [PATCH 1/3] cover function as well --- .../Fixture/function_return.php.inc | 28 +++++++++++++++++++ ...turnArrayFromDirectArrayInstanceRector.php | 5 ++-- 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/function_return.php.inc diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/function_return.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/function_return.php.inc new file mode 100644 index 00000000000..a18a5a568fe --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/function_return.php.inc @@ -0,0 +1,28 @@ + 'value', + ]; +} + +?> +----- + + */ +function functionReturn() +{ + return [ + 'key' => 'value', + ]; +} + +?> diff --git a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php index 1e5e51fdb2e..a9b42199b82 100644 --- a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php @@ -7,6 +7,7 @@ use PhpParser\Node; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Stmt\ClassMethod; +use PhpParser\Node\Stmt\Function_; use PhpParser\Node\Stmt\Return_; use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; @@ -39,7 +40,7 @@ public function __construct( public function getNodeTypes(): array { - return [ClassMethod::class]; + return [ClassMethod::class, Function_::class]; } public function getRuleDefinition(): RuleDefinition @@ -81,7 +82,7 @@ public function getItems(): array } /** - * @param ClassMethod $node + * @param ClassMethod|Function_ $node */ public function refactor(Node $node): ?Node { From f88227552e8d7f7597df21c5896572e4122962df Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 12 Sep 2025 11:18:44 +0200 Subject: [PATCH 2/3] [type-declaration-docblocks] Add AddReturnDocblockForCommonObjectDenominatorRector --- ...ckForCommonObjectDenominatorRectorTest.php | 28 ++++ .../some_property_with_array_docblock.php.inc | 40 ++++++ .../Source/ExtensionInterface.php | 8 ++ .../Source/FirstExtension.php | 7 + .../Source/SecondExtension.php | 7 + .../config/configured_rule.php | 10 ++ .../ArraySpreadInsteadOfArrayMergeRector.php | 3 +- ...cblockForCommonObjectDenominatorRector.php | 130 ++++++++++++++++++ 8 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/AddReturnDocblockForCommonObjectDenominatorRectorTest.php create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/some_property_with_array_docblock.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/ExtensionInterface.php create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/FirstExtension.php create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/SecondExtension.php create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/config/configured_rule.php create mode 100644 rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/AddReturnDocblockForCommonObjectDenominatorRectorTest.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/AddReturnDocblockForCommonObjectDenominatorRectorTest.php new file mode 100644 index 00000000000..d26637482f4 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/AddReturnDocblockForCommonObjectDenominatorRectorTest.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/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/some_property_with_array_docblock.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/some_property_with_array_docblock.php.inc new file mode 100644 index 00000000000..8fdccb7bd3a --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/some_property_with_array_docblock.php.inc @@ -0,0 +1,40 @@ +names; + } +} + +?> +----- +names; + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/ExtensionInterface.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/ExtensionInterface.php new file mode 100644 index 00000000000..ede7ff4efda --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/ExtensionInterface.php @@ -0,0 +1,8 @@ +withRules( + [\Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector::class] + ); diff --git a/rules/CodingStyle/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php b/rules/CodingStyle/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php index da0cccb5cd4..cdcf424659f 100644 --- a/rules/CodingStyle/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php +++ b/rules/CodingStyle/Rector/FuncCall/ArraySpreadInsteadOfArrayMergeRector.php @@ -140,7 +140,8 @@ private function shouldSkipArrayForInvalidKeys(Expr $expr): bool // @see https://3v4l.org/DuYHu#v7.4.33 if (! $this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::ARRAY_ON_ARRAY_MERGE)) { $nativeType = $this->nodeTypeResolver->getNativeType($expr); - return ! $nativeType->isArray()->yes(); + return ! $nativeType->isArray() + ->yes(); } return false; diff --git a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php new file mode 100644 index 00000000000..41336c01870 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php @@ -0,0 +1,130 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Function_::class]; + } + + /** + * @param ClassMethod|Function_ $node + */ + public function refactor(Node $node): ?Node + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + $returnType = $phpDocInfo->getReturnType(); + + if (! $returnType instanceof MixedType || $returnType->isExplicitMixed()) { + return null; + } + + dump(123); + die; + + if ($node->returnType instanceof Node && ! $this->isName($node->returnType, 'array')) { + return null; + } + + $returnsScoped = $this->betterNodeFinder->findReturnsScoped($node); + + if (! $this->returnAnalyzer->hasOnlyReturnWithExpr($node, $returnsScoped)) { + return null; + } + + // $arrayType = new ArrayType(new MixedType(), $firstScalarType); + // + // $hasChanged = $this->phpDocTypeChanger->changeReturnType($node, $phpDocInfo, $arrayType); + // if ($hasChanged) { + // return $node; + // } + + return null; + } +} From dbdf9dea721ed49da1b99162be97a2ed9c519310 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 12 Sep 2025 11:23:38 +0200 Subject: [PATCH 3/3] add fixture and test --- config/set/type-declaration-docblocks.php | 2 + .../Fixture/return_of_objects.php.inc | 42 ++++++++ .../skip_mix_of_object_and_scalar.php.inc | 16 +++ .../skip_objects_without_parents.php.inc | 17 ++++ ...skip_objects_without_shared_parent.php.inc | 17 ++++ .../Fixture/skip_scalars.php.inc | 15 +++ .../some_property_with_array_docblock.php.inc | 40 -------- ...AnotherExtensionWithDifferentInterface.php | 9 ++ .../Source/Contract/DifferentInterface.php | 8 ++ .../Source/Contract/ExtensionInterface.php | 8 ++ .../Source/FirstExtension.php | 2 + ...ExtensionInterface.php => FirstOrphan.php} | 3 +- .../Source/SecondExtension.php | 2 + .../Source/SecondOrphan.php | 7 ++ .../config/configured_rule.php | 5 +- .../NodeFinder/ReturnNodeFinder.php | 30 ++++++ ...cblockForCommonObjectDenominatorRector.php | 98 ++++++++++++++----- 17 files changed, 252 insertions(+), 69 deletions(-) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/return_of_objects.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/skip_mix_of_object_and_scalar.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/skip_objects_without_parents.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/skip_objects_without_shared_parent.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/skip_scalars.php.inc delete mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/some_property_with_array_docblock.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/AnotherExtensionWithDifferentInterface.php create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/Contract/DifferentInterface.php create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/Contract/ExtensionInterface.php rename rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/{ExtensionInterface.php => FirstOrphan.php} (82%) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/SecondOrphan.php create mode 100644 rules/TypeDeclarationDocblocks/NodeFinder/ReturnNodeFinder.php diff --git a/config/set/type-declaration-docblocks.php b/config/set/type-declaration-docblocks.php index ef4ba27d115..69f0c3d21b7 100644 --- a/config/set/type-declaration-docblocks.php +++ b/config/set/type-declaration-docblocks.php @@ -6,6 +6,7 @@ use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnArrayDocblockBasedOnArrayMapRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnDocblockForScalarArrayFromAssignsRector; use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector; +use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector; /** @@ -18,5 +19,6 @@ DocblockVarFromParamDocblockInConstructorRector::class, DocblockVarFromParamDocblockInConstructorRector::class, DocblockGetterReturnArrayFromPropertyDocblockVarRector::class, + AddReturnDocblockForCommonObjectDenominatorRector::class, ]); }; diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/return_of_objects.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/return_of_objects.php.inc new file mode 100644 index 00000000000..b5f4e7c69a7 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/return_of_objects.php.inc @@ -0,0 +1,42 @@ + +----- + diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/skip_mix_of_object_and_scalar.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/skip_mix_of_object_and_scalar.php.inc new file mode 100644 index 00000000000..47df3ade2b9 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Fixture/skip_mix_of_object_and_scalar.php.inc @@ -0,0 +1,16 @@ +names; - } -} - -?> ------ -names; - } -} - -?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/AnotherExtensionWithDifferentInterface.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/AnotherExtensionWithDifferentInterface.php new file mode 100644 index 00000000000..8c31b52bd5a --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector/Source/AnotherExtensionWithDifferentInterface.php @@ -0,0 +1,9 @@ +withRules( - [\Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector::class] - ); + ->withRules([AddReturnDocblockForCommonObjectDenominatorRector::class]); diff --git a/rules/TypeDeclarationDocblocks/NodeFinder/ReturnNodeFinder.php b/rules/TypeDeclarationDocblocks/NodeFinder/ReturnNodeFinder.php new file mode 100644 index 00000000000..4835d8b5111 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/NodeFinder/ReturnNodeFinder.php @@ -0,0 +1,30 @@ +betterNodeFinder->findReturnsScoped($functionLike); + if (! $this->returnAnalyzer->hasOnlyReturnWithExpr($functionLike, $returnsScoped)) { + return null; + } + + return $returnsScoped[0]; + } +} diff --git a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php index 41336c01870..5e61dccd78e 100644 --- a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php @@ -5,15 +5,19 @@ namespace Rector\TypeDeclarationDocblocks\Rector\ClassMethod; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; +use PhpParser\Node\Stmt\Return_; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\ArrayType; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\MixedType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\BetterPhpDocParser\PhpDocManipulator\PhpDocTypeChanger; -use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; -use Rector\TypeDeclaration\NodeAnalyzer\ReturnAnalyzer; +use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType; +use Rector\TypeDeclarationDocblocks\NodeFinder\ReturnNodeFinder; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -23,10 +27,10 @@ final class AddReturnDocblockForCommonObjectDenominatorRector extends AbstractRector { public function __construct( - private readonly BetterNodeFinder $betterNodeFinder, - private readonly ReturnAnalyzer $returnAnalyzer, - private readonly PhpDocTypeChanger $phpDocTypeChanger, private readonly PhpDocInfoFactory $phpDocInfoFactory, + private readonly ReturnNodeFinder $returnNodeFinder, + private readonly ReflectionProvider $reflectionProvider, + private readonly PhpDocTypeChanger $phpDocTypeChanger, ) { } @@ -35,8 +39,8 @@ public function getRuleDefinition(): RuleDefinition return new RuleDefinition( 'Add @return docblock array of objects, that have common denominator interface/parent class', [ - new CodeSample( - <<<'CODE_SAMPLE' + new CodeSample( + <<<'CODE_SAMPLE' final class ExtensionProvider { public function getExtensions(): array @@ -56,8 +60,8 @@ class SecondExtension implements ExtensionInterface { } CODE_SAMPLE - , - <<<'CODE_SAMPLE' + , + <<<'CODE_SAMPLE' final class ExtensionProvider { /** @@ -80,9 +84,10 @@ class SecondExtension implements ExtensionInterface { } CODE_SAMPLE - ), - - ]); + ), + + ] + ); } /** @@ -105,26 +110,71 @@ public function refactor(Node $node): ?Node return null; } - dump(123); - die; - + // definitely not an array return if ($node->returnType instanceof Node && ! $this->isName($node->returnType, 'array')) { return null; } - $returnsScoped = $this->betterNodeFinder->findReturnsScoped($node); + $onlyReturnWithExpr = $this->returnNodeFinder->findOnlyReturnWithExpr($node); + if (! $onlyReturnWithExpr instanceof Return_ || ! $onlyReturnWithExpr->expr instanceof Expr) { + return null; + } + + $returnedType = $this->getType($onlyReturnWithExpr->expr); + if (! $returnedType instanceof ConstantArrayType) { + return null; + } + + $referencedClasses = []; + foreach ($returnedType->getValueTypes() as $valueType) { + // each item must refer some classes + if ($valueType->getReferencedClasses() === []) { + return null; + } + + $referencedClasses = array_merge($referencedClasses, $valueType->getReferencedClasses()); + } + + // nothing to find here + if ($referencedClasses === []) { + return null; + } + + $parentClassesAndInterfaces = []; + + foreach ($referencedClasses as $referencedClass) { + $parentClassesAndInterfaces[] = $this->resolveParentClassesAndInterfaces($referencedClass); + } + + $firstSharedTypes = array_intersect(...$parentClassesAndInterfaces); + $firstSharedType = $firstSharedTypes[0] ?? null; - if (! $this->returnAnalyzer->hasOnlyReturnWithExpr($node, $returnsScoped)) { + if ($firstSharedType === null) { return null; } - // $arrayType = new ArrayType(new MixedType(), $firstScalarType); - // - // $hasChanged = $this->phpDocTypeChanger->changeReturnType($node, $phpDocInfo, $arrayType); - // if ($hasChanged) { - // return $node; - // } + $objectTypeArrayType = new ArrayType(new MixedType(), new FullyQualifiedObjectType($firstSharedType)); + $hasChanged = $this->phpDocTypeChanger->changeReturnType($node, $phpDocInfo, $objectTypeArrayType); + if (! $hasChanged) { + return null; + } + + return $node; + } + + /** + * @return string[] + */ + private function resolveParentClassesAndInterfaces(string $className): array + { + $referenceClassReflection = $this->reflectionProvider->getClass($className); + + $currentParentClassesAndInterfaces = $referenceClassReflection->getParentClassesNames(); + + foreach ($referenceClassReflection->getInterfaces() as $classReflection) { + $currentParentClassesAndInterfaces[] = $classReflection->getName(); + } - return null; + return $currentParentClassesAndInterfaces; } }