From a69e1ff013720727f052437d051ca5ab2c11b89e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 26 Jan 2024 22:40:53 +0100 Subject: [PATCH] Add PHPUnit 9 annotations support detectoin in "custom-rule" command, add RectorConfig::withPreparedSets() (#5506) * add RectorConfig::withPreparedSets() * add phpunit 9 support for custom rule --- phpstan.neon | 1 + rector.php | 28 ++++---- src/Configuration/RectorConfigBuilder.php | 51 +++++++++++++++ src/Console/Command/CustomRuleCommand.php | 65 +++++++++++++++++-- .../utils/rector/src/Rector/__Name__.php | 2 +- .../tests/Rector/__Name__/__Name__Test.php | 28 ++++++++ templates/rector.php.dist | 19 +++--- 7 files changed, 165 insertions(+), 29 deletions(-) create mode 100644 templates/custom-rules-annotations/utils/rector/tests/Rector/__Name__/__Name__Test.php diff --git a/phpstan.neon b/phpstan.neon index 462bd1aa899..c37437091c6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -291,6 +291,7 @@ parameters: - src/Bootstrap/RectorConfigsResolver.php - tests/FileSystem/FilesFinder/FilesFinderTest.php - tests/Skipper/Skipper/SkipperTest.php + - src/Console/Command/CustomRuleCommand.php # validation - '#Call to static method Webmozart\\Assert\\Assert\:\:(.*?) always evaluate to true#' diff --git a/rector.php b/rector.php index c90bd4765d2..9d6a8ac12fc 100644 --- a/rector.php +++ b/rector.php @@ -8,24 +8,24 @@ use Rector\DeadCode\Rector\ConstFetch\RemovePhpVersionIdCheckRector; use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; use Rector\Set\ValueObject\LevelSetList; -use Rector\Set\ValueObject\SetList; use Rector\TypeDeclaration\Rector\ClassMethod\AddMethodCallBasedStrictParamTypeRector; use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector; -use Rector\Utils\Rector\MoveAbstractRectorToChildrenRector; return RectorConfig::configure() - ->withSets([ - LevelSetList::UP_TO_PHP_82, - SetList::CODE_QUALITY, - SetList::DEAD_CODE, - SetList::PRIVATIZATION, - SetList::NAMING, - SetList::TYPE_DECLARATION, - SetList::INSTANCEOF, - SetList::EARLY_RETURN, - SetList::CODING_STYLE, - SetList::STRICT_BOOLEANS, - ])->withRules([DeclareStrictTypesRector::class, MoveAbstractRectorToChildrenRector::class]) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + typeDeclarations: true, + privatization: true, + strictBooleans: true, + instanceOf: true, + earlyReturn: true, + naming: true + ) + // @todo improve + ->withSets([LevelSetList::UP_TO_PHP_82]) + ->withRules([DeclareStrictTypesRector::class]) ->withPaths([ __DIR__ . '/bin', __DIR__ . '/src', diff --git a/src/Configuration/RectorConfigBuilder.php b/src/Configuration/RectorConfigBuilder.php index 9e91a8194f4..15f6e59489c 100644 --- a/src/Configuration/RectorConfigBuilder.php +++ b/src/Configuration/RectorConfigBuilder.php @@ -7,6 +7,7 @@ use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface; use Rector\Config\RectorConfig; use Rector\Contract\Rector\RectorInterface; +use Rector\Set\ValueObject\SetList; use Rector\ValueObject\PhpVersion; use Symfony\Component\Finder\Finder; @@ -230,6 +231,56 @@ public function withSets(array $sets): self return $this; } + public function withPreparedSets( + bool $deadCode = false, + bool $codeQuality = false, + bool $codingStyle = false, + bool $typeDeclarations = false, + bool $privatization = false, + bool $naming = false, + bool $instanceOf = false, + bool $earlyReturn = false, + bool $strictBooleans = false + ): self { + if ($deadCode) { + $this->sets[] = SetList::DEAD_CODE; + } + + if ($codeQuality) { + $this->sets[] = SetList::CODE_QUALITY; + } + + if ($codingStyle) { + $this->sets[] = SetList::CODING_STYLE; + } + + if ($typeDeclarations) { + $this->sets[] = SetList::TYPE_DECLARATION; + } + + if ($privatization) { + $this->sets[] = SetList::PRIVATIZATION; + } + + if ($naming) { + $this->sets[] = SetList::NAMING; + } + + if ($instanceOf) { + $this->sets[] = SetList::INSTANCEOF; + } + + if ($earlyReturn) { + $this->sets[] = SetList::EARLY_RETURN; + } + + if ($strictBooleans) { + $this->sets[] = SetList::STRICT_BOOLEANS; + } + + return $this; + } + /** * @param array> $rules */ diff --git a/src/Console/Command/CustomRuleCommand.php b/src/Console/Command/CustomRuleCommand.php index 3da6d1732a1..d6fd9f83c25 100644 --- a/src/Console/Command/CustomRuleCommand.php +++ b/src/Console/Command/CustomRuleCommand.php @@ -5,6 +5,7 @@ namespace Rector\Console\Command; use Nette\Utils\FileSystem; +use Nette\Utils\Strings; use Rector\Exception\ShouldNotHappenException; use Rector\FileSystem\JsonFileSystem; use Symfony\Component\Console\Command\Command; @@ -12,9 +13,16 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; final class CustomRuleCommand extends Command { + /** + * @see https://regex101.com/r/2eP4rw/1 + * @var string + */ + private const START_WITH_10_REGEX = '#(\^10\.|>=10\.|10\.)#'; + public function __construct( private readonly SymfonyStyle $symfonyStyle, ) { @@ -50,17 +58,42 @@ static function (string $answer): string { $rectorName = ucfirst((string) $rectorName); // find all files in templates directory - $fileInfos = Finder::create() + $finder = Finder::create() ->files() ->in(__DIR__ . '/../../../templates/custom-rule') - ->getIterator(); + ->notName('__Name__Test.php'); + + // 0. resolve if local phpunit is at least PHPUnit 10 (which supports #[DataProvider]) + // to provide annotation if not + $arePHPUnitAttributesSupported = $this->detectPHPUnitAttributeSupport(); + + if ($arePHPUnitAttributesSupported) { + $finder->append([ + new SplFileInfo( + __DIR__ . '/../../../templates/custom-rule/utils/rector/tests/Rector/__Name__/__Name__Test.php', + 'utils/rector/tests/Rector/__Name__', + 'utils/rector/tests/Rector/__Name__/__Name__Test.php', + ), + ]); + } else { + // use @annotations for PHPUnit 9 and bellow + $finder->append([ + new SplFileInfo( + __DIR__ . '/../../../templates/custom-rules-annotations/utils/rector/tests/Rector/__Name__/__Name__Test.php', + 'utils/rector/tests/Rector/__Name__', + 'utils/rector/tests/Rector/__Name__/__Name__Test.php', + ), + ]); + } $generatedFilePaths = []; + $fileInfos = iterator_to_array($finder->getIterator()); + foreach ($fileInfos as $fileInfo) { // replace __Name__ with $rectorName - $newContent = str_replace('__Name__', $rectorName, $fileInfo->getContents()); - $newFilePath = str_replace('__Name__', $rectorName, $fileInfo->getRelativePathname()); + $newContent = $this->replaceNameVariable($rectorName, $fileInfo->getContents()); + $newFilePath = $this->replaceNameVariable($rectorName, $fileInfo->getRelativePathname()); FileSystem::write(getcwd() . '/' . $newFilePath, $newContent); @@ -95,4 +128,28 @@ static function (string $answer): string { return Command::SUCCESS; } + + private function replaceNameVariable(string $rectorName, string $contents): string + { + return str_replace('__Name__', $rectorName, $contents); + } + + private function detectPHPUnitAttributeSupport(): bool + { + $composerJsonFilePath = getcwd() . '/composer.json'; + + if (! file_exists($composerJsonFilePath)) { + // be safe + return false; + } + + $composerJson = JsonFileSystem::readFilePath($composerJsonFilePath); + $phpunitVersion = $composerJson['require-dev']['phpunit/phpunit'] ?? null; + + if ($phpunitVersion === null) { + return false; + } + + return (bool) Strings::match($phpunitVersion, self::START_WITH_10_REGEX); + } } diff --git a/templates/custom-rule/utils/rector/src/Rector/__Name__.php b/templates/custom-rule/utils/rector/src/Rector/__Name__.php index 5aafd3ba085..bb209149a54 100644 --- a/templates/custom-rule/utils/rector/src/Rector/__Name__.php +++ b/templates/custom-rule/utils/rector/src/Rector/__Name__.php @@ -10,7 +10,7 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** - * @see \Utils\Rector\Tests\Rector\__Name__\__Name__Test + * @see \rector\tests\Rector\__Name__\__Name__Test */ final class __Name__ extends AbstractRector { diff --git a/templates/custom-rules-annotations/utils/rector/tests/Rector/__Name__/__Name__Test.php b/templates/custom-rules-annotations/utils/rector/tests/Rector/__Name__/__Name__Test.php new file mode 100644 index 00000000000..3e252b5f7cf --- /dev/null +++ b/templates/custom-rules-annotations/utils/rector/tests/Rector/__Name__/__Name__Test.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/templates/rector.php.dist b/templates/rector.php.dist index 9dcfa922c2a..ae09f219555 100644 --- a/templates/rector.php.dist +++ b/templates/rector.php.dist @@ -6,16 +6,15 @@ use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; use Rector\Config\RectorConfig; use Rector\Set\ValueObject\LevelSetList; -return static function (RectorConfig $rectorConfig): void { - $rectorConfig->paths([ +return RectorConfig::configure() + ->withPaths([ __PATHS__ + ]) + ->withRules([ + InlineConstructorDefaultToPropertyRector::class, + ]) + ->withSets([ + // define sets of rules + // LevelSetList::UP_TO_PHP_XY ]); - - // register a single rule - $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); - - // define sets of rules - // $rectorConfig->sets([ - // LevelSetList::UP_TO_PHP_XY - // ]); };