[DeadCode] Add RemoveDefaultArgumentValueRector

This commit is contained in:
Tomas Votruba 2019-05-05 21:44:05 +02:00
parent 4f47659ce6
commit bd909ab3ef
12 changed files with 489 additions and 0 deletions

View File

@ -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": {

View File

@ -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: ~

View File

@ -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);
}
}

View File

@ -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;
}
}
?>

View File

@ -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();
?>

View File

@ -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;
}
}

View File

@ -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');
}
}
?>

View File

@ -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();
}
}
?>

View File

@ -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;
}
}

View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\MethodCall\RemoveDefaultArgumentValueRector\Source;
function userDefinedFunction($values = [])
{
}

View File

@ -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) {

View 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();
}
}