[DeadCode] Add RemoveUnusedAssignVariableRector

This commit is contained in:
TomasVotruba 2020-04-13 23:44:44 +02:00
parent d254821f7a
commit a7f5abe2f1
36 changed files with 1229 additions and 99 deletions

View File

@ -1,4 +1,4 @@
# All 482 Rectors Overview
# All 483 Rectors Overview
- [Projects](#projects)
- [General](#general)
@ -3001,6 +3001,32 @@ Remove unreachable statements
<br>
### `RemoveUnusedAssignVariableRector`
- class: [`Rector\DeadCode\Rector\Assign\RemoveUnusedAssignVariableRector`](/../master/rules/dead-code/src/Rector/Assign/RemoveUnusedAssignVariableRector.php)
- [test fixtures](/../master/rules/dead-code/tests/Rector/Assign/RemoveUnusedAssignVariableRector/Fixture)
Remove assigned unused variable
```diff
class SomeClass
{
public function run()
{
- $value = $this->process();
+ $this->process();
}
public function process()
{
// something going on
return 5;
}
}
```
<br>
### `RemoveUnusedClassConstantRector`
- class: [`Rector\DeadCode\Rector\ClassConst\RemoveUnusedClassConstantRector`](/../master/rules/dead-code/src/Rector/ClassConst/RemoveUnusedClassConstantRector.php)

View File

@ -13,6 +13,7 @@ use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\DeadCode\ScopeNesting\ParentScopeFinder;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
@ -33,14 +34,21 @@ final class PregMatchTypeCorrector
*/
private $betterStandardPrinter;
/**
* @var ParentScopeFinder
*/
private $parentScopeFinder;
public function __construct(
BetterNodeFinder $betterNodeFinder,
NodeNameResolver $nodeNameResolver,
BetterStandardPrinter $betterStandardPrinter
BetterStandardPrinter $betterStandardPrinter,
ParentScopeFinder $parentScopeFinder
) {
$this->betterNodeFinder = $betterNodeFinder;
$this->nodeNameResolver = $nodeNameResolver;
$this->betterStandardPrinter = $betterStandardPrinter;
$this->parentScopeFinder = $parentScopeFinder;
}
/**
@ -93,8 +101,7 @@ final class PregMatchTypeCorrector
*/
private function getVariableUsages(Variable $variable): array
{
$scope = $this->getScopeNode($variable);
$scope = $this->parentScopeFinder->find($variable);
if ($scope === null) {
return [];
}
@ -103,11 +110,4 @@ final class PregMatchTypeCorrector
return $node instanceof Variable && $node->name === $variable->name;
});
}
private function getScopeNode(Node $node): ?Node
{
return $node->getAttribute(AttributeKey::METHOD_NODE)
?? $node->getAttribute(AttributeKey::FUNCTION_NODE)
?? $node->getAttribute(AttributeKey::NAMESPACE_NODE);
}
}

View File

@ -95,10 +95,6 @@ parameters:
# node finder
- '#Method Rector\\(.*?) should return array<PhpParser\\Node\\(.*?)> but returns array<PhpParser\\Node\>#'
# known values
- '#Parameter \#2 \$variableName of class Rector\\DeadCode\\Data\\VariableNodeUseInfo constructor expects string, string\|null given#'
- '#Cannot call method getParentNode\(\) on Rector\\DeadCode\\Data\\VariableNodeUseInfo\|null#'
# part of test
- '#(.*?)(AttributeAwareNodeInterface|AttributeAware(.*?)TagValueNode)(.*?)#'
@ -255,10 +251,12 @@ parameters:
- '#Method Rector\\BetterPhpDocParser\\AnnotationReader\\NodeAnnotationReader\:\:createClassReflectionFromNode\(\) return type with generic class ReflectionClass does not specify its types\: T#'
- '#Method Rector\\NodeCollector\\StaticAnalyzer\:\:hasStaticAnnotation\(\) has parameter \$reflectionClass with generic class ReflectionClass but does not specify its types\: T#'
- '#Method Rector\\BetterPhpDocParser\\AnnotationReader\\AnnotationReaderFactory\:\:create\(\) should return Doctrine\\Common\\Annotations\\Reader but returns Doctrine\\Common\\Annotations\\AnnotationReader\|Rector\\DoctrineAnnotationGenerated\\ConstantPreservingAnnotationReader#'
# mixed
- '#Property Rector\\Polyfill\\ValueObject\\BinaryToVersionCompareCondition\:\:\$expectedValue has no typehint specified#'
# node finder
- '#Method Rector\\Core\\PhpParser\\Node\\Manipulator\\MethodCallManipulator\:\:findAssignToVariableName\(\) should return PhpParser\\Node\\Expr\\Assign\|null but returns PhpParser\\Node\|null#'
# broken
- '#Cannot call method getParentNode\(\) on Rector\\DeadCode\\ValueObject\\VariableNodeUse\|null#'
- '#Method Rector\\DeadCode\\NodeFinder\\PreviousVariableAssignNodeFinder\:\:find\(\) should return PhpParser\\Node\\Expr\\Assign\|null but returns PhpParser\\Node\|null#'
- '#Parameter \#2 \$name of method Rector\\NodeNameResolver\\NodeNameResolver\:\:isName\(\) expects string, string\|null given#'

View File

@ -7,4 +7,4 @@ services:
resource: '../src'
exclude:
- '../src/Rector/**/*Rector.php'
- '../src/Data/*'
- '../src/ValueObject/*'

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\NodeFinder;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\NodeTraverser;
use Rector\Core\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\Core\PhpParser\Printer\BetterStandardPrinter;
use Rector\DeadCode\ScopeNesting\ParentScopeFinder;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class NextVariableUsageNodeFinder
{
/**
* @var CallableNodeTraverser
*/
private $callableNodeTraverser;
/**
* @var BetterStandardPrinter
*/
private $betterStandardPrinter;
/**
* @var ParentScopeFinder
*/
private $parentScopeFinder;
public function __construct(
ParentScopeFinder $parentScopeFinder,
CallableNodeTraverser $callableNodeTraverser,
BetterStandardPrinter $betterStandardPrinter
) {
$this->callableNodeTraverser = $callableNodeTraverser;
$this->betterStandardPrinter = $betterStandardPrinter;
$this->parentScopeFinder = $parentScopeFinder;
}
public function find(Assign $assign): ?Node
{
$scopeNode = $this->parentScopeFinder->find($assign);
if ($scopeNode === null) {
return null;
}
$expr = $assign->var;
$this->callableNodeTraverser->traverseNodesWithCallable((array) $scopeNode->stmts, function (Node $currentNode) use (
$expr,
&$nextUsageOfVariable
) {
// used above the assign
if ($currentNode->getStartTokenPos() < $expr->getStartTokenPos()) {
return null;
}
// skip self
if ($currentNode === $expr) {
return null;
}
if (! $this->betterStandardPrinter->areNodesEqual($currentNode, $expr)) {
return null;
}
$currentNodeParent = $currentNode->getAttribute(AttributeKey::PARENT_NODE);
// stop at next assign
if ($currentNodeParent instanceof Assign) {
return NodeTraverser::STOP_TRAVERSAL;
}
$nextUsageOfVariable = $currentNode;
return NodeTraverser::STOP_TRAVERSAL;
});
return $nextUsageOfVariable;
}
}

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\NodeFinder;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\NodeNameResolver\NodeNameResolver;
final class PreviousVariableAssignNodeFinder
{
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
public function __construct(BetterNodeFinder $betterNodeFinder, NodeNameResolver $nodeNameResolver)
{
$this->betterNodeFinder = $betterNodeFinder;
$this->nodeNameResolver = $nodeNameResolver;
}
public function find(Assign $assign): ?Assign
{
$currentAssign = $assign;
$variableName = $this->nodeNameResolver->getName($assign->var);
return $this->betterNodeFinder->findFirstPrevious($assign, function (Node $node) use (
$variableName,
$currentAssign
): bool {
if (! $node instanceof Assign) {
return false;
}
// skil self
if ($node === $currentAssign) {
return false;
}
return $this->nodeNameResolver->isName($node->var, $variableName);
});
}
}

View File

@ -12,18 +12,11 @@ use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Case_;
use PhpParser\Node\Stmt\Catch_;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\While_;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\DeadCode\ScopeNesting\ScopeNestingComparator;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
@ -32,18 +25,14 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
final class RemoveDoubleAssignRector extends AbstractRector
{
/**
* @var string[]
* @var ScopeNestingComparator
*/
private const CONTROL_STRUCTURE_NODES = [
Foreach_::class,
If_::class,
While_::class,
Do_::class,
Else_::class,
ElseIf_::class,
Catch_::class,
Case_::class,
];
private $scopeNestingComparator;
public function __construct(ScopeNestingComparator $scopeNestingComparator)
{
$this->scopeNestingComparator = $scopeNestingComparator;
}
public function getDefinition(): RectorDefinition
{
@ -113,11 +102,7 @@ PHP
return true;
}
if ($this->shouldSkipDueToForeachOverride($assign, $anotherNode)) {
return true;
}
return $this->shouldSkipForDifferenceParent($assign, $anotherNode);
return ! $this->scopeNestingComparator->areScopeNestingEqual($assign, $anotherNode);
}
private function isSelfReferencing(Assign $assign): bool
@ -134,33 +119,4 @@ PHP
$previousExpression->getAttribute(AttributeKey::METHOD_NODE)
);
}
private function shouldSkipDueToForeachOverride(Assign $assign, Node $node): bool
{
// is nested in a foreach and the previous expression is not?
$nodePreviousForeach = $this->betterNodeFinder->findFirstParentInstanceOf($assign, Foreach_::class);
$previousExpressionPreviousForeach = $this->betterNodeFinder->findFirstParentInstanceOf(
$node,
Foreach_::class
);
return $nodePreviousForeach !== $previousExpressionPreviousForeach;
}
private function shouldSkipForDifferenceParent(Node $firstNode, Node $secondNode): bool
{
$firstNodeParent = $this->findParentControlStructure($firstNode);
$secondNodeParent = $this->findParentControlStructure($secondNode);
if ($firstNodeParent === null || $secondNodeParent === null) {
return false;
}
return ! $this->areNodesEqual($firstNodeParent, $secondNodeParent);
}
private function findParentControlStructure(Node $node): ?Node
{
return $this->betterNodeFinder->findFirstParentInstanceOf($node, self::CONTROL_STRUCTURE_NODES);
}
}

View File

@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Rector\Assign;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PHPStan\Analyser\Scope;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\DeadCode\NodeFinder\NextVariableUsageNodeFinder;
use Rector\DeadCode\NodeFinder\PreviousVariableAssignNodeFinder;
use Rector\DeadCode\ScopeNesting\ScopeNestingComparator;
use Rector\DeadCode\SideEffect\SideEffectNodeDetector;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
* @see \Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\RemoveUnusedAssignVariableRectorTest
*/
final class RemoveUnusedAssignVariableRector extends AbstractRector
{
/**
* @var SideEffectNodeDetector
*/
private $sideEffectNodeDetector;
/**
* @var PreviousVariableAssignNodeFinder
*/
private $previousVariableAssignNodeFinder;
/**
* @var ScopeNestingComparator
*/
private $scopeNestingComparator;
/**
* @var NextVariableUsageNodeFinder
*/
private $nextVariableUsageNodeFinder;
public function __construct(
SideEffectNodeDetector $sideEffectNodeDetector,
PreviousVariableAssignNodeFinder $previousVariableAssignNodeFinder,
ScopeNestingComparator $scopeNestingComparator,
NextVariableUsageNodeFinder $nextVariableUsageNodeFinder
) {
$this->sideEffectNodeDetector = $sideEffectNodeDetector;
$this->previousVariableAssignNodeFinder = $previousVariableAssignNodeFinder;
$this->scopeNestingComparator = $scopeNestingComparator;
$this->nextVariableUsageNodeFinder = $nextVariableUsageNodeFinder;
}
/**
* @return class-string[]
*/
public function getNodeTypes(): array
{
return [Assign::class];
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Remove assigned unused variable', [
new CodeSample(
<<<'CODE_SAMPLE'
class SomeClass
{
public function run()
{
$value = $this->process();
}
public function process()
{
// something going on
return 5;
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class SomeClass
{
public function run()
{
$this->process();
}
public function process()
{
// something going on
return 5;
}
}
CODE_SAMPLE
),
]);
}
/**
* @param Assign $node
*/
public function refactor(Node $node): ?Node
{
if ($this->shouldSkipAssign($node)) {
return null;
}
if ($this->isVariableTypeInScope($node) && ! $this->isPreviousVariablePartOfOverridingAssign($node)) {
return null;
}
// is scalar assign? remove whole
if (! $this->sideEffectNodeDetector->detect($node->expr)) {
$this->removeNode($node);
return null;
}
return $node->expr;
}
private function shouldSkipAssign(Assign $assign): bool
{
if (! $assign->var instanceof Variable) {
return true;
}
// unable to resolve name
$variableName = $this->getName($assign->var);
if ($variableName === null) {
return true;
}
if ($this->isNestedAssign($assign)) {
return true;
}
$nextUsedVariable = $this->nextVariableUsageNodeFinder->find($assign);
return $nextUsedVariable !== null;
}
/**
* Nested assign, e.g "$oldValues = <$values> = 5;"
*/
private function isNestedAssign(Assign $assign): bool
{
$parentNode = $assign->getAttribute(AttributeKey::PARENT_NODE);
return $parentNode instanceof Assign;
}
private function isPreviousVariablePartOfOverridingAssign(Assign $assign): bool
{
// is previous variable node as part of assign?
$previousVariableAssign = $this->previousVariableAssignNodeFinder->find($assign);
if ($previousVariableAssign === null) {
return false;
}
return $this->scopeNestingComparator->areScopeNestingEqual($assign, $previousVariableAssign);
}
private function isVariableTypeInScope(Assign $assign): bool
{
/** @var Scope|null $scope */
$scope = $assign->getAttribute(AttributeKey::SCOPE);
if (! $scope instanceof Scope) {
return false;
}
/** @var string $variableName */
$variableName = $this->getName($assign->var);
return ! $scope->hasVariableType($variableName)->no();
}
}

View File

@ -12,8 +12,8 @@ use Rector\Core\Context\ContextAnalyzer;
use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\DeadCode\Data\VariableNodeUseInfo;
use Rector\DeadCode\FlowOfControlLocator;
use Rector\DeadCode\ScopeNesting\FlowOfControlLocator;
use Rector\DeadCode\ValueObject\VariableNodeUse;
use Rector\NodeTypeResolver\Node\AttributeKey;
/**
@ -180,7 +180,7 @@ PHP
/**
* @param Variable[] $assignedVariables
* @param Variable[] $assignedVariablesUse
* @return VariableNodeUseInfo[]
* @return VariableNodeUse[]
*/
private function collectNodesByTypeAndPosition(
array $assignedVariables,
@ -199,10 +199,13 @@ PHP
$assignNode = $assignedVariable->getAttribute(AttributeKey::PARENT_NODE);
$nestingHash = $this->flowOfControlLocator->resolveNestingHashFromFunctionLike($functionLike, $assignNode);
$nodesByTypeAndPosition[] = new VariableNodeUseInfo(
/** @var string $variableName */
$variableName = $this->getName($assignedVariable);
$nodesByTypeAndPosition[] = new VariableNodeUse(
$startTokenPos,
$this->getName($assignedVariable),
VariableNodeUseInfo::TYPE_ASSIGN,
$variableName,
VariableNodeUse::TYPE_ASSIGN,
$assignedVariable,
$nestingHash
);
@ -212,10 +215,13 @@ PHP
/** @var int $startTokenPos */
$startTokenPos = $assignedVariableUse->getAttribute(AttributeKey::START_TOKEN_POSITION);
$nodesByTypeAndPosition[] = new VariableNodeUseInfo(
/** @var string $variableName */
$variableName = $this->getName($assignedVariableUse);
$nodesByTypeAndPosition[] = new VariableNodeUse(
$startTokenPos,
$this->getName($assignedVariableUse),
VariableNodeUseInfo::TYPE_USE,
$variableName,
VariableNodeUse::TYPE_USE,
$assignedVariableUse
);
}
@ -223,11 +229,8 @@ PHP
// sort
usort(
$nodesByTypeAndPosition,
function (
VariableNodeUseInfo $firstVariableNodeUseInfo,
VariableNodeUseInfo $secondVariableNodeUseInfo
): int {
return $firstVariableNodeUseInfo->getStartTokenPosition() <=> $secondVariableNodeUseInfo->getStartTokenPosition();
function (VariableNodeUse $firstVariableNodeUse, VariableNodeUse $secondVariableNodeUse): int {
return $firstVariableNodeUse->getStartTokenPosition() <=> $secondVariableNodeUse->getStartTokenPosition();
}
);
@ -236,7 +239,7 @@ PHP
/**
* @param string[] $assignedVariableNames
* @param VariableNodeUseInfo[] $nodesByTypeAndPosition
* @param VariableNodeUse[] $nodesByTypeAndPosition
* @return Node[]
*/
private function resolveNodesToRemove(array $assignedVariableNames, array $nodesByTypeAndPosition): array
@ -244,7 +247,7 @@ PHP
$nodesToRemove = [];
foreach ($assignedVariableNames as $assignedVariableName) {
/** @var VariableNodeUseInfo|null $previousNode */
/** @var VariableNodeUse|null $previousNode */
$previousNode = null;
foreach ($nodesByTypeAndPosition as $nodeByTypeAndPosition) {
@ -257,6 +260,7 @@ PHP
// instant override → remove
} elseif ($this->shouldRemoveAssignNode($previousNode, $nodeByTypeAndPosition)) {
/** @var VariableNodeUse $previousNode */
$nodesToRemove[] = $previousNode->getParentNode();
}
@ -268,31 +272,31 @@ PHP
}
private function isAssignNodeUsed(
?VariableNodeUseInfo $previousNode,
VariableNodeUseInfo $nodeByTypeAndPosition
?VariableNodeUse $previousNode,
VariableNodeUse $nodeByTypeAndPosition
): bool {
// this node was just used, skip to next one
if ($previousNode === null) {
return false;
}
if (! $previousNode->isType(VariableNodeUseInfo::TYPE_ASSIGN)) {
if (! $previousNode->isType(VariableNodeUse::TYPE_ASSIGN)) {
return false;
}
return $nodeByTypeAndPosition->isType(VariableNodeUseInfo::TYPE_USE);
return $nodeByTypeAndPosition->isType(VariableNodeUse::TYPE_USE);
}
private function shouldRemoveAssignNode(
?VariableNodeUseInfo $previousNode,
VariableNodeUseInfo $nodeByTypeAndPosition
?VariableNodeUse $previousNode,
VariableNodeUse $nodeByTypeAndPosition
): bool {
if ($previousNode === null) {
return false;
}
if (! $previousNode->isType(VariableNodeUseInfo::TYPE_ASSIGN) || ! $nodeByTypeAndPosition->isType(
VariableNodeUseInfo::TYPE_ASSIGN
if (! $previousNode->isType(VariableNodeUse::TYPE_ASSIGN) || ! $nodeByTypeAndPosition->isType(
VariableNodeUse::TYPE_ASSIGN
)) {
return false;
}

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\DeadCode;
namespace Rector\DeadCode\ScopeNesting;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\ScopeNesting;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\Namespace_;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class ParentScopeFinder
{
/**
* @return ClassMethod|Function_|Class_|Namespace_|null
*/
public function find(Node $node): ?Node
{
return $node->getAttribute(AttributeKey::METHOD_NODE)
?? $node->getAttribute(AttributeKey::FUNCTION_NODE)
?? $node->getAttribute(AttributeKey::CLASS_NODE)
?? $node->getAttribute(AttributeKey::NAMESPACE_NODE);
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\ScopeNesting;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\Case_;
use PhpParser\Node\Stmt\Catch_;
use PhpParser\Node\Stmt\Do_;
use PhpParser\Node\Stmt\Else_;
use PhpParser\Node\Stmt\ElseIf_;
use PhpParser\Node\Stmt\For_;
use PhpParser\Node\Stmt\Foreach_;
use PhpParser\Node\Stmt\If_;
use PhpParser\Node\Stmt\While_;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
final class ScopeNestingComparator
{
/**
* @var class-string[]
*/
private const CONTROL_STRUCTURE_NODES = [
For_::class,
Foreach_::class,
If_::class,
While_::class,
Do_::class,
Else_::class,
ElseIf_::class,
Catch_::class,
Case_::class,
FunctionLike::class,
];
/**
* @var BetterNodeFinder
*/
private $betterNodeFinder;
public function __construct(BetterNodeFinder $betterNodeFinder)
{
$this->betterNodeFinder = $betterNodeFinder;
}
public function areScopeNestingEqual(Node $firstNode, Node $secondNode): bool
{
$firstNodeScopeNode = $this->findParentControlStructure($firstNode);
$secondNodeScopeNode = $this->findParentControlStructure($secondNode);
return $firstNodeScopeNode === $secondNodeScopeNode;
}
private function findParentControlStructure(Node $node): ?Node
{
return $this->betterNodeFinder->findFirstParentInstanceOf($node, self::CONTROL_STRUCTURE_NODES);
}
}

View File

@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\SideEffect;
use PhpParser\Node\Expr\FuncCall;
use Rector\NodeNameResolver\NodeNameResolver;
final class PureFunctionDetector
{
/**
* @see https://github.com/vimeo/psalm/blob/d470903722cfcbc1cd04744c5491d3e6d13ec3d9/src/Psalm/Internal/Codebase/Functions.php#L288
* @var string[]
*/
private const IMPURE_FUNCTIONS = [
'chdir', 'chgrp', 'chmod', 'chown', 'chroot', 'closedir', 'copy', 'file_put_contents',
'fopen', 'fread', 'fwrite', 'fclose', 'touch', 'fpassthru', 'fputs', 'fscanf', 'fseek',
'ftruncate', 'fprintf', 'symlink', 'mkdir', 'unlink', 'rename', 'rmdir', 'popen', 'pclose',
'fputcsv', 'umask', 'finfo_close', 'readline_add_history', 'stream_set_timeout', 'fflush',
// stream/socket io
'stream_context_set_option', 'socket_write', 'stream_set_blocking', 'socket_close',
'socket_set_option', 'stream_set_write_buffer',
// meta calls
'call_user_func', 'call_user_func_array', 'define', 'create_function',
// http
'header', 'header_remove', 'http_response_code', 'setcookie',
// output buffer
'ob_start', 'ob_end_clean', 'readfile', 'printf', 'var_dump', 'phpinfo',
'ob_implicit_flush', 'vprintf',
// mcrypt
'mcrypt_generic_init', 'mcrypt_generic_deinit', 'mcrypt_module_close',
// internal optimisation
'opcache_compile_file', 'clearstatcache',
// process-related
'pcntl_signal', 'posix_kill', 'cli_set_process_title', 'pcntl_async_signals', 'proc_close',
'proc_nice', 'proc_open', 'proc_terminate',
// curl
'curl_setopt', 'curl_close', 'curl_multi_add_handle', 'curl_multi_remove_handle',
'curl_multi_select', 'curl_multi_close', 'curl_setopt_array',
// apc, apcu
'apc_store', 'apc_delete', 'apc_clear_cache', 'apc_add', 'apc_inc', 'apc_dec', 'apc_cas',
'apcu_store', 'apcu_delete', 'apcu_clear_cache', 'apcu_add', 'apcu_inc', 'apcu_dec', 'apcu_cas',
// gz
'gzwrite', 'gzrewind', 'gzseek', 'gzclose',
// newrelic
'newrelic_start_transaction', 'newrelic_name_transaction', 'newrelic_add_custom_parameter',
'newrelic_add_custom_tracer', 'newrelic_background_job', 'newrelic_end_transaction',
'newrelic_set_appname',
// execution
'shell_exec', 'exec', 'system', 'passthru', 'pcntl_exec',
// well-known functions
'libxml_use_internal_errors', 'libxml_disable_entity_loader', 'curl_exec',
'mt_srand', 'openssl_pkcs7_sign',
'mt_rand', 'rand', 'random_int', 'random_bytes',
'wincache_ucache_delete', 'wincache_ucache_set', 'wincache_ucache_inc',
'class_alias',
// php environment
'ini_set', 'sleep', 'usleep', 'register_shutdown_function',
'error_reporting', 'register_tick_function', 'unregister_tick_function',
'set_error_handler', 'user_error', 'trigger_error', 'restore_error_handler',
'date_default_timezone_set', 'assert_options', 'setlocale',
'set_exception_handler', 'set_time_limit', 'putenv', 'spl_autoload_register',
'microtime', 'array_rand',
// logging
'openlog', 'syslog', 'error_log', 'define_syslog_variables',
// session
'session_id', 'session_name', 'session_set_cookie_params', 'session_set_save_handler',
'session_regenerate_id', 'mb_internal_encoding', 'session_start',
// ldap
'ldap_set_option',
// iterators
'rewind', 'iterator_apply',
// mysqli
'mysqli_select_db', 'mysqli_dump_debug_info', 'mysqli_kill', 'mysqli_multi_query',
'mysqli_next_result', 'mysqli_options', 'mysqli_ping', 'mysqli_query', 'mysqli_report',
'mysqli_rollback', 'mysqli_savepoint', 'mysqli_set_charset', 'mysqli_ssl_set',
// postgres
'pg_exec', 'pg_execute', 'pg_connect', 'pg_query_params',
// ftp
'ftp_close',
];
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
public function __construct(NodeNameResolver $nodeNameResolver)
{
$this->nodeNameResolver = $nodeNameResolver;
}
public function detect(FuncCall $funcCall): bool
{
return ! $this->nodeNameResolver->isNames($funcCall, self::IMPURE_FUNCTIONS);
}
}

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\SideEffect;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\ArrayDimFetch;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\New_;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Scalar\Encapsed;
use PHPStan\Type\ConstantType;
use Rector\NodeTypeResolver\NodeTypeResolver;
final class SideEffectNodeDetector
{
/**
* @var class-string[]
*/
private const SIDE_EFFECT_NODE_TYPES = [Encapsed::class, New_::class, Concat::class, PropertyFetch::class];
/**
* @var PureFunctionDetector
*/
private $pureFunctionDetector;
/**
* @var NodeTypeResolver
*/
private $nodeTypeResolver;
public function __construct(PureFunctionDetector $pureFunctionDetector, NodeTypeResolver $nodeTypeResolver)
{
$this->pureFunctionDetector = $pureFunctionDetector;
$this->nodeTypeResolver = $nodeTypeResolver;
}
public function detect(Expr $expr): bool
{
if ($expr instanceof Assign) {
return true;
}
$exprStaticType = $this->nodeTypeResolver->resolve($expr);
if ($exprStaticType instanceof ConstantType) {
return false;
}
foreach (self::SIDE_EFFECT_NODE_TYPES as $sideEffectNodeType) {
if (is_a($expr, $sideEffectNodeType, true)) {
return false;
}
}
if ($expr instanceof FuncCall) {
return ! $this->pureFunctionDetector->detect($expr);
}
if ($expr instanceof Variable || $expr instanceof ArrayDimFetch) {
$variable = $this->resolveVariable($expr);
// variables don't have side effects
return ! $variable instanceof Variable;
}
return true;
}
private function resolveVariable(Expr $expr): ?Variable
{
while ($expr instanceof ArrayDimFetch) {
$expr = $expr->var;
}
if (! $expr instanceof Variable) {
return null;
}
return $expr;
}
}

View File

@ -64,7 +64,6 @@ final class ClassUnusedPrivateClassMethodResolver
$unusedMethods = array_diff($classPublicMethodNames, $usedMethodNames);
$unusedMethods = $this->filterOutSystemMethods($unusedMethods);
$unusedMethods = $this->filterOutInterfaceRequiredMethods($class, $unusedMethods);
return $this->filterOutParentAbstractMethods($class, $unusedMethods);

View File

@ -2,14 +2,14 @@
declare(strict_types=1);
namespace Rector\DeadCode\Data;
namespace Rector\DeadCode\ValueObject;
use PhpParser\Node;
use PhpParser\Node\Expr\Variable;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\AttributeKey;
final class VariableNodeUseInfo
final class VariableNodeUse
{
/**
* @var string

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignConCat
{
public function run()
{
$removeMe = 'a' . 5 . 'b';
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignConCat
{
public function run()
{
}
}
?>

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignDouble
{
public function run()
{
$days = [1 => 'one', 'two'];
$days = ['something_else'];
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignDouble
{
public function run()
{
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignFuncCallInternal
{
public function run()
{
$days = substr('lololo', 5);
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignFuncCallInternal
{
public function run()
{
}
}
?>

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
final class AssignMultiFirstToGo
{
public function run()
{
$peValId = $values = 0;
if ($values) {
return true;
}
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
final class AssignMultiFirstToGo
{
public function run()
{
$values = 0;
if ($values) {
return true;
}
}
}
?>

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
final class AssignMultiSameLine
{
public function run()
{
$me = $this->createMe();
$me = $this->createMe();
$me = $this->createMe();
return $me;
}
public function createMe()
{
return new self();
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
final class AssignMultiSameLine
{
public function run()
{
$this->createMe();
$this->createMe();
$me = $this->createMe();
return $me;
}
public function createMe()
{
return new self();
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignNewInstance
{
public function run()
{
$days = new \stdClass();
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignNewInstance
{
public function run()
{
}
}
?>

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignProperty
{
private $days = '123';
public function run()
{
$days = $this->days;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignProperty
{
private $days = '123';
public function run()
{
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignScalar
{
public function run()
{
$days = [1 => 'one', 'two'];
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignScalar
{
public function run()
{
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignSession
{
public function run()
{
$days = $_SESSION['ID'];
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignSession
{
public function run()
{
}
}
?>

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignHereDoc
{
public function run()
{
$removeMe = <<<EOF
Value: {$embed}<br />
EOF;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class AssignHereDoc
{
public function run()
{
}
}
?>

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class SomeClass
{
public function run()
{
$value = $this->process();
}
public function process()
{
// something going on
return 5;
}
}
?>
-----
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class SomeClass
{
public function run()
{
$this->process();
}
public function process()
{
// something going on
return 5;
}
}
?>

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
final class SkipAssignEmbedHtml
{
public function content()
{
for ($c = 0; $c <= \pg_numrows($result); $c++) {
if ($a !== $b) {
if ($row === 0) {
StaticCaller::callMe($suspect);
}
$suspect = 0;
}
if ($row[5] === 1) {
$suspect = $row[6];
}
}
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class SkipCommented
{
public function run()
{
/* multi
line */
$employCount = \pg_num_rows($result);
SomeClassMe::callStatic('Items', $employCount);
}
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class SkipFor
{
public function run($nesting = 100)
{
for ($j = 2; $j < $nesting; $j++) {
}
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class SkipIfAssignNestedUse
{
public function run($token)
{
if (($id = $this->get($token))) {
pg_query_params('SELECT ... =$1', [
$id,
]);
}
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class SkipMultiAssign
{
public function run()
{
$values = $peValId = $peColId = $auto = 0;
if ($values) {
return true;
}
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
final class SkipProperty
{
/**
* @var string[]
*/
private $days;
public function run()
{
$this->days = [1 => 'one', 'two'];
}
public function useMe()
{
return $this->days;
}
}

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
class SkipUseAnd
{
public function run()
{
if (($format = $this->getImageFormat()) && $format !== false) {
return 'png';
}
return 'non_png';
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector\Fixture;
final class SkipVarOverrideAndReUse
{
public function run()
{
$sep = '';
while ($row = \pg_fetch_assoc($query)) {
$text .= "{$sep}" . ($row['b'] ?: $row['a']);
$sep = ', ';
}
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\DeadCode\Tests\Rector\Assign\RemoveUnusedAssignVariableRector;
use Iterator;
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
use Rector\DeadCode\Rector\Assign\RemoveUnusedAssignVariableRector;
final class RemoveUnusedAssignVariableRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideData(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return RemoveUnusedAssignVariableRector::class;
}
}