From 2a1295adbd7840ba99b68229324fc1986d9aa1e4 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 12 Sep 2025 13:26:47 +0200 Subject: [PATCH] [type-declaration-docblocks] Add AddParamArrayDocblockFromDimFetchAccessRector --- config/set/php85.php | 2 +- config/set/type-declaration-docblocks.php | 2 + ...ayDocblockFromDimFetchAccessRectorTest.php | 28 +++ .../Fixture/int_keys.php.inc | 34 +++ .../Fixture/param_array.php.inc | 34 +++ .../Fixture/skip_mixed.php.inc | 11 + .../Fixture/skip_no_access.php.inc | 11 + .../config/configured_rule.php | 9 + .../Class_/WakeupToUnserializeRector.php | 24 +-- .../NodeFinder/DimFetchFinder.php | 36 ++++ ...mArrayDocblockFromDimFetchAccessRector.php | 198 ++++++++++++++++++ src/ValueObject/PhpVersionFeature.php | 4 +- 12 files changed, 377 insertions(+), 16 deletions(-) create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/AddParamArrayDocblockFromDimFetchAccessRectorTest.php create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/int_keys.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/param_array.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/skip_mixed.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/skip_no_access.php.inc create mode 100644 rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/config/configured_rule.php create mode 100644 rules/TypeDeclarationDocblocks/NodeFinder/DimFetchFinder.php create mode 100644 rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector.php diff --git a/config/set/php85.php b/config/set/php85.php index 3bd05b90193..a118ca891b9 100644 --- a/config/set/php85.php +++ b/config/set/php85.php @@ -42,7 +42,7 @@ ChrArgModuloRector::class, SleepToSerializeRector::class, OrdSingleByteRector::class, - WakeupToUnserializeRector::class + WakeupToUnserializeRector::class, ] ); diff --git a/config/set/type-declaration-docblocks.php b/config/set/type-declaration-docblocks.php index 69f0c3d21b7..79b2806614e 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\AddParamArrayDocblockFromDimFetchAccessRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\AddReturnDocblockForCommonObjectDenominatorRector; use Rector\TypeDeclarationDocblocks\Rector\ClassMethod\DocblockGetterReturnArrayFromPropertyDocblockVarRector; @@ -20,5 +21,6 @@ DocblockVarFromParamDocblockInConstructorRector::class, DocblockGetterReturnArrayFromPropertyDocblockVarRector::class, AddReturnDocblockForCommonObjectDenominatorRector::class, + AddParamArrayDocblockFromDimFetchAccessRector::class, ]); }; diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/AddParamArrayDocblockFromDimFetchAccessRectorTest.php b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/AddParamArrayDocblockFromDimFetchAccessRectorTest.php new file mode 100644 index 00000000000..01d4d9222f7 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/AddParamArrayDocblockFromDimFetchAccessRectorTest.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/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/int_keys.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/int_keys.php.inc new file mode 100644 index 00000000000..f3102f2f8e9 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/int_keys.php.inc @@ -0,0 +1,34 @@ + +----- + $data + */ + public function process(array $data): void + { + $item = $data[55]; + + $anotherItem = $data[132]; + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/param_array.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/param_array.php.inc new file mode 100644 index 00000000000..7f41dfb0a42 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/param_array.php.inc @@ -0,0 +1,34 @@ + +----- + $data + */ + public function process(array $data): void + { + $item = $data['key']; + + $anotherItem = $data['another_key']; + } +} + +?> diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/skip_mixed.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/skip_mixed.php.inc new file mode 100644 index 00000000000..82f8ae9d471 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector/Fixture/skip_mixed.php.inc @@ -0,0 +1,11 @@ +withRules([AddParamArrayDocblockFromDimFetchAccessRector::class]); diff --git a/rules/Php85/Rector/Class_/WakeupToUnserializeRector.php b/rules/Php85/Rector/Class_/WakeupToUnserializeRector.php index 87b7dd8256b..6f3e0c2b6a0 100644 --- a/rules/Php85/Rector/Class_/WakeupToUnserializeRector.php +++ b/rules/Php85/Rector/Class_/WakeupToUnserializeRector.php @@ -5,6 +5,7 @@ namespace Rector\Php85\Rector\Class_; use PhpParser\Node; +use PhpParser\Node\Arg; use PhpParser\Node\Expr\Assign; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Expr\PropertyFetch; @@ -86,22 +87,21 @@ public function refactor(Node $node): ?Node if (! $classMethod instanceof ClassMethod) { return null; } - + $classMethod->name = new Identifier('__unserialize'); $classMethod->returnType = new Identifier('void'); - $param = new Param( - var: new Variable('data'), - type: new Identifier('array') - ); + + $param = new Param(var: new Variable('data'), type: new Identifier('array')); $classMethod->params[] = $param; $classMethod->stmts = [$this->assignProperties()]; - + return $node; } - protected function assignProperties(): Foreach_{ + private function assignProperties(): Foreach_ + { $assign = new Assign( new PropertyFetch(new Variable('this'), new Variable('property')), new Variable('value') @@ -109,23 +109,21 @@ protected function assignProperties(): Foreach_{ $if = new If_( new FuncCall(new Name('property_exists'), [ - new Node\Arg(new Variable('this')), - new Node\Arg(new Variable('property')), + new Arg(new Variable('this')), + new Arg(new Variable('property')), ]), [ 'stmts' => [new Expression($assign)], ] ); - $foreach = new Foreach_( + return new Foreach_( new Variable('data'), new Variable('value'), [ 'keyVar' => new Variable('property'), - 'stmts' => [$if], + 'stmts' => [$if], ] ); - - return $foreach; } } diff --git a/rules/TypeDeclarationDocblocks/NodeFinder/DimFetchFinder.php b/rules/TypeDeclarationDocblocks/NodeFinder/DimFetchFinder.php new file mode 100644 index 00000000000..81b71a6bd80 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/NodeFinder/DimFetchFinder.php @@ -0,0 +1,36 @@ +betterNodeFinder->findInstancesOfScoped([$node], ArrayDimFetch::class); + + return array_filter($dimFetches, function (ArrayDimFetch $arrayDimFetch) use ($variableName): bool { + if (! $arrayDimFetch->var instanceof Variable) { + return false; + } + + return $this->nodeNameResolver->isName($arrayDimFetch->var, $variableName); + }); + } +} diff --git a/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector.php b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector.php new file mode 100644 index 00000000000..0942ba3a4b0 --- /dev/null +++ b/rules/TypeDeclarationDocblocks/Rector/ClassMethod/AddParamArrayDocblockFromDimFetchAccessRector.php @@ -0,0 +1,198 @@ + $data + */ + public function process(array $data): void + { + $item = $data['key']; + + $anotherItem = $data['another_key']; + } +} +CODE_SAMPLE + ), + + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Function_::class]; + } + + /** + * @param ClassMethod|Function_ $node + */ + public function refactor(Node $node): ?Node + { + if ($node->getParams() === []) { + return null; + } + + $phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($node); + + $hasChanged = false; + + foreach ($node->getParams() as $param) { + if (! $param->type instanceof Node) { + continue; + } + + if (! $this->isName($param->type, 'array')) { + continue; + } + + /** @var string $paramName */ + $paramName = $this->getName($param->var); + + $paramTagValueNode = $phpDocInfo->getParamTagValueByName($paramName); + + // already defined, lets skip it + if ($paramTagValueNode instanceof ParamTagValueNode) { + continue; + } + + $dimFetches = $this->dimFetchFinder->findByVariableName($node, $paramName); + if ($dimFetches === []) { + continue; + } + + $keyTypes = []; + foreach ($dimFetches as $dimFetch) { + // nothing to resolve here + if (! $dimFetch->dim instanceof Expr) { + continue; + } + + $keyTypes[] = $this->getType($dimFetch->dim); + } + + // most likely not being read + if ($keyTypes === []) { + continue; + } + + if ($this->isOnlyStringType($keyTypes)) { + $paramTagValueNode = $this->createParamTagValueNode($paramName, 'string'); + $phpDocInfo->addTagValueNode($paramTagValueNode); + $hasChanged = true; + continue; + } + + if ($this->isOnlyIntegerType($keyTypes)) { + $paramTagValueNode = $this->createParamTagValueNode($paramName, 'int'); + $phpDocInfo->addTagValueNode($paramTagValueNode); + $hasChanged = true; + continue; + } + + } + + if ($hasChanged === false) { + return null; + } + + $this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($node); + + return $node; + } + + /** + * @param Type[] $keyTypes + */ + private function isOnlyStringType(array $keyTypes): bool + { + foreach ($keyTypes as $keyType) { + if ($keyType->isString()->yes()) { + continue; + } + + return false; + } + + return true; + } + + /** + * @param Type[] $keyTypes + */ + private function isOnlyIntegerType(array $keyTypes): bool + { + foreach ($keyTypes as $keyType) { + if ($keyType->isInteger()->yes()) { + continue; + } + + return false; + } + + return true; + } + + private function createParamTagValueNode(string $paramName, string $keyTypeValue): ParamTagValueNode + { + $arrayGenericTypeNode = new GenericTypeNode(new IdentifierTypeNode('array'), [ + new IdentifierTypeNode($keyTypeValue), + new IdentifierTypeNode('mixed'), + ]); + + return new ParamTagValueNode($arrayGenericTypeNode, false, '$' . $paramName, '', false); + } +} diff --git a/src/ValueObject/PhpVersionFeature.php b/src/ValueObject/PhpVersionFeature.php index 182b8b4a3cb..6730eebcb47 100644 --- a/src/ValueObject/PhpVersionFeature.php +++ b/src/ValueObject/PhpVersionFeature.php @@ -822,13 +822,13 @@ final class PhpVersionFeature * @var int */ public const DEPRECATED_METHOD_SLEEP = PhpVersion::PHP_85; - + /** * @see https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_sleep_and_wakeup_magic_methods * @var int */ public const DEPRECATED_METHOD_WAKEUP = PhpVersion::PHP_85; - + /** * @see https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_passing_string_which_are_not_one_byte_long_to_ord * @var int