Skip to content

Commit 27df6ec

Browse files
committed
[Console] Add --max-changes option to stop after N changed files
1 parent 6d22545 commit 27df6ec

9 files changed

Lines changed: 98 additions & 0 deletions

File tree

‎src/Application/ApplicationFileProcessor.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ public function processFiles(
162162
if ($fileProcessResult->hasChanged()) {
163163
++$totalChanged;
164164
}
165+
166+
// stop once the requested number of files is changed, leaving the rest untouched
167+
$maxChanges = $configuration->getMaxChanges();
168+
if ($maxChanges !== null && $totalChanged >= $maxChanges) {
169+
break;
170+
}
165171
} catch (Throwable $throwable) {
166172
$this->changedFilesDetector->invalidateFile($filePath);
167173

‎src/Configuration/ConfigurationFactory.php‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Rector\ValueObject\Configuration;
1111
use Symfony\Component\Console\Input\InputInterface;
1212
use Symfony\Component\Console\Style\SymfonyStyle;
13+
use Webmozart\Assert\Assert;
1314

1415
/**
1516
* @see \Rector\Tests\Configuration\ConfigurationFactoryTest
@@ -97,6 +98,13 @@ public function createFromInput(InputInterface $input): Configuration
9798
$isParallel = false;
9899
}
99100

101+
$maxChanges = $this->resolveMaxChanges($input);
102+
103+
// a global change counter cannot be shared across parallel workers, so enforce the limit in a single process
104+
if ($maxChanges !== null) {
105+
$isParallel = false;
106+
}
107+
100108
$memoryLimit = $this->resolveMemoryLimit($input);
101109

102110
$isReportingWithRealPath = SimpleParameterProvider::provideBoolParameter(Option::ABSOLUTE_FILE_PATH);
@@ -140,9 +148,23 @@ public function createFromInput(InputInterface $input): Configuration
140148
$isComposerBased,
141149
$isPhpOnly,
142150
$filters,
151+
$maxChanges,
143152
);
144153
}
145154

155+
private function resolveMaxChanges(InputInterface $input): ?int
156+
{
157+
$maxChanges = $input->getOption(Option::MAX_CHANGES);
158+
if ($maxChanges === null) {
159+
return null;
160+
}
161+
162+
$maxChanges = (int) $maxChanges;
163+
Assert::positiveInteger($maxChanges);
164+
165+
return $maxChanges;
166+
}
167+
146168
private function shouldShowProgressBar(InputInterface $input, string $outputFormat): bool
147169
{
148170
$noProgressBar = (bool) $input->getOption(Option::NO_PROGRESS_BAR);

‎src/Configuration/Option.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ final class Option
148148

149149
public const string RULES_SUMMARY = 'rules-summary';
150150

151+
public const string MAX_CHANGES = 'max-changes';
152+
151153
public const string CONFIG = 'config';
152154

153155
/**

‎src/Console/ProcessConfigureDecorator.php‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,12 @@ public static function decorate(Command $command): void
102102
InputOption::VALUE_NONE,
103103
'Show summary of rules applied during the run.'
104104
);
105+
106+
$command->addOption(
107+
Option::MAX_CHANGES,
108+
null,
109+
InputOption::VALUE_REQUIRED,
110+
'Stop after this many files are changed, leaving the rest untouched. Forces non-parallel run.'
111+
);
105112
}
106113
}

‎src/ValueObject/Configuration.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,15 @@ public function __construct(
3939
private bool $isComposerBased = false,
4040
private bool $isPhpOnly = false,
4141
private array $filters = [],
42+
private ?int $maxChanges = null,
4243
) {
4344
}
4445

46+
public function getMaxChanges(): ?int
47+
{
48+
return $this->maxChanges;
49+
}
50+
4551
public function isComposerBased(): bool
4652
{
4753
return $this->isComposerBased;

‎tests/Application/ApplicationFileProcessor/ApplicationFileProcessorTest.php‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,24 @@ public function testOnlySuffixRunCachesUnderOwnScopeWithoutPoisoningFullRun(): v
7676
$this->changedFilesDetector->setActiveScope(null, null);
7777
$this->assertTrue($this->changedFilesDetector->hasFileChanged($filePath));
7878
}
79+
80+
public function testMaxChangesStopsAfterLimit(): void
81+
{
82+
self::$rectorConfig = null;
83+
$this->bootFromConfigFiles([__DIR__ . '/config-max-changes.php']);
84+
$applicationFileProcessor = $this->make(ApplicationFileProcessor::class);
85+
86+
$filePaths = [
87+
__DIR__ . '/Source/WithEmptyMethodFirst.php',
88+
__DIR__ . '/Source/WithEmptyMethodSecond.php',
89+
];
90+
91+
$processResult = $applicationFileProcessor->processFiles($filePaths, new Configuration(
92+
isDryRun: true,
93+
maxChanges: 1,
94+
));
95+
96+
// both files would change, but the limit stops the run after the first
97+
$this->assertSame(1, $processResult->getTotalChanged());
98+
}
7999
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\Application\ApplicationFileProcessor\Source;
6+
7+
final class WithEmptyMethodFirst
8+
{
9+
public function run(): void
10+
{
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rector\Tests\Application\ApplicationFileProcessor\Source;
6+
7+
final class WithEmptyMethodSecond
8+
{
9+
public function run(): void
10+
{
11+
}
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\DeadCode\Rector\ClassMethod\RemoveEmptyClassMethodRector;
7+
8+
return static function (RectorConfig $rectorConfig): void {
9+
$rectorConfig->cacheDirectory(sys_get_temp_dir() . '/_rector_max_changes_test');
10+
$rectorConfig->rule(RemoveEmptyClassMethodRector::class);
11+
};

0 commit comments

Comments
 (0)