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/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/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 @@ +withRules([AddReturnDocblockForCommonObjectDenominatorRector::class]); 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/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/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 new file mode 100644 index 00000000000..5e61dccd78e --- /dev/null +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddReturnDocblockForCommonObjectDenominatorRector.php @@ -0,0 +1,180 @@ +> + */ + 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; + } + + // definitely not an array return + if ($node->returnType instanceof Node && ! $this->isName($node->returnType, 'array')) { + return null; + } + + $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 ($firstSharedType === null) { + return null; + } + + $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 $currentParentClassesAndInterfaces; + } +} 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 {