diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/AddParamTypeFromStrictMethodCallPassRectorTest.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/AddParamTypeFromStrictMethodCallPassRectorTest.php new file mode 100644 index 00000000000..f61365953ca --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/AddParamTypeFromStrictMethodCallPassRectorTest.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/AddParamTypeFromStrictMethodCallPassRector/Fixture/fixture.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..ff53ff1cf3c --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/Fixture/fixture.php.inc @@ -0,0 +1,35 @@ +checkInt($number); + } + + private function checkInt(int $integer) + { + } +} + +?> +----- +checkInt($number); + } + + private function checkInt(int $integer) + { + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/Fixture/skip_if_check.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/Fixture/skip_if_check.php.inc new file mode 100644 index 00000000000..d7be8720a5a --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/Fixture/skip_if_check.php.inc @@ -0,0 +1,19 @@ +checkInt($number); + } + + private function checkInt(int $integer) + { + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/config/configured_rule.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/config/configured_rule.php new file mode 100644 index 00000000000..a1329044217 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([AddParamTypeFromStrictMethodCallPassRector::class]); diff --git a/rules/DeadCode/Rector/Stmt/RemoveConditionExactReturnRector.php b/rules/DeadCode/Rector/Stmt/RemoveConditionExactReturnRector.php index b7ccb574b5a..b9f76999273 100644 --- a/rules/DeadCode/Rector/Stmt/RemoveConditionExactReturnRector.php +++ b/rules/DeadCode/Rector/Stmt/RemoveConditionExactReturnRector.php @@ -8,8 +8,6 @@ use PhpParser\Node\Expr; use PhpParser\Node\Expr\BinaryOp\Equal; use PhpParser\Node\Expr\BinaryOp\Identical; -use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Stmt\If_; use PhpParser\Node\Stmt\Return_; use Rector\Contract\PhpParser\Node\StmtsAwareInterface; diff --git a/rules/TypeDeclaration/NodeTypeAnalyzer/MethodCallParamTypeResolver.php b/rules/TypeDeclaration/NodeTypeAnalyzer/MethodCallParamTypeResolver.php new file mode 100644 index 00000000000..53e096b1981 --- /dev/null +++ b/rules/TypeDeclaration/NodeTypeAnalyzer/MethodCallParamTypeResolver.php @@ -0,0 +1,39 @@ + + */ + public function resolve(MethodCall $methodCall): array + { + $methodReflection = $this->reflectionResolver->resolveMethodReflectionFromMethodCall($methodCall); + if (! $methodReflection instanceof MethodReflection) { + return []; + } + + $extendedParametersAcceptor = ParametersAcceptorSelector::combineAcceptors($methodReflection->getVariants()); + + $typeByPosition = []; + foreach ($extendedParametersAcceptor->getParameters() as $position => $parameterReflection) { + $typeByPosition[$position] = $parameterReflection->getNativeType(); + } + + return $typeByPosition; + } +} diff --git a/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector.php b/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector.php new file mode 100644 index 00000000000..10c0e77c84a --- /dev/null +++ b/rules/TypeDeclaration/Rector/ClassMethod/AddParamTypeFromStrictMethodCallPassRector.php @@ -0,0 +1,167 @@ +resolve($value); + } + + private function resolve(int $value) + { + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +final class SomeClass +{ + public function run(int $value) + { + $this->resolve($value); + } + + private function resolve(int $value) + { + } +} +CODE_SAMPLE + ), + + ] + ); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Function_::class, Closure::class]; + } + + /** + * @param ClassMethod|Function_|Closure $node + */ + public function refactor(Node $node): ?Node + { + if ($node->getParams() === []) { + return null; + } + + if ($node instanceof ClassMethod && $node->stmts === null) { + return null; + } + + $hasChanged = false; + + $usedVariables = $this->betterNodeFinder->findInstancesOfScoped((array) $node->stmts, Variable::class); + + foreach ($node->getParams() as $param) { + // already known type + if ($param->type instanceof Node) { + continue; + } + + /** @var string $paramName */ + $paramName = $this->getName($param->var); + + $paramVariables = array_filter( + $usedVariables, + fn (Variable $variable): bool => $this->isName($variable, $paramName) + ); + + if (count($paramVariables) >= 2) { + // skip for now, as we look for sole use + continue; + } + + $methodCalls = $this->betterNodeFinder->findInstancesOfScoped((array) $node->stmts, MethodCall::class); + foreach ($methodCalls as $methodCall) { + if ($methodCall->isFirstClassCallable()) { + continue; + } + + $typesByPosition = $this->methodCallParamTypeResolver->resolve($methodCall); + + $usedPosition = $this->matchParamMethodCallUsedPosition($methodCall, $paramName); + if (! is_int($usedPosition)) { + continue; + } + + $paramType = $typesByPosition[$usedPosition] ?? null; + if (! $paramType instanceof Type) { + continue; + } + + $paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($paramType, TypeKind::PARAM); + if (! $paramTypeNode instanceof Node) { + continue; + } + + $param->type = $paramTypeNode; + $hasChanged = true; + + // go to next param + continue 2; + } + } + + return null; + } + + private function matchParamMethodCallUsedPosition(MethodCall $methodCall, string $paramName): int|null + { + foreach ($methodCall->getArgs() as $position => $arg) { + if (! $arg->value instanceof Variable) { + continue; + } + + if (! $this->isName($arg->value, $paramName)) { + continue; + } + + return $position; + } + + return null; + } +} diff --git a/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromIterableMethodCallRector.php b/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromIterableMethodCallRector.php index 6f54f3fe03d..a1a511d096d 100644 --- a/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromIterableMethodCallRector.php +++ b/rules/TypeDeclaration/Rector/FunctionLike/AddClosureParamTypeFromIterableMethodCallRector.php @@ -95,13 +95,11 @@ public function refactor(Node $node): ?Node } $varType = $this->getType($node->var); - if (! $varType instanceof IntersectionType || ! $varType->isIterable()->yes()) { return null; } $className = $varType->getObjectClassNames()[0] ?? null; - if ($className === null) { return null; } diff --git a/src/Config/Level/TypeDeclarationLevel.php b/src/Config/Level/TypeDeclarationLevel.php index f4ae649db3e..d883d511da3 100644 --- a/src/Config/Level/TypeDeclarationLevel.php +++ b/src/Config/Level/TypeDeclarationLevel.php @@ -20,6 +20,7 @@ use Rector\TypeDeclaration\Rector\ClassMethod\AddParamStringTypeFromSprintfUseRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeBasedOnPHPUnitDataProviderRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromPropertyTypeRector; +use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeFromStrictMethodCallPassRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeDeclarationBasedOnParentClassMethodRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeFromTryCatchTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; @@ -149,6 +150,7 @@ final class TypeDeclarationLevel // array parameter from dim fetch assign inside StrictArrayParamDimFetchRector::class, AddParamFromDimFetchKeyUseRector::class, + AddParamTypeFromStrictMethodCallPassRector::class, AddParamStringTypeFromSprintfUseRector::class, // possibly based on docblocks, but also helpful, intentionally last diff --git a/src/Reflection/ReflectionResolver.php b/src/Reflection/ReflectionResolver.php index 9ec329c1086..3631c3a9b0d 100644 --- a/src/Reflection/ReflectionResolver.php +++ b/src/Reflection/ReflectionResolver.php @@ -15,8 +15,6 @@ use PhpParser\Node\Name; use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassLike; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Function_; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionReflection;