diff --git a/rules-tests/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector/Fixture/typed_params.php.inc b/rules-tests/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector/Fixture/typed_params.php.inc index 244ec16a5a9..719bb40be34 100644 --- a/rules-tests/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector/Fixture/typed_params.php.inc +++ b/rules-tests/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector/Fixture/typed_params.php.inc @@ -19,6 +19,26 @@ class TypedParams function run4($optional = 1, A&B $required) { } + + function run5($optional = 1, array $required) + { + } + + function run6($optional = 1, true $required) + { + } + + function run7($optional = 1, false $required) + { + } + + function run8($optional = 1, null $required) + { + } + + function run9($optional = 1, mixed $required) + { + } } ?> @@ -29,11 +49,11 @@ namespace Rector\Tests\CodeQuality\Rector\ClassMethod\OptionalParametersAfterReq class TypedParams { - function run1($optional = 1, ?int $required = null) + function run1($optional = 1, int $required = 0) { } - function run2($optional = 1, string|int|null $required = null) + function run2($optional = 1, string|int $required = '') { } @@ -44,6 +64,26 @@ class TypedParams function run4($optional = 1, (A&B)|null $required = null) { } + + function run5($optional = 1, array $required = []) + { + } + + function run6($optional = 1, true $required = true) + { + } + + function run7($optional = 1, false $required = false) + { + } + + function run8($optional = 1, null $required = null) + { + } + + function run9($optional = 1, mixed $required = null) + { + } } ?> diff --git a/rules/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php b/rules/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php index 5dc2fb3624e..b9be5cbff33 100644 --- a/rules/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php +++ b/rules/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php @@ -14,9 +14,13 @@ use PhpParser\Node\Name; use PhpParser\Node\NullableType; use PhpParser\Node\Param; +use PhpParser\Node\Scalar\Float_; +use PhpParser\Node\Scalar\Int_; +use PhpParser\Node\Scalar\String_; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Function_; use PhpParser\Node\UnionType; +use Rector\PhpParser\Node\Value\ValueResolver; use Rector\Rector\AbstractRector; use Rector\ValueObject\PhpVersionFeature; use Rector\VersionBonding\Contract\MinPhpVersionInterface; @@ -28,14 +32,19 @@ */ final class OptionalParametersAfterRequiredRector extends AbstractRector implements MinPhpVersionInterface { + public function __construct( + private readonly ValueResolver $valueResolver, + ) { + } + public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition('Add null default value when a required parameter follows an optional one', [ + return new RuleDefinition('Add reasonable default value when a required parameter follows an optional one', [ new CodeSample( <<<'CODE_SAMPLE' class SomeObject { - public function run($optional = 1, $required) + public function run($optional = 1, int $required) { } } @@ -45,7 +54,7 @@ public function run($optional = 1, $required) <<<'CODE_SAMPLE' class SomeObject { - public function run($optional = 1, $required = null) + public function run($optional = 1, int $required = 0) { } } @@ -84,47 +93,109 @@ public function refactor(Node $node): ClassMethod|Function_|Closure|null $previousParam = $node->params[$key - 1] ?? null; if ($previousParam instanceof Param && $previousParam->default instanceof Expr) { $hasChanged = true; + $this->processParam($param); + } + } - $param->default = new ConstFetch(new Name('null')); + return $hasChanged ? $node : null; + } - if (! $param->type instanceof Node) { - continue; - } + public function provideMinPhpVersion(): int + { + return PhpVersionFeature::NULLABLE_TYPE; + } - if ($param->type instanceof NullableType) { - continue; - } + /** + * Look first found type reasonable value + * + * @param Node[] $types + */ + private function mapReasonableParamValue(array $types): Expr + { + foreach ($types as $type) { + if ($this->isName($type, 'string')) { + return new String_(''); + } - if ($param->type instanceof UnionType) { - foreach ($param->type->types as $unionedType) { - if ($unionedType instanceof Identifier && $this->isName($unionedType, 'null')) { - continue 2; - } - } + if ($this->isName($type, 'int')) { + return new Int_(0); + } - $param->type->types[] = new Identifier('null'); - continue; - } + if ($this->isName($type, 'float')) { + return new Float_(0.0); + } - if ($param->type instanceof IntersectionType) { - $param->type = new UnionType([$param->type, new Identifier('null')]); + if ($this->isName($type, 'bool')) { + return $this->nodeFactory->createFalse(); + } - continue; - } + if ($this->isName($type, 'array')) { + return $this->nodeFactory->createArray([]); + } - if ($param->type instanceof ComplexType) { - continue; - } + if ($this->isName($type, 'true')) { + return $this->nodeFactory->createTrue(); + } - $param->type = new NullableType($param->type); + if ($this->isName($type, 'false')) { + return $this->nodeFactory->createFalse(); } } - return $hasChanged ? $node : null; + return new ConstFetch(new Name('null')); } - public function provideMinPhpVersion(): int + private function processParam(Param $param): void { - return PhpVersionFeature::NULLABLE_TYPE; + if (! $param->type instanceof Node) { + $param->default = new ConstFetch(new Name('null')); + return; + } + + if ($param->type instanceof NullableType) { + $param->default = new ConstFetch(new Name('null')); + return; + } + + if ($param->type instanceof IntersectionType) { + $param->default = new ConstFetch(new Name('null')); + $param->type = new UnionType([$param->type, new Identifier('null')]); + return; + } + + if ($param->type instanceof UnionType) { + foreach ($param->type->types as $unionedType) { + if ($unionedType instanceof Identifier && $this->isName($unionedType, 'null')) { + $param->default = new ConstFetch(new Name('null')); + return; + } + } + + $reasonableValue = $this->mapReasonableParamValue($param->type->types); + if ($this->valueResolver->isNull($reasonableValue)) { + $param->default = new ConstFetch(new Name('null')); + $param->type->types[] = new Identifier('null'); + return; + } + + $param->default = $reasonableValue; + return; + } + + if ($param->type instanceof ComplexType) { + return; + } + + $reasonableValue = $this->mapReasonableParamValue([$param->type]); + if ($this->valueResolver->isNull($reasonableValue)) { + if (! $param->type instanceof Identifier || ! $this->isNames($param->type, ['null', 'mixed'])) { + $param->type = new NullableType($param->type); + } + + $param->default = new ConstFetch(new Name('null')); + return; + } + + $param->default = $reasonableValue; } }