diff --git a/README.md b/README.md index 5f1e46d2884..b2593d09150 100644 --- a/README.md +++ b/README.md @@ -259,29 +259,53 @@ rectors: rectors: Rector\Rector\Dynamic\ValueObjectRemoverRector: # type: new simple type - 'ValueObjects\Name': 'string' + 'ValueObject\Name': 'string' ``` For example: ```diff -- $value = new ValueObjects\Name('Tomas'); +- $value = new ValueObject\Name('Tomas'); + $value = 'Tomas'; ``` ```diff /** --* @var ValueObjects\Name +-* @var ValueObject\Name +* @var string */ private $name; ``` ```diff -- public function someMethod(ValueObjects\Name $name) { ... +- public function someMethod(ValueObject\Name $name) { ... + public function someMethod(string $name) { ... ``` +## Replace Property and Method Annotations + +```yml +rectors: + Rector\Rector\Dynamic\AnnotationReplacerRector: + # type + PHPUnit\Framework\TestCase: + # old annotation: new annotation + scenario: test +``` + +```diff + final class SomeTest extends PHPUnit\Framework\TestCase + { + /** +- * @scenario ++ * @test + */ + public function test() + { + } + } +``` + ## Turn Magic to Methods ### Replace `get/set` magic methods with real ones diff --git a/packages/ReflectionDocBlock/src/NodeAnalyzer/DocBlockAnalyzer.php b/packages/ReflectionDocBlock/src/NodeAnalyzer/DocBlockAnalyzer.php index eeb42ba07ec..f3eb9bcc98c 100644 --- a/packages/ReflectionDocBlock/src/NodeAnalyzer/DocBlockAnalyzer.php +++ b/packages/ReflectionDocBlock/src/NodeAnalyzer/DocBlockAnalyzer.php @@ -2,6 +2,7 @@ namespace Rector\ReflectionDocBlock\NodeAnalyzer; +use Nette\Utils\Strings; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Tag; use phpDocumentor\Reflection\DocBlock\Tags\Param; @@ -60,6 +61,19 @@ final class DocBlockAnalyzer $this->saveNewDocBlockToNode($node, $docBlock); } + public function replaceAnnotationInNode(Node $node, string $oldAnnotation, string $newAnnotation): void + { + if (! $node->getDocComment()) { + return; + } + + $oldContent = $node->getDocComment()->getText(); + $newContent = Strings::replace($oldContent, sprintf('#@%s#', $oldAnnotation), '@' . $newAnnotation); + + $doc = new Doc($newContent); + $node->setDocComment($doc); + } + /** * @return string[]|null */ diff --git a/src/Rector/Dynamic/AnnotationReplacerRector.php b/src/Rector/Dynamic/AnnotationReplacerRector.php new file mode 100644 index 00000000000..5aacd61526b --- /dev/null +++ b/src/Rector/Dynamic/AnnotationReplacerRector.php @@ -0,0 +1,104 @@ +docBlockAnalyzer = $docBlockAnalyzer; + $this->classToAnnotationMap = $classToAnnotationMap; + } + + public function isCandidate(Node $node): bool + { + if ($this->shouldSkip($node)) { + return false; + } + + $parentNode = $node->getAttribute(Attribute::PARENT_NODE); + foreach ($this->classToAnnotationMap as $type => $annotationMap) { + if (! in_array($type, $parentNode->getAttribute(Attribute::TYPES), true)) { + continue; + } + + $this->activeAnnotationMap = $annotationMap; + + if ($this->hasAnyAnnotation($node)) { + return true; + } + } + + return false; + } + + /** + * @param ClassMethod|Property $node + */ + public function refactor(Node $node): ?Node + { + foreach ($this->activeAnnotationMap as $oldAnnotation => $newAnnotation) { + $this->docBlockAnalyzer->replaceAnnotationInNode($node, $oldAnnotation, $newAnnotation); + } + + return $node; + } + + private function shouldSkip(Node $node): bool + { + if (! $node instanceof ClassMethod && ! $node instanceof Property) { + return true; + } + + /** @var Node|null $parentNode */ + $parentNode = $node->getAttribute(Attribute::PARENT_NODE); + if (! $parentNode) { + return true; + } + + return false; + } + + private function hasAnyAnnotation(Node $node): bool + { + foreach ($this->activeAnnotationMap as $oldAnnotation => $newAnnotation) { + if ($this->docBlockAnalyzer->hasAnnotation($node, $oldAnnotation)) { + return true; + } + } + + return false; + } +} diff --git a/src/config/level/phpunit/phpunit70.yml b/src/config/level/phpunit/phpunit70.yml new file mode 100644 index 00000000000..2a76cdea332 --- /dev/null +++ b/src/config/level/phpunit/phpunit70.yml @@ -0,0 +1,4 @@ +rectors: + Rector\Rector\Dynamic\AnnotationReplacerRector: + PHPUnit\Framework\TestCase: + scenario: test diff --git a/tests/Rector/Dynamic/AnnotationReplacerRector/Correct/correct.php.inc b/tests/Rector/Dynamic/AnnotationReplacerRector/Correct/correct.php.inc new file mode 100644 index 00000000000..6b210d869aa --- /dev/null +++ b/tests/Rector/Dynamic/AnnotationReplacerRector/Correct/correct.php.inc @@ -0,0 +1,11 @@ +doTestFileMatchesExpectedContent($wrong, $fixed); + } + + /** + * @return string[][] + */ + public function provideWrongToFixedFiles(): array + { + return [ + [__DIR__ . '/Wrong/wrong.php.inc', __DIR__ . '/Correct/correct.php.inc'], + ]; + } + + /** + * @return string[] + */ + protected function getRectorClasses(): array + { + return [AnnotationReplacerRector::class]; + } + + protected function provideConfig(): string + { + return __DIR__ . '/config/rector.yml'; + } +} diff --git a/tests/Rector/Dynamic/AnnotationReplacerRector/Wrong/wrong.php.inc b/tests/Rector/Dynamic/AnnotationReplacerRector/Wrong/wrong.php.inc new file mode 100644 index 00000000000..a571932d61f --- /dev/null +++ b/tests/Rector/Dynamic/AnnotationReplacerRector/Wrong/wrong.php.inc @@ -0,0 +1,11 @@ +