diff --git a/config/set/type-declaration-docblocks.php b/config/set/type-declaration-docblocks.php index c50cb08831d..cdb3ec5375c 100644 --- a/config/set/type-declaration-docblocks.php +++ b/config/set/type-declaration-docblocks.php @@ -7,6 +7,7 @@ use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnDocblockForScalarArrayFromAssignsRector; use Rector\TypeDeclarationDocblocks\Rector\Class_\AddReturnDocblockDataProviderRector; use Rector\TypeDeclarationDocblocks\Rector\Class_\ClassMethodArrayDocblockParamFromLocalCallsRector; +use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarArrayFromPropertyDefaultsRector; use Rector\TypeDeclarationDocblocks\Rector\Class_\DocblockVarFromParamDocblockInConstructorRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDataProviderRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddParamArrayDocblockFromDimFetchAccessRector; @@ -22,6 +23,7 @@ $rectorConfig->rules([ // property var DocblockVarFromParamDocblockInConstructorRector::class, + DocblockVarArrayFromPropertyDefaultsRector::class, // param AddParamArrayDocblockFromDimFetchAccessRector::class, diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/DocblockVarArrayFromPropertyDefaultsRectorTest.php b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/DocblockVarArrayFromPropertyDefaultsRectorTest.php new file mode 100644 index 00000000000..f4f8f116aa2 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/DocblockVarArrayFromPropertyDefaultsRectorTest.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/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_multiple_properties.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_multiple_properties.php.inc new file mode 100644 index 00000000000..94b45d2b2b9 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/Fixture/skip_multiple_properties.php.inc @@ -0,0 +1,8 @@ + +----- + diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/config/configured_rule.php b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/config/configured_rule.php new file mode 100644 index 00000000000..e7761d2ecc9 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(DocblockVarArrayFromPropertyDefaultsRector::class); +}; diff --git a/rules/TypeDeclarationDocblocks/NodeDocblockTypeDecorator.php b/rules/TypeDeclarationDocblocks/NodeDocblockTypeDecorator.php new file mode 100644 index 00000000000..f9ddfc93148 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/NodeDocblockTypeDecorator.php @@ -0,0 +1,133 @@ +isBareMixedType($type)) { + // no value + return false; + } + + $normalizedType = $this->typeNormalizer->generalizeConstantTypes($type); + $typeNode = $this->createTypeNode($normalizedType); + + $paramTagValueNode = new ParamTagValueNode($typeNode, false, '$' . $parameterName, '', false); + + $this->addTagValueNodeAndUpdatePhpDocInfo($phpDocInfo, $paramTagValueNode, $classMethod); + + return true; + } + + public function decorateGenericIterableReturnType( + Type $type, + PhpDocInfo $classMethodPhpDocInfo, + ClassMethod $classMethod + ): bool { + $typeNode = $this->createTypeNode($type); + + if ($this->isBareMixedType($type)) { + // no value + return false; + } + + $returnTagValueNode = new ReturnTagValueNode($typeNode, ''); + + $this->addTagValueNodeAndUpdatePhpDocInfo($classMethodPhpDocInfo, $returnTagValueNode, $classMethod); + + return true; + } + + public function decorateGenericIterableVarType(Type $type, PhpDocInfo $phpDocInfo, Property $property): bool + { + $typeNode = $this->createTypeNode($type); + + if ($this->isBareMixedType($type)) { + // no value + return false; + } + + $varTagValueNode = new VarTagValueNode($typeNode, '', ''); + + $this->addTagValueNodeAndUpdatePhpDocInfo($phpDocInfo, $varTagValueNode, $property); + + return true; + } + + private function createTypeNode(Type $type): TypeNode + { + $generalizedReturnType = $this->typeNormalizer->generalizeConstantTypes($type); + + // turn into rather generic short return type, to keep it open to extension later and readable to human + $typeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($generalizedReturnType); + + if ($typeNode instanceof IdentifierTypeNode && $typeNode->name === 'mixed') { + return new ArrayTypeNode($typeNode); + } + + return $typeNode; + } + + private function addTagValueNodeAndUpdatePhpDocInfo( + PhpDocInfo $phpDocInfo, + ParamTagValueNode|VarTagValueNode|ReturnTagValueNode $tagValueNode, + Property|ClassMethod $stmt + ): void { + $phpDocInfo->addTagValueNode($tagValueNode); + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($stmt); + } + + private function isBareMixedType(Type $type): bool + { + $normalizedResolvedParameterType = $this->typeNormalizer->generalizeConstantTypes($type); + + // most likely mixed, skip + return $this->isArrayMixed($normalizedResolvedParameterType); + } + + private function isArrayMixed(Type $type): bool + { + if (! $type instanceof ArrayType) { + return false; + } + + if (! $type->getItemType() instanceof MixedType) { + return false; + } + + return $type->getKeyType() instanceof IntegerType; + } +} diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/AddReturnDocblockDataProviderRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/AddReturnDocblockDataProviderRector.php index 6ff34c31698..51bdf3071d7 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/AddReturnDocblockDataProviderRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/AddReturnDocblockDataProviderRector.php @@ -7,18 +7,13 @@ use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Stmt\Class_; -use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Return_; use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; -use PHPStan\Type\Type; -use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; -use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\PHPUnit\NodeAnalyzer\TestsNodeAnalyzer; -use Rector\Privatization\TypeManipulator\TypeNormalizer; use Rector\Rector\AbstractRector; -use Rector\StaticTypeMapper\StaticTypeMapper; use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedGenericObjectType; +use Rector\TypeDeclarationDocblocks\NodeDocblockTypeDecorator; use Rector\TypeDeclarationDocblocks\NodeFinder\DataProviderMethodsFinder; use Rector\TypeDeclarationDocblocks\NodeFinder\ReturnNodeFinder; use Rector\TypeDeclarationDocblocks\NodeFinder\YieldNodeFinder; @@ -36,11 +31,9 @@ public function __construct( private readonly TestsNodeAnalyzer $testsNodeAnalyzer, private readonly DataProviderMethodsFinder $dataProviderMethodsFinder, private readonly ReturnNodeFinder $returnNodeFinder, - private readonly StaticTypeMapper $staticTypeMapper, - private readonly DocBlockUpdater $docBlockUpdater, - private readonly TypeNormalizer $typeNormalizer, private readonly YieldTypeResolver $yieldTypeResolver, private readonly YieldNodeFinder $yieldNodeFinder, + private readonly NodeDocblockTypeDecorator $nodeDocblockTypeDecorator, ) { } @@ -140,12 +133,16 @@ public function refactor(Node $node): ?Node $soleReturnType = $this->getType($soleReturn->expr); - $this->addGeneratedTypeReturnDocblockType( + $hasClassMethodChanged = $this->nodeDocblockTypeDecorator->decorateGenericIterableReturnType( $soleReturnType, $classMethodPhpDocInfo, $dataProviderClassMethod ); + if (! $hasClassMethodChanged) { + continue; + } + $hasChanged = true; continue; } @@ -159,7 +156,16 @@ public function refactor(Node $node): ?Node $yieldType = new FullyQualifiedGenericObjectType('Iterator', $yieldType->getTypes()); } - $this->addGeneratedTypeReturnDocblockType($yieldType, $classMethodPhpDocInfo, $dataProviderClassMethod); + $hasClassMethodChanged = $this->nodeDocblockTypeDecorator->decorateGenericIterableReturnType( + $yieldType, + $classMethodPhpDocInfo, + $dataProviderClassMethod + ); + + if (! $hasClassMethodChanged) { + continue; + } + $hasChanged = true; } } @@ -170,20 +176,4 @@ public function refactor(Node $node): ?Node return $node; } - - private function addGeneratedTypeReturnDocblockType( - Type $soleReturnType, - PhpDocInfo $classMethodPhpDocInfo, - ClassMethod $dataProviderClassMethod - ): void { - $generalizedReturnType = $this->typeNormalizer->generalizeConstantTypes($soleReturnType); - - // turn into rather generic short return type, to keep it open to extension later and readable to human - $typeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($generalizedReturnType); - - $returnTagValueNode = new ReturnTagValueNode($typeNode, ''); - $classMethodPhpDocInfo->addTagValueNode($returnTagValueNode); - - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($dataProviderClassMethod); - } } diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php index 65016b5e907..3b87d293b59 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/ClassMethodArrayDocblockParamFromLocalCallsRector.php @@ -7,19 +7,12 @@ use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; -use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; -use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; -use PHPStan\Type\ArrayType; -use PHPStan\Type\IntegerType; -use PHPStan\Type\MixedType; use PHPStan\Type\Type; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; -use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\PhpParser\NodeFinder\LocalMethodCallFinder; -use Rector\Privatization\TypeManipulator\TypeNormalizer; use Rector\Rector\AbstractRector; -use Rector\StaticTypeMapper\StaticTypeMapper; use Rector\TypeDeclaration\NodeAnalyzer\CallTypesResolver; +use Rector\TypeDeclarationDocblocks\NodeDocblockTypeDecorator; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -30,11 +23,9 @@ final class ClassMethodArrayDocblockParamFromLocalCallsRector extends AbstractRe { public function __construct( private readonly PhpDocInfoFactory $phpDocInfoFactory, - private readonly DocBlockUpdater $docBlockUpdater, - private readonly StaticTypeMapper $staticTypeMapper, private readonly CallTypesResolver $callTypesResolver, private readonly LocalMethodCallFinder $localMethodCallFinder, - private readonly TypeNormalizer $typeNormalizer + private readonly NodeDocblockTypeDecorator $nodeDocblockTypeDecorator ) { } @@ -121,28 +112,16 @@ public function refactor(Node $node): ?Node continue; } - $normalizedResolvedParameterType = $this->typeNormalizer->generalizeConstantTypes( - $resolvedParameterType + $hasClassMethodChanged = $this->nodeDocblockTypeDecorator->decorateGenericIterableParamType( + $resolvedParameterType, + $classMethodPhpDocInfo, + $classMethod, + $parameterName ); - // most likely mixed, skip - if ($this->isArrayMixed($normalizedResolvedParameterType)) { - continue; + if ($hasClassMethodChanged) { + $hasChanged = true; } - - $arrayDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode( - $normalizedResolvedParameterType - ); - - if ($arrayDocTypeNode instanceof IdentifierTypeNode && $arrayDocTypeNode->name === 'mixed') { - $arrayDocTypeNode = new ArrayTypeNode($arrayDocTypeNode); - } - - $paramTagValueNode = new ParamTagValueNode($arrayDocTypeNode, false, '$' . $parameterName, '', false); - $classMethodPhpDocInfo->addTagValueNode($paramTagValueNode); - - $hasChanged = true; - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($classMethod); } } @@ -152,17 +131,4 @@ public function refactor(Node $node): ?Node return $node; } - - private function isArrayMixed(Type $type): bool - { - if (! $type instanceof ArrayType) { - return false; - } - - if (! $type->getItemType() instanceof MixedType) { - return false; - } - - return $type->getKeyType() instanceof IntegerType; - } } diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php new file mode 100644 index 00000000000..61a077be7cc --- /dev/null +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarArrayFromPropertyDefaultsRector.php @@ -0,0 +1,106 @@ +getProperties() as $property) { + if (! $property->type instanceof Identifier) { + continue; + } + + if (! $this->isName($property->type, 'array')) { + continue; + } + + if (count($property->props) > 1) { + continue; + } + + $soleProperty = $property->props[0]; + if (! $soleProperty->default instanceof Array_) { + continue; + } + + $propertyDefaultType = $this->getType($soleProperty->default); + + $propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($property); + + // type is already known + if ($propertyPhpDocInfo->getVarTagValueNode() instanceof VarTagValueNode) { + continue; + } + + $this->nodeDocblockTypeDecorator->decorateGenericIterableVarType( + $propertyDefaultType, + $propertyPhpDocInfo, + $property + ); + $hasChanged = true; + } + + if (! $hasChanged) { + return null; + } + + return $node; + } +} diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php index ddf0ce7300c..2ca43d1c4c6 100644 --- a/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/DocblockVarFromParamDocblockInConstructorRector.php @@ -11,10 +11,9 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; use PHPStan\Type\ArrayType; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; -use Rector\Comments\NodeDocBlock\DocBlockUpdater; use Rector\Rector\AbstractRector; -use Rector\StaticTypeMapper\StaticTypeMapper; use Rector\TypeDeclarationDocblocks\NodeAnalyzer\ConstructorAssignedTypeResolver; +use Rector\TypeDeclarationDocblocks\NodeDocblockTypeDecorator; use Rector\ValueObject\MethodName; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; @@ -26,9 +25,8 @@ final class DocblockVarFromParamDocblockInConstructorRector extends AbstractRect { public function __construct( private readonly PhpDocInfoFactory $phpDocInfoFactory, - private readonly DocBlockUpdater $docBlockUpdater, - private readonly StaticTypeMapper $staticTypeMapper, private readonly ConstructorAssignedTypeResolver $constructorAssignedTypeResolver, + private readonly NodeDocblockTypeDecorator $nodeDocblockTypeDecorator ) { } @@ -109,14 +107,17 @@ public function refactor(Node $node): ?Node continue; } - $arrayDocTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPHPStanPhpDocTypeNode($assignedType); + $hasPropertyChanged = $this->nodeDocblockTypeDecorator->decorateGenericIterableVarType( + $assignedType, + $propertyPhpDocInfo, + $property + ); - $returnTagValueNode = new VarTagValueNode($arrayDocTypeNode, '', ''); - $propertyPhpDocInfo->addTagValueNode($returnTagValueNode); + if (! $hasPropertyChanged) { + continue; + } $hasChanged = true; - - $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($property); } if (! $hasChanged) {