diff --git a/config/set/dead-code/dead-code.yaml b/config/set/dead-code/dead-code.yaml index 17d8ae805e00..f3e9b30dd81e 100644 --- a/config/set/dead-code/dead-code.yaml +++ b/config/set/dead-code/dead-code.yaml @@ -21,3 +21,4 @@ services: Rector\CodeQuality\Rector\Return_\SimplifyUselessVariableRector: ~ Rector\DeadCode\Rector\Plus\RemoveZeroAndOneBinaryRector: ~ + Rector\DeadCode\Rector\ClassMethod\RemoveDelegatingParentCallRector: ~ diff --git a/packages/DeadCode/src/Rector/ClassMethod/RemoveDelegatingParentCallRector.php b/packages/DeadCode/src/Rector/ClassMethod/RemoveDelegatingParentCallRector.php new file mode 100644 index 000000000000..9cc2f4faea0e --- /dev/null +++ b/packages/DeadCode/src/Rector/ClassMethod/RemoveDelegatingParentCallRector.php @@ -0,0 +1,119 @@ +stmts) !== 1) { + return null; + } + + if ($node->stmts[0] instanceof Node\Stmt\Expression) { + $onlyStmt = $node->stmts[0]->expr; + } else { + $onlyStmt = $node->stmts[0]; + } + + // are both return? + if ($node->returnType && ! $this->isName( + $node->returnType, + 'void' + ) && ! $onlyStmt instanceof Node\Stmt\Return_) { + return null; + } + + /** @var Node $onlyStmt */ + $staticCall = $this->matchStaticCall($onlyStmt); + + if (! $this->isParentCallMatching($node, $staticCall)) { + return null; + } + + // the method is just delegation, nothing extra + $this->removeNode($node); + + return null; + } + + private function matchStaticCall(Node $node): ?Node\Expr\StaticCall + { + // must be static call + if ($node instanceof Node\Stmt\Return_) { + if ($node->expr instanceof Node\Expr\StaticCall) { + return $node->expr; + } + + return null; + } + + if ($node instanceof Node\Expr\StaticCall) { + return $node; + } + + return null; + } + + private function isParentCallMatching(ClassMethod $classMethod, ?Node\Expr\StaticCall $staticCall): bool + { + if ($staticCall === null) { + return false; + } + + if (! $this->areNamesEqual($staticCall, $classMethod)) { + return false; + } + + if (! $this->isName($staticCall->class, 'parent')) { + return false; + } + + // are arguments the same? + if (count($staticCall->args) !== count($classMethod->params)) { + return false; + } + + return true; + } +} diff --git a/packages/DeadCode/src/Rector/Plus/RemoveZeroAndOneBinaryRector.php b/packages/DeadCode/src/Rector/Plus/RemoveZeroAndOneBinaryRector.php new file mode 100644 index 000000000000..80bc629eef09 --- /dev/null +++ b/packages/DeadCode/src/Rector/Plus/RemoveZeroAndOneBinaryRector.php @@ -0,0 +1,162 @@ +processAssignOp($node); + } + + // -, + + if ($node instanceof BinaryOp) { + return $this->processBinaryOp($node); + } + } + + /** + * @param Plus|Minus $binaryOp + */ + private function processBinaryPlusAndMinus(BinaryOp $binaryOp): ?Expr + { + if ($this->isValue($binaryOp->left, 0)) { + if ($this->isNumberType($binaryOp->right)) { + return $binaryOp->right; + } + } + + if ($this->isValue($binaryOp->right, 0)) { + if ($this->isNumberType($binaryOp->left)) { + return $binaryOp->left; + } + } + + return null; + } + + /** + * @param Mul|Div $binaryOp + */ + private function processBinaryMulAndDiv(BinaryOp $binaryOp): ?Expr + { + if ($this->isValue($binaryOp->left, 1)) { + if ($this->isNumberType($binaryOp->right)) { + return $binaryOp->right; + } + } + + if ($this->isValue($binaryOp->right, 1)) { + if ($this->isNumberType($binaryOp->left)) { + return $binaryOp->left; + } + } + + return null; + } + + private function processAssignOp(Node $node): ?Expr + { + // +=, -= + if ($node instanceof Node\Expr\AssignOp\Plus || $node instanceof Node\Expr\AssignOp\Minus) { + if (! $this->isValue($node->expr, 0)) { + return null; + } + + if ($this->isNumberType($node->var)) { + return $node->var; + } + } + + // *, / + if ($node instanceof Node\Expr\AssignOp\Mul || $node instanceof Node\Expr\AssignOp\Div) { + if (! $this->isValue($node->expr, 1)) { + return null; + } + if ($this->isNumberType($node->var)) { + return $node->var; + } + } + + return null; + } + + private function processBinaryOp(Node $node): ?Expr + { + if ($node instanceof Plus || $node instanceof Minus) { + return $this->processBinaryPlusAndMinus($node); + } + + // *, / + if ($node instanceof Mul || $node instanceof Div) { + return $this->processBinaryMulAndDiv($node); + } + + return null; + } +} diff --git a/packages/DeadCode/tests/Rector/ClassMethod/RemoveDelegatingParentCallRector/Fixture/fixture.php.inc b/packages/DeadCode/tests/Rector/ClassMethod/RemoveDelegatingParentCallRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..e1add4e2b98c --- /dev/null +++ b/packages/DeadCode/tests/Rector/ClassMethod/RemoveDelegatingParentCallRector/Fixture/fixture.php.inc @@ -0,0 +1,28 @@ + +----- + diff --git a/packages/DeadCode/tests/Rector/ClassMethod/RemoveDelegatingParentCallRector/Fixture/skip_changed_arguments.php.inc b/packages/DeadCode/tests/Rector/ClassMethod/RemoveDelegatingParentCallRector/Fixture/skip_changed_arguments.php.inc new file mode 100644 index 000000000000..97db3816185b --- /dev/null +++ b/packages/DeadCode/tests/Rector/ClassMethod/RemoveDelegatingParentCallRector/Fixture/skip_changed_arguments.php.inc @@ -0,0 +1,11 @@ +doTestFiles([ + __DIR__ . '/Fixture/fixture.php.inc', + __DIR__ . '/Fixture/skip_extra_content.php.inc', + __DIR__ . '/Fixture/skip_different_method_name.php.inc', + __DIR__ . '/Fixture/skip_changed_arguments.php.inc', + ]); + } + + protected function getRectorClass(): string + { + return RemoveDelegatingParentCallRector::class; + } +} diff --git a/packages/DeadCode/tests/Rector/Plus/RemoveZeroAndOneBinaryRector/Fixture/assigns.php.inc b/packages/DeadCode/tests/Rector/Plus/RemoveZeroAndOneBinaryRector/Fixture/assigns.php.inc new file mode 100644 index 000000000000..153febf98e3f --- /dev/null +++ b/packages/DeadCode/tests/Rector/Plus/RemoveZeroAndOneBinaryRector/Fixture/assigns.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/packages/DeadCode/tests/Rector/Plus/RemoveZeroAndOneBinaryRector/Fixture/fixture.php.inc b/packages/DeadCode/tests/Rector/Plus/RemoveZeroAndOneBinaryRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..109fc1c77733 --- /dev/null +++ b/packages/DeadCode/tests/Rector/Plus/RemoveZeroAndOneBinaryRector/Fixture/fixture.php.inc @@ -0,0 +1,29 @@ + +----- + diff --git a/packages/DeadCode/tests/Rector/Plus/RemoveZeroAndOneBinaryRector/Fixture/skip_floats.php.inc b/packages/DeadCode/tests/Rector/Plus/RemoveZeroAndOneBinaryRector/Fixture/skip_floats.php.inc new file mode 100644 index 000000000000..7f5dd25e4b64 --- /dev/null +++ b/packages/DeadCode/tests/Rector/Plus/RemoveZeroAndOneBinaryRector/Fixture/skip_floats.php.inc @@ -0,0 +1,15 @@ +doTestFiles([ + __DIR__ . '/Fixture/fixture.php.inc', + __DIR__ . '/Fixture/assigns.php.inc', + __DIR__ . '/Fixture/skip_type_change.php.inc', + __DIR__ . '/Fixture/skip_floats.php.inc', + ]); + } + + protected function getRectorClass(): string + { + return RemoveZeroAndOneBinaryRector::class; + } +} diff --git a/phpstan.neon b/phpstan.neon index a33fba8db50e..586e8bf1a935 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,7 +7,7 @@ includes: parameters: # to allow intalling with various phsptan versions without reporting old errors here - reportUnmatchedIgnoredErrors: false +# reportUnmatchedIgnoredErrors: false level: max excludes_analyse: @@ -35,7 +35,6 @@ parameters: # false positive - '#Call to function method_exists\(\) with string and (.*?) will always evaluate to false#' - '#PHPDoc tag \@param for parameter \$node with type float is incompatible with native type PhpParser\\Node#' - - '#Result of && is always true#' # missuse of interface and class - '#Parameter \#1 (.*?) expects Symfony\\Component\\DependencyInjection\\ContainerBuilder, Symfony\\Component\\DependencyInjection\\ContainerInterface given#' @@ -63,7 +62,8 @@ parameters: # known values - '#Access to an undefined property PhpParser\\Node\\Expr::\$left#' - '#Access to an undefined property PhpParser\\Node\\Expr::\$right#' - - '#Method Rector\\ContributorTools\\Configuration\\ConfigurationFactory::resolveCategoryFromFqnNodeTypes\(\) should return string but returns string\|false#' + - '#Method Rector\\ContributorTools\\Configuration\\ConfigurationFactory\:\:resolveCategoryFromFqnNodeTypes\(\) should return string but returns string\|null#' + - '#Array \(array\) does not accept PhpParser\\Node\\Expr#' - '#Cannot access property \$expr on PhpParser\\Node\\Stmt\|null#' - '#Access to an undefined property PhpParser\\Node\\Expr\\MethodCall\|PhpParser\\Node\\Stmt\\ClassMethod::\$params#' @@ -108,6 +108,7 @@ parameters: - '#Method Rector\\NetteToSymfony\\Rector\\ClassMethod\\RouterListToControllerAnnotationsRector\:\:resolveAssignRouteNodes\(\) should return array but returns array#' - '#Access to an undefined property PhpParser\\Node\\Stmt\:\:\$expr#' - '#Cannot access property \$stmts on PhpParser\\Node\\Stmt\\Else_\|null#' + - '#Parameter \#1 \$node of method Rector\\DeadCode\\Rector\\ClassMethod\\RemoveDelegatingParentCallRector\:\:matchStaticCall\(\) expects PhpParser\\Node, PhpParser\\Node\\Expr\|PhpParser\\Node\\Stmt\|null given#' # node finder - '#Method Rector\\(.*?) should return array but returns array#' @@ -158,15 +159,12 @@ parameters: - '#Access to an undefined property PhpParser\\Node\\Expr\:\:\$args#' - '#Parameter \#2 \$name of method Rector\\Rector\\AbstractRector\:\:isName\(\) expects string, string\|null given#' - # file always exists here - - '#Cannot call method getRealPath\(\) on Symplify\\PackageBuilder\\FileSystem\\SmartFileInfo\|null#' # cascade irelevant - '#Parameter (.*?) expects array, array given#' # known value - '#Parameter \#1 \$node of method Rector\\Rector\\AbstractRector\:\:getName\(\) expects PhpParser\\Node, PhpParser\\Node\\Identifier\|null given#' - '#Cannot cast array\|bool\|string\|null to string#' - - '#Method Rector\\ContributorTools\\Configuration\\ConfigurationFactory\:\:resolveCategoryFromFqnNodeTypes\(\) should return string but returns string\|null#' - '#Method Rector\\Legacy\\Rector\\ClassMethod\\ChangeSingletonToServiceRector\:\:matchStaticPropertyFetchAndGetSingletonMethodName\(\) should return array\|null but returns array#' # future compat