Skip to content

Commit c32b630

Browse files
authored
[CodeQuality] Fill reasonable default value on OptionalParametersAfterRequiredRector (#7226)
* [CodeQuality] Fill reasonable default value on OptionalParametersAfterRequiredRector * [CodeQuality] Fill reasonable default value on OptionalParametersAfterRequiredRector * Fix independent true, false, null param * Fix mixed nullable
1 parent f414fd2 commit c32b630

File tree

2 files changed

+143
-32
lines changed

2 files changed

+143
-32
lines changed

rules-tests/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector/Fixture/typed_params.php.inc

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,26 @@ class TypedParams
1919
function run4($optional = 1, A&B $required)
2020
{
2121
}
22+
23+
function run5($optional = 1, array $required)
24+
{
25+
}
26+
27+
function run6($optional = 1, true $required)
28+
{
29+
}
30+
31+
function run7($optional = 1, false $required)
32+
{
33+
}
34+
35+
function run8($optional = 1, null $required)
36+
{
37+
}
38+
39+
function run9($optional = 1, mixed $required)
40+
{
41+
}
2242
}
2343

2444
?>
@@ -29,11 +49,11 @@ namespace Rector\Tests\CodeQuality\Rector\ClassMethod\OptionalParametersAfterReq
2949

3050
class TypedParams
3151
{
32-
function run1($optional = 1, ?int $required = null)
52+
function run1($optional = 1, int $required = 0)
3353
{
3454
}
3555

36-
function run2($optional = 1, string|int|null $required = null)
56+
function run2($optional = 1, string|int $required = '')
3757
{
3858
}
3959

@@ -44,6 +64,26 @@ class TypedParams
4464
function run4($optional = 1, (A&B)|null $required = null)
4565
{
4666
}
67+
68+
function run5($optional = 1, array $required = [])
69+
{
70+
}
71+
72+
function run6($optional = 1, true $required = true)
73+
{
74+
}
75+
76+
function run7($optional = 1, false $required = false)
77+
{
78+
}
79+
80+
function run8($optional = 1, null $required = null)
81+
{
82+
}
83+
84+
function run9($optional = 1, mixed $required = null)
85+
{
86+
}
4787
}
4888

4989
?>

rules/CodeQuality/Rector/ClassMethod/OptionalParametersAfterRequiredRector.php

Lines changed: 101 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@
1414
use PhpParser\Node\Name;
1515
use PhpParser\Node\NullableType;
1616
use PhpParser\Node\Param;
17+
use PhpParser\Node\Scalar\Float_;
18+
use PhpParser\Node\Scalar\Int_;
19+
use PhpParser\Node\Scalar\String_;
1720
use PhpParser\Node\Stmt\ClassMethod;
1821
use PhpParser\Node\Stmt\Function_;
1922
use PhpParser\Node\UnionType;
23+
use Rector\PhpParser\Node\Value\ValueResolver;
2024
use Rector\Rector\AbstractRector;
2125
use Rector\ValueObject\PhpVersionFeature;
2226
use Rector\VersionBonding\Contract\MinPhpVersionInterface;
@@ -28,14 +32,19 @@
2832
*/
2933
final class OptionalParametersAfterRequiredRector extends AbstractRector implements MinPhpVersionInterface
3034
{
35+
public function __construct(
36+
private readonly ValueResolver $valueResolver,
37+
) {
38+
}
39+
3140
public function getRuleDefinition(): RuleDefinition
3241
{
33-
return new RuleDefinition('Add null default value when a required parameter follows an optional one', [
42+
return new RuleDefinition('Add reasonable default value when a required parameter follows an optional one', [
3443
new CodeSample(
3544
<<<'CODE_SAMPLE'
3645
class SomeObject
3746
{
38-
public function run($optional = 1, $required)
47+
public function run($optional = 1, int $required)
3948
{
4049
}
4150
}
@@ -45,7 +54,7 @@ public function run($optional = 1, $required)
4554
<<<'CODE_SAMPLE'
4655
class SomeObject
4756
{
48-
public function run($optional = 1, $required = null)
57+
public function run($optional = 1, int $required = 0)
4958
{
5059
}
5160
}
@@ -84,47 +93,109 @@ public function refactor(Node $node): ClassMethod|Function_|Closure|null
8493
$previousParam = $node->params[$key - 1] ?? null;
8594
if ($previousParam instanceof Param && $previousParam->default instanceof Expr) {
8695
$hasChanged = true;
96+
$this->processParam($param);
97+
}
98+
}
8799

88-
$param->default = new ConstFetch(new Name('null'));
100+
return $hasChanged ? $node : null;
101+
}
89102

90-
if (! $param->type instanceof Node) {
91-
continue;
92-
}
103+
public function provideMinPhpVersion(): int
104+
{
105+
return PhpVersionFeature::NULLABLE_TYPE;
106+
}
93107

94-
if ($param->type instanceof NullableType) {
95-
continue;
96-
}
108+
/**
109+
* Look first found type reasonable value
110+
*
111+
* @param Node[] $types
112+
*/
113+
private function mapReasonableParamValue(array $types): Expr
114+
{
115+
foreach ($types as $type) {
116+
if ($this->isName($type, 'string')) {
117+
return new String_('');
118+
}
97119

98-
if ($param->type instanceof UnionType) {
99-
foreach ($param->type->types as $unionedType) {
100-
if ($unionedType instanceof Identifier && $this->isName($unionedType, 'null')) {
101-
continue 2;
102-
}
103-
}
120+
if ($this->isName($type, 'int')) {
121+
return new Int_(0);
122+
}
104123

105-
$param->type->types[] = new Identifier('null');
106-
continue;
107-
}
124+
if ($this->isName($type, 'float')) {
125+
return new Float_(0.0);
126+
}
108127

109-
if ($param->type instanceof IntersectionType) {
110-
$param->type = new UnionType([$param->type, new Identifier('null')]);
128+
if ($this->isName($type, 'bool')) {
129+
return $this->nodeFactory->createFalse();
130+
}
111131

112-
continue;
113-
}
132+
if ($this->isName($type, 'array')) {
133+
return $this->nodeFactory->createArray([]);
134+
}
114135

115-
if ($param->type instanceof ComplexType) {
116-
continue;
117-
}
136+
if ($this->isName($type, 'true')) {
137+
return $this->nodeFactory->createTrue();
138+
}
118139

119-
$param->type = new NullableType($param->type);
140+
if ($this->isName($type, 'false')) {
141+
return $this->nodeFactory->createFalse();
120142
}
121143
}
122144

123-
return $hasChanged ? $node : null;
145+
return new ConstFetch(new Name('null'));
124146
}
125147

126-
public function provideMinPhpVersion(): int
148+
private function processParam(Param $param): void
127149
{
128-
return PhpVersionFeature::NULLABLE_TYPE;
150+
if (! $param->type instanceof Node) {
151+
$param->default = new ConstFetch(new Name('null'));
152+
return;
153+
}
154+
155+
if ($param->type instanceof NullableType) {
156+
$param->default = new ConstFetch(new Name('null'));
157+
return;
158+
}
159+
160+
if ($param->type instanceof IntersectionType) {
161+
$param->default = new ConstFetch(new Name('null'));
162+
$param->type = new UnionType([$param->type, new Identifier('null')]);
163+
return;
164+
}
165+
166+
if ($param->type instanceof UnionType) {
167+
foreach ($param->type->types as $unionedType) {
168+
if ($unionedType instanceof Identifier && $this->isName($unionedType, 'null')) {
169+
$param->default = new ConstFetch(new Name('null'));
170+
return;
171+
}
172+
}
173+
174+
$reasonableValue = $this->mapReasonableParamValue($param->type->types);
175+
if ($this->valueResolver->isNull($reasonableValue)) {
176+
$param->default = new ConstFetch(new Name('null'));
177+
$param->type->types[] = new Identifier('null');
178+
return;
179+
}
180+
181+
$param->default = $reasonableValue;
182+
return;
183+
}
184+
185+
if ($param->type instanceof ComplexType) {
186+
return;
187+
}
188+
189+
$reasonableValue = $this->mapReasonableParamValue([$param->type]);
190+
if ($this->valueResolver->isNull($reasonableValue)) {
191+
if (! $param->type instanceof Identifier || ! $this->isNames($param->type, ['null', 'mixed'])) {
192+
$param->type = new NullableType($param->type);
193+
}
194+
195+
$param->default = new ConstFetch(new Name('null'));
196+
return;
197+
}
198+
199+
$param->default = $reasonableValue;
129200
}
130201
}

0 commit comments

Comments
 (0)