From a3997359c03a1df67dc6df5fa8efc91ccd7019fc Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Sat, 4 Oct 2025 20:05:00 +0200 Subject: [PATCH] [type-declaration] Add AddParamFromDimFetchKeyUseRector --- .../AddParamFromDimFetchKeyUseRectorTest.php | 28 ++++ .../Fixture/fixture.php.inc | 37 +++++ .../Fixture/int_or_string.php.inc | 39 ++++++ .../Fixture/skip_existing_type.php.inc | 16 +++ .../Fixture/skip_unclear_type.php.inc | 11 ++ .../config/configured_rule.php | 10 ++ .../config/configured_rule.php | 2 +- .../AddParamFromDimFetchKeyUseRector.php | 131 ++++++++++++++++++ .../NodeFinder/ArrayDimFetchFinder.php | 16 +++ src/Config/Level/TypeDeclarationLevel.php | 3 + 10 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/AddParamFromDimFetchKeyUseRectorTest.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/fixture.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/int_or_string.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/skip_existing_type.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/skip_unclear_type.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/config/configured_rule.php create mode 100644 rules/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector.php diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/AddParamFromDimFetchKeyUseRectorTest.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/AddParamFromDimFetchKeyUseRectorTest.php new file mode 100644 index 00000000000..b947ade8f89 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/AddParamFromDimFetchKeyUseRectorTest.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/AddParamFromDimFetchKeyUseRector/Fixture/fixture.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..d53ab9c48e9 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/fixture.php.inc @@ -0,0 +1,37 @@ + 'Firstitem', + 'second' => 'Seconditem', + ]; + + return $items[$key]; + } +} + +?> +----- + 'Firstitem', + 'second' => 'Seconditem', + ]; + + return $items[$key]; + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/int_or_string.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/int_or_string.php.inc new file mode 100644 index 00000000000..4fff25673ad --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/int_or_string.php.inc @@ -0,0 +1,39 @@ + 'Firstitem', + 111 => 'Seconditem', + 'second' => 'Seconditem', + ]; + + return $items[$key]; + } +} + +?> +----- + 'Firstitem', + 111 => 'Seconditem', + 'second' => 'Seconditem', + ]; + + return $items[$key]; + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/skip_existing_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/skip_existing_type.php.inc new file mode 100644 index 00000000000..6337a78d847 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/skip_existing_type.php.inc @@ -0,0 +1,16 @@ + 'Firstitem', + 'second' => 'Seconditem', + ]; + + return $items[$key]; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/skip_unclear_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/skip_unclear_type.php.inc new file mode 100644 index 00000000000..282f08dfe48 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector/Fixture/skip_unclear_type.php.inc @@ -0,0 +1,11 @@ +rule(AddParamFromDimFetchKeyUseRector::class); +}; diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/KnownMagicClassMethodTypeRector/config/configured_rule.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/KnownMagicClassMethodTypeRector/config/configured_rule.php index 93c7de5d39f..270b20ad471 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/KnownMagicClassMethodTypeRector/config/configured_rule.php +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/KnownMagicClassMethodTypeRector/config/configured_rule.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use Rector\ValueObject\PhpVersionFeature; use Rector\Config\RectorConfig; use Rector\TypeDeclaration\Rector\ClassMethod\KnownMagicClassMethodTypeRector; +use Rector\ValueObject\PhpVersionFeature; return RectorConfig::configure() ->withRules([KnownMagicClassMethodTypeRector::class]) diff --git a/rules/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector.php b/rules/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector.php new file mode 100644 index 00000000000..ee74dc5ae60 --- /dev/null +++ b/rules/TypeDeclaration/Rector/ClassMethod/AddParamFromDimFetchKeyUseRector.php @@ -0,0 +1,131 @@ + 'John', + 'age' => 30, + ]; + + return $data[$key]; + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +final class SomeClass +{ + public function get(string $key) + { + $data = [ + 'name' => 'John', + 'age' => 30, + ]; + + return $data[$key]; + } +} +CODE_SAMPLE + ), + + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + $hasChanged = false; + + foreach ($node->getMethods() as $classMethod) { + if ($classMethod->params === []) { + continue; + } + + foreach ($classMethod->getParams() as $param) { + if ($param->type instanceof Node) { + continue; + } + + /** @var string $paramName */ + $paramName = $this->getName($param->var); + + $dimFetches = $this->arrayDimFetchFinder->findByDimName($classMethod, $paramName); + if ($dimFetches === []) { + continue; + } + + foreach ($dimFetches as $dimFetch) { + $dimFetchType = $this->getType($dimFetch->var); + + if (! $dimFetchType instanceof ArrayType && ! $dimFetchType instanceof ConstantArrayType) { + continue; + } + + $paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode( + $dimFetchType->getKeyType(), + TypeKind::PARAM + ); + + if (! $paramTypeNode instanceof Node) { + continue; + } + + $param->type = $paramTypeNode; + $hasChanged = true; + } + } + } + + if ($hasChanged) { + return $node; + } + + return null; + } +} diff --git a/rules/TypeDeclarationDocblocks/NodeFinder/ArrayDimFetchFinder.php b/rules/TypeDeclarationDocblocks/NodeFinder/ArrayDimFetchFinder.php index c2222081b4c..a0481e794ce 100644 --- a/rules/TypeDeclarationDocblocks/NodeFinder/ArrayDimFetchFinder.php +++ b/rules/TypeDeclarationDocblocks/NodeFinder/ArrayDimFetchFinder.php @@ -64,4 +64,20 @@ public function findByVariableName(Node $node, string $variableName): array return $this->nodeNameResolver->isName($arrayDimFetch->var, $variableName); }); } + + /** + * @return ArrayDimFetch[] + */ + public function findByDimName(ClassMethod $classMethod, string $dimName): array + { + $dimFetches = $this->betterNodeFinder->findInstancesOfScoped([$classMethod], ArrayDimFetch::class); + + return array_filter($dimFetches, function (ArrayDimFetch $arrayDimFetch) use ($dimName): bool { + if (! $arrayDimFetch->dim instanceof Variable) { + return false; + } + + return $this->nodeNameResolver->isName($arrayDimFetch->dim, $dimName); + }); + } } diff --git a/src/Config/Level/TypeDeclarationLevel.php b/src/Config/Level/TypeDeclarationLevel.php index 6b98029ae28..f74a24201b1 100644 --- a/src/Config/Level/TypeDeclarationLevel.php +++ b/src/Config/Level/TypeDeclarationLevel.php @@ -16,6 +16,7 @@ use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromDocblockSetUpDefinedRector; use Rector\TypeDeclaration\Rector\Class_\TypedPropertyFromJMSSerializerAttributeTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector; +use Rector\TypeDeclaration\Rector\ClassMethod\AddParamFromDimFetchKeyUseRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeBasedOnPHPUnitDataProviderRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromPropertyTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeDeclarationBasedOnParentClassMethodRector; @@ -146,9 +147,11 @@ final class TypeDeclarationLevel // array parameter from dim fetch assign inside StrictArrayParamDimFetchRector::class, + AddParamFromDimFetchKeyUseRector::class, // possibly based on docblocks, but also helpful, intentionally last AddArrayFunctionClosureParamTypeRector::class, TypedPropertyFromDocblockSetUpDefinedRector::class, + ]; }