diff --git a/composer.json b/composer.json index f103b8d46db5..97ff13111390 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,8 @@ "Rector\\PhpSpecToPHPUnit\\": "packages/PhpSpecToPHPUnit/src", "Rector\\Shopware\\": "packages/Shopware/src", "Rector\\NetteTesterToPHPUnit\\": "packages/NetteTesterToPHPUnit/src", - "Rector\\Nette\\": "packages/Nette/src" + "Rector\\Nette\\": "packages/Nette/src", + "Rector\\SOLID\\": "packages/SOLID/src" } }, "autoload-dev": { @@ -94,7 +95,8 @@ "Rector\\PHPStanExtensions\\": "utils/PHPStanExtensions/src", "Rector\\Shopware\\Tests\\": "packages/Shopware/tests", "Rector\\NetteTesterToPHPUnit\\Tests\\": "packages/NetteTesterToPHPUnit/tests", - "Rector\\Nette\\Tests\\": "packages/Nette/tests" + "Rector\\Nette\\Tests\\": "packages/Nette/tests", + "Rector\\SOLID\\Tests\\": "packages/SOLID/tests" }, "classmap": [ "packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource", @@ -138,4 +140,4 @@ "dev-master": "0.5-dev" } } -} +} \ No newline at end of file diff --git a/config/level/solid/solid.yaml b/config/level/solid/solid.yaml new file mode 100644 index 000000000000..028ae61f9d4a --- /dev/null +++ b/config/level/solid/solid.yaml @@ -0,0 +1,2 @@ +services: + Rector\SOLID\Rector\Class_\FinalizeClassesWithoutChildrenRector: ~ diff --git a/packages/NodeTypeResolver/src/Application/ClassLikeNodeCollector.php b/packages/NodeTypeResolver/src/Application/ClassLikeNodeCollector.php index 038701cd3b1d..bce5851d4127 100644 --- a/packages/NodeTypeResolver/src/Application/ClassLikeNodeCollector.php +++ b/packages/NodeTypeResolver/src/Application/ClassLikeNodeCollector.php @@ -200,4 +200,9 @@ public function findClassesBySuffix(string $suffix): array return $classNodes; } + + public function hasClassChildren(string $class): bool + { + return $this->findChildrenOfClass($class) !== []; + } } diff --git a/packages/SOLID/src/Rector/Class_/FinalizeClassesWithoutChildrenRector.php b/packages/SOLID/src/Rector/Class_/FinalizeClassesWithoutChildrenRector.php new file mode 100644 index 000000000000..391104068384 --- /dev/null +++ b/packages/SOLID/src/Rector/Class_/FinalizeClassesWithoutChildrenRector.php @@ -0,0 +1,99 @@ +classLikeNodeCollector = $classLikeNodeCollector; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Finalize every class that has no children', [ + new CodeSample( + <<<'CODE_SAMPLE' +class FirstClass +{ +} + +class SecondClass +{ +} + +class ThirdClass extends SecondClass +{ +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +final class FirstClass +{ +} + +class SecondClass +{ +} + +final class ThirdClass extends SecondClass +{ +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Node\Stmt\Class_::class]; + } + + /** + * @param Node\Stmt\Class_ $node + */ + public function refactor(Node $node): ?Node + { + if ($node->isFinal() || $node->isAbstract() || $node->isAnonymous()) { + return null; + } + + if ($this->isDoctrineEntity($node)) { + return null; + } + + /** @var string $class */ + $class = $this->getName($node); + if ($this->classLikeNodeCollector->hasClassChildren($class)) { + return null; + } + + $node->flags |= Node\Stmt\Class_::MODIFIER_FINAL; + + return $node; + } + + private function isDoctrineEntity(Node $node): bool + { + if ($node->getDocComment() === null) { + return false; + } + + return Strings::contains($node->getDocComment()->getText(), 'Entity'); + } +} diff --git a/packages/SOLID/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/FinalizeClassesWithoutChildrenRectorTest.php b/packages/SOLID/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/FinalizeClassesWithoutChildrenRectorTest.php new file mode 100644 index 000000000000..b3af5f390823 --- /dev/null +++ b/packages/SOLID/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/FinalizeClassesWithoutChildrenRectorTest.php @@ -0,0 +1,23 @@ +doTestFiles([ + __DIR__ . '/Fixture/fixture.php.inc', + __DIR__ . '/Fixture/skip.php.inc', + __DIR__ . '/Fixture/entity.php.inc', + ]); + } + + protected function getRectorClass(): string + { + return FinalizeClassesWithoutChildrenRector::class; + } +} diff --git a/packages/SOLID/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/Fixture/entity.php.inc b/packages/SOLID/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/Fixture/entity.php.inc new file mode 100644 index 000000000000..cf09d3d4016d --- /dev/null +++ b/packages/SOLID/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/Fixture/entity.php.inc @@ -0,0 +1,10 @@ + +----- + diff --git a/packages/SOLID/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/Fixture/skip.php.inc b/packages/SOLID/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/Fixture/skip.php.inc new file mode 100644 index 000000000000..1416d9be18a6 --- /dev/null +++ b/packages/SOLID/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/Fixture/skip.php.inc @@ -0,0 +1,7 @@ +