From 3b75943f4bd456cac63bd8a932d6699f2a7351c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Parafi=C5=84ski?= Date: Mon, 24 Jun 2024 14:08:15 +0200 Subject: [PATCH] IBX-8224: Added rector rule to remove interface & methods implementations (#4) For more details see https://issues.ibexa.co/browse/IBX-8224 and https://github.com/ibexa/rector/pull/4 Key changes: * Added rector rule to remove interface & methods implementations * [Tests] Added test coverage --- .../RemoveInterfaceWithMethodsRector.php | 126 ++++++++++++++++++ .../DependentMethodCallRemovingVisitor.php | 48 +++++++ .../Fixture/SomeInterface.php | 17 +++ .../Fixture/class_with_extra_methods.php.inc | 43 ++++++ .../Fixture/class_with_interface.php.inc | 23 ++++ .../class_with_removed_method_call.php.inc | 52 ++++++++ .../Fixture/class_with_two_interfaces.php.inc | 34 +++++ .../RemoveInterfaceWithMethodsRectorTest.php | 37 +++++ .../config/configured_rule.php | 18 +++ 9 files changed, 398 insertions(+) create mode 100644 src/lib/Rule/Internal/RemoveInterfaceWithMethodsRector.php create mode 100644 src/lib/Visitor/DependentMethodCallRemovingVisitor.php create mode 100644 tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/SomeInterface.php create mode 100644 tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_extra_methods.php.inc create mode 100644 tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_interface.php.inc create mode 100644 tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_removed_method_call.php.inc create mode 100644 tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_two_interfaces.php.inc create mode 100644 tests/lib/Rule/Internal/RemoveInterfaceWithMethods/RemoveInterfaceWithMethodsRectorTest.php create mode 100644 tests/lib/Rule/Internal/RemoveInterfaceWithMethods/config/configured_rule.php diff --git a/src/lib/Rule/Internal/RemoveInterfaceWithMethodsRector.php b/src/lib/Rule/Internal/RemoveInterfaceWithMethodsRector.php new file mode 100644 index 0000000..78cb92c --- /dev/null +++ b/src/lib/Rule/Internal/RemoveInterfaceWithMethodsRector.php @@ -0,0 +1,126 @@ +removeInterfacesRector = $removeInterfacesRector; + } + + /** + * @throws \Exception + */ + public function getRuleDefinition(): RuleDefinition + { + return new RuleDefinition('Remove Interface implementation with all its methods', [new ConfiguredCodeSample( + <<<'CODE_SAMPLE' + class SomeClassWithInterface implements InterfaceToRemove + { + public function interfaceMethod(): array + { + return ['smth']; + } + } + CODE_SAMPLE + , + <<<'CODE_SAMPLE' + class SomeClassWithInterface + { + } + CODE_SAMPLE + , + ['var_dump'] + )]); + } + + /** + * @return array> + */ + public function getNodeTypes(): array + { + return $this->removeInterfacesRector->getNodeTypes(); + } + + /** + * @param \PhpParser\Node\Stmt\Class_ $node + */ + public function refactor(Node $node): ?int + { + if ($node->implements === []) { + return null; + } + + foreach ($node->implements as $implement) { + if ($this->isNames($implement, $this->interfacesToRemove)) { + foreach ($this->interfacesToRemove as $interface) { + $ref = new ReflectionClass($interface); + $methods = array_map(static fn (ReflectionMethod $reflectionMethod): string => $reflectionMethod->getName(), $ref->getMethods()); + + // Remove method definition + foreach ($node->stmts as $key => $stmt) { + if ($stmt instanceof ClassMethod && $this->isNames($stmt, $methods)) { + unset($node->stmts[$key]); + } + } + + // Remove method calls, if one of the arguments was removed method + foreach ($node->getMethods() as $classMethod) { + $nodeTraverser = new NodeTraverser(); + $nodeTraverser->addVisitor( + new DependentMethodCallRemovingVisitor( + $this->nodeNameResolver, + $methods + ) + ); + + if ($classMethod->stmts !== null) { + /** @var array<\PhpParser\Node\Stmt>|null $traversedStmts */ + $traversedStmts = $nodeTraverser->traverse($classMethod->stmts); + $classMethod->stmts = $traversedStmts; + } + } + } + } + } + + // Remove interface + $this->removeInterfacesRector->refactor($node); + + return null; + } + + /** + * @param class-string[] $configuration + */ + public function configure(array $configuration): void + { + $this->interfacesToRemove = $configuration; + + $this->removeInterfacesRector->configure($configuration); + } +} diff --git a/src/lib/Visitor/DependentMethodCallRemovingVisitor.php b/src/lib/Visitor/DependentMethodCallRemovingVisitor.php new file mode 100644 index 0000000..4bae362 --- /dev/null +++ b/src/lib/Visitor/DependentMethodCallRemovingVisitor.php @@ -0,0 +1,48 @@ +nodeNameResolver = $nodeNameResolver; + $this->methods = $methods; + } + + public function leaveNode(Node $node) + { + if ($node instanceof MethodCall) { + foreach ($node->getArgs() as $arg) { + $argValue = $arg->value; + if ($argValue instanceof MethodCall && $this->nodeNameResolver->isNames($argValue->name, $this->methods)) { + return $node->var; + } + } + } + + return null; + } +} diff --git a/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/SomeInterface.php b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/SomeInterface.php new file mode 100644 index 0000000..fbbfaa1 --- /dev/null +++ b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/SomeInterface.php @@ -0,0 +1,17 @@ + +----- + diff --git a/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_interface.php.inc b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_interface.php.inc new file mode 100644 index 0000000..88582af --- /dev/null +++ b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_interface.php.inc @@ -0,0 +1,23 @@ + +----- + diff --git a/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_removed_method_call.php.inc b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_removed_method_call.php.inc new file mode 100644 index 0000000..a665b36 --- /dev/null +++ b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_removed_method_call.php.inc @@ -0,0 +1,52 @@ +firstMethod()->firstMethod($this->someMethod())->thirdMethod(); + } + + public function someMethod(): array + { + return ['ezplatform:some_old:command_with_interface']; + } + + public function thirdMethod(): void + { + + } +} + +?> +----- +firstMethod()->thirdMethod(); + } + public function thirdMethod(): void + { + + } +} + +?> diff --git a/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_two_interfaces.php.inc b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_two_interfaces.php.inc new file mode 100644 index 0000000..e9aed71 --- /dev/null +++ b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/Fixture/class_with_two_interfaces.php.inc @@ -0,0 +1,34 @@ + +----- + diff --git a/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/RemoveInterfaceWithMethodsRectorTest.php b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/RemoveInterfaceWithMethodsRectorTest.php new file mode 100644 index 0000000..55e4400 --- /dev/null +++ b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/RemoveInterfaceWithMethodsRectorTest.php @@ -0,0 +1,37 @@ +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/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/config/configured_rule.php b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/config/configured_rule.php new file mode 100644 index 0000000..c0f0032 --- /dev/null +++ b/tests/lib/Rule/Internal/RemoveInterfaceWithMethods/config/configured_rule.php @@ -0,0 +1,18 @@ +ruleWithConfiguration( + RemoveInterfaceWithMethodsRector::class, + [SomeInterface::class] + ); +};