diff --git a/config/level/symfony/symfony43.yaml b/config/level/symfony/symfony43.yaml index 2bb6a9528716..e118f9c47bb5 100644 --- a/config/level/symfony/symfony43.yaml +++ b/config/level/symfony/symfony43.yaml @@ -71,3 +71,7 @@ services: name: 'context' default_value: [] # type: array + + Rector\Rector\ClassMethod\AddMethodParentCallRector: + Symfony\Component\EventDispatcher\EventDispatcher: + - '__construct' diff --git a/packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/ConsistentPregDelimiterRectorTest.php b/packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/ConsistentPregDelimiterRectorTest.php index 1747fc2ed037..dadc4315468b 100644 --- a/packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/ConsistentPregDelimiterRectorTest.php +++ b/packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/ConsistentPregDelimiterRectorTest.php @@ -11,7 +11,7 @@ public function test(): void { $this->doTestFiles([ __DIR__ . '/Fixture/fixture.php.inc', - __DIR__ . '/Fixture/static_call.php.inc', + __DIR__ . '/Fixture/escape_nette_static_call.php.inc', __DIR__ . '/Fixture/skip_concat.php.inc', ]); } diff --git a/packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/Fixture/static_call.php.inc b/packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/Fixture/escape_nette_static_call.php.inc similarity index 88% rename from packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/Fixture/static_call.php.inc rename to packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/Fixture/escape_nette_static_call.php.inc index 6eb5b8e8634e..bd3f1d6a3d7e 100644 --- a/packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/Fixture/static_call.php.inc +++ b/packages/CodingStyle/tests/Rector/FuncCall/ConsistentPregDelimiterRector/Fixture/escape_nette_static_call.php.inc @@ -4,7 +4,7 @@ namespace Rector\CodingStyle\Tests\Rector\FuncCall\ConsistentPregDelimiterRector use Nette\Utils\Strings; -class StaticCall +class EscapeNetteStaticCall { public function run($value) { @@ -20,7 +20,7 @@ namespace Rector\CodingStyle\Tests\Rector\FuncCall\ConsistentPregDelimiterRector use Nette\Utils\Strings; -class StaticCall +class EscapeNetteStaticCall { public function run($value) { diff --git a/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php b/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php index 38b68515b5c4..8d16907db92c 100644 --- a/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php +++ b/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php @@ -13,7 +13,7 @@ use Rector\PhpParser\Parser\Parser; use Rector\PhpParser\Printer\BetterStandardPrinter; use Rector\PhpParser\Printer\FormatPerservingPrinter; -use Rector\Rector\AbstractRectorTrait; +use Rector\Rector\AbstractRector\AbstractRectorTrait; use Symplify\PackageBuilder\FileSystem\SmartFileInfo; use TypeError; diff --git a/src/Rector/AbstractRector.php b/src/Rector/AbstractRector.php index 6c611b0b84d4..3f48c137f1d3 100644 --- a/src/Rector/AbstractRector.php +++ b/src/Rector/AbstractRector.php @@ -13,6 +13,7 @@ use Rector\Contract\Rector\PhpRectorInterface; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\Php\PhpVersionProvider; +use Rector\Rector\AbstractRector\AbstractRectorTrait; use Symfony\Component\Console\Style\SymfonyStyle; use Symplify\PackageBuilder\FileSystem\SmartFileInfo; diff --git a/src/Rector/AbstractRectorTrait.php b/src/Rector/AbstractRector/AbstractRectorTrait.php similarity index 90% rename from src/Rector/AbstractRectorTrait.php rename to src/Rector/AbstractRector/AbstractRectorTrait.php index 67a3bcf0de64..16276403ba68 100644 --- a/src/Rector/AbstractRectorTrait.php +++ b/src/Rector/AbstractRector/AbstractRectorTrait.php @@ -1,6 +1,6 @@ methodsByParentTypes = $methodsByParentTypes; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition('Add method parent call, in case new parent method is added', [ + new CodeSample( + <<<'CODE_SAMPLE' +class SunshineCommand extends ParentClassWithNewConstructor +{ + public function __construct() + { + $value = 5; + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SunshineCommand extends ParentClassWithNewConstructor +{ + public function __construct() + { + $value = 5; + + parent::__construct(); + } +} +CODE_SAMPLE + ), + ]); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [ClassMethod::class]; + } + + /** + * @param ClassMethod $node + */ + public function refactor(Node $node): ?Node + { + $class = $node->getAttribute(AttributeKey::CLASS_NODE); + if ($class === null) { + return null; + } + + /** @var string $className */ + $className = $node->getAttribute(AttributeKey::CLASS_NAME); + + foreach ($this->methodsByParentTypes as $type => $methods) { + if (! $this->isType($class, $type)) { + continue; + } + + // not itself + if ($className === $type) { + continue; + } + + foreach ($methods as $method) { + if ($this->shouldSkipMethod($node, $method)) { + continue; + } + + $node->stmts[] = $this->createParentStaticCall($method); + + return $node; + } + } + + return null; + } + + /** + * Looks for "parent:: + */ + private function hasParentCallOfMethod(Node\Stmt\ClassMethod $classMethod, string $method): bool + { + return (bool) $this->betterNodeFinder->findFirst((array) $classMethod->stmts, function (Node $node) use ( + $method + ): bool { + if (! $node instanceof StaticCall) { + return false; + } + + if (! $this->isName($node->class, 'parent')) { + return false; + } + + return $this->isName($node, $method); + }); + } + + private function createParentStaticCall(string $method): Expression + { + $parentStaticCall = new StaticCall(new Node\Name('parent'), new Node\Identifier($method)); + + return new Expression($parentStaticCall); + } + + private function shouldSkipMethod(ClassMethod $classMethod, string $method): bool + { + if (! $this->isName($classMethod, $method)) { + return true; + } + + return $this->hasParentCallOfMethod($classMethod, $method); + } +} diff --git a/tests/Rector/ClassMethod/AddMethodParentCallRector/AddMethodParentCallRectorTest.php b/tests/Rector/ClassMethod/AddMethodParentCallRector/AddMethodParentCallRectorTest.php new file mode 100644 index 000000000000..6ffd28e7cadf --- /dev/null +++ b/tests/Rector/ClassMethod/AddMethodParentCallRector/AddMethodParentCallRectorTest.php @@ -0,0 +1,32 @@ +doTestFiles([ + __DIR__ . '/Fixture/fixture.php.inc', + __DIR__ . '/Fixture/skip_already_has.php.inc', + ]); + } + + /** + * @return mixed[] + */ + protected function getRectorsWithConfiguration(): array + { + return [ + AddMethodParentCallRector::class => [ + '$methodsByParentTypes' => [ + ParentClassWithNewConstructor::class => ['__construct'], + ], + ], + ]; + } +} diff --git a/tests/Rector/ClassMethod/AddMethodParentCallRector/Fixture/fixture.php.inc b/tests/Rector/ClassMethod/AddMethodParentCallRector/Fixture/fixture.php.inc new file mode 100644 index 000000000000..7486d4d2c164 --- /dev/null +++ b/tests/Rector/ClassMethod/AddMethodParentCallRector/Fixture/fixture.php.inc @@ -0,0 +1,32 @@ + +----- + diff --git a/tests/Rector/ClassMethod/AddMethodParentCallRector/Fixture/skip_already_has.php.inc b/tests/Rector/ClassMethod/AddMethodParentCallRector/Fixture/skip_already_has.php.inc new file mode 100644 index 000000000000..2284e6d3fb92 --- /dev/null +++ b/tests/Rector/ClassMethod/AddMethodParentCallRector/Fixture/skip_already_has.php.inc @@ -0,0 +1,15 @@ +defaultValue = 5; + } +}