Merge pull request #3539 from rectorphp/single-rule

Re-add --only option
This commit is contained in:
kodiakhq[bot] 2020-06-17 22:22:09 +00:00 committed by GitHub
commit 2ecd056bca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 304 additions and 2 deletions

View File

@ -211,6 +211,22 @@ class SomeClass
}
```
### Run Just 1 Rector Rule
Do you have config that includes many sets and Rectors? You might want to run only a single Rector. The `--only` argument allows that, e.g.:
```bash
vendor/bin/rector process src --set solid --only Rector\SOLID\Rector\Class_\FinalizeClassesWithoutChildrenRector
```
Or just short name:
```bash
vendor/bin/rector process src --set solid --only FinalizeClassesWithoutChildrenRector
```
Both will run only `Rector\SOLID\Rector\Class_\FinalizeClassesWithoutChildrenRector`.
### Provide PHP Version
By default Rector uses the language features matching your system version of PHP. You can configure it for a different PHP version:

View File

@ -1,6 +1,13 @@
imports:
- { resource: "create-rector.yaml", ignore_errors: 'not_found' }
services:
# @todo add compiler pass that makes every Rector public service
# _defaults:
# public: true
Rector\Php72\Rector\Each\ListEachRector: null
parameters:
# bleeding edge feature
# is_cache_enabled: true

View File

@ -11,6 +11,7 @@ use Rector\Core\Application\FileSystem\RemovedAndAddedFilesCollector;
use Rector\Core\Application\FileSystem\RemovedAndAddedFilesProcessor;
use Rector\Core\Configuration\Configuration;
use Rector\Core\EventDispatcher\Event\AfterProcessEvent;
use Rector\Core\Testing\Application\EnabledRectorsProvider;
use Rector\FileSystemRector\FileSystemFileProcessor;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Style\SymfonyStyle;
@ -70,6 +71,11 @@ final class RectorApplication
*/
private $removedAndAddedFilesProcessor;
/**
* @var EnabledRectorsProvider
*/
private $enabledRectorsProvider;
/**
* @var NodeScopeResolver
*/
@ -86,6 +92,7 @@ final class RectorApplication
ErrorAndDiffCollector $errorAndDiffCollector,
Configuration $configuration,
FileProcessor $fileProcessor,
EnabledRectorsProvider $enabledRectorsProvider,
RemovedAndAddedFilesCollector $removedAndAddedFilesCollector,
RemovedAndAddedFilesProcessor $removedAndAddedFilesProcessor,
NodeScopeResolver $nodeScopeResolver,
@ -98,6 +105,7 @@ final class RectorApplication
$this->fileProcessor = $fileProcessor;
$this->removedAndAddedFilesCollector = $removedAndAddedFilesCollector;
$this->removedAndAddedFilesProcessor = $removedAndAddedFilesProcessor;
$this->enabledRectorsProvider = $enabledRectorsProvider;
$this->nodeScopeResolver = $nodeScopeResolver;
$this->eventDispatcher = $eventDispatcher;
}
@ -129,6 +137,12 @@ final class RectorApplication
}, 'parsing');
}
// active only one rule
if ($this->configuration->getOnlyRector() !== null) {
$onlyRector = $this->configuration->getOnlyRector();
$this->enabledRectorsProvider->addEnabledRector($onlyRector);
}
// 2. change nodes with Rectors
foreach ($fileInfos as $fileInfo) {
$this->tryCatchWrapper($fileInfo, function (SmartFileInfo $smartFileInfo): void {

View File

@ -29,6 +29,11 @@ final class Configuration
*/
private $showProgressBar = true;
/**
* @var string|null
*/
private $onlyRector;
/**
* @var bool
*/
@ -84,16 +89,27 @@ final class Configuration
*/
private $paths = [];
/**
* @var OnlyRuleResolver
*/
private $onlyRuleResolver;
/**
* @param string[] $fileExtensions
* @param string[] $paths
*/
public function __construct(CiDetector $ciDetector, bool $isCacheEnabled, array $fileExtensions, array $paths)
{
public function __construct(
CiDetector $ciDetector,
bool $isCacheEnabled,
array $fileExtensions,
array $paths,
OnlyRuleResolver $onlyRuleResolver
) {
$this->ciDetector = $ciDetector;
$this->isCacheEnabled = $isCacheEnabled;
$this->fileExtensions = $fileExtensions;
$this->paths = $paths;
$this->onlyRuleResolver = $onlyRuleResolver;
}
/**
@ -112,6 +128,12 @@ final class Configuration
$this->outputFormat = (string) $input->getOption(Option::OPTION_OUTPUT_FORMAT);
/** @var string|null $onlyRector */
$onlyRector = $input->getOption(Option::OPTION_ONLY);
if ($onlyRector !== null) {
$this->setOnlyRector($onlyRector);
}
$commandLinePaths = (array) $input->getArgument(Option::SOURCE);
// manual command line value has priority
if (count($commandLinePaths) > 0) {
@ -184,6 +206,11 @@ final class Configuration
return $this->mustMatchGitDiff;
}
public function getOnlyRector(): ?string
{
return $this->onlyRector;
}
public function getOutputFile(): ?string
{
return $this->outputFile;
@ -253,4 +280,9 @@ final class Configuration
}
return $input->getOption(Option::OPTION_OUTPUT_FORMAT) !== CheckstyleOutputFormatter::NAME;
}
private function setOnlyRector(string $rector): void
{
$this->onlyRector = $this->onlyRuleResolver->resolve($rector);
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Configuration;
use Nette\Utils\Strings;
use Rector\Core\Exception\Configuration\RectorRuleNotFoundException;
/**
* @see \Rector\Core\Tests\Configuration\OnlyRuleResolverTest
*/
final class OnlyRuleResolver
{
/**
* @var RectorClassesProvider
*/
private $rectorClassesProvider;
public function __construct(RectorClassesProvider $rectorClassesProvider)
{
$this->rectorClassesProvider = $rectorClassesProvider;
}
public function resolve(string $rule): string
{
$rule = ltrim($rule, '\\');
$rectorClasses = $this->rectorClassesProvider->provide();
// 1. exact class
foreach ($rectorClasses as $rectorClass) {
if (is_a($rectorClass, $rule, true)) {
return $rule;
}
}
// 2. short class
foreach ($rectorClasses as $rectorClass) {
if (Strings::endsWith($rectorClass, '\\' . $rule)) {
return $rectorClass;
}
}
// 3. class without slashes
foreach ($rectorClasses as $rectorClass) {
$rectorClassWithoutSlashes = Strings::replace($rectorClass, '#\\\\#');
if ($rectorClassWithoutSlashes === $rule) {
return $rectorClass;
}
}
$message = sprintf(
'Rule "%s" was not found.%sMake sure it is registered in your config or in one of the sets',
$rule,
PHP_EOL
);
throw new RectorRuleNotFoundException($message);
}
}

View File

@ -36,6 +36,11 @@ final class Option
*/
public const PHP_VERSION_FEATURES = 'php_version_features';
/**
* @var string
*/
public const OPTION_ONLY = 'only';
/**
* @var string
*/

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Configuration;
use Psr\Container\ContainerInterface;
use Rector\Core\Contract\Rector\RectorInterface;
use Symfony\Component\DependencyInjection\Container;
final class RectorClassesProvider
{
/**
* @var ContainerInterface&Container
*/
private $container;
/**
* This is only to prevent circular dependencies, where this service is being used.
* We only need list of classes.
*
* @param ContainerInterface&Container $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* @return class-string[]
*/
public function provide(): array
{
$rectorClasses = [];
foreach ($this->container->getServiceIds() as $class) {
if (! class_exists($class)) {
continue;
}
if (! is_a($class, RectorInterface::class, true)) {
continue;
}
$rectorClasses[] = $class;
}
sort($rectorClasses);
return $rectorClasses;
}
}

View File

@ -168,6 +168,13 @@ final class ProcessCommand extends AbstractCommand
'Execute only on file(s) matching the git diff.'
);
$this->addOption(
Option::OPTION_ONLY,
null,
InputOption::VALUE_REQUIRED,
'Run only one single Rector from the loaded Rectors (in services, sets, etc).'
);
$availableOutputFormatters = $this->outputFormatterCollector->getNames();
$this->addOption(
Option::OPTION_OUTPUT_FORMAT,

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\Core\DependencyInjection\CompilerPass;
use Rector\Core\Contract\Rector\RectorInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Needed for @see \Rector\Core\Configuration\RectorClassesProvider
*/
final class MakeRectorsPublicCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $containerBuilder): void
{
foreach ($containerBuilder->getDefinitions() as $definition) {
if ($definition->getClass() === null) {
continue;
}
if (! is_a($definition->getClass(), RectorInterface::class, true)) {
continue;
}
$definition->setPublic(true);
}
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Exception\Configuration;
use Exception;
final class RectorRuleNotFoundException extends Exception
{
}

View File

@ -6,6 +6,7 @@ namespace Rector\Core\HttpKernel;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\DependencyInjection\Collector\RectorServiceArgumentCollector;
use Rector\Core\DependencyInjection\CompilerPass\MakeRectorsPublicCompilerPass;
use Rector\Core\DependencyInjection\CompilerPass\MergeImportedRectorServiceArgumentsCompilerPass;
use Rector\Core\DependencyInjection\CompilerPass\RemoveExcludedRectorsCompilerPass;
use Rector\Core\DependencyInjection\Loader\TolerantRectorYamlFileLoader;
@ -91,6 +92,7 @@ final class RectorKernel extends Kernel implements ExtraConfigAwareKernelInterfa
$containerBuilder->addCompilerPass(new AutowireInterfacesCompilerPass([RectorInterface::class]));
$containerBuilder->addCompilerPass(new AutoBindParameterCompilerPass());
$containerBuilder->addCompilerPass(new MakeRectorsPublicCompilerPass());
// add all merged arguments of Rector services
$containerBuilder->addCompilerPass(

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\Configuration;
use Iterator;
use Rector\Core\Configuration\OnlyRuleResolver;
use Rector\Core\HttpKernel\RectorKernel;
use Rector\Core\Tests\Configuration\Source\CustomLocalRector;
use Rector\Php72\Rector\Each\ListEachRector;
use Symplify\PackageBuilder\Tests\AbstractKernelTestCase;
final class OnlyRuleResolverTest extends AbstractKernelTestCase
{
/**
* @var OnlyRuleResolver
*/
private $onlyRuleResolver;
protected function setUp(): void
{
$this->bootKernelWithConfigs(RectorKernel::class, [__DIR__ . '/Source/config/some_config.yaml']);
$this->onlyRuleResolver = self::$container->get(OnlyRuleResolver::class);
}
/**
* @dataProvider provideData()
*/
public function test(string $rule, string $expectedRectorClass): void
{
$resolveRectorClass = $this->onlyRuleResolver->resolve($rule);
$this->assertSame($expectedRectorClass, $resolveRectorClass);
}
public function provideData(): Iterator
{
yield [ListEachRector::class, ListEachRector::class];
yield ['ListEachRector', ListEachRector::class];
yield [CustomLocalRector::class, CustomLocalRector::class];
yield ['CustomLocalRector', CustomLocalRector::class];
// miss-formats
yield ['\\' . ListEachRector::class, ListEachRector::class];
yield ['RectorCoreTestsConfigurationSourceCustomLocalRector', CustomLocalRector::class];
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\Configuration\Source;
use Rector\Core\Contract\Rector\RectorInterface;
use Rector\Core\RectorDefinition\RectorDefinition;
final class CustomLocalRector implements RectorInterface
{
public function getDefinition(): RectorDefinition
{
// TODO: Implement getDefinition() method.
}
}

View File

@ -0,0 +1,3 @@
services:
Rector\Php72\Rector\Each\ListEachRector: null
Rector\Core\Tests\Configuration\Source\CustomLocalRector: null