From e7d1fc84ac214995e8bbfbce3f5dfafed26a4f9d Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 10 Sep 2025 18:50:43 +0200 Subject: [PATCH 1/2] add fixture with nullable setter --- .../Fixture/pass_to_nullable_int.php.inc | 35 +++++++++++++++++++ .../Source/NullableSetter.php | 4 +++ 2 files changed, 39 insertions(+) create mode 100644 rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Fixture/pass_to_nullable_int.php.inc diff --git a/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Fixture/pass_to_nullable_int.php.inc b/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Fixture/pass_to_nullable_int.php.inc new file mode 100644 index 00000000..aa77facc --- /dev/null +++ b/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Fixture/pass_to_nullable_int.php.inc @@ -0,0 +1,35 @@ +setValue('letter', '123456'); + } +} + +?> +----- +setValue('letter', 123456); + } +} + +?> diff --git a/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Source/NullableSetter.php b/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Source/NullableSetter.php index 1de7575c..faf0e82b 100644 --- a/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Source/NullableSetter.php +++ b/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Source/NullableSetter.php @@ -9,4 +9,8 @@ final class NullableSetter public function setMaybe(?string $status) { } + + public function setValue(?string $letter, ?int $number = null) + { + } } From f19faadbbc22347e299322926f5599e4dc409564 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 10 Sep 2025 19:03:20 +0200 Subject: [PATCH 2/2] include New_ --- .../Fixture/new_instance.php.inc | 33 ++++++++++++ .../Source/ValueObjectWithConstructor.php | 12 +++++ ...calarArgumentToExpectedParamTypeRector.php | 21 ++++---- ...MethodParametersAndReturnTypesResolver.php | 54 +++++++++++++++---- 4 files changed, 101 insertions(+), 19 deletions(-) create mode 100644 rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Fixture/new_instance.php.inc create mode 100644 rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Source/ValueObjectWithConstructor.php diff --git a/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Fixture/new_instance.php.inc b/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Fixture/new_instance.php.inc new file mode 100644 index 00000000..694d903d --- /dev/null +++ b/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Fixture/new_instance.php.inc @@ -0,0 +1,33 @@ + +----- + diff --git a/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Source/ValueObjectWithConstructor.php b/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Source/ValueObjectWithConstructor.php new file mode 100644 index 00000000..288b8eb5 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/MethodCall/ScalarArgumentToExpectedParamTypeRector/Source/ValueObjectWithConstructor.php @@ -0,0 +1,12 @@ +isInTestClass($call)) { + if (! $this->isInTestClass($callLike)) { return true; } - if ($call->isFirstClassCallable()) { + if ($callLike->isFirstClassCallable()) { return true; } - if ($call->getArgs() === []) { + if ($callLike->getArgs() === []) { return true; } - return ! $this->hasStringOrNumberArguments($call); + return ! $this->hasStringOrNumberArguments($callLike); } - private function hasStringOrNumberArguments(StaticCall|MethodCall $call): bool + private function hasStringOrNumberArguments(StaticCall|MethodCall|New_ $callLike): bool { - foreach ($call->getArgs() as $arg) { + foreach ($callLike->getArgs() as $arg) { if ($arg->value instanceof Int_) { return true; } @@ -192,7 +193,7 @@ private function hasStringOrNumberArguments(StaticCall|MethodCall $call): bool return false; } - private function isInTestClass(StaticCall|MethodCall $call): bool + private function isInTestClass(StaticCall|MethodCall|New_ $call): bool { $callerClassReflection = $this->reflectionResolver->resolveClassReflection($call); if (! $callerClassReflection instanceof ClassReflection) { diff --git a/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php b/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php index e4a7b2f6..3fc0fd1a 100644 --- a/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php +++ b/rules/CodeQuality/Reflection/MethodParametersAndReturnTypesResolver.php @@ -5,11 +5,14 @@ namespace Rector\PHPUnit\CodeQuality\Reflection; use PhpParser\Node\Expr\MethodCall; +use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Identifier; +use PhpParser\Node\Name; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\ExtendedMethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\IntersectionType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; @@ -23,7 +26,8 @@ final readonly class MethodParametersAndReturnTypesResolver { public function __construct( - private NodeTypeResolver $nodeTypeResolver + private NodeTypeResolver $nodeTypeResolver, + private ReflectionProvider $reflectionProvider ) { } @@ -64,15 +68,31 @@ public function resolveFromReflection( /** * @return null|Type[] */ - public function resolveCallParameterTypes(MethodCall|StaticCall $call): ?array + public function resolveCallParameterTypes(MethodCall|StaticCall|New_ $callLike): ?array { - if (! $call->name instanceof Identifier) { + if ($callLike instanceof New_) { + if (! $callLike->class instanceof Name) { + return null; + } + + $className = $callLike->class->toString(); + if (! $this->reflectionProvider->hasClass($className)) { + return null; + } + + $classReflection = $this->reflectionProvider->getClass($className); + return $this->resolveParameterTypes($classReflection->getConstructor(), $classReflection); + } + + if (! $callLike->name instanceof Identifier) { return null; } - $methodName = $call->name->toString(); + $methodName = $callLike->name->toString(); - $callerType = $this->nodeTypeResolver->getType($call instanceof MethodCall ? $call->var : $call->class); + $callerType = $this->nodeTypeResolver->getType( + $callLike instanceof MethodCall ? $callLike->var : $callLike->class + ); if ($callerType instanceof ThisType) { $callerType = $callerType->getStaticObjectType(); } @@ -97,15 +117,31 @@ public function resolveCallParameterTypes(MethodCall|StaticCall $call): ?array /** * @return string[] */ - public function resolveCallParameterNames(MethodCall|StaticCall $call): array + public function resolveCallParameterNames(MethodCall|StaticCall|New_ $callLike): array { - if (! $call->name instanceof Identifier) { + if ($callLike instanceof New_) { + if (! $callLike->class instanceof Name) { + return []; + } + + $className = $callLike->class->toString(); + if (! $this->reflectionProvider->hasClass($className)) { + return []; + } + + $classReflection = $this->reflectionProvider->getClass($className); + return $this->resolveParameterNames($classReflection->getConstructor()); + } + + if (! $callLike->name instanceof Identifier) { return []; } - $methodName = $call->name->toString(); + $methodName = $callLike->name->toString(); - $callerType = $this->nodeTypeResolver->getType($call instanceof MethodCall ? $call->var : $call->class); + $callerType = $this->nodeTypeResolver->getType( + $callLike instanceof MethodCall ? $callLike->var : $callLike->class + ); if (! $callerType instanceof ObjectType) { return []; }