File tree Expand file tree Collapse file tree
tests/Application/ApplicationFileProcessor Expand file tree Collapse file tree Original file line number Diff line number Diff 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
Original file line number Diff line number Diff line change 1010use Rector \ValueObject \Configuration ;
1111use Symfony \Component \Console \Input \InputInterface ;
1212use 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 );
Original file line number Diff line number Diff 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 /**
Original file line number Diff line number Diff 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}
Original file line number Diff line number Diff 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 ;
Original file line number Diff line number Diff 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}
Original file line number Diff line number Diff line change 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+ }
Original file line number Diff line number Diff line change 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+ }
Original file line number Diff line number Diff line change 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+ };
You can’t perform that action at this time.
0 commit comments