diff --git a/config/set/code-quality.php b/config/set/code-quality.php index 0d6aa51c27c..f07eabc2969 100644 --- a/config/set/code-quality.php +++ b/config/set/code-quality.php @@ -11,6 +11,7 @@ use Rector\CodeQuality\Rector\Catch_\ThrowWithPreviousExceptionRector; use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector; use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; +use Rector\CodeQuality\Rector\Class_\StaticToSelfStaticMethodCallOnFinalClassRector; use Rector\CodeQuality\Rector\ClassConstFetch\ConvertStaticPrivateConstantToSelfRector; use Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector; use Rector\CodeQuality\Rector\ClassMethod\LocallyCalledStaticMethodToNonStaticRector; @@ -192,5 +193,6 @@ NumberCompareToMaxFuncCallRector::class, CompleteMissingIfElseBracketRector::class, RemoveUselessIsObjectCheckRector::class, + StaticToSelfStaticMethodCallOnFinalClassRector::class, ]); }; diff --git a/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/Fixture/call_statically.php.inc b/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/Fixture/call_statically.php.inc new file mode 100644 index 00000000000..24651fbcaf2 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/Fixture/call_statically.php.inc @@ -0,0 +1,37 @@ + +----- + \ No newline at end of file diff --git a/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/Fixture/skip_already_self.php.inc b/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/Fixture/skip_already_self.php.inc new file mode 100644 index 00000000000..594d1ef8d99 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/Fixture/skip_already_self.php.inc @@ -0,0 +1,15 @@ +run(); + } + + private function run() + { + } +} diff --git a/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/StaticToSelfStaticMethodCallOnFinalClassRectorTest.php b/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/StaticToSelfStaticMethodCallOnFinalClassRectorTest.php new file mode 100644 index 00000000000..1463be7d355 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/StaticToSelfStaticMethodCallOnFinalClassRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/config/configured_rule.php b/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/config/configured_rule.php new file mode 100644 index 00000000000..14e1d98bcd7 --- /dev/null +++ b/rules-tests/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector/config/configured_rule.php @@ -0,0 +1,9 @@ +withRules([StaticToSelfStaticMethodCallOnFinalClassRector::class]); diff --git a/rules/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector.php b/rules/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector.php new file mode 100644 index 00000000000..6faaf048f0d --- /dev/null +++ b/rules/CodeQuality/Rector/Class_/StaticToSelfStaticMethodCallOnFinalClassRector.php @@ -0,0 +1,118 @@ +> + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Class_ + { + if (! $node->isFinal()) { + return null; + } + + $hasChanged = false; + + $this->traverseNodesWithCallable($node->stmts, function (Node $subNode) use (&$hasChanged, $node): ?StaticCall { + if (! $subNode instanceof StaticCall) { + return null; + } + + if (! $this->isName($subNode->class, ObjectReference::STATIC)) { + return null; + } + + // skip dynamic method + if (! $subNode->name instanceof Identifier) { + return null; + } + + $methodName = (string) $this->getName($subNode->name); + $targetClassMethod = $node->getMethod($methodName); + + // skip call non-existing method from current class to ensure transformation is safe + if (! $targetClassMethod instanceof ClassMethod) { + return null; + } + + // avoid overlapped change + if (! $targetClassMethod->isStatic()) { + return null; + } + + $hasChanged = true; + $subNode->class = new Name('self'); + return $subNode; + }); + + if ($hasChanged) { + return $node; + } + + return null; + } +}