Skip to content

Commit bd909ab

Browse files
committed
[DeadCode] Add RemoveDefaultArgumentValueRector
1 parent 4f47659 commit bd909ab

File tree

12 files changed

+489
-0
lines changed

12 files changed

+489
-0
lines changed

composer.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"php": "^7.1",
99
"composer/xdebug-handler": "^1.3",
1010
"jean85/pretty-package-versions": "^1.2",
11+
"jetbrains/phpstorm-stubs": "^2019.1",
1112
"nette/robot-loader": "^3.1",
1213
"nette/utils": "^2.5|^3.0",
1314
"nikic/php-parser": "^4.2.1",
@@ -110,6 +111,9 @@
110111
"tests/Source",
111112
"tests/Rector/Psr4/MultipleClassFileToPsr4ClassesRector/Source",
112113
"tests/Rector/Namespace_/PseudoNamespaceToNamespaceRector/Source"
114+
],
115+
"files": [
116+
"packages/DeadCode/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/Source/UserDefined.php"
113117
]
114118
},
115119
"suggest": {

config/level/dead-code/dead-code.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ services:
1616
Rector\DeadCode\Rector\FunctionLike\RemoveDeadReturnRector: ~
1717
Rector\DeadCode\Rector\For_\RemoveDeadIfForeachForRector: ~
1818
Rector\DeadCode\Rector\BooleanAnd\RemoveAndTrueRector: ~
19+
Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector: ~
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Rector\DeadCode\Rector\MethodCall;
4+
5+
use PhpParser\BuilderHelpers;
6+
use PhpParser\Node;
7+
use PhpParser\Node\Expr;
8+
use PhpParser\Node\Expr\FuncCall;
9+
use PhpParser\Node\Expr\MethodCall;
10+
use PhpParser\Node\Expr\StaticCall;
11+
use PhpParser\Node\FunctionLike;
12+
use Rector\NodeContainer\ParsedNodesByType;
13+
use Rector\NodeTypeResolver\Node\AttributeKey;
14+
use Rector\Rector\AbstractRector;
15+
use Rector\RectorDefinition\CodeSample;
16+
use Rector\RectorDefinition\RectorDefinition;
17+
use Rector\Reflection\FunctionReflectionResolver;
18+
use ReflectionFunction;
19+
20+
final class RemoveDefaultArgumentValueRector extends AbstractRector
21+
{
22+
/**
23+
* @var ParsedNodesByType
24+
*/
25+
private $parsedNodesByType;
26+
27+
/**
28+
* @var FunctionReflectionResolver
29+
*/
30+
private $functionReflectionResolver;
31+
32+
public function __construct(
33+
ParsedNodesByType $parsedNodesByType,
34+
FunctionReflectionResolver $functionReflectionResolver
35+
) {
36+
$this->parsedNodesByType = $parsedNodesByType;
37+
$this->functionReflectionResolver = $functionReflectionResolver;
38+
}
39+
40+
public function getDefinition(): RectorDefinition
41+
{
42+
return new RectorDefinition('Remove argument value, if it is the same as default value', [
43+
new CodeSample(
44+
<<<'CODE_SAMPLE'
45+
class SomeClass
46+
{
47+
public function run()
48+
{
49+
$this->runWithDefault([]);
50+
$card = self::runWithStaticDefault([]);
51+
}
52+
53+
public function runWithDefault($items = [])
54+
{
55+
return $items;
56+
}
57+
58+
public function runStaticWithDefault($cards = [])
59+
{
60+
return $cards;
61+
}
62+
}
63+
CODE_SAMPLE
64+
,
65+
<<<'CODE_SAMPLE'
66+
class SomeClass
67+
{
68+
public function run()
69+
{
70+
$this->runWithDefault();
71+
$card = self::runWithStaticDefault();
72+
}
73+
74+
public function runWithDefault($items = [])
75+
{
76+
return $items;
77+
}
78+
79+
public function runStaticWithDefault($cards = [])
80+
{
81+
return $cards;
82+
}
83+
}
84+
CODE_SAMPLE
85+
),
86+
]);
87+
}
88+
89+
/**
90+
* @return string[]
91+
*/
92+
public function getNodeTypes(): array
93+
{
94+
return [MethodCall::class, StaticCall::class, FuncCall::class];
95+
}
96+
97+
/**
98+
* @param MethodCall|StaticCall|FuncCall $node
99+
*/
100+
public function refactor(Node $node): ?Node
101+
{
102+
if ($node->args === []) {
103+
return null;
104+
}
105+
106+
$defaultValues = $this->resolveDefaultValuesFromCall($node);
107+
108+
$keysToRemove = $this->resolveKeysToRemove($node, $defaultValues);
109+
foreach ($keysToRemove as $keyToRemove) {
110+
unset($node->args[$keyToRemove]);
111+
}
112+
113+
return $node;
114+
}
115+
116+
/**
117+
* @param StaticCall|MethodCall|FuncCall $node
118+
* @param Expr[]|mixed[] $defaultValues
119+
* @return int[]
120+
*/
121+
private function resolveKeysToRemove(Node $node, array $defaultValues): array
122+
{
123+
$keysToRemove = [];
124+
$keysToKeep = [];
125+
126+
/** @var int $key */
127+
foreach ($node->args as $key => $arg) {
128+
if (! isset($defaultValues[$key])) {
129+
$keysToKeep[] = $key;
130+
continue;
131+
}
132+
133+
if ($this->areNodesEqual($defaultValues[$key], $arg->value)) {
134+
$keysToRemove[] = $key;
135+
} else {
136+
$keysToKeep[] = $key;
137+
}
138+
}
139+
140+
if ($keysToRemove === []) {
141+
return [];
142+
}
143+
144+
if ($keysToKeep !== []) {
145+
if (max($keysToKeep) > max($keysToRemove)) {
146+
return [];
147+
}
148+
}
149+
150+
return $keysToRemove;
151+
}
152+
153+
/**
154+
* @param StaticCall|FuncCall|MethodCall $node
155+
* @return Expr[]
156+
*/
157+
private function resolveDefaultValuesFromCall(Node $node): array
158+
{
159+
/** @var string $nodeName */
160+
$nodeName = $this->getName($node);
161+
162+
if ($node instanceof FuncCall) {
163+
return $this->resolveFuncCallDefaultParamValues($nodeName);
164+
}
165+
166+
/** @var string|null $className */
167+
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
168+
if ($className === null) { // anonymous class
169+
return [];
170+
}
171+
172+
$classMethodNode = $this->parsedNodesByType->findMethod($nodeName, $className);
173+
if ($classMethodNode !== null) {
174+
return $this->resolveDefaultParamValuesFromFunctionLike($classMethodNode);
175+
}
176+
177+
return [];
178+
}
179+
180+
/**
181+
* @return Node[]
182+
*/
183+
private function resolveDefaultParamValuesFromFunctionLike(FunctionLike $functionLike): array
184+
{
185+
$defaultValues = [];
186+
foreach ($functionLike->getParams() as $key => $param) {
187+
if ($param->default === null) {
188+
continue;
189+
}
190+
191+
$defaultValues[$key] = $param->default;
192+
}
193+
194+
return $defaultValues;
195+
}
196+
197+
/**
198+
* @return Expr[]
199+
*/
200+
private function resolveFuncCallDefaultParamValues(string $nodeName): array
201+
{
202+
$functionNode = $this->parsedNodesByType->findFunction($nodeName);
203+
if ($functionNode) {
204+
return $this->resolveDefaultParamValuesFromFunctionLike($functionNode);
205+
}
206+
207+
// non existing function
208+
if (! function_exists($nodeName)) {
209+
return [];
210+
}
211+
212+
$reflectionFunction = new ReflectionFunction($nodeName);
213+
if ($reflectionFunction->isUserDefined()) {
214+
$defaultValues = [];
215+
foreach ($reflectionFunction->getParameters() as $key => $reflectionParameter) {
216+
if ($reflectionParameter->isDefaultValueAvailable()) {
217+
$defaultValues[$key] = BuilderHelpers::normalizeValue($reflectionParameter->getDefaultValue());
218+
}
219+
}
220+
221+
return $defaultValues;
222+
}
223+
224+
$coreFunctionReflection = $this->functionReflectionResolver->resolveCoreStubFunctionNode($nodeName);
225+
226+
// unable to found
227+
if ($coreFunctionReflection === null) {
228+
return [];
229+
}
230+
231+
return $this->resolveDefaultParamValuesFromFunctionLike($coreFunctionReflection);
232+
}
233+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
4+
5+
class SomeClass
6+
{
7+
public function run()
8+
{
9+
$this->runWithDefault([]);
10+
$card = self::runStaticWithDefault([]);
11+
}
12+
13+
public function runWithDefault($items = [])
14+
{
15+
return $items;
16+
}
17+
18+
public static function runStaticWithDefault($cards = [])
19+
{
20+
return $cards;
21+
}
22+
}
23+
24+
?>
25+
-----
26+
<?php
27+
28+
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
29+
30+
class SomeClass
31+
{
32+
public function run()
33+
{
34+
$this->runWithDefault();
35+
$card = self::runStaticWithDefault();
36+
}
37+
38+
public function runWithDefault($items = [])
39+
{
40+
return $items;
41+
}
42+
43+
public static function runStaticWithDefault($cards = [])
44+
{
45+
return $cards;
46+
}
47+
}
48+
49+
?>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
4+
5+
function someLocalFunction($items = [])
6+
{
7+
}
8+
9+
someLocalFunction([]);
10+
11+
?>
12+
-----
13+
<?php
14+
15+
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
16+
17+
function someLocalFunction($items = [])
18+
{
19+
}
20+
21+
someLocalFunction();
22+
23+
?>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
4+
5+
class SkipPreviousOrder
6+
{
7+
public function run()
8+
{
9+
$this->runWithDefault([], 5);
10+
}
11+
12+
public function runWithDefault($items = [], $value)
13+
{
14+
return $items;
15+
}
16+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
4+
5+
class SystemFunction
6+
{
7+
public function run()
8+
{
9+
trigger_error('Error message', E_USER_NOTICE);
10+
}
11+
}
12+
13+
?>
14+
-----
15+
<?php
16+
17+
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
18+
19+
class SystemFunction
20+
{
21+
public function run()
22+
{
23+
trigger_error('Error message');
24+
}
25+
}
26+
27+
?>

0 commit comments

Comments
 (0)