From a885833597ce264ba041ede792918fda9091135d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 11 Sep 2025 17:30:22 +0200 Subject: [PATCH 1/2] [type-declaration-docblock] kick of docblock rules to help with iterables --- config/set/type-declaration-docblocks.php | 1 + ...ArrayFromDirectArrayInstanceRectorTest.php | 28 +++ .../some_property_with_array_docblock.php.inc | 34 ++++ .../config/configured_rule.php | 9 + ...turnArrayFromDirectArrayInstanceRector.php | 165 ++++++++++++++++++ 5 files changed, 237 insertions(+) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/DocblockReturnArrayFromDirectArrayInstanceRectorTest.php create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/some_property_with_array_docblock.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/config/configured_rule.php create mode 100644 rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php diff --git a/config/set/type-declaration-docblocks.php b/config/set/type-declaration-docblocks.php index ed14898e71d..278fa71ca72 100644 --- a/config/set/type-declaration-docblocks.php +++ b/config/set/type-declaration-docblocks.php @@ -11,6 +11,7 @@ */ return static function (RectorConfig $rectorConfig): void { $rectorConfig->rules([ + DocblockVarFromParamDocblockInConstructorRector::class, DocblockVarFromParamDocblockInConstructorRector::class, DocblockGetterReturnArrayFromPropertyDocblockVarRector::class, ]); diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/DocblockReturnArrayFromDirectArrayInstanceRectorTest.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/DocblockReturnArrayFromDirectArrayInstanceRectorTest.php new file mode 100644 index 00000000000..fba532f8546 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/DocblockReturnArrayFromDirectArrayInstanceRectorTest.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/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/some_property_with_array_docblock.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/some_property_with_array_docblock.php.inc new file mode 100644 index 00000000000..3ca0274f9bf --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/some_property_with_array_docblock.php.inc @@ -0,0 +1,34 @@ + 'value', + ]; + } +} + +?> +----- + + */ + public function getNames(): array + { + return [ + 'key' => 'value', + ]; + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/config/configured_rule.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/config/configured_rule.php new file mode 100644 index 00000000000..d49324f6def --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([DocblockReturnArrayFromDirectArrayInstanceRector::class]); diff --git a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php new file mode 100644 index 00000000000..202c46e9265 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php @@ -0,0 +1,165 @@ + 'now', + ]; + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SomeClass +{ + /** + * @return array + */ + public function getItems(): array + { + return [ + 'hey' => 'now', + ]; + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + if (! $node->returnType instanceof Node) { + return null; + } + + if (! $this->isName($node->returnType, 'array')) { + return null; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + + // return tag is already given + if ($phpDocInfo->getReturnTagValue() instanceof ReturnTagValueNode) { + return null; + } + + if (count($node->stmts) !== 1) { + return null; + } + + $soleReturn = $node->stmts[0]; + if (! $soleReturn instanceof Return_) { + return null; + } + + if (! $soleReturn->expr instanceof Array_) { + return null; + } + + // resolve simple type + $returnedType = $this->getType($soleReturn->expr); + + if (! $returnedType instanceof ConstantArrayType) { + return null; + } + + $genericKeyType = $this->constantToGenericType($returnedType->getKeyType()); + $genericItemType = $this->constantToGenericType($returnedType->getItemType()); + + $genericTypeNode = $this->createArrayGenericTypeNode($genericKeyType, $genericItemType); + + $returnTagValueNode = new ReturnTagValueNode($genericTypeNode, ''); + $phpDocInfo->addTagValueNode($returnTagValueNode); + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + return $node; + } + + /** + * covers constant types too and makes them more generic + */ + private function constantToGenericType(\PHPStan\Type\Type $type): \PHPStan\Type\Type + { + if ($type instanceof StringType) { + return new StringType(); + } + + if ($type instanceof IntegerType) { + return new IntegerType(); + } + + if ($type instanceof BooleanType) { + return new BooleanType(); + } + + if ($type instanceof FloatType) { + return new FloatType(); + } + + // unclear + return new MixedType(); + } + + private function createArrayGenericTypeNode( + \PHPStan\Type\Type $keyType, + \PHPStan\Type\Type $itemType + ): GenericTypeNode { + $keyDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($keyType); + $itemDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($itemType); + + return new GenericTypeNode(new IdentifierTypeNode('array'), [$keyDocTypeNode, $itemDocTypeNode]); + } +} From 4714683834172e3b5a24c5d5bee06505338fe510 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 11 Sep 2025 18:29:09 +0200 Subject: [PATCH 2/2] cover no direct return array too --- .../include_missing_return_array.php.inc | 34 ++++++++++++++++++ .../Fixture/skip_already_set_docblock.php.inc | 16 +++++++++ .../Fixture/skip_more_variables.php.inc | 16 +++++++++ ...turnArrayFromDirectArrayInstanceRector.php | 35 +++++++++---------- 4 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/include_missing_return_array.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/skip_already_set_docblock.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/skip_more_variables.php.inc diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/include_missing_return_array.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/include_missing_return_array.php.inc new file mode 100644 index 00000000000..078f42a648f --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/include_missing_return_array.php.inc @@ -0,0 +1,34 @@ + 'value', + ]; + } +} + +?> +----- + + */ + public function getNames() + { + return [ + 'key' => 'value', + ]; + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/skip_already_set_docblock.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/skip_already_set_docblock.php.inc new file mode 100644 index 00000000000..cd9fc5cd09a --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/skip_already_set_docblock.php.inc @@ -0,0 +1,16 @@ + + */ + public function getNames(): array + { + return [ + 'key' => 'value', + ]; + } +} diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/skip_more_variables.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/skip_more_variables.php.inc new file mode 100644 index 00000000000..d5950c897bf --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector/Fixture/skip_more_variables.php.inc @@ -0,0 +1,16 @@ + 'value', + ]; + + + return $variable; + } +} diff --git a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php index 202c46e9265..bdf260fbef5 100644 --- a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php @@ -4,6 +4,7 @@ namespace Rector\TypeDeclarationDocblocks\Rector\ClassMethod; +use PHPStan\Type\Type; use PhpParser\Node; use PhpParser\Node\Expr\Array_; use PhpParser\Node\Stmt\ClassMethod; @@ -43,9 +44,11 @@ public function getNodeTypes(): array public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition('Add @return array docblock based on direct single level direct return of []', [ - new CodeSample( - <<<'CODE_SAMPLE' + return new RuleDefinition( + 'Add simple @return array docblock based on direct single level direct return of []', + [ + new CodeSample( + <<<'CODE_SAMPLE' class SomeClass { public function getItems(): array @@ -56,8 +59,8 @@ public function getItems(): array } } CODE_SAMPLE - , - <<<'CODE_SAMPLE' + , + <<<'CODE_SAMPLE' class SomeClass { /** @@ -71,8 +74,10 @@ public function getItems(): array } } CODE_SAMPLE - ), - ]); + ), + + ] + ); } /** @@ -80,14 +85,6 @@ public function getItems(): array */ public function refactor(Node $node): ?Node { - if (! $node->returnType instanceof Node) { - return null; - } - - if (! $this->isName($node->returnType, 'array')) { - return null; - } - $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); // return tag is already given @@ -95,7 +92,7 @@ public function refactor(Node $node): ?Node return null; } - if (count($node->stmts) !== 1) { + if ($node->stmts === null || count($node->stmts) !== 1) { return null; } @@ -131,7 +128,7 @@ public function refactor(Node $node): ?Node /** * covers constant types too and makes them more generic */ - private function constantToGenericType(\PHPStan\Type\Type $type): \PHPStan\Type\Type + private function constantToGenericType(Type $type): Type { if ($type instanceof StringType) { return new StringType(); @@ -154,8 +151,8 @@ private function constantToGenericType(\PHPStan\Type\Type $type): \PHPStan\Type\ } private function createArrayGenericTypeNode( - \PHPStan\Type\Type $keyType, - \PHPStan\Type\Type $itemType + Type $keyType, + Type $itemType ): GenericTypeNode { $keyDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($keyType); $itemDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($itemType);