Skip to content

Commit 898298f

Browse files
committed
Refactor: added NullableParameterFixer
1 parent c3364e6 commit 898298f

File tree

10 files changed

+596
-0
lines changed

10 files changed

+596
-0
lines changed

src/Ast/DefaultValue.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ public function toString(): string
3535
}
3636

3737

38+
public function isNull(): bool
39+
{
40+
$literalValue = trim($this->literal->getLiteral());
41+
return strtolower($literalValue) === 'null';
42+
}
43+
44+
3845
public static function parseForFunctionParameter(NodeParser $parser): self
3946
{
4047
$nodeIndentation = $parser->consumeNodeIndentation();

src/Ast/NamedType.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ public function toString(): string
4848
}
4949

5050

51+
public function isNullable(): bool
52+
{
53+
return $this->nullableSign === '?';
54+
}
55+
56+
57+
public function setNullable(bool $nullable): void
58+
{
59+
$this->nullableSign = $nullable ? '?' : '';
60+
}
61+
62+
5163
public static function parse(NodeParser $parser): self
5264
{
5365
$nodeIndentation = $parser->consumeNodeIndentation();

src/Ast/Parameter.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ public function hasPromotedProperty(): bool
6262
}
6363

6464

65+
public function getType(): ?Type
66+
{
67+
return $this->type;
68+
}
69+
70+
71+
public function getDefaultValue(): ?DefaultValue
72+
{
73+
return $this->defaultValue;
74+
}
75+
76+
6577
public function toString()
6678
{
6779
$s = $this->indentation;

src/Ast/Type.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,47 @@ public function __construct(string $indentation, array $types)
2828
}
2929

3030

31+
/**
32+
* @return NamedType[]
33+
*/
34+
public function getNamedTypes(): array
35+
{
36+
return $this->types;
37+
}
38+
39+
40+
public function isNullable(): bool
41+
{
42+
foreach ($this->types as $namedType) {
43+
if ($namedType->isNullable()) {
44+
return TRUE;
45+
}
46+
}
47+
48+
return FALSE;
49+
}
50+
51+
52+
public function setNullable(bool $nullable): void
53+
{
54+
$namedTypes = $this->types;
55+
56+
if (count($namedTypes) > 1) {
57+
throw new \CzProject\PhpSimpleAst\InvalidStateException('Union types are not supported.');
58+
}
59+
60+
foreach ($namedTypes as $namedType) {
61+
$namedType->setNullable($nullable);
62+
}
63+
}
64+
65+
66+
public function isSingle(): bool
67+
{
68+
return count($this->types) === 1;
69+
}
70+
71+
3172
public function toString(): string
3273
{
3374
$s = $this->indentation;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CzProject\PhpSimpleAst\Refactor;
6+
7+
use CzProject\PhpSimpleAst\Reflection;
8+
9+
10+
class NullableParameterFixer
11+
{
12+
/**
13+
* @param Reflection\ClassReflection[] $classes
14+
*/
15+
public static function processClasses(array $classes): void
16+
{
17+
foreach ($classes as $class) {
18+
self::processClass($class);
19+
}
20+
}
21+
22+
23+
public static function processClass(Reflection\ClassReflection $classReflection): void
24+
{
25+
foreach ($classReflection->getMethods() as $methodReflection) {
26+
$parameters = $methodReflection->getParameters();
27+
28+
foreach ($parameters as $parameter) {
29+
$type = $parameter->getType();
30+
$defaultValue = $parameter->getDefaultValue();
31+
32+
if ($type !== NULL
33+
&& $defaultValue !== NULL
34+
&& $defaultValue->isNull()
35+
&& $type->isSingle()
36+
&& !$type->isNullable()
37+
) {
38+
$parameter->setNullable(true);
39+
}
40+
}
41+
}
42+
}
43+
}

src/Reflection/ParameterReflection.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,40 @@ public function isPassedByReference(): bool
4545
{
4646
return $this->parameter->isPassedByReference();
4747
}
48+
49+
50+
public function getType(): ?Ast\Type
51+
{
52+
return $this->parameter->getType();
53+
}
54+
55+
56+
public function getDefaultValue(): ?Ast\DefaultValue
57+
{
58+
return $this->parameter->getDefaultValue();
59+
}
60+
61+
62+
public function isNullable(): bool
63+
{
64+
$type = $this->parameter->getType();
65+
66+
if ($type === NULL) {
67+
return FALSE;
68+
}
69+
70+
return $type->isNullable();
71+
}
72+
73+
74+
public function setNullable(bool $nullable): void
75+
{
76+
$type = $this->parameter->getType();
77+
78+
if ($type === NULL) {
79+
throw new \CzProject\PhpSimpleAst\InvalidStateException('Parameter has no type.');
80+
}
81+
82+
$type->setNullable($nullable);
83+
}
4884
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use CzProject\PhpSimpleAst;
6+
use Tester\Assert;
7+
8+
require __DIR__ . '/../../bootstrap.php';
9+
10+
11+
test('nullable parameter fixer', function () {
12+
$reflection = PhpSimpleAst\Reflection\FilesReflection::scanFile(Fixtures::path('Refactoring/NullableParameterFixer.php'));
13+
$classReflection = $reflection->getClass(\Foo\TestClass::class);
14+
15+
PhpSimpleAst\Refactor\NullableParameterFixer::processClass($classReflection);
16+
17+
$method = $classReflection->getMethod('methodWithNullableParams');
18+
$parameters = $method->getParameters();
19+
20+
// param1: string $param1 = null should become nullable
21+
Assert::true($parameters['param1']->isNullable());
22+
23+
// param2: int $param2 = 42 should NOT become nullable (default is not null)
24+
Assert::false($parameters['param2']->isNullable());
25+
26+
// param3: array $param3 = null should become nullable
27+
Assert::true($parameters['param3']->isNullable());
28+
29+
// param4: $param4 = null should NOT be affected (no type declaration)
30+
Assert::false($parameters['param4']->isNullable());
31+
32+
// Test method without defaults - should not be affected
33+
$method2 = $classReflection->getMethod('methodWithoutDefaults');
34+
$parameters2 = $method2->getParameters();
35+
Assert::false($parameters2['param1']->isNullable());
36+
Assert::false($parameters2['param2']->isNullable());
37+
38+
// Test method with already nullable types - should remain unchanged
39+
$method3 = $classReflection->getMethod('methodWithNullableTypes');
40+
$parameters3 = $method3->getParameters();
41+
Assert::true($parameters3['param1']->isNullable());
42+
Assert::true($parameters3['param2']->isNullable());
43+
44+
Assert::same(
45+
Fixtures::load('Refactoring/NullableParameterFixer.result'),
46+
$reflection->getFiles()[0]->toString()
47+
);
48+
});

0 commit comments

Comments
 (0)