mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-01 17:00:51 +00:00
[DeadCode] Add RemoveDefaultArgumentValueRector
This commit is contained in:
parent
4f47659ce6
commit
bd909ab3ef
|
@ -8,6 +8,7 @@
|
|||
"php": "^7.1",
|
||||
"composer/xdebug-handler": "^1.3",
|
||||
"jean85/pretty-package-versions": "^1.2",
|
||||
"jetbrains/phpstorm-stubs": "^2019.1",
|
||||
"nette/robot-loader": "^3.1",
|
||||
"nette/utils": "^2.5|^3.0",
|
||||
"nikic/php-parser": "^4.2.1",
|
||||
|
@ -110,6 +111,9 @@
|
|||
"tests/Source",
|
||||
"tests/Rector/Psr4/MultipleClassFileToPsr4ClassesRector/Source",
|
||||
"tests/Rector/Namespace_/PseudoNamespaceToNamespaceRector/Source"
|
||||
],
|
||||
"files": [
|
||||
"packages/DeadCode/tests/Rector/MethodCall/RemoveDefaultArgumentValueRector/Source/UserDefined.php"
|
||||
]
|
||||
},
|
||||
"suggest": {
|
||||
|
|
|
@ -16,3 +16,4 @@ services:
|
|||
Rector\DeadCode\Rector\FunctionLike\RemoveDeadReturnRector: ~
|
||||
Rector\DeadCode\Rector\For_\RemoveDeadIfForeachForRector: ~
|
||||
Rector\DeadCode\Rector\BooleanAnd\RemoveAndTrueRector: ~
|
||||
Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector: ~
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Rector\MethodCall;
|
||||
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PhpParser\Node\Expr\StaticCall;
|
||||
use PhpParser\Node\FunctionLike;
|
||||
use Rector\NodeContainer\ParsedNodesByType;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\Rector\AbstractRector;
|
||||
use Rector\RectorDefinition\CodeSample;
|
||||
use Rector\RectorDefinition\RectorDefinition;
|
||||
use Rector\Reflection\FunctionReflectionResolver;
|
||||
use ReflectionFunction;
|
||||
|
||||
final class RemoveDefaultArgumentValueRector extends AbstractRector
|
||||
{
|
||||
/**
|
||||
* @var ParsedNodesByType
|
||||
*/
|
||||
private $parsedNodesByType;
|
||||
|
||||
/**
|
||||
* @var FunctionReflectionResolver
|
||||
*/
|
||||
private $functionReflectionResolver;
|
||||
|
||||
public function __construct(
|
||||
ParsedNodesByType $parsedNodesByType,
|
||||
FunctionReflectionResolver $functionReflectionResolver
|
||||
) {
|
||||
$this->parsedNodesByType = $parsedNodesByType;
|
||||
$this->functionReflectionResolver = $functionReflectionResolver;
|
||||
}
|
||||
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Remove argument value, if it is the same as default value', [
|
||||
new CodeSample(
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$this->runWithDefault([]);
|
||||
$card = self::runWithStaticDefault([]);
|
||||
}
|
||||
|
||||
public function runWithDefault($items = [])
|
||||
{
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function runStaticWithDefault($cards = [])
|
||||
{
|
||||
return $cards;
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
,
|
||||
<<<'CODE_SAMPLE'
|
||||
class SomeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$this->runWithDefault();
|
||||
$card = self::runWithStaticDefault();
|
||||
}
|
||||
|
||||
public function runWithDefault($items = [])
|
||||
{
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function runStaticWithDefault($cards = [])
|
||||
{
|
||||
return $cards;
|
||||
}
|
||||
}
|
||||
CODE_SAMPLE
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [MethodCall::class, StaticCall::class, FuncCall::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MethodCall|StaticCall|FuncCall $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($node->args === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$defaultValues = $this->resolveDefaultValuesFromCall($node);
|
||||
|
||||
$keysToRemove = $this->resolveKeysToRemove($node, $defaultValues);
|
||||
foreach ($keysToRemove as $keyToRemove) {
|
||||
unset($node->args[$keyToRemove]);
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StaticCall|MethodCall|FuncCall $node
|
||||
* @param Expr[]|mixed[] $defaultValues
|
||||
* @return int[]
|
||||
*/
|
||||
private function resolveKeysToRemove(Node $node, array $defaultValues): array
|
||||
{
|
||||
$keysToRemove = [];
|
||||
$keysToKeep = [];
|
||||
|
||||
/** @var int $key */
|
||||
foreach ($node->args as $key => $arg) {
|
||||
if (! isset($defaultValues[$key])) {
|
||||
$keysToKeep[] = $key;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->areNodesEqual($defaultValues[$key], $arg->value)) {
|
||||
$keysToRemove[] = $key;
|
||||
} else {
|
||||
$keysToKeep[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if ($keysToRemove === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($keysToKeep !== []) {
|
||||
if (max($keysToKeep) > max($keysToRemove)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return $keysToRemove;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StaticCall|FuncCall|MethodCall $node
|
||||
* @return Expr[]
|
||||
*/
|
||||
private function resolveDefaultValuesFromCall(Node $node): array
|
||||
{
|
||||
/** @var string $nodeName */
|
||||
$nodeName = $this->getName($node);
|
||||
|
||||
if ($node instanceof FuncCall) {
|
||||
return $this->resolveFuncCallDefaultParamValues($nodeName);
|
||||
}
|
||||
|
||||
/** @var string|null $className */
|
||||
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
|
||||
if ($className === null) { // anonymous class
|
||||
return [];
|
||||
}
|
||||
|
||||
$classMethodNode = $this->parsedNodesByType->findMethod($nodeName, $className);
|
||||
if ($classMethodNode !== null) {
|
||||
return $this->resolveDefaultParamValuesFromFunctionLike($classMethodNode);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Node[]
|
||||
*/
|
||||
private function resolveDefaultParamValuesFromFunctionLike(FunctionLike $functionLike): array
|
||||
{
|
||||
$defaultValues = [];
|
||||
foreach ($functionLike->getParams() as $key => $param) {
|
||||
if ($param->default === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$defaultValues[$key] = $param->default;
|
||||
}
|
||||
|
||||
return $defaultValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Expr[]
|
||||
*/
|
||||
private function resolveFuncCallDefaultParamValues(string $nodeName): array
|
||||
{
|
||||
$functionNode = $this->parsedNodesByType->findFunction($nodeName);
|
||||
if ($functionNode) {
|
||||
return $this->resolveDefaultParamValuesFromFunctionLike($functionNode);
|
||||
}
|
||||
|
||||
// non existing function
|
||||
if (! function_exists($nodeName)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$reflectionFunction = new ReflectionFunction($nodeName);
|
||||
if ($reflectionFunction->isUserDefined()) {
|
||||
$defaultValues = [];
|
||||
foreach ($reflectionFunction->getParameters() as $key => $reflectionParameter) {
|
||||
if ($reflectionParameter->isDefaultValueAvailable()) {
|
||||
$defaultValues[$key] = BuilderHelpers::normalizeValue($reflectionParameter->getDefaultValue());
|
||||
}
|
||||
}
|
||||
|
||||
return $defaultValues;
|
||||
}
|
||||
|
||||
$coreFunctionReflection = $this->functionReflectionResolver->resolveCoreStubFunctionNode($nodeName);
|
||||
|
||||
// unable to found
|
||||
if ($coreFunctionReflection === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->resolveDefaultParamValuesFromFunctionLike($coreFunctionReflection);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
|
||||
|
||||
class SomeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$this->runWithDefault([]);
|
||||
$card = self::runStaticWithDefault([]);
|
||||
}
|
||||
|
||||
public function runWithDefault($items = [])
|
||||
{
|
||||
return $items;
|
||||
}
|
||||
|
||||
public static function runStaticWithDefault($cards = [])
|
||||
{
|
||||
return $cards;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
|
||||
|
||||
class SomeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$this->runWithDefault();
|
||||
$card = self::runStaticWithDefault();
|
||||
}
|
||||
|
||||
public function runWithDefault($items = [])
|
||||
{
|
||||
return $items;
|
||||
}
|
||||
|
||||
public static function runStaticWithDefault($cards = [])
|
||||
{
|
||||
return $cards;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
|
||||
|
||||
function someLocalFunction($items = [])
|
||||
{
|
||||
}
|
||||
|
||||
someLocalFunction([]);
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
|
||||
|
||||
function someLocalFunction($items = [])
|
||||
{
|
||||
}
|
||||
|
||||
someLocalFunction();
|
||||
|
||||
?>
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
|
||||
|
||||
class SkipPreviousOrder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$this->runWithDefault([], 5);
|
||||
}
|
||||
|
||||
public function runWithDefault($items = [], $value)
|
||||
{
|
||||
return $items;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
|
||||
|
||||
class SystemFunction
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
trigger_error('Error message', E_USER_NOTICE);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
|
||||
|
||||
class SystemFunction
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
trigger_error('Error message');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
|
||||
|
||||
use function Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Source\userDefinedFunction;
|
||||
|
||||
class UserVendorFunction
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
userDefinedFunction([]);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Fixture;
|
||||
|
||||
use function Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Source\userDefinedFunction;
|
||||
|
||||
class UserVendorFunction
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
userDefinedFunction();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,26 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector;
|
||||
|
||||
use Rector\DeadCode\Rector\MethodCall\RemoveDefaultArgumentValueRector;
|
||||
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
|
||||
final class RemoveDefaultArgumentValueRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
public function test(): void
|
||||
{
|
||||
$this->doTestFiles([
|
||||
__DIR__ . '/Fixture/fixture.php.inc',
|
||||
__DIR__ . '/Fixture/skip_previous_order.php.inc',
|
||||
__DIR__ . '/Fixture/function.php.inc',
|
||||
// reflection
|
||||
__DIR__ . '/Fixture/user_vendor_function.php.inc',
|
||||
__DIR__ . '/Fixture/system_function.php.inc',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
{
|
||||
return RemoveDefaultArgumentValueRector::class;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Source;
|
||||
|
||||
function userDefinedFunction($values = [])
|
||||
{
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ namespace Rector\PhpParser\Node\Value;
|
|||
use PhpParser\ConstExprEvaluator;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\ClassConstFetch;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Scalar\MagicConst\Dir;
|
||||
use PhpParser\Node\Scalar\MagicConst\File;
|
||||
use PHPStan\Type\Constant\ConstantArrayType;
|
||||
|
@ -58,6 +59,10 @@ final class ValueResolver
|
|||
return $value;
|
||||
}
|
||||
|
||||
if ($expr instanceof ConstFetch) {
|
||||
return $this->nameResolver->resolve($expr);
|
||||
}
|
||||
|
||||
$nodeStaticType = $this->nodeTypeResolver->getNodeStaticType($expr);
|
||||
|
||||
if ($nodeStaticType instanceof ConstantArrayType) {
|
||||
|
|
66
src/Reflection/FunctionReflectionResolver.php
Normal file
66
src/Reflection/FunctionReflectionResolver.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Rector\Reflection;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt\Function_;
|
||||
use Rector\Exception\ShouldNotHappenException;
|
||||
use Rector\PhpParser\Node\BetterNodeFinder;
|
||||
use Rector\PhpParser\Parser\Parser;
|
||||
|
||||
final class FunctionReflectionResolver
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private const POSSIBLE_CORE_STUB_LOCATIONS = [
|
||||
__DIR__ . '/../../../../jetbrains/phpstorm-stubs/Core/Core.php',
|
||||
__DIR__ . '/../../vendor/jetbrains/phpstorm-stubs/Core/Core.php',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Parser
|
||||
*/
|
||||
private $parser;
|
||||
|
||||
/**
|
||||
* @var BetterNodeFinder
|
||||
*/
|
||||
private $betterNodeFinder;
|
||||
|
||||
public function __construct(Parser $parser, BetterNodeFinder $betterNodeFinder)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
$this->betterNodeFinder = $betterNodeFinder;
|
||||
}
|
||||
|
||||
public function resolveCoreStubFunctionNode(string $nodeName): ?Function_
|
||||
{
|
||||
$stubFileLocation = $this->resolveCoreStubLocation();
|
||||
|
||||
$nodes = $this->parser->parseFile($stubFileLocation);
|
||||
|
||||
/** @var Function_|null $function */
|
||||
$function = $this->betterNodeFinder->findFirst($nodes, function (Node $node) use ($nodeName) {
|
||||
if (! $node instanceof Function_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (string) $node->name === $nodeName;
|
||||
});
|
||||
|
||||
return $function;
|
||||
}
|
||||
|
||||
private function resolveCoreStubLocation(): string
|
||||
{
|
||||
foreach (self::POSSIBLE_CORE_STUB_LOCATIONS as $possibleCoreStubLocation) {
|
||||
if (file_exists($possibleCoreStubLocation)) {
|
||||
/** @var string $stubFileLocation */
|
||||
return $possibleCoreStubLocation;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user