From 1a0db5e25cd59b494fd811d3718ded040f07f1a3 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 3 Sep 2019 10:39:34 +0200 Subject: [PATCH] [PHPUnit] Add AddSeeTestAnnotationRector --- config/set/phpunit/phpunit-code-quality.yaml | 1 + .../Class_/AddSeeTestAnnotationRector.php | 142 ++++++++++++++++++ .../AddSeeTestAnnotationRectorTest.php | 25 +++ .../Fixture/add_to_doc_block.php.inc | 34 +++++ .../Fixture/different_namespace.php.inc | 26 ++++ .../Fixture/fixture.php.inc | 30 ++++ .../Fixture/skip_existing.php.inc | 14 ++ .../Source/DifferentNamespaceTest.php | 8 + .../Configuration/ConfigurationFactory.php | 20 +-- 9 files changed, 290 insertions(+), 10 deletions(-) create mode 100644 packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php create mode 100644 packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/AddSeeTestAnnotationRectorTest.php create mode 100644 packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/add_to_doc_block.php.inc create mode 100644 packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/different_namespace.php.inc create mode 100644 packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/fixture.php.inc create mode 100644 packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/skip_existing.php.inc create mode 100644 packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Source/DifferentNamespaceTest.php diff --git a/config/set/phpunit/phpunit-code-quality.yaml b/config/set/phpunit/phpunit-code-quality.yaml index 66adbb17464..8ff08c6c4b9 100644 --- a/config/set/phpunit/phpunit-code-quality.yaml +++ b/config/set/phpunit/phpunit-code-quality.yaml @@ -1,2 +1,3 @@ services: Rector\PHPUnit\Rector\MethodCall\RemoveExpectAnyFromMockRector: ~ + Rector\PHPUnit\Rector\Class_\AddSeeTestAnnotationRector: ~ diff --git a/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php b/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php new file mode 100644 index 00000000000..d9b2c7295c5 --- /dev/null +++ b/packages/PHPUnit/src/Rector/Class_/AddSeeTestAnnotationRector.php @@ -0,0 +1,142 @@ +docBlockManipulator = $docBlockManipulator; + } + + public function getDefinition(): RectorDefinition + { + return new RectorDefinition( + 'Add @see annotation test of the class for faster jump to test. Make it FQN, so it stays in the annotation, not in the PHP source code.', + [ + new CodeSample( + <<<'CODE_SAMPLE' +class SomeService +{ +} + +class SomeServiceTest extends \PHPUnit\Framework\TestCase +{ +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +/** + * @see \SomeServiceTest + */ +class SomeService +{ +} + +class SomeServiceTest extends \PHPUnit\Framework\TestCase +{ +} +CODE_SAMPLE + ), + ] + ); + } + + /** + * @return string[] + */ + public function getNodeTypes(): array + { + return [Class_::class]; + } + + /** + * @param Class_ $node + */ + public function refactor(Node $node): ?Node + { + if ($this->shouldSkipClass($node)) { + return null; + } + + /** @var string $className */ + $className = $this->getName($node); + + $testCaseClassName = $this->resolveTestCaseClassName($className); + if ($testCaseClassName === null) { + return null; + } + + $this->docBlockManipulator->addTag($node, $this->createSeePhpDocTagNode($testCaseClassName)); + + return $node; + } + + private function resolveTestCaseClassName(string $className): ?string + { + if (class_exists($className . 'Test')) { + return $className . 'Test'; + } + + $shortClassName = Strings::after($className, '\\', -1); + $testShortClassName = $shortClassName . 'Test'; + + $declaredClasses = get_declared_classes(); + foreach ($declaredClasses as $declaredClass) { + if (Strings::endsWith($declaredClass, '\\' . $testShortClassName)) { + return $declaredClass; + } + } + + return null; + } + + private function createSeePhpDocTagNode(string $className): PhpDocTagNode + { + return new PhpDocTagNode('@see', new AttributeAwareGenericTagValueNode('\\' . $className)); + } + + private function shouldSkipClass(Class_ $class): bool + { + if ($class->isAnonymous()) { + return true; + } + + $className = $this->getName($class); + if ($className === null) { + return true; + } + + // is a test case + if (Strings::endsWith($className, 'Test')) { + return true; + } + + // is the @see annotation already added + if ($class->getDocComment()) { + $docCommentText = $class->getDocComment()->getText(); + $seeClassPattern = '#@see (.*?)' . preg_quote($className, '#') . 'Test#'; + + if (Strings::match($docCommentText, $seeClassPattern)) { + return true; + } + } + + return false; + } +} diff --git a/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/AddSeeTestAnnotationRectorTest.php b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/AddSeeTestAnnotationRectorTest.php new file mode 100644 index 00000000000..77b971582a0 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/AddSeeTestAnnotationRectorTest.php @@ -0,0 +1,25 @@ +doTestFiles([ + __DIR__ . '/Fixture/fixture.php.inc', + __DIR__ . '/Fixture/different_namespace.php.inc', + __DIR__ . '/Fixture/add_to_doc_block.php.inc', + // skip + __DIR__ . '/Fixture/skip_existing.php.inc', + ]); + } + + protected function getRectorClass(): string + { + return AddSeeTestAnnotationRector::class; + } +} diff --git a/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/add_to_doc_block.php.inc b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/add_to_doc_block.php.inc new file mode 100644 index 00000000000..9dd02525dec --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/add_to_doc_block.php.inc @@ -0,0 +1,34 @@ + +----- + diff --git a/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/different_namespace.php.inc b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/different_namespace.php.inc new file mode 100644 index 00000000000..7d8b63cd78d --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/different_namespace.php.inc @@ -0,0 +1,26 @@ + +----- + diff --git a/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/fixture.php.inc b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/fixture.php.inc new file mode 100644 index 00000000000..65598d2fde9 --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/fixture.php.inc @@ -0,0 +1,30 @@ + +----- + diff --git a/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/skip_existing.php.inc b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/skip_existing.php.inc new file mode 100644 index 00000000000..a08873f3b6b --- /dev/null +++ b/packages/PHPUnit/tests/Rector/Class_/AddSeeTestAnnotationRector/Fixture/skip_existing.php.inc @@ -0,0 +1,14 @@ +files() ->in($this->setsDirectory) - ->name($fileLevel); + ->name($fileSet); /** @var SplFileInfo[] $fileInfos */ $fileInfos = iterator_to_array($finder->getIterator()); if (count($fileInfos) === 0) { // assume new one is created - $match = Strings::match($level, '#\/(?[a-zA-Z_-]+])#'); + $match = Strings::match($set, '#\/(?[a-zA-Z_-]+])#'); if (isset($match['name'])) { - return $this->setsDirectory . '/' . $match['name'] . '/' . $level; + return $this->setsDirectory . '/' . $match['name'] . '/' . $set; } return null; } - /** @var SplFileInfo $foundLevelConfigFileInfo */ - $foundLevelConfigFileInfo = array_pop($fileInfos); + /** @var SplFileInfo $foundSetConfigFileInfo */ + $foundSetConfigFileInfo = array_pop($fileInfos); - return $foundLevelConfigFileInfo->getRealPath(); + return $foundSetConfigFileInfo->getRealPath(); } private function isNodeClassMatch(string $nodeClass, string $nodeType): bool