add links to each rule and test fixture in docs

This commit is contained in:
TomasVotruba 2020-02-21 14:59:21 +01:00
parent 1127400a06
commit d0afc945e0
5 changed files with 1056 additions and 533 deletions

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Rector\PHPUnit\Rector\Class_;
use Nette\Loaders\RobotLoader;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
@ -17,7 +15,7 @@ use Rector\Core\Rector\AbstractRector;
use Rector\Core\RectorDefinition\CodeSample;
use Rector\Core\RectorDefinition\RectorDefinition;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\PHPUnit\Composer\ComposerAutoloadedDirectoryProvider;
use Rector\PHPUnit\TestClassResolver\TestClassResolver;
/**
* @see \Rector\PHPUnit\Tests\Rector\Class_\AddSeeTestAnnotationRector\AddSeeTestAnnotationRectorTest
@ -25,18 +23,13 @@ use Rector\PHPUnit\Composer\ComposerAutoloadedDirectoryProvider;
final class AddSeeTestAnnotationRector extends AbstractRector
{
/**
* @var string[]
* @var TestClassResolver
*/
private $phpUnitTestCaseClasses = [];
private $testClassResolver;
/**
* @var ComposerAutoloadedDirectoryProvider
*/
private $composerAutoloadedDirectoryProvider;
public function __construct(ComposerAutoloadedDirectoryProvider $composerAutoloadedDirectoryProvider)
public function __construct(TestClassResolver $testClassResolver)
{
$this->composerAutoloadedDirectoryProvider = $composerAutoloadedDirectoryProvider;
$this->testClassResolver = $testClassResolver;
}
public function getDefinition(): RectorDefinition
@ -89,7 +82,7 @@ PHP
*/
public function refactor(Node $node): ?Node
{
$testCaseClassName = $this->resolveTestCaseClassName($node);
$testCaseClassName = $this->testClassResolver->resolveFromClass($node);
if ($testCaseClassName === null) {
return null;
}
@ -134,71 +127,8 @@ PHP
return false;
}
private function resolveTestCaseClassName(Class_ $class): ?string
{
if ($this->isAnonymousClass($class)) {
return null;
}
$className = $this->getName($class);
if ($className === null) {
return null;
}
// fallback for unit tests that only have extra "Test" suffix
if (class_exists($className . 'Test')) {
return $className . 'Test';
}
$shortClassName = Strings::after($className, '\\', -1);
$testShortClassName = $shortClassName . 'Test';
$phpUnitTestCaseClasses = $this->getPhpUnitTestCaseClasses();
foreach ($phpUnitTestCaseClasses as $declaredClass) {
if (Strings::endsWith($declaredClass, '\\' . $testShortClassName)) {
return $declaredClass;
}
}
return null;
}
private function createSeePhpDocTagNode(string $className): PhpDocTagNode
{
return new AttributeAwarePhpDocTagNode('@see', new AttributeAwareGenericTagValueNode('\\' . $className));
}
/**
* @return string[]
*/
private function getPhpUnitTestCaseClasses(): array
{
if ($this->phpUnitTestCaseClasses !== []) {
return $this->phpUnitTestCaseClasses;
}
$robotLoader = $this->createRobotLoadForDirectories();
$robotLoader->rebuild();
$this->phpUnitTestCaseClasses = array_keys($robotLoader->getIndexedClasses());
return $this->phpUnitTestCaseClasses;
}
private function createRobotLoadForDirectories(): RobotLoader
{
$robotLoader = new RobotLoader();
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/tests_add_see_rector_tests');
$directories = $this->composerAutoloadedDirectoryProvider->provide();
foreach ($directories as $directory) {
$robotLoader->addDirectory($directory);
}
$robotLoader->acceptFiles = ['*Test.php'];
$robotLoader->ignoreDirs[] = '*Expected*';
$robotLoader->ignoreDirs[] = '*Fixture*';
return $robotLoader;
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Rector\PHPUnit\TestClassResolver;
use Nette\Loaders\RobotLoader;
use Rector\PHPUnit\Composer\ComposerAutoloadedDirectoryProvider;
final class PHPUnitTestCaseClassesProvider
{
/**
* @var string[]
*/
private $phpUnitTestCaseClasses = [];
/**
* @var ComposerAutoloadedDirectoryProvider
*/
private $composerAutoloadedDirectoryProvider;
public function __construct(ComposerAutoloadedDirectoryProvider $composerAutoloadedDirectoryProvider)
{
$this->composerAutoloadedDirectoryProvider = $composerAutoloadedDirectoryProvider;
}
/**
* @return string[]
*/
public function provide(): array
{
if ($this->phpUnitTestCaseClasses !== []) {
return $this->phpUnitTestCaseClasses;
}
$robotLoader = $this->createRobotLoadForDirectories();
$robotLoader->rebuild();
$this->phpUnitTestCaseClasses = array_keys($robotLoader->getIndexedClasses());
return $this->phpUnitTestCaseClasses;
}
private function createRobotLoadForDirectories(): RobotLoader
{
$robotLoader = new RobotLoader();
$robotLoader->setTempDirectory(sys_get_temp_dir() . '/tests_add_see_rector_tests');
$directories = $this->composerAutoloadedDirectoryProvider->provide();
foreach ($directories as $directory) {
$robotLoader->addDirectory($directory);
}
$robotLoader->acceptFiles = ['*Test.php'];
$robotLoader->ignoreDirs[] = '*Expected*';
$robotLoader->ignoreDirs[] = '*Fixture*';
return $robotLoader;
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Rector\PHPUnit\TestClassResolver;
use Nette\Utils\Strings;
use PhpParser\Node\Stmt\Class_;
use Rector\NodeNameResolver\NodeNameResolver;
final class TestClassResolver
{
/**
* @var NodeNameResolver
*/
private $nodeNameResolver;
/**
* @var PHPUnitTestCaseClassesProvider
*/
private $phpUnitTestCaseClassesProvider;
public function __construct(
NodeNameResolver $nodeNameResolver,
PHPUnitTestCaseClassesProvider $phpUnitTestCaseClassesProvider
) {
$this->nodeNameResolver = $nodeNameResolver;
$this->phpUnitTestCaseClassesProvider = $phpUnitTestCaseClassesProvider;
}
public function resolveFromClassName(string $className): ?string
{
// fallback for unit tests that only have extra "Test" suffix
if (class_exists($className . 'Test')) {
return $className . 'Test';
}
$shortClassName = Strings::after($className, '\\', -1);
$testShortClassName = $shortClassName . 'Test';
$phpUnitTestCaseClasses = $this->phpUnitTestCaseClassesProvider->provide();
foreach ($phpUnitTestCaseClasses as $declaredClass) {
if (Strings::endsWith($declaredClass, '\\' . $testShortClassName)) {
return $declaredClass;
}
}
return null;
}
public function resolveFromClass(Class_ $class): ?string
{
$className = $this->nodeNameResolver->getName($class);
if ($className === null) {
return null;
}
return $this->resolveFromClassName($className);
}
}

View File

@ -9,10 +9,13 @@ use Rector\ConsoleDiffer\MarkdownDifferAndFormatter;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\Contract\RectorDefinition\CodeSampleInterface;
use Rector\Core\RectorDefinition\ConfiguredCodeSample;
use Rector\PHPUnit\TestClassResolver\TestClassResolver;
use Rector\Utils\DocumentationGenerator\Contract\OutputFormatter\DumpRectorsOutputFormatterInterface;
use Rector\Utils\DocumentationGenerator\RectorMetadataResolver;
use ReflectionClass;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Yaml\Yaml;
use Symplify\SmartFileSystem\SmartFileInfo;
final class MarkdownDumpRectorsOutputFormatter implements DumpRectorsOutputFormatterInterface
{
@ -31,14 +34,21 @@ final class MarkdownDumpRectorsOutputFormatter implements DumpRectorsOutputForma
*/
private $rectorMetadataResolver;
/**
* @var TestClassResolver
*/
private $testClassResolver;
public function __construct(
SymfonyStyle $symfonyStyle,
MarkdownDifferAndFormatter $markdownDifferAndFormatter,
RectorMetadataResolver $rectorMetadataResolver
RectorMetadataResolver $rectorMetadataResolver,
TestClassResolver $testClassResolver
) {
$this->symfonyStyle = $symfonyStyle;
$this->markdownDifferAndFormatter = $markdownDifferAndFormatter;
$this->rectorMetadataResolver = $rectorMetadataResolver;
$this->testClassResolver = $testClassResolver;
}
public function getName(): string
@ -130,8 +140,22 @@ final class MarkdownDumpRectorsOutputFormatter implements DumpRectorsOutputForma
$headline = $this->getRectorClassWithoutNamespace($rector);
$this->symfonyStyle->writeln(sprintf('### `%s`', $headline));
$rectorClass = get_class($rector);
$this->symfonyStyle->newLine();
$this->symfonyStyle->writeln(sprintf('- class: `%s`', get_class($rector)));
$this->symfonyStyle->writeln(sprintf(
'- class: [`%s`](%s)',
get_class($rector),
$this->resolveClassFilePathOnGitHub($rectorClass)
));
$rectorTestClass = $this->testClassResolver->resolveFromClassName($rectorClass);
if ($rectorTestClass !== null) {
$fixtureDirectoryPath = $this->resolveFixtureDirectoryPathOnGitHub($rectorTestClass);
if ($fixtureDirectoryPath !== null) {
$this->symfonyStyle->writeln(sprintf('- [test fixtures](%s)', $fixtureDirectoryPath));
}
}
$rectorDefinition = $rector->getDefinition();
if ($rectorDefinition->getDescription() !== '') {
@ -193,4 +217,30 @@ final class MarkdownDumpRectorsOutputFormatter implements DumpRectorsOutputForma
{
$this->symfonyStyle->writeln(sprintf('```%s%s%s%s```', $format, PHP_EOL, rtrim($content), PHP_EOL));
}
private function resolveClassFilePathOnGitHub(string $className): string
{
$classRelativePath = $this->getClassRelativePath($className);
return '/../master/' . $classRelativePath;
}
private function resolveFixtureDirectoryPathOnGitHub(string $className): ?string
{
$classRelativePath = $this->getClassRelativePath($className);
$fixtureDirectory = dirname($classRelativePath) . '/Fixture';
if (is_dir($fixtureDirectory)) {
return '/../master/' . $fixtureDirectory;
}
return null;
}
private function getClassRelativePath(string $className): string
{
$rectorReflectionClass = new ReflectionClass($className);
$rectorSmartFileInfo = new SmartFileInfo($rectorReflectionClass->getFileName());
return $rectorSmartFileInfo->getRelativeFilePathFromCwd();
}
}