From a1414e2b09cb8804c6d3b8a27f1eaa39b9477cae 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 | 18 ++ ...ArrayFromPropertyDocblockVarRectorTest.php | 28 +++ .../Fixture/skip_missing_return_type.php.inc | 16 ++ .../some_property_with_array_docblock.php.inc | 40 ++++ .../config/configured_rule.php | 9 + ...omParamDocblockInConstructorRectorTest.php | 28 +++ .../skip_if_array_type_missing.php.inc | 16 ++ .../Fixture/some_class.php.inc | 40 ++++ .../config/configured_rule.php | 10 + ...turnArrayFromPropertyDocblockVarRector.php | 130 +++++++++++++ ...arFromParamDocblockInConstructorRector.php | 174 ++++++++++++++++++ 11 files changed, 509 insertions(+) create mode 100644 config/set/type-declaration-docblocks.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/some_property_with_array_docblock.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/config/configured_rule.php create mode 100644 rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php create mode 100644 rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/some_class.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/config/configured_rule.php create mode 100644 rules/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php create mode 100644 rules/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php diff --git a/config/set/type-declaration-docblocks.php b/config/set/type-declaration-docblocks.php new file mode 100644 index 00000000000..a4e8fdcac72 --- /dev/null +++ b/config/set/type-declaration-docblocks.php @@ -0,0 +1,18 @@ +rules([ + // properties + DocblockVarFromParamDocblockInConstructorRector::class, + + DocblockGetterReturnArrayFromPropertyDocblockVarRector::class, + ]); +}; diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php new file mode 100644 index 00000000000..d668e8cd6fc --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.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/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc new file mode 100644 index 00000000000..9b69a3dc94d --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc @@ -0,0 +1,16 @@ +names; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/some_property_with_array_docblock.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/some_property_with_array_docblock.php.inc new file mode 100644 index 00000000000..53fc30a4e2b --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/some_property_with_array_docblock.php.inc @@ -0,0 +1,40 @@ +names; + } +} + +?> +----- +names; + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/config/configured_rule.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/config/configured_rule.php new file mode 100644 index 00000000000..75c4d5e62f3 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([DocblockGetterReturnArrayFromPropertyDocblockVarRector::class]); diff --git a/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php b/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php new file mode 100644 index 00000000000..e32b717b0cd --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.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/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc new file mode 100644 index 00000000000..9543bd92070 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc @@ -0,0 +1,16 @@ +names = $names; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/some_class.php.inc b/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/some_class.php.inc new file mode 100644 index 00000000000..3bd1102a82a --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/some_class.php.inc @@ -0,0 +1,40 @@ +names = $names; + } +} + +?> +----- +names = $names; + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/config/configured_rule.php b/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/config/configured_rule.php new file mode 100644 index 00000000000..6746c479e0a --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(DocblockVarFromParamDocblockInConstructorRector::class); +}; diff --git a/rules/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php b/rules/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php new file mode 100644 index 00000000000..2a5db1c9edd --- /dev/null +++ b/rules/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php @@ -0,0 +1,130 @@ +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 ($node->stmts === null) { + return null; + } + + // we need exactly one statement of return + if (count($node->stmts) !== 1) { + return null; + } + + $onlyStmt = $node->stmts[0]; + if (! $onlyStmt instanceof Return_) { + return null; + } + + if (! $onlyStmt->expr instanceof PropertyFetch) { + return null; + } + + $propertyFetch = $onlyStmt->expr; + if (! $this->isName($propertyFetch->var, 'this')) { + return null; + } + + $propertyFetchType = $this->getType($propertyFetch); + + $propertyFetchDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($propertyFetchType); + + $returnTagValueNode = new ReturnTagValueNode($propertyFetchDocTypeNode, ''); + $phpDocInfo->addTagValueNode($returnTagValueNode); + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + return $node; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Add @return array docblock to a getter method based on @var of the property', [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ + /** + * @var int[] + */ + private array $items; + + public function getItems(): array + { + return $this->items; + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SomeClass +{ + /** + * @var int[] + */ + private array $items; + + /** + * @return int[] + */ + public function getItems(): array + { + return $this->items; + } +} +CODE_SAMPLE + ), + ]); + } +} diff --git a/rules/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php b/rules/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php new file mode 100644 index 00000000000..b4fd3f3c76e --- /dev/null +++ b/rules/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php @@ -0,0 +1,174 @@ +getMethod(MethodName::CONSTRUCT); + if (! $constructorClassMethod instanceof ClassMethod) { + return null; + } + + $hasChanged = false; + + foreach ($node->getProperties() as $property) { + if (! $this->isArrayTypedProperty($property)) { + continue; + } + + $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); + + // @var tag already given + if ($propertyPhpDocInfo->getVarTagValueNode() instanceof VarTagValueNode) { + continue; + } + + $propertyName = $this->getName($property); + $isAssignedInConstructor = $this->constructorAssignDetector->isPropertyAssigned($node, $propertyName); + if ($isAssignedInConstructor === false) { + continue; + } + + $assignedType = $this->matchAssignedPropertyArrayType($constructorClassMethod, $propertyName); + if (! $assignedType instanceof ArrayType) { + continue; + } + + $arrayDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($assignedType); + + $returnTagValueNode = new VarTagValueNode($arrayDocTypeNode, '', ''); + $propertyPhpDocInfo->addTagValueNode($returnTagValueNode); + + $hasChanged = true; + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($property); + } + + if (! $hasChanged) { + return null; + } + + return $node; + } + + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Add @var array docblock to a property based on @param of constructor assign', [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ + private array $items; + + /** + * @param string[] $items + */ + public function __construct(array $items) + { + $this->items = $items; + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SomeClass +{ + /** + * @var string[] + */ + private array $items; + + /** + * @param string[] $items + */ + public function __construct(array $items) + { + $this->items = $items; + } +} +CODE_SAMPLE + ), + + ]); + } + + private function isArrayTypedProperty(Property $property): bool + { + if (! $property->type instanceof Node) { + return false; + } + + return $this->isName($property->type, 'array'); + } + + private function matchAssignedPropertyArrayType( + ClassMethod $constructorClassMethod, + string $propertyName + ): ?ArrayType { + $assigns = $this->betterNodeFinder->findInstancesOfScoped($constructorClassMethod->stmts, Assign::class); + foreach ($assigns as $assign) { + if (! $assign->var instanceof PropertyFetch) { + continue; + } + + $propertyFetch = $assign->var; + if (! $this->isName($propertyFetch->var, 'this')) { + continue; + } + + if (! $this->isName($propertyFetch->name, $propertyName)) { + continue; + } + + $assignedType = $this->getType($assign->expr); + if (! $assignedType instanceof ArrayType) { + continue; + } + + return $assignedType; + } + + return null; + } +} From 39bd4d7fb809a1dd084d45e30e8af3d89500495b Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Thu, 11 Sep 2025 17:48:02 +0200 Subject: [PATCH 2/2] add set --- config/set/type-declaration-docblocks.php | 11 +- ...ArrayFromPropertyDocblockVarRectorTest.php | 2 +- .../Fixture/skip_missing_return_type.php.inc | 2 +- .../some_property_with_array_docblock.php.inc | 4 +- .../config/configured_rule.php | 2 +- ...omParamDocblockInConstructorRectorTest.php | 2 +- .../skip_if_array_type_missing.php.inc | 2 +- .../Fixture/some_class.php.inc | 4 +- .../config/configured_rule.php | 2 +- .../ConstructorAssignedTypeResolver.php | 57 ++++++++ ...turnArrayFromPropertyDocblockVarRector.php | 115 ++++++++-------- ...arFromParamDocblockInConstructorRector.php | 125 ++++++------------ src/Set/ValueObject/SetList.php | 5 + 13 files changed, 181 insertions(+), 152 deletions(-) rename rules-tests/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php (83%) rename rules-tests/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc (59%) rename rules-tests/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/some_property_with_array_docblock.php.inc (63%) rename rules-tests/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/config/configured_rule.php (61%) rename rules-tests/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php (85%) rename rules-tests/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc (63%) rename rules-tests/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/some_class.php.inc (65%) rename rules-tests/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/config/configured_rule.php (67%) create mode 100644 rules/TypeDeclarationDocblocks/NodeAnalyzer/ConstructorAssignedTypeResolver.php rename rules/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php (82%) rename rules/{TypeDeclaration => TypeDeclarationDocblocks}/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php (65%) diff --git a/config/set/type-declaration-docblocks.php b/config/set/type-declaration-docblocks.php index a4e8fdcac72..ed14898e71d 100644 --- a/config/set/type-declaration-docblocks.php +++ b/config/set/type-declaration-docblocks.php @@ -3,16 +3,15 @@ declare(strict_types=1); use Rector\Config\RectorConfig; -use Rector\TypeDeclaration\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector; -use Rector\TypeDeclaration\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector; +use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector; +use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector; +/** + * @experimental * 2025-09, experimental hidden set for type declaration in docblocks + */ return static function (RectorConfig $rectorConfig): void { - // 2025-09, experimental hidden set for type declaration in docblocks - $rectorConfig->rules([ - // properties DocblockVarFromParamDocblockInConstructorRector::class, - DocblockGetterReturnArrayFromPropertyDocblockVarRector::class, ]); }; diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php similarity index 83% rename from rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php rename to rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php index d668e8cd6fc..84bc7fbeb43 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector; +namespace Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector; use Iterator; use PHPUnit\Framework\Attributes\DataProvider; diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc similarity index 59% rename from rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc rename to rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc index 9b69a3dc94d..12328162b48 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector/Fixture/skip_missing_return_type.php.inc @@ -1,6 +1,6 @@ withRules([DocblockGetterReturnArrayFromPropertyDocblockVarRector::class]); diff --git a/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php similarity index 85% rename from rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php rename to rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php index e32b717b0cd..85f501cba87 100644 --- a/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/DocblockVarFromParamDocblockInConstructorRectorTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\Tests\TypeDeclaration\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector; +namespace Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector; use Iterator; use PHPUnit\Framework\Attributes\DataProvider; diff --git a/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc similarity index 63% rename from rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc rename to rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc index 9543bd92070..037e1a0f85e 100644 --- a/rules-tests/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector/Fixture/skip_if_array_type_missing.php.inc @@ -1,6 +1,6 @@ rule(DocblockVarFromParamDocblockInConstructorRector::class); diff --git a/rules/TypeDeclarationDocblocks/NodeAnalyzer/ConstructorAssignedTypeResolver.php b/rules/TypeDeclarationDocblocks/NodeAnalyzer/ConstructorAssignedTypeResolver.php new file mode 100644 index 00000000000..913af1ba7a7 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/NodeAnalyzer/ConstructorAssignedTypeResolver.php @@ -0,0 +1,57 @@ +getMethod(MethodName::CONSTRUCT); + if (! $constructorClassMethod instanceof ClassMethod) { + return null; + } + + if ($constructorClassMethod->stmts === null) { + return null; + } + + $assigns = $this->betterNodeFinder->findInstancesOfScoped($constructorClassMethod->stmts, Assign::class); + foreach ($assigns as $assign) { + if (! $assign->var instanceof PropertyFetch) { + continue; + } + + $propertyFetch = $assign->var; + if (! $this->nodeNameResolver-> isName($propertyFetch->var, 'this')) { + continue; + } + + if (! $this->nodeNameResolver-> isName($propertyFetch->name, $propertyName)) { + continue; + } + + return $this->nodeTypeResolver->getType($assign->expr); + } + + return null; + } +} diff --git a/rules/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php similarity index 82% rename from rules/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php rename to rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php index 2a5db1c9edd..b8400fd6b56 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/DocblockGetterReturnArrayFromPropertyDocblockVarRector.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Rector\TypeDeclaration\Rector\ClassMethod; +namespace Rector\TypeDeclarationDocblocks\Rector\ClassMethod; use PhpParser\Node; use PhpParser\Node\Expr\PropertyFetch; @@ -17,7 +17,7 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector\DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest + * @see \Rector\Tests\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector\DocblockGetterReturnArrayFromPropertyDocblockVarRectorTest */ final class DocblockGetterReturnArrayFromPropertyDocblockVarRector extends AbstractRector { @@ -33,6 +33,46 @@ public function getNodeTypes(): array return [ClassMethod::class]; } + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Add @return array docblock to a getter method based on @var of the property', [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeClass +{ + /** + * @var int[] + */ + private array $items; + + public function getItems(): array + { + return $this->items; + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SomeClass +{ + /** + * @var int[] + */ + private array $items; + + /** + * @return int[] + */ + public function getItems(): array + { + return $this->items; + } +} +CODE_SAMPLE + ), + ]); + } + /** * @param ClassMethod $node */ @@ -53,16 +93,30 @@ public function refactor(Node $node): ?Node return null; } - if ($node->stmts === null) { + $propertyFetch = $this->matchReturnLocalPropertyFetch($node); + if (! $propertyFetch instanceof PropertyFetch) { return null; } + $propertyFetchType = $this->getType($propertyFetch); + $propertyFetchDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($propertyFetchType); + + $returnTagValueNode = new ReturnTagValueNode($propertyFetchDocTypeNode, ''); + $phpDocInfo->addTagValueNode($returnTagValueNode); + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + return $node; + } + + private function matchReturnLocalPropertyFetch(ClassMethod $classMethod): ?PropertyFetch + { // we need exactly one statement of return - if (count($node->stmts) !== 1) { + if ($classMethod->stmts === null || count($classMethod->stmts) !== 1) { return null; } - $onlyStmt = $node->stmts[0]; + $onlyStmt = $classMethod->stmts[0]; if (! $onlyStmt instanceof Return_) { return null; } @@ -76,55 +130,6 @@ public function refactor(Node $node): ?Node return null; } - $propertyFetchType = $this->getType($propertyFetch); - - $propertyFetchDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($propertyFetchType); - - $returnTagValueNode = new ReturnTagValueNode($propertyFetchDocTypeNode, ''); - $phpDocInfo->addTagValueNode($returnTagValueNode); - - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); - - return $node; - } - - public function getRuleDefinition(): RuleDefinition - { - return new RuleDefinition('Add @return array docblock to a getter method based on @var of the property', [ - new CodeSample( - <<<'CODE_SAMPLE' -class SomeClass -{ - /** - * @var int[] - */ - private array $items; - - public function getItems(): array - { - return $this->items; - } -} -CODE_SAMPLE - , - <<<'CODE_SAMPLE' -class SomeClass -{ - /** - * @var int[] - */ - private array $items; - - /** - * @return int[] - */ - public function getItems(): array - { - return $this->items; - } -} -CODE_SAMPLE - ), - ]); + return $propertyFetch; } } diff --git a/rules/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php similarity index 65% rename from rules/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php rename to rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php index b4fd3f3c76e..ddf0ce7300c 100644 --- a/rules/TypeDeclaration/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php @@ -2,11 +2,9 @@ declare(strict_types=1); -namespace Rector\TypeDeclaration\Rector\Class_; +namespace Rector\TypeDeclarationDocblocks\Rector\Class_; use PhpParser\Node; -use PhpParser\Node\Expr\Assign; -use PhpParser\Node\Expr\PropertyFetch; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Property; @@ -14,24 +12,23 @@ use PHPStan\Type\ArrayType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; use Rector\Comments\NodeDocBlock\DocBlockUpdater; -use Rector\PhpParser\Node\BetterNodeFinder; use Rector\Rector\AbstractRector; use Rector\StaticTypeMapper\StaticTypeMapper; -use Rector\TypeDeclaration\AlreadyAssignDetector\ConstructorAssignDetector; -use Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer\TrustedClassMethodPropertyTypeInferer; +use Rector\TypeDeclarationDocblocks\NodeAnalyzer\ConstructorAssignedTypeResolver; use Rector\ValueObject\MethodName; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; +/** + * @see \Rector\Tests\TypeDeclarationDocblocks\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector\DocblockVarFromParamDocblockInConstructorRectorTest + */ final class DocblockVarFromParamDocblockInConstructorRector extends AbstractRector { public function __construct( private readonly PhpDocInfoFactory $phpDocInfoFactory, private readonly DocBlockUpdater $docBlockUpdater, private readonly StaticTypeMapper $staticTypeMapper, - private readonly ConstructorAssignDetector $constructorAssignDetector, - TrustedClassMethodPropertyTypeInferer $trustedClassMethodPropertyTypeInferer, - private readonly BetterNodeFinder $betterNodeFinder + private readonly ConstructorAssignedTypeResolver $constructorAssignedTypeResolver, ) { } @@ -40,58 +37,6 @@ public function getNodeTypes(): array return [Class_::class]; } - /** - * @param Class_ $node - */ - public function refactor(Node $node): ?Node - { - $constructorClassMethod = $node->getMethod(MethodName::CONSTRUCT); - if (! $constructorClassMethod instanceof ClassMethod) { - return null; - } - - $hasChanged = false; - - foreach ($node->getProperties() as $property) { - if (! $this->isArrayTypedProperty($property)) { - continue; - } - - $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); - - // @var tag already given - if ($propertyPhpDocInfo->getVarTagValueNode() instanceof VarTagValueNode) { - continue; - } - - $propertyName = $this->getName($property); - $isAssignedInConstructor = $this->constructorAssignDetector->isPropertyAssigned($node, $propertyName); - if ($isAssignedInConstructor === false) { - continue; - } - - $assignedType = $this->matchAssignedPropertyArrayType($constructorClassMethod, $propertyName); - if (! $assignedType instanceof ArrayType) { - continue; - } - - $arrayDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($assignedType); - - $returnTagValueNode = new VarTagValueNode($arrayDocTypeNode, '', ''); - $propertyPhpDocInfo->addTagValueNode($returnTagValueNode); - - $hasChanged = true; - - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($property); - } - - if (! $hasChanged) { - return null; - } - - return $node; - } - public function getRuleDefinition(): RuleDefinition { return new RuleDefinition('Add @var array docblock to a property based on @param of constructor assign', [ @@ -133,42 +78,60 @@ public function __construct(array $items) ]); } - private function isArrayTypedProperty(Property $property): bool + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node { - if (! $property->type instanceof Node) { - return false; + $constructorClassMethod = $node->getMethod(MethodName::CONSTRUCT); + if (! $constructorClassMethod instanceof ClassMethod) { + return null; } - return $this->isName($property->type, 'array'); - } + $hasChanged = false; - private function matchAssignedPropertyArrayType( - ClassMethod $constructorClassMethod, - string $propertyName - ): ?ArrayType { - $assigns = $this->betterNodeFinder->findInstancesOfScoped($constructorClassMethod->stmts, Assign::class); - foreach ($assigns as $assign) { - if (! $assign->var instanceof PropertyFetch) { + foreach ($node->getProperties() as $property) { + if (! $this->isArrayTypedProperty($property)) { continue; } - $propertyFetch = $assign->var; - if (! $this->isName($propertyFetch->var, 'this')) { - continue; - } + $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); - if (! $this->isName($propertyFetch->name, $propertyName)) { + // @var tag already given + if ($propertyPhpDocInfo->getVarTagValueNode() instanceof VarTagValueNode) { continue; } - $assignedType = $this->getType($assign->expr); + $propertyName = $this->getName($property); + + $assignedType = $this->constructorAssignedTypeResolver->resolve($node, $propertyName); if (! $assignedType instanceof ArrayType) { continue; } - return $assignedType; + $arrayDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($assignedType); + + $returnTagValueNode = new VarTagValueNode($arrayDocTypeNode, '', ''); + $propertyPhpDocInfo->addTagValueNode($returnTagValueNode); + + $hasChanged = true; + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($property); } - return null; + if (! $hasChanged) { + return null; + } + + return $node; + } + + private function isArrayTypedProperty(Property $property): bool + { + if (! $property->type instanceof Node) { + return false; + } + + return $this->isName($property->type, 'array'); } } diff --git a/src/Set/ValueObject/SetList.php b/src/Set/ValueObject/SetList.php index b6015410557..d7e17163802 100644 --- a/src/Set/ValueObject/SetList.php +++ b/src/Set/ValueObject/SetList.php @@ -142,6 +142,11 @@ final class SetList */ public const TYPE_DECLARATION = __DIR__ . '/../../../config/set/type-declaration.php'; + /** + * @var string + */ + public const TYPE_DECLARATION_DOCBLOCKS = __DIR__ . '/../../../config/set/type-declaration-docblocks.php'; + /** * @var string */