diff --git a/config/set/rector-preset.php b/config/set/rector-preset.php index ba74445ba5e..3602ea12c12 100644 --- a/config/set/rector-preset.php +++ b/config/set/rector-preset.php @@ -7,6 +7,7 @@ use Rector\PHPUnit\CodeQuality\Rector\Class_\AddSeeTestAnnotationRector; use Rector\Privatization\Rector\Class_\FinalizeTestCaseClassRector; use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector; +use Rector\TypeDeclarationDocblocks\Rector\Class_\AddParamTypeToRefactorMethodRector; use Rector\Utils\Rector\RemoveRefactorDuplicatedNodeInstanceCheckRector; return static function (RectorConfig $rectorConfig): void { @@ -14,6 +15,7 @@ DeclareStrictTypesRector::class, PostIncDecToPreIncDecRector::class, FinalizeTestCaseClassRector::class, + AddParamTypeToRefactorMethodRector::class, RemoveRefactorDuplicatedNodeInstanceCheckRector::class, AddSeeTestAnnotationRector::class, ]); diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/AddParamTypeToRefactorMethodRectorTest.php b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/AddParamTypeToRefactorMethodRectorTest.php new file mode 100644 index 00000000000..0c1717e7be3 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/AddParamTypeToRefactorMethodRectorTest.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_/AddParamTypeToRefactorMethodRector/Fixture/add_multiple_node_types.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/Fixture/add_multiple_node_types.php.inc new file mode 100644 index 00000000000..e1bd208e8ee --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/Fixture/add_multiple_node_types.php.inc @@ -0,0 +1,58 @@ + +----- + diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/Fixture/add_single_node_type.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/Fixture/add_single_node_type.php.inc new file mode 100644 index 00000000000..6fe6c981261 --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/Fixture/add_single_node_type.php.inc @@ -0,0 +1,56 @@ + +----- + diff --git a/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/Fixture/skip_already_has_param.php.inc b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/Fixture/skip_already_has_param.php.inc new file mode 100644 index 00000000000..fb9ae61c72f --- /dev/null +++ b/rules-tests/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector/Fixture/skip_already_has_param.php.inc @@ -0,0 +1,27 @@ +withRules([AddParamTypeToRefactorMethodRector::class]) + ->withImportNames(); diff --git a/rules/Transform/Rector/ConstFetch/ConstFetchToClassConstFetchRector.php b/rules/Transform/Rector/ConstFetch/ConstFetchToClassConstFetchRector.php index 7a6f3ab1e2e..b49b4f08707 100644 --- a/rules/Transform/Rector/ConstFetch/ConstFetchToClassConstFetchRector.php +++ b/rules/Transform/Rector/ConstFetch/ConstFetchToClassConstFetchRector.php @@ -38,6 +38,9 @@ public function getNodeTypes(): array return [ConstFetch::class]; } + /** + * @param ConstFetch $node + */ public function refactor(Node $node): ?ClassConstFetch { foreach ($this->constFetchToClassConsts as $constFetchToClassConst) { diff --git a/rules/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector.php b/rules/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector.php new file mode 100644 index 00000000000..47132806f6e --- /dev/null +++ b/rules/TypeDeclarationDocblocks/Rector/Class_/AddParamTypeToRefactorMethodRector.php @@ -0,0 +1,171 @@ +extends instanceof Name) { + return null; + } + + if (! $this->isObjectType($node, new ObjectType('Rector\Rector\AbstractRector'))) { + return null; + } + + $refactorClassMethod = $node->getMethod('refactor'); + if (! $refactorClassMethod instanceof ClassMethod) { + return null; + } + + $param = $refactorClassMethod->params[0] ?? null; + if ($param === null) { + return null; + } + + $refactorPhpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($refactorClassMethod); + + // only add when missing, never override an existing, possibly more precise @param + if ($refactorPhpDocInfo->getParamTagValueByName('node') instanceof ParamTagValueNode) { + return null; + } + + $nodeType = $this->matchSingleNodeTypesType($node); + if (! $nodeType instanceof Type) { + return null; + } + + $hasChanged = $this->phpDocTypeChanger->changeParamType( + $refactorClassMethod, + $refactorPhpDocInfo, + $nodeType, + $param, + 'node' + ); + + if (! $hasChanged) { + return null; + } + + return $node; + } + + private function matchSingleNodeTypesType(Class_ $class): ?Type + { + $getNodeTypesClassMethod = $class->getMethod('getNodeTypes'); + if (! $getNodeTypesClassMethod instanceof ClassMethod) { + return null; + } + + $soleReturn = $getNodeTypesClassMethod->stmts[0] ?? null; + if (! $soleReturn instanceof Return_) { + return null; + } + + // only inline array literals, e.g. "return [MethodCall::class];" + // constant groups like NodeGroup::STMTS_AWARE expand to an unwieldy union + if (! $soleReturn->expr instanceof Array_) { + return null; + } + + $nodeTypes = $this->valueResolver->getValue($soleReturn->expr); + if (! is_array($nodeTypes) || $nodeTypes === []) { + return null; + } + + $objectTypes = []; + foreach ($nodeTypes as $nodeType) { + if (! is_string($nodeType)) { + return null; + } + + // skip interface node types, they expand to an unwieldy union of all implementers + if (! $this->reflectionProvider->hasClass($nodeType)) { + return null; + } + + if ($this->reflectionProvider->getClass($nodeType)->isInterface()) { + return null; + } + + $objectTypes[] = new ObjectType($nodeType); + } + + return TypeCombinator::union(...$objectTypes); + } +} diff --git a/tests/Issues/ScopeNotAvailable/Variable/ArrayItemForeachValueRector.php b/tests/Issues/ScopeNotAvailable/Variable/ArrayItemForeachValueRector.php index 2b0bb0a9a87..629739b7d36 100644 --- a/tests/Issues/ScopeNotAvailable/Variable/ArrayItemForeachValueRector.php +++ b/tests/Issues/ScopeNotAvailable/Variable/ArrayItemForeachValueRector.php @@ -26,6 +26,9 @@ public function getNodeTypes(): array return [Variable::class]; } + /** + * @param Variable $node + */ public function refactor(Node $node): Node { return $node; diff --git a/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php b/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php index 44361f9432d..45e4f073cd3 100644 --- a/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php +++ b/tests/PhpParser/NodeTraverser/ClassLike/RuleUsingClassLikeRector.php @@ -28,6 +28,9 @@ public function getNodeTypes(): array return [ClassLike::class]; } + /** + * @param ClassLike $node + */ public function refactor(Node $node): Node { return $node; diff --git a/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php b/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php index 02e8070f83f..0ac080c7b5a 100644 --- a/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php +++ b/tests/PhpParser/NodeTraverser/Class_/RuleUsingClassRector.php @@ -28,6 +28,9 @@ public function getNodeTypes(): array return [Class_::class]; } + /** + * @param Class_ $node + */ public function refactor(Node $node): Node { return $node; diff --git a/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php b/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php index 6c23a37406d..d844b367451 100644 --- a/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php +++ b/tests/PhpParser/NodeTraverser/Function_/RuleUsingFunctionRector.php @@ -28,6 +28,9 @@ public function getNodeTypes(): array return [Function_::class]; } + /** + * @param Function_ $node + */ public function refactor(Node $node): Node { return $node;