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/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-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..bdf260fbef5 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockReturnArrayFromDirectArrayInstanceRector.php @@ -0,0 +1,162 @@ + '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 + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + + // return tag is already given + if ($phpDocInfo->getReturnTagValue() instanceof ReturnTagValueNode) { + return null; + } + + if ($node->stmts === null || 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(Type $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( + Type $keyType, + Type $itemType + ): GenericTypeNode { + $keyDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($keyType); + $itemDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($itemType); + + return new GenericTypeNode(new IdentifierTypeNode('array'), [$keyDocTypeNode, $itemDocTypeNode]); + } +}