Skip to content

Commit

Permalink
Add PHPUnit 9 annotations support detectoin in "custom-rule" command,…
Browse files Browse the repository at this point in the history
… add RectorConfig::withPreparedSets() (#5506)

* add RectorConfig::withPreparedSets()

* add phpunit 9 support for custom rule
  • Loading branch information
TomasVotruba committed Jan 26, 2024
1 parent 701ab22 commit a69e1ff
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 29 deletions.
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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#'
Expand Down
28 changes: 14 additions & 14 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
51 changes: 51 additions & 0 deletions src/Configuration/RectorConfigBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<class-string<RectorInterface>> $rules
*/
Expand Down
65 changes: 61 additions & 4 deletions src/Console/Command/CustomRuleCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,24 @@
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;
use Symfony\Component\Console\Input\InputInterface;
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,
) {
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion templates/custom-rule/utils/rector/src/Rector/__Name__.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Utils\Rector\Tests\Rector\__Name__;

use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class __Name__Test extends AbstractRectorTestCase
{
/**
* @dataProvider provideData()
*/
public function test(string $filePath): void
{
$this->doTestFile($filePath);
}

public static function provideData(): \Iterator
{
return self::yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

public function provideConfigFilePath(): string
{
return __DIR__ . '/config/configured_rule.php';
}
}
19 changes: 9 additions & 10 deletions templates/rector.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ]);
};

0 comments on commit a69e1ff

Please sign in to comment.