diff --git a/config/set/dead-code/dead-code.yaml b/config/set/dead-code/dead-code.yaml
index 9482142212e9..750419a447d7 100644
--- a/config/set/dead-code/dead-code.yaml
+++ b/config/set/dead-code/dead-code.yaml
@@ -35,3 +35,4 @@ services:
Rector\DeadCode\Rector\ClassConst\RemoveUnusedClassConstantRector: null
Rector\DeadCode\Rector\Assign\RemoveUnusedVariableAssignRector: null
Rector\DeadCode\Rector\FunctionLike\RemoveDuplicatedIfReturnRector: null
+ Rector\DeadCode\Rector\Function_\RemoveUnusedFunctionRector: null
diff --git a/docs/AllRectorsOverview.md b/docs/AllRectorsOverview.md
index 0370acceff30..ec7e4eb788d3 100644
--- a/docs/AllRectorsOverview.md
+++ b/docs/AllRectorsOverview.md
@@ -1,4 +1,4 @@
-# All 467 Rectors Overview
+# All 468 Rectors Overview
- [Projects](#projects)
- [General](#general)
@@ -3062,6 +3062,27 @@ Remove unused key in foreach
+### `RemoveUnusedFunctionRector`
+
+- class: [`Rector\DeadCode\Rector\Function_\RemoveUnusedFunctionRector`](/../master/rules/dead-code/src/Rector/Function_/RemoveUnusedFunctionRector.php)
+- [test fixtures](/../master/rules/dead-code/tests/Rector/Function_/RemoveUnusedFunctionRector/Fixture)
+
+Remove unused function
+
+```diff
+-function removeMe()
+-{
+-}
+-
+ function useMe()
+ {
+ }
+
+ useMe();
+```
+
+
+
### `RemoveUnusedParameterRector`
- class: [`Rector\DeadCode\Rector\ClassMethod\RemoveUnusedParameterRector`](/../master/rules/dead-code/src/Rector/ClassMethod/RemoveUnusedParameterRector.php)
diff --git a/packages/node-collector/src/NodeCollector/ParsedFunctionLikeNodeCollector.php b/packages/node-collector/src/NodeCollector/ParsedFunctionLikeNodeCollector.php
index 9438eceb3e2a..9178b960a1fb 100644
--- a/packages/node-collector/src/NodeCollector/ParsedFunctionLikeNodeCollector.php
+++ b/packages/node-collector/src/NodeCollector/ParsedFunctionLikeNodeCollector.php
@@ -7,6 +7,7 @@
use PhpParser\Node;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\ClassConstFetch;
+use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
@@ -36,6 +37,11 @@ final class ParsedFunctionLikeNodeCollector
*/
private $functionsByName = [];
+ /**
+ * @var FuncCall[][]
+ */
+ private $funcCallsByName = [];
+
/**
* @var MethodCall[][][]|StaticCall[][][]
*/
@@ -100,12 +106,13 @@ public function collect(Node $node): void
if ($node instanceof Function_) {
$functionName = $this->nodeNameResolver->getName($node);
- if ($functionName === null) {
- return;
- }
-
$this->functionsByName[$functionName] = $node;
}
+
+ if ($node instanceof FuncCall) {
+ $functionName = $this->nodeNameResolver->getName($node);
+ $this->funcCallsByName[$functionName][] = $node;
+ }
}
public function findFunction(string $name): ?Function_
@@ -145,6 +152,11 @@ public function findMethod(string $className, string $methodName): ?ClassMethod
return null;
}
+ public function isFunctionUsed(string $functionName): bool
+ {
+ return isset($this->funcCallsByName[$functionName]);
+ }
+
private function addMethod(ClassMethod $classMethod): void
{
$className = $classMethod->getAttribute(AttributeKey::CLASS_NAME);
diff --git a/packages/node-name-resolver/src/NodeNameResolver/FuncCallNameResolver.php b/packages/node-name-resolver/src/NodeNameResolver/FuncCallNameResolver.php
index 681eca0b38ad..56bcd70b5776 100644
--- a/packages/node-name-resolver/src/NodeNameResolver/FuncCallNameResolver.php
+++ b/packages/node-name-resolver/src/NodeNameResolver/FuncCallNameResolver.php
@@ -31,7 +31,6 @@ public function resolve(Node $node): ?string
}
$functionName = $node->name;
-
if (! $functionName instanceof Name) {
return (string) $functionName;
}
diff --git a/packages/node-name-resolver/src/NodeNameResolver/FunctionNameResolver.php b/packages/node-name-resolver/src/NodeNameResolver/FunctionNameResolver.php
new file mode 100644
index 000000000000..595d62f103d8
--- /dev/null
+++ b/packages/node-name-resolver/src/NodeNameResolver/FunctionNameResolver.php
@@ -0,0 +1,34 @@
+name;
+
+ $namespaceName = $node->getAttribute(AttributeKey::NAMESPACE_NAME);
+
+ if ($namespaceName) {
+ return $namespaceName . '\\' . $bareName;
+ }
+
+ return $bareName;
+ }
+}
diff --git a/rules/dead-code/src/Rector/Function_/RemoveUnusedFunctionRector.php b/rules/dead-code/src/Rector/Function_/RemoveUnusedFunctionRector.php
new file mode 100644
index 000000000000..0db746f02cd6
--- /dev/null
+++ b/rules/dead-code/src/Rector/Function_/RemoveUnusedFunctionRector.php
@@ -0,0 +1,81 @@
+parsedFunctionLikeNodeCollector = $parsedFunctionLikeNodeCollector;
+ }
+
+ public function getDefinition(): RectorDefinition
+ {
+ return new RectorDefinition('Remove unused function', [
+ new CodeSample(
+ <<<'PHP'
+function removeMe()
+{
+}
+
+function useMe()
+{
+}
+
+useMe();
+PHP
+,
+ <<<'PHP'
+function useMe()
+{
+}
+
+useMe();
+PHP
+
+ ),
+ ]);
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getNodeTypes(): array
+ {
+ return [Function_::class];
+ }
+
+ /**
+ * @param Function_ $node
+ */
+ public function refactor(Node $node): ?Node
+ {
+ /** @var string $functionName */
+ $functionName = $this->getName($node);
+
+ if ($this->parsedFunctionLikeNodeCollector->isFunctionUsed($functionName)) {
+ return null;
+ }
+
+ $this->removeNode($node);
+
+ return $node;
+ }
+}
diff --git a/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php b/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php
index f4a024521b8e..8d108b58ceaf 100644
--- a/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php
+++ b/rules/dead-code/src/Rector/MethodCall/RemoveDefaultArgumentValueRector.php
@@ -116,7 +116,7 @@ private function shouldSkip(Node $node): bool
return true;
}
- $functionName = $this->getName($node->name);
+ $functionName = $this->getName($node);
if ($functionName === null) {
return false;
}
@@ -137,8 +137,7 @@ private function shouldSkip(Node $node): bool
*/
private function resolveDefaultValuesFromCall(Node $node): array
{
- /** @var string|null $nodeName */
- $nodeName = $this->getName($node->name);
+ $nodeName = $this->resolveNodeName($node);
if ($nodeName === null) {
return [];
}
@@ -242,4 +241,16 @@ private function resolveDefaultParamValuesFromFunctionLike(FunctionLike $functio
return $defaultValues;
}
+
+ /**
+ * @param StaticCall|FuncCall|MethodCall $node
+ */
+ private function resolveNodeName(Node $node): ?string
+ {
+ if ($node instanceof FuncCall) {
+ return $this->getName($node);
+ }
+
+ return $this->getName($node->name);
+ }
}
diff --git a/rules/dead-code/tests/Rector/Function_/RemoveUnusedFunctionRector/Fixture/fixture.php.inc b/rules/dead-code/tests/Rector/Function_/RemoveUnusedFunctionRector/Fixture/fixture.php.inc
new file mode 100644
index 000000000000..2be90bbd4c6e
--- /dev/null
+++ b/rules/dead-code/tests/Rector/Function_/RemoveUnusedFunctionRector/Fixture/fixture.php.inc
@@ -0,0 +1,26 @@
+
+-----
+
diff --git a/rules/dead-code/tests/Rector/Function_/RemoveUnusedFunctionRector/RemoveUnusedFunctionRectorTest.php b/rules/dead-code/tests/Rector/Function_/RemoveUnusedFunctionRector/RemoveUnusedFunctionRectorTest.php
new file mode 100644
index 000000000000..bd1ab30407ef
--- /dev/null
+++ b/rules/dead-code/tests/Rector/Function_/RemoveUnusedFunctionRector/RemoveUnusedFunctionRectorTest.php
@@ -0,0 +1,30 @@
+doTestFile($file);
+ }
+
+ public function provideData(): Iterator
+ {
+ return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
+ }
+
+ protected function getRectorClass(): string
+ {
+ return RemoveUnusedFunctionRector::class;
+ }
+}