From 0b1a45c91405245a35bcb0c1ae3527bc0f19efaf Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 23 Oct 2026 14:43:22 +0200 Subject: [PATCH 1/5] Replace symfony/console with entropy/entropy console framework --- bin/rector.php | 117 +++++++- build/build-preload.php | 13 +- composer.json | 2 +- e2e/e2eTestRunner.php | 17 +- phpstan.neon | 8 +- src/Application/ApplicationFileProcessor.php | 23 +- src/Application/FileProcessor.php | 6 +- src/Autoloading/AdditionalAutoloader.php | 10 +- .../AutoloadFileParameterResolver.php | 6 +- src/Bootstrap/CliOptionValueResolver.php | 39 +++ src/Bootstrap/RectorConfigsResolver.php | 26 +- .../Output/ConsoleOutputFormatter.php | 34 +-- src/Config/RectorConfig.php | 4 +- src/Configuration/ConfigInitializer.php | 16 +- src/Configuration/ConfigurationFactory.php | 86 +++--- src/Configuration/Option.php | 19 -- src/Console/Command/ComposerBasedCommand.php | 95 ++----- src/Console/Command/CustomRuleCommand.php | 33 ++- src/Console/Command/ListRulesCommand.php | 78 +++-- src/Console/Command/ProcessCommand.php | 188 ++++++------ src/Console/Command/SetupCICommand.php | 33 ++- src/Console/Command/ValidateConfigCommand.php | 59 ++-- src/Console/Command/WorkerCommand.php | 132 ++++++--- src/Console/ConsoleApplication.php | 165 ----------- src/Console/ExitCode.php | 6 +- .../Formatter/ColorConsoleDiffFormatter.php | 5 +- src/Console/Notifier.php | 16 +- src/Console/ProcessConfigureDecorator.php | 118 -------- src/Console/Style/RectorStyle.php | 267 ++++++++++++++---- src/Console/Style/RectorStyleFactory.php | 46 +++ src/Console/Style/SymfonyStyleFactory.php | 59 ---- .../LazyContainerFactory.php | 26 +- .../PHPStanServicesFactory.php | 8 +- .../Application/ParallelFileProcessor.php | 14 +- .../Command/WorkerCommandLineFactory.php | 194 ++++--------- .../CommandFromReflectionFactory.php | 37 --- src/Reporting/DeprecatedRulesReporter.php | 16 +- src/Reporting/MissConfigurationReporter.php | 18 +- src/Util/NodePrinter.php | 8 +- src/functions/node_helper.php | 11 +- tests/Bin/RectorTest.php | 2 +- .../ConfigurationFactoryTest.php | 39 ++- .../Command/ComposerBasedCommandTest.php | 34 ++- .../Command/ValidateConfigCommandTest.php | 31 +- .../Command/WorkerCommandLineFactoryTest.php | 227 +++++---------- .../MissConfigurationReporterTest.php | 21 +- 46 files changed, 1097 insertions(+), 1315 deletions(-) create mode 100644 src/Bootstrap/CliOptionValueResolver.php delete mode 100644 src/Console/ConsoleApplication.php delete mode 100644 src/Console/ProcessConfigureDecorator.php create mode 100644 src/Console/Style/RectorStyleFactory.php delete mode 100644 src/Console/Style/SymfonyStyleFactory.php delete mode 100644 src/Parallel/Reflection/CommandFromReflectionFactory.php diff --git a/bin/rector.php b/bin/rector.php index d0e4b36d955..ea3837663e3 100755 --- a/bin/rector.php +++ b/bin/rector.php @@ -3,18 +3,18 @@ declare(strict_types=1); use Composer\XdebugHandler\XdebugHandler; +use Entropy\Console\ConsoleApplication; use Nette\Utils\Json; +use Rector\Application\VersionResolver; use Rector\Bootstrap\AutoloadFileParameterResolver; +use Rector\Bootstrap\CliOptionValueResolver; use Rector\Bootstrap\RectorConfigsResolver; use Rector\ChangesReporting\Output\JsonOutputFormatter; use Rector\Configuration\Option; -use Rector\Console\Style\SymfonyStyleFactory; +use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyleFactory; use Rector\DependencyInjection\LazyContainerFactory; use Rector\DependencyInjection\RectorContainerFactory; -use Rector\Util\Reflection\PrivatesAccessor; -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\ArgvInput; // @ intentionally: continue anyway @ini_set('memory_limit', '-1'); @@ -125,7 +125,7 @@ public function loadIfExistsAndNotLoadedYet(string $filePath): void $autoloadIncluder->autoloadFromCommandLine(); // restart without Xdebug before the container is built, so the restarted process does not repeat that work -if (! new ArgvInput()->hasParameterOption('--xdebug')) { +if (! in_array('--xdebug', $_SERVER['argv'] ?? [], true)) { $xdebugHandler = new XdebugHandler('rector'); $xdebugHandler->setPersistent(); $xdebugHandler->check(); @@ -142,8 +142,7 @@ public function loadIfExistsAndNotLoadedYet(string $filePath): void $container = $rectorContainerFactory->createFromBootstrapConfigs($bootstrapConfigs); } catch (Throwable $throwable) { // for json output - $argvInput = new ArgvInput(); - $outputFormat = $argvInput->getParameterOption('--' . Option::OUTPUT_FORMAT); + $outputFormat = CliOptionValueResolver::resolve($_SERVER['argv'] ?? [], ['--' . Option::OUTPUT_FORMAT]); // report fatal error in json format if ($outputFormat === JsonOutputFormatter::NAME) { @@ -157,16 +156,104 @@ public function loadIfExistsAndNotLoadedYet(string $filePath): void ]); } else { // report fatal errors in console format - $symfonyStyleFactory = new SymfonyStyleFactory(new PrivatesAccessor()); - $symfonyStyle = $symfonyStyleFactory->create(); + $rectorStyleFactory = new RectorStyleFactory(); + $rectorStyle = $rectorStyleFactory->create(); do { - $symfonyStyle->error(str_replace("\r\n", "\n", $throwable->getMessage())); + $rectorStyle->error(str_replace("\r\n", "\n", $throwable->getMessage())); } while ($throwable = $throwable->getPrevious()); } - exit(Command::FAILURE); + exit(ExitCode::FAILURE); } -/** @var Application $application */ -$application = $container->get(Application::class); -exit($application->run()); +// print version and exit +$rawArgv = $_SERVER['argv'] ?? []; +if (in_array('--version', $rawArgv, true) || in_array('-V', $rawArgv, true)) { + echo sprintf('Rector %s', VersionResolver::PACKAGE_VERSION) . PHP_EOL; + exit(ExitCode::SUCCESS); +} + +$application = $container->make(ConsoleApplication::class); +exit($application->run(rector_normalize_argv($rawArgv))); + +/** + * Strip global decoration flags the former symfony/console handled implicitly, and normalize the + * short "-c"/"-n"/"-a" options to their long forms, so the Entropy input parser does not treat them + * as unknown options. Inject the default "process" command when the first argument is an option or a + * path, and expand the "p" alias. + * + * @param array $argv + * @return array + */ +function rector_normalize_argv(array $argv): array +{ + $decorationFlags = ['--ansi', '--no-ansi', '--quiet', '-q', '--no-interaction', '-v', '-vv', '-vvv', '--xdebug']; + + if (in_array('--no-ansi', $argv, true)) { + putenv('NO_COLOR=1'); + } + + $shortToLong = [ + '-c' => '--' . Option::CONFIG, + '-n' => '--' . Option::DRY_RUN, + '-a' => '--' . Option::AUTOLOAD_FILE, + ]; + + $normalized = []; + foreach ($argv as $arg) { + if (in_array($arg, $decorationFlags, true)) { + continue; + } + + if (isset($shortToLong[$arg])) { + $normalized[] = $shortToLong[$arg]; + continue; + } + + $matched = false; + foreach ($shortToLong as $short => $long) { + if (str_starts_with($arg, $short . '=')) { + $normalized[] = $long . '=' . substr($arg, strlen($short) + 1); + $matched = true; + break; + } + } + + if ($matched) { + continue; + } + + $normalized[] = $arg; + } + + // expand the "p" alias to the "process" command name + if (isset($normalized[1]) && $normalized[1] === 'p') { + $normalized[1] = 'process'; + } + + // first real argument is an option or a path - inject the default "process" command, so the parser + // does not mistake a leading "--option" or a path for an unknown command + $helpFlags = ['-h', '--help']; + if ( + isset($normalized[1]) + && $normalized[1] !== 'process' + && ! in_array($normalized[1], $helpFlags, true) + && (str_starts_with($normalized[1], '-') || ! rector_is_known_command($normalized[1])) + ) { + array_splice($normalized, 1, 0, 'process'); + } + + return $normalized; +} + +/** + * The commands a leading token may name; anything else is a path for the default "process" command. + */ +function rector_is_known_command(string $token): bool +{ + return in_array( + $token, + ['process', 'worker', 'list-rules', 'validate-config', 'custom-rule', 'setup-ci', 'composer-based'], + true + ); +} diff --git a/build/build-preload.php b/build/build-preload.php index a2216797532..6704edb2d53 100755 --- a/build/build-preload.php +++ b/build/build-preload.php @@ -5,9 +5,8 @@ declare(strict_types=1); use Nette\Utils\Strings; -use Rector\Console\Style\SymfonyStyleFactory; -use Rector\Util\Reflection\PrivatesAccessor; -use Symfony\Component\Console\Command\Command; +use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyleFactory; use Symfony\Component\Finder\Finder; $possiblePaths = [ @@ -28,13 +27,13 @@ $buildDirectory = $argv[1]; -$symfonyStyleFactory = new SymfonyStyleFactory(new PrivatesAccessor()); -$symfonyStyle = $symfonyStyleFactory->create(); +$rectorStyleFactory = new RectorStyleFactory(); +$rectorStyle = $rectorStyleFactory->create(); if (! is_string($buildDirectory)) { $errorMessage = 'Provide build directory path as an argument, e.g. "php build-preload.php rector-build-directory"'; - $symfonyStyle->error($errorMessage); - exit(Command::FAILURE); + $rectorStyle->error($errorMessage); + exit(ExitCode::FAILURE); } $preloadBuilder = new PreloadBuilder(); diff --git a/composer.json b/composer.json index e8d1fb52fd5..4d211db4299 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,6 @@ "rector/rector-phpunit": "dev-main", "rector/rector-symfony": "dev-main", "sebastian/diff": "^9.0", - "symfony/console": "^6.4.24", "symfony/filesystem": "^8.1", "symfony/finder": "^8.1", "webmozart/assert": "^2.4" @@ -60,6 +59,7 @@ }, "replace": { "rector/rector": "self.version", + "symfony/console": "*", "symplify/rule-doc-generator-contracts": "*" }, "autoload": { diff --git a/e2e/e2eTestRunner.php b/e2e/e2eTestRunner.php index 7734416a891..7029a811e50 100644 --- a/e2e/e2eTestRunner.php +++ b/e2e/e2eTestRunner.php @@ -6,10 +6,9 @@ use Rector\Console\Formatter\ColorConsoleDiffFormatter; use Rector\Console\Formatter\ConsoleDiffer; -use Rector\Console\Style\SymfonyStyleFactory; +use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyleFactory; use Rector\Differ\DefaultDiffer; -use Rector\Util\Reflection\PrivatesAccessor; -use Symfony\Component\Console\Command\Command; $projectRoot = __DIR__ .'/..'; $rectorBin = $projectRoot . '/bin/rector'; @@ -59,20 +58,20 @@ exit($exitCode); } -$symfonyStyleFactory = new SymfonyStyleFactory(new PrivatesAccessor()); -$symfonyStyle = $symfonyStyleFactory->create(); +$rectorStyleFactory = new RectorStyleFactory(); +$rectorStyle = $rectorStyleFactory->create(); $matchedExpectedOutput = false; $expectedOutput = trim((string) file_get_contents($expectedDiff)); if ($output === $expectedOutput) { - $symfonyStyle->success('End-to-end test successfully completed'); - exit(Command::SUCCESS); + $rectorStyle->success('End-to-end test successfully completed'); + exit(ExitCode::SUCCESS); } // print color diff, to make easy find the differences $defaultDiffer = new DefaultDiffer(); $colorConsoleDiffFormatter = new ColorConsoleDiffFormatter(); $diff = $colorConsoleDiffFormatter->format($defaultDiffer->diff($output, $expectedOutput)); -$symfonyStyle->writeln($diff); +$rectorStyle->writeln($diff); -exit(Command::FAILURE); +exit(ExitCode::FAILURE); diff --git a/phpstan.neon b/phpstan.neon index ef2ef1e13e0..c88b1b9bc8c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -77,6 +77,11 @@ parameters: offloadCollectorData: false ignoreErrors: + # services are instantiated by the container via reflection, which shipmonk cannot see + - + identifier: shipmonk.deadMethod + message: '#::__construct$#' + # called magically by parent - message: '#^Unused Rector\\PhpParser\\Printer\\BetterStandardPrinter\:\:(pInterpolatedStringPart|pStmt_FileNode)$#' @@ -100,9 +105,6 @@ parameters: identifier: argument.type message: '#expects array, array given#' - # unable for the downgraded rector to find the class, but it is there - - identifier: symplify.commandHasAsCommandAttribute - # false positive, can accept non-class string - identifier: argument.type diff --git a/src/Application/ApplicationFileProcessor.php b/src/Application/ApplicationFileProcessor.php index b14914ff269..8231bd42b24 100644 --- a/src/Application/ApplicationFileProcessor.php +++ b/src/Application/ApplicationFileProcessor.php @@ -10,6 +10,7 @@ use Rector\Caching\Detector\ChangedFilesDetector; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; +use Rector\Console\Style\RectorStyle; use Rector\FileSystem\FilesFinder; use Rector\Parallel\Application\ParallelFileProcessor; use Rector\Parallel\CpuCoreCountProvider; @@ -26,8 +27,6 @@ use Rector\ValueObject\FileProcessResult; use Rector\ValueObject\ProcessResult; use Rector\ValueObject\Reporting\FileDiff; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; use Throwable; final class ApplicationFileProcessor @@ -40,7 +39,7 @@ final class ApplicationFileProcessor private array $systemErrors = []; public function __construct( - private readonly SymfonyStyle $symfonyStyle, + private readonly RectorStyle $rectorStyle, private readonly FilesFinder $filesFinder, private readonly ParallelFileProcessor $parallelFileProcessor, private readonly ScheduleFactory $scheduleFactory, @@ -54,7 +53,7 @@ public function __construct( ) { } - public function run(Configuration $configuration, InputInterface $input): ProcessResult + public function run(Configuration $configuration): ProcessResult { // scope the cache to this run's --only / --only-suffix selection before any cache read/write $this->changedFilesDetector->setActiveScope($configuration->getOnlyRules(), $configuration->getOnlySuffix(), $configuration->getFilters()); @@ -76,11 +75,11 @@ public function run(Configuration $configuration, InputInterface $input): Proces */ if ($configuration->shouldShowProgressBar()) { $fileCount = count($filePaths); - $this->symfonyStyle->progressStart($fileCount); - $this->symfonyStyle->progressAdvance(0); + $this->rectorStyle->progressStart($fileCount); + $this->rectorStyle->progressAdvance(0); $postFileCallback = function (int $stepCount): void { - $this->symfonyStyle->progressAdvance($stepCount); + $this->rectorStyle->progressAdvance($stepCount); // running in parallel here → nothing else to do }; } else { @@ -90,14 +89,14 @@ public function run(Configuration $configuration, InputInterface $input): Proces if ($configuration->isDebug()) { $preFileCallback = function (string $filePath): void { - $this->symfonyStyle->writeln('[file] ' . $filePath); + $this->rectorStyle->writeln('[file] ' . $filePath); }; } else { $preFileCallback = null; } if ($configuration->isParallel()) { - $processResult = $this->runParallel($filePaths, $input, $postFileCallback); + $processResult = $this->runParallel($filePaths, $configuration, $postFileCallback); } else { $processResult = $this->processFiles($filePaths, $configuration, $preFileCallback, $postFileCallback); } @@ -206,7 +205,7 @@ private function resolveSystemError(Throwable $throwable, string $filePath): Sys { $errorMessage = sprintf('System error: "%s"', $throwable->getMessage()) . PHP_EOL; - if ($this->symfonyStyle->isDebug()) { + if ($this->rectorStyle->isDebug()) { $errorMessage .= PHP_EOL . 'Stack trace:' . PHP_EOL . $throwable->getTraceAsString(); } else { $errorMessage .= 'Run Rector with "--debug" option and post the report here: https://github.com/rectorphp/rector/issues/new'; @@ -254,7 +253,7 @@ private function restoreErrorHandler(): void */ private function runParallel( array $filePaths, - InputInterface $input, + Configuration $configuration, callable $postFileCallback, ): ProcessResult { $mainScript = $this->resolveCalledRectorBinary(); @@ -271,7 +270,7 @@ private function runParallel( $schedule = $this->scheduleFactory->create($cpuCores, $jobSize, $maxNumberOfProcesses, $filePaths); // mimics see https://github.com/phpstan/phpstan-src/commit/9124c66dcc55a222e21b1717ba5f60771f7dda92#diff-387b8f04e0db7a06678eb52ce0c0d0aff73e0d7d8fc5df834d0a5fbec198e5daR139 - return $this->parallelFileProcessor->process($schedule, $mainScript, $postFileCallback, $input); + return $this->parallelFileProcessor->process($schedule, $mainScript, $postFileCallback, $configuration); } /** diff --git a/src/Application/FileProcessor.php b/src/Application/FileProcessor.php index 4385f4aceec..f09492bd45e 100644 --- a/src/Application/FileProcessor.php +++ b/src/Application/FileProcessor.php @@ -13,6 +13,7 @@ use Rector\ChangesReporting\ValueObjectFactory\ErrorFactory; use Rector\ChangesReporting\ValueObjectFactory\FileDiffFactory; use Rector\CodingStyle\ClassNameImport\UsedImportsResolver; +use Rector\Console\Style\RectorStyle; use Rector\Exception\ShouldNotHappenException; use Rector\FileSystem\FilePathHelper; use Rector\NodeTypeResolver\NodeScopeAndMetadataDecorator; @@ -27,7 +28,6 @@ use Rector\ValueObject\Configuration; use Rector\ValueObject\Error\SystemError; use Rector\ValueObject\FileProcessResult; -use Symfony\Component\Console\Style\SymfonyStyle; use Throwable; final readonly class FileProcessor @@ -35,7 +35,7 @@ public function __construct( private BetterStandardPrinter $betterStandardPrinter, private RectorNodeTraverser $rectorNodeTraverser, - private SymfonyStyle $symfonyStyle, + private RectorStyle $rectorStyle, private FileDiffFactory $fileDiffFactory, private ChangedFilesDetector $changedFilesDetector, private ErrorFactory $errorFactory, @@ -163,7 +163,7 @@ private function parseFileAndDecorateNodes(File $file): ?SystemError return $this->errorFactory->createAutoloadError($analysedCodeException, $file->getFilePath()); } catch (Throwable $throwable) { - if ($this->symfonyStyle->isVerbose() || StaticPHPUnitEnvironment::isPHPUnitRun()) { + if ($this->rectorStyle->isVerbose() || StaticPHPUnitEnvironment::isPHPUnitRun()) { throw $throwable; } diff --git a/src/Autoloading/AdditionalAutoloader.php b/src/Autoloading/AdditionalAutoloader.php index 699b9073497..eeceff8aa17 100644 --- a/src/Autoloading/AdditionalAutoloader.php +++ b/src/Autoloading/AdditionalAutoloader.php @@ -7,7 +7,6 @@ use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\StaticReflection\DynamicSourceLocatorDecorator; -use Symfony\Component\Console\Input\InputInterface; use Webmozart\Assert\Assert; /** @@ -20,15 +19,14 @@ public function __construct( ) { } - public function autoloadInput(InputInterface $input): void + public function autoloadFile(): void { - if (! $input->hasOption(Option::AUTOLOAD_FILE)) { + if (! SimpleParameterProvider::hasParameter(Option::AUTOLOAD_FILE)) { return; } - /** @var string|null $autoloadFile */ - $autoloadFile = $input->getOption(Option::AUTOLOAD_FILE); - if ($autoloadFile === null) { + $autoloadFile = SimpleParameterProvider::provideStringParameter(Option::AUTOLOAD_FILE); + if ($autoloadFile === '') { return; } diff --git a/src/Bootstrap/AutoloadFileParameterResolver.php b/src/Bootstrap/AutoloadFileParameterResolver.php index b760f4a9c44..febcf0c3c4c 100644 --- a/src/Bootstrap/AutoloadFileParameterResolver.php +++ b/src/Bootstrap/AutoloadFileParameterResolver.php @@ -6,7 +6,6 @@ use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; -use Symfony\Component\Console\Input\ArgvInput; /** * A different extra autoload changes what PHPStan can resolve, so cached results @@ -27,9 +26,8 @@ public static function resolveFromArgv(array $argv): void // handles "--autoload-file path", "--autoload-file=path" and "-a path"; // parallel workers receive the space-separated form, so all spellings must // normalize to the same value or main process and workers would disagree - $autoloadFile = new ArgvInput($argv) - ->getParameterOption(['--autoload-file', '-a'], null); - if (! is_string($autoloadFile) || $autoloadFile === '') { + $autoloadFile = CliOptionValueResolver::resolve($argv, ['--autoload-file', '-a']); + if ($autoloadFile === null || $autoloadFile === '') { return; } diff --git a/src/Bootstrap/CliOptionValueResolver.php b/src/Bootstrap/CliOptionValueResolver.php new file mode 100644 index 00000000000..b17a3f3c81c --- /dev/null +++ b/src/Bootstrap/CliOptionValueResolver.php @@ -0,0 +1,39 @@ + $argv + * @param string[] $optionNames fully dashed, e.g. ['--config', '-c'] + */ + public static function resolve(array $argv, array $optionNames): ?string + { + foreach ($argv as $index => $arg) { + if (! is_string($arg)) { + continue; + } + + foreach ($optionNames as $optionName) { + if ($arg === $optionName) { + $nextArg = $argv[$index + 1] ?? null; + return is_string($nextArg) ? $nextArg : null; + } + + if (str_starts_with($arg, $optionName . '=')) { + return substr($arg, strlen($optionName) + 1); + } + } + } + + return null; + } +} diff --git a/src/Bootstrap/RectorConfigsResolver.php b/src/Bootstrap/RectorConfigsResolver.php index 052f75a58f9..7216a6183b3 100644 --- a/src/Bootstrap/RectorConfigsResolver.php +++ b/src/Bootstrap/RectorConfigsResolver.php @@ -5,7 +5,6 @@ namespace Rector\Bootstrap; use Rector\ValueObject\Bootstrap\BootstrapConfigs; -use Symfony\Component\Console\Input\ArgvInput; use Webmozart\Assert\Assert; final class RectorConfigsResolver @@ -16,15 +15,14 @@ final class RectorConfigsResolver public function provide(): BootstrapConfigs { - $argvInput = new ArgvInput(); - $mainConfigFile = $this->resolveFromInputWithFallback($argvInput); + $mainConfigFile = $this->resolveFromInputWithFallback(); return new BootstrapConfigs($mainConfigFile, []); } - private function resolveFromInput(ArgvInput $argvInput): ?string + private function resolveFromInput(): ?string { - $configFile = $this->getOptionValue($argvInput, ['--config', '-c']); + $configFile = CliOptionValueResolver::resolve($_SERVER['argv'] ?? [], ['--config', '-c']); if ($configFile === null) { return null; } @@ -34,9 +32,9 @@ private function resolveFromInput(ArgvInput $argvInput): ?string return realpath($configFile); } - private function resolveFromInputWithFallback(ArgvInput $argvInput): ?string + private function resolveFromInputWithFallback(): ?string { - $configFile = $this->resolveFromInput($argvInput); + $configFile = $this->resolveFromInput(); if ($configFile !== null) { return $configFile; } @@ -59,18 +57,4 @@ private function createFallbackFileInfoIfFound(string $fallbackFile): ?string return $rootFallbackFile; } - - /** - * @param string[] $optionNames - */ - private function getOptionValue(ArgvInput $argvInput, array $optionNames): ?string - { - foreach ($optionNames as $optionName) { - if ($argvInput->hasParameterOption($optionName, true)) { - return $argvInput->getParameterOption($optionName, null, true); - } - } - - return null; - } } diff --git a/src/ChangesReporting/Output/ConsoleOutputFormatter.php b/src/ChangesReporting/Output/ConsoleOutputFormatter.php index afd48e240d0..16dc21996ad 100644 --- a/src/ChangesReporting/Output/ConsoleOutputFormatter.php +++ b/src/ChangesReporting/Output/ConsoleOutputFormatter.php @@ -8,12 +8,11 @@ use Rector\ChangesReporting\Contract\Output\OutputFormatterInterface; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; +use Rector\Console\Style\RectorStyle; use Rector\ValueObject\Configuration; use Rector\ValueObject\Error\SystemError; use Rector\ValueObject\ProcessResult; use Rector\ValueObject\Reporting\FileDiff; -use Symfony\Component\Console\Formatter\OutputFormatter; -use Symfony\Component\Console\Style\SymfonyStyle; final readonly class ConsoleOutputFormatter implements OutputFormatterInterface { @@ -25,7 +24,7 @@ private const string ON_LINE_REGEX = '# on line #'; public function __construct( - private SymfonyStyle $symfonyStyle, + private RectorStyle $rectorStyle, ) { } @@ -43,7 +42,7 @@ public function report(ProcessResult $processResult, Configuration $configuratio // to keep space between progress bar and success message if ($configuration->shouldShowProgressBar() && $processResult->getFileDiffs() === []) { - $this->symfonyStyle->newLine(); + $this->rectorStyle->newLine(); } if ($configuration->shouldShowRulesSummary()) { @@ -51,7 +50,7 @@ public function report(ProcessResult $processResult, Configuration $configuratio } $message = $this->createSuccessMessage($processResult, $configuration); - $this->symfonyStyle->success($message); + $this->rectorStyle->success($message); } public function getName(): string @@ -72,7 +71,7 @@ private function reportFileDiffs(array $fileDiffs, bool $absoluteFilePath): void ksort($fileDiffs); $message = sprintf('%d file%s with changes', count($fileDiffs), count($fileDiffs) === 1 ? '' : 's'); - $this->symfonyStyle->title($message); + $this->rectorStyle->title($message); $i = 0; foreach ($fileDiffs as $fileDiff) { @@ -95,14 +94,14 @@ private function reportFileDiffs(array $fileDiffs, bool $absoluteFilePath): void $message = sprintf('%d) %s', ++$i, $filePathWithUrl); - $this->symfonyStyle->writeln($message); - $this->symfonyStyle->newLine(); - $this->symfonyStyle->writeln($fileDiff->getDiffConsoleFormatted()); + $this->rectorStyle->writeln($message); + $this->rectorStyle->newLine(); + $this->rectorStyle->writeln($fileDiff->getDiffConsoleFormatted()); if ($fileDiff->getRectorChanges() !== []) { - $this->symfonyStyle->writeln('Applied rules:'); - $this->symfonyStyle->listing($fileDiff->getRectorShortClasses()); - $this->symfonyStyle->newLine(); + $this->rectorStyle->writeln('Applied rules:'); + $this->rectorStyle->listing($fileDiff->getRectorShortClasses()); + $this->rectorStyle->newLine(); } } } @@ -131,7 +130,7 @@ private function reportErrors(array $errors, bool $absoluteFilePath): void $message .= ' On line: ' . $error->getLine(); } - $this->symfonyStyle->error($message); + $this->rectorStyle->error($message); } } @@ -144,11 +143,11 @@ private function reportRulesSummary(ProcessResult $processResult, Configuration $verb = $configuration->isDryRun() ? 'would have been applied' : 'was applied'; - $this->symfonyStyle->section('Rules Summary'); + $this->rectorStyle->section('Rules Summary'); foreach ($ruleApplicationCounts as $ruleClass => $count) { $ruleShortClass = (string) Strings::after($ruleClass, '\\', -1); - $this->symfonyStyle->writeln(sprintf( + $this->rectorStyle->writeln(sprintf( ' * %s %s %d time%s', $ruleShortClass, $verb, @@ -157,7 +156,7 @@ private function reportRulesSummary(ProcessResult $processResult, Configuration )); } - $this->symfonyStyle->newLine(); + $this->rectorStyle->newLine(); } private function normalizePathsToRelativeWithLine(string $errorMessage): string @@ -196,7 +195,8 @@ private function addEditorUrl( [(string) $absoluteFilePath, (string) $relativeFilePath, (string) $lineNumber], $editorUrl, ); - $filePath = '' . $filePath . ''; + // OSC-8 hyperlink keeps the path clickable without symfony/console's tag + $filePath = sprintf("\e]8;;%s\e\\%s\e]8;;\e\\", $editorUrl, $filePath); } return $filePath; diff --git a/src/Config/RectorConfig.php b/src/Config/RectorConfig.php index a43d1b245a8..0e915cac955 100644 --- a/src/Config/RectorConfig.php +++ b/src/Config/RectorConfig.php @@ -6,6 +6,7 @@ use Composer\Semver\Semver; use Deprecated; +use Entropy\Console\Contract\CommandInterface; use Entropy\Container\Container; use Rector\Caching\Contract\ValueObject\Storage\CacheStorageInterface; use Rector\Composer\InstalledPackageResolver; @@ -23,7 +24,6 @@ use Rector\ValueObject\PhpVersion; use Rector\ValueObject\PolyfillPackage; use Rector\VersionBonding\ValueObject\ComposerBoundRuleConfiguration; -use Symfony\Component\Console\Command\Command; use Webmozart\Assert\Assert; /** @@ -319,7 +319,7 @@ public function rule(string $rectorClass): void } /** - * @param class-string $commandClass + * @param class-string $commandClass */ public function command(string $commandClass): void { diff --git a/src/Configuration/ConfigInitializer.php b/src/Configuration/ConfigInitializer.php index 8cb5e634b5e..a52283a1ca7 100644 --- a/src/Configuration/ConfigInitializer.php +++ b/src/Configuration/ConfigInitializer.php @@ -7,10 +7,10 @@ use Nette\Utils\FileSystem; use Rector\Agentic\TerminalDetector; use Rector\Bootstrap\RectorConfigsResolver; +use Rector\Console\Style\RectorStyle; use Rector\Contract\Rector\RectorInterface; use Rector\FileSystem\InitFilePathsResolver; use Rector\PostRector\Contract\Rector\PostRectorInterface; -use Symfony\Component\Console\Style\SymfonyStyle; final readonly class ConfigInitializer { @@ -20,7 +20,7 @@ public function __construct( private array $rectors, private InitFilePathsResolver $initFilePathsResolver, - private SymfonyStyle $symfonyStyle, + private RectorStyle $rectorStyle, private RectorConfigsResolver $rectorConfigsResolver, ) { } @@ -31,14 +31,14 @@ public function createConfig(string $projectDirectory): void $distRectorConfigPath = $projectDirectory . '/' . RectorConfigsResolver::DEFAULT_DIST_CONFIG_FILE; if (file_exists($commonRectorConfigPath)) { - $this->symfonyStyle->warning( + $this->rectorStyle->warning( 'Register rules or sets in your "' . RectorConfigsResolver::DEFAULT_CONFIG_FILE . '" config' ); return; } if (file_exists($distRectorConfigPath)) { - $this->symfonyStyle->warning( + $this->rectorStyle->warning( 'Register rules or sets in your "' . RectorConfigsResolver::DEFAULT_DIST_CONFIG_FILE . '" config' ); return; @@ -47,7 +47,7 @@ public function createConfig(string $projectDirectory): void $mainConfigFile = $this->rectorConfigsResolver->provide() ->getMainConfigFile(); if ($mainConfigFile !== null && file_exists($mainConfigFile)) { - $this->symfonyStyle->warning( + $this->rectorStyle->warning( 'Register rules or sets in your "' . basename($mainConfigFile) . '" config' ); return; @@ -56,14 +56,14 @@ public function createConfig(string $projectDirectory): void // non-interactive terminal, e.g. piped output, CI or an agent: never prompt or silently write a // config, just say what to do - Symfony still treats a closed STDIN as interactive here if (! TerminalDetector::isInputTty()) { - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'No "%s" config found. Create one, or pass "--config ".', RectorConfigsResolver::DEFAULT_CONFIG_FILE )); return; } - $response = $this->symfonyStyle->ask( + $response = $this->rectorStyle->ask( 'No "' . RectorConfigsResolver::DEFAULT_CONFIG_FILE . '" config found. Should we generate it for you?', 'yes' ); @@ -77,7 +77,7 @@ public function createConfig(string $projectDirectory): void $configContents = $this->replacePathsContents($configContents, $projectDirectory); FileSystem::write($commonRectorConfigPath, $configContents, null); - $this->symfonyStyle->success('The config is added now. Re-run command to make Rector do the work!'); + $this->rectorStyle->success('The config is added now. Re-run command to make Rector do the work!'); } public function areSomeRectorsLoaded(): bool diff --git a/src/Configuration/ConfigurationFactory.php b/src/Configuration/ConfigurationFactory.php index b6ccf99055c..3ed5814e470 100644 --- a/src/Configuration/ConfigurationFactory.php +++ b/src/Configuration/ConfigurationFactory.php @@ -7,10 +7,9 @@ use Rector\Agentic\TerminalDetector; use Rector\ChangesReporting\Output\ConsoleOutputFormatter; use Rector\Configuration\Parameter\SimpleParameterProvider; +use Rector\Console\Style\RectorStyle; use Rector\FileSystem\FilePathFilter; use Rector\ValueObject\Configuration; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; use Webmozart\Assert\Assert; /** @@ -19,7 +18,7 @@ final readonly class ConfigurationFactory { public function __construct( - private SymfonyStyle $symfonyStyle, + private RectorStyle $rectorStyle, private OnlyRuleResolver $onlyRuleResolver, private FilePathFilter $filePathFilter, ) { @@ -52,38 +51,50 @@ public function createForTests(array $paths): Configuration /** * Needs to run in the start of the life cycle, since the rest of workflow uses it. + * + * @param string[] $commandLinePaths + * @param string[] $onlyRuleInputs */ - public function createFromInput(InputInterface $input): Configuration - { - $isDryRun = (bool) $input->getOption(Option::DRY_RUN); - $shouldClearCache = (bool) $input->getOption(Option::CLEAR_CACHE); - - $outputFormat = (string) $input->getOption(Option::OUTPUT_FORMAT); - $showProgressBar = $this->shouldShowProgressBar($input, $outputFormat); - - $showDiffs = $this->shouldShowDiffs($input); - - $paths = $this->resolvePaths($input); + public function create( + array $commandLinePaths, + bool $isDryRun, + bool $shouldClearCache, + bool $noProgressBar, + bool $noDiffs, + string $outputFormat, + array $onlyRuleInputs, + bool $isComposerBased, + bool $isPhpOnly, + ?string $onlySuffix, + ?string $rawFilter, + bool $isDebug, + ?string $memoryLimit, + string $parallelPort, + string $parallelIdentifier, + ?int $maxChanges, + bool $showRulesSummary, + ): Configuration { + $showProgressBar = $this->shouldShowProgressBar($noProgressBar, $outputFormat); + + $showDiffs = $this->shouldShowDiffs($noDiffs); + + $paths = $this->resolvePaths($commandLinePaths); $fileExtensions = SimpleParameterProvider::provideArrayParameter(Option::FILE_EXTENSIONS); // filter rule and path - /** @var string[] $onlyRuleInputs */ - $onlyRuleInputs = (array) $input->getOption(Option::ONLY); $onlyRules = array_map( $this->onlyRuleResolver->resolve(...), $onlyRuleInputs ); - $onlySuffix = $input->getOption(Option::ONLY_SUFFIX); if ($onlySuffix !== null) { - $this->symfonyStyle->warning( + $this->rectorStyle->warning( 'The "--only-suffix" option is deprecated and will be removed. Use "--filter" instead, e.g. --filter="*Controller.php"' ); } - $rawFilter = $input->getOption(Option::FILTER); - $filters = $rawFilter !== null ? $this->filePathFilter->parsePatterns((string) $rawFilter) : []; + $filters = $rawFilter !== null ? $this->filePathFilter->parsePatterns($rawFilter) : []; // "--only"/"--only-suffix"/"--filter" narrow the run, so skips outside the scope look falsely unused; // mark the run as narrowed to disable unused skip reporting and avoid false positives @@ -92,39 +103,30 @@ public function createFromInput(InputInterface $input): Configuration } $isParallel = SimpleParameterProvider::provideBoolParameter(Option::PARALLEL); - $parallelPort = (string) $input->getOption(Option::PARALLEL_PORT); - $parallelIdentifier = (string) $input->getOption(Option::PARALLEL_IDENTIFIER); - $isDebug = (bool) $input->getOption(Option::DEBUG); // using debug disables parallel, so emitting exception is straightforward and easier to debug if ($isDebug) { $isParallel = false; } - $maxChanges = $this->resolveMaxChanges($input); + $maxChanges = $this->resolveMaxChanges($maxChanges); // a global change counter cannot be shared across parallel workers, so enforce the limit in a single process if ($maxChanges !== null) { $isParallel = false; } - $memoryLimit = $this->resolveMemoryLimit($input); + $memoryLimit = $this->resolveMemoryLimit($memoryLimit); $isReportingWithRealPath = SimpleParameterProvider::provideBoolParameter(Option::ABSOLUTE_FILE_PATH); $levelOverflows = SimpleParameterProvider::provideArrayParameter(Option::LEVEL_OVERFLOWS); - $showRulesSummary = (bool) $input->getOption(Option::RULES_SUMMARY); - - $isComposerBased = (bool) $input->getOption(Option::COMPOSER_BASED); - // "--composer-based" narrows the run the same way "--only" does if ($isComposerBased) { SimpleParameterProvider::setParameter(Option::IS_RUN_NARROWED, true); } - $isPhpOnly = (bool) $input->getOption(Option::PHP); - // "--php" narrows the run the same way "--only" does if ($isPhpOnly) { SimpleParameterProvider::setParameter(Option::IS_RUN_NARROWED, true); @@ -155,22 +157,19 @@ public function createFromInput(InputInterface $input): Configuration ); } - private function resolveMaxChanges(InputInterface $input): ?int + private function resolveMaxChanges(?int $maxChanges): ?int { - $maxChanges = $input->getOption(Option::MAX_CHANGES); if ($maxChanges === null) { return null; } - $maxChanges = (int) $maxChanges; Assert::positiveInteger($maxChanges); return $maxChanges; } - private function shouldShowProgressBar(InputInterface $input, string $outputFormat): bool + private function shouldShowProgressBar(bool $noProgressBar, string $outputFormat): bool { - $noProgressBar = (bool) $input->getOption(Option::NO_PROGRESS_BAR); if ($noProgressBar) { return false; } @@ -180,16 +179,15 @@ private function shouldShowProgressBar(InputInterface $input, string $outputForm return false; } - if ($this->symfonyStyle->isVerbose()) { + if ($this->rectorStyle->isVerbose()) { return false; } return $outputFormat === ConsoleOutputFormatter::NAME; } - private function shouldShowDiffs(InputInterface $input): bool + private function shouldShowDiffs(bool $noDiffs): bool { - $noDiffs = (bool) $input->getOption(Option::NO_DIFFS); if ($noDiffs) { return false; } @@ -199,12 +197,11 @@ private function shouldShowDiffs(InputInterface $input): bool } /** + * @param string[] $commandLinePaths * @return string[]|mixed[] */ - private function resolvePaths(InputInterface $input): array + private function resolvePaths(array $commandLinePaths): array { - $commandLinePaths = (array) $input->getArgument(Option::SOURCE); - // give priority to command line if ($commandLinePaths !== []) { // mark the run as narrowed, so unused skip reporting can be disabled to avoid false positives @@ -242,11 +239,10 @@ private function setFilesWithoutExtensionParameter(array $paths): void } } - private function resolveMemoryLimit(InputInterface $input): string|null + private function resolveMemoryLimit(?string $memoryLimit): string|null { - $memoryLimit = $input->getOption(Option::MEMORY_LIMIT); if ($memoryLimit !== null) { - return (string) $memoryLimit; + return $memoryLimit; } if (! SimpleParameterProvider::hasParameter(Option::MEMORY_LIMIT)) { diff --git a/src/Configuration/Option.php b/src/Configuration/Option.php index 8abd1bef860..2286fb6e00a 100644 --- a/src/Configuration/Option.php +++ b/src/Configuration/Option.php @@ -18,12 +18,8 @@ final class Option public const string DRY_RUN = 'dry-run'; - public const string DRY_RUN_SHORT = 'n'; - public const string OUTPUT_FORMAT = 'output-format'; - public const string NO_PROGRESS_BAR = 'no-progress-bar'; - /** * @internal Use @see \Rector\Config\RectorConfig::phpVersion() instead * @var string @@ -142,14 +138,6 @@ final class Option */ public const string CACHE_DIR = 'cache_dir'; - public const string DEBUG = 'debug'; - - public const string XDEBUG = 'xdebug'; - - public const string RULES_SUMMARY = 'rules-summary'; - - public const string MAX_CHANGES = 'max-changes'; - public const string CONFIG = 'config'; /** @@ -160,8 +148,6 @@ final class Option public const string NO_DIFFS = 'no-diffs'; - public const string AUTOLOAD_FILE_SHORT = 'a'; - public const string PARALLEL_IDENTIFIER = 'identifier'; public const string PARALLEL_PORT = 'port'; @@ -304,11 +290,6 @@ final class Option */ public const string ONLY_SUFFIX = 'only-suffix'; - /** - * @internal To keep only files matching all given patterns - */ - public const string FILTER = 'filter'; - /** * @internal To report overflow levels in ->with*Level() methods */ diff --git a/src/Console/Command/ComposerBasedCommand.php b/src/Console/Command/ComposerBasedCommand.php index 60be06c7729..14d7b02ff34 100644 --- a/src/Console/Command/ComposerBasedCommand.php +++ b/src/Console/Command/ComposerBasedCommand.php @@ -5,89 +5,80 @@ namespace Rector\Console\Command; use Composer\Semver\Semver; +use Entropy\Console\Contract\CommandInterface; use Nette\Utils\Strings; use Rector\Composer\InstalledPackageResolver; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; +use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyle; use Rector\Contract\Rector\RectorInterface; use Rector\VersionBonding\Contract\ComposerPackageConstraintInterface; use Rector\VersionBonding\ValueObject\ComposerBoundRuleConfiguration; use ReflectionObject; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\TableCell; -use Symfony\Component\Console\Helper\TableSeparator; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; /** * @see \Rector\Tests\Console\Command\ComposerBasedCommandTest */ -final class ComposerBasedCommand extends Command +final readonly class ComposerBasedCommand implements CommandInterface { /** * @param RectorInterface[] $rectors */ public function __construct( - private readonly SymfonyStyle $symfonyStyle, - private readonly InstalledPackageResolver $installedPackageResolver, - private readonly array $rectors + private RectorStyle $rectorStyle, + private InstalledPackageResolver $installedPackageResolver, + private array $rectors ) { - parent::__construct(); } - protected function configure(): void + public function getName(): string { - $this->setName('composer-based'); - $this->setDescription('Show loaded rules that are triggered by an installed composer package version'); + return 'composer-based'; } - protected function execute(InputInterface $input, OutputInterface $output): int + public function getDescription(): string + { + return 'Show loaded rules that are triggered by an installed composer package version'; + } + + /** + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run(): int { $tableRows = $this->createTableRows(); $configurationTableRows = $this->createConfigurationTableRows(); if ($tableRows === [] && $configurationTableRows === []) { - $this->symfonyStyle->warning('No composer package bound rule is loaded'); + $this->rectorStyle->warning('No composer package bound rule is loaded'); - return Command::SUCCESS; + return ExitCode::SUCCESS; } if ($tableRows !== []) { - $this->symfonyStyle->title('Composer package bound rules'); - $this->symfonyStyle->table(['Rule', 'Package', 'Requires', 'Installed', 'Active'], $tableRows); + $this->rectorStyle->title('Composer package bound rules'); + $this->rectorStyle->table(['Rule', 'Package', 'Requires', 'Installed', 'Active'], $tableRows); } if ($configurationTableRows !== []) { - $this->symfonyStyle->title('Composer package bound rule configuration'); - - $headers = ['Rule', 'Package', 'Requires', 'Installed', 'Active']; - - $table = $this->symfonyStyle->createTable(); - $table->setHeaders($headers); - $table->setRows($this->createConfigurationDisplayRows($configurationTableRows)); - - // keep columns as narrow as their content, so the full-width configuration line does not stretch them - foreach ($headers as $columnPosition => $header) { - $columnWidth = max(array_map( - static fn (array $configurationTableRow): int => strlen($configurationTableRow[$columnPosition]), - $configurationTableRows - )); - - $table->setColumnMaxWidth($columnPosition, max($columnWidth, strlen($header))); - } - - $table->render(); + $this->rectorStyle->title('Composer package bound rule configuration'); + $this->rectorStyle->table( + ['Rule', 'Package', 'Requires', 'Installed', 'Active', 'Configuration'], + $configurationTableRows + ); } $allTableRows = [...$tableRows, ...$configurationTableRows]; $activeCount = count(array_filter($allTableRows, static fn (array $tableRow): bool => $tableRow[4] === 'yes')); - $this->symfonyStyle->note( + $this->rectorStyle->note( sprintf('%d of %d composer package bound items are active', $activeCount, count($allTableRows)) ); - return Command::SUCCESS; + return ExitCode::SUCCESS; } /** @@ -165,30 +156,6 @@ private function createConfigurationTableRows(): array return $tableRows; } - /** - * The configuration is printed on a separated full-width line, to avoid breaking the table layout - * - * @param array $configurationTableRows - * @return array|TableSeparator> - */ - private function createConfigurationDisplayRows(array $configurationTableRows): array - { - $displayRows = []; - - foreach ($configurationTableRows as $configurationTableRow) { - if ($displayRows !== []) { - $displayRows[] = new TableSeparator(); - } - - $displayRows[] = array_slice($configurationTableRow, 0, 5); - $displayRows[] = [new TableCell($configurationTableRow[5], [ - 'colspan' => 5, - ])]; - } - - return $displayRows; - } - /** * @param mixed[] $configuration */ diff --git a/src/Console/Command/CustomRuleCommand.php b/src/Console/Command/CustomRuleCommand.php index af97edcce63..c708ae7e3af 100644 --- a/src/Console/Command/CustomRuleCommand.php +++ b/src/Console/Command/CustomRuleCommand.php @@ -4,31 +4,38 @@ namespace Rector\Console\Command; -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 Entropy\Console\Contract\CommandInterface; +use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyle; -final class CustomRuleCommand extends Command +final readonly class CustomRuleCommand implements CommandInterface { public function __construct( - private readonly SymfonyStyle $symfonyStyle + private RectorStyle $rectorStyle ) { - parent::__construct(); } - protected function configure(): void + public function getName(): string { - $this->setName('custom-rule'); - $this->setDescription('[DEPRECATED] Create base of local custom rule with tests'); + return 'custom-rule'; } - protected function execute(InputInterface $input, OutputInterface $output): int + public function getDescription(): string { - $this->symfonyStyle->error( + return '[DEPRECATED] Create base of local custom rule with tests'; + } + + /** + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run(): int + { + $this->rectorStyle->error( 'The "custom-rule" command is deprecated and no longer generates files. Use an AI agent to scaffold a custom rule instead - it handles the setup faster and with less guesswork.' ); - return Command::FAILURE; + return ExitCode::FAILURE; } } diff --git a/src/Console/Command/ListRulesCommand.php b/src/Console/Command/ListRulesCommand.php index 182660b20ca..cc3318275ef 100644 --- a/src/Console/Command/ListRulesCommand.php +++ b/src/Console/Command/ListRulesCommand.php @@ -4,67 +4,57 @@ namespace Rector\Console\Command; +use Entropy\Console\Contract\CommandInterface; use Nette\Utils\Json; use Rector\ChangesReporting\Output\ConsoleOutputFormatter; -use Rector\Configuration\Option; +use Rector\Console\ExitCode; use Rector\Console\Rule\AllRectorRulesProvider; +use Rector\Console\Style\RectorStyle; use Rector\Contract\Rector\RectorInterface; use Rector\PostRector\Contract\Rector\PostRectorInterface; use Rector\Skipper\Skipper\Skipper; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; -final class ListRulesCommand extends Command +final readonly class ListRulesCommand implements CommandInterface { /** * @param RectorInterface[] $rectors */ public function __construct( - private readonly SymfonyStyle $symfonyStyle, - private readonly Skipper $skipper, - private readonly AllRectorRulesProvider $allRectorRulesProvider, - private readonly array $rectors + private RectorStyle $rectorStyle, + private Skipper $skipper, + private AllRectorRulesProvider $allRectorRulesProvider, + private array $rectors ) { - parent::__construct(); } - protected function configure(): void + public function getName(): string { - $this->setName('list-rules'); - $this->setDescription('Show loaded Rectors'); - - $this->setAliases(['show-rules']); - - $this->addOption( - Option::OUTPUT_FORMAT, - null, - InputOption::VALUE_REQUIRED, - 'Select output format', - ConsoleOutputFormatter::NAME - ); - - $this->addOption(Option::ONLY, null, InputOption::VALUE_REQUIRED, 'Fully qualified rule class name'); + return 'list-rules'; + } - $this->addOption( - 'all', - null, - InputOption::VALUE_NONE, - 'List all existing rules in core and rector-* packages, not only the loaded ones' - ); + public function getDescription(): string + { + return 'Show loaded Rectors'; } - protected function execute(InputInterface $input, OutputInterface $output): int + /** + * @param bool $all List all existing rules in core and rector-* packages, not only the loaded ones + * @param string $outputFormat Select output format + * + * @option $outputFormat + * + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run(bool $all = false, string $outputFormat = ConsoleOutputFormatter::NAME): int { - $rectorClasses = $input->getOption('all') + $rectorClasses = $all ? $this->allRectorRulesProvider->provide() : $this->resolveRectorClasses(); $skippedClasses = $this->getSkippedRectorClasses(); - $outputFormat = $input->getOption(Option::OUTPUT_FORMAT); if ($outputFormat === 'json') { $data = [ 'rectors' => $rectorClasses, @@ -72,21 +62,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int ]; echo Json::encode($data, pretty: true) . PHP_EOL; - return Command::SUCCESS; + return ExitCode::SUCCESS; } - $this->symfonyStyle->title('Loaded Rector rules'); - $this->symfonyStyle->listing($rectorClasses); + $this->rectorStyle->title('Loaded Rector rules'); + $this->rectorStyle->listing($rectorClasses); if ($skippedClasses !== []) { - $this->symfonyStyle->title('Skipped Rector rules'); - $this->symfonyStyle->listing($skippedClasses); + $this->rectorStyle->title('Skipped Rector rules'); + $this->rectorStyle->listing($skippedClasses); } - $this->symfonyStyle->newLine(); - $this->symfonyStyle->note(sprintf('Loaded %d rules', count($rectorClasses))); + $this->rectorStyle->newLine(); + $this->rectorStyle->note(sprintf('Loaded %d rules', count($rectorClasses))); - return Command::SUCCESS; + return ExitCode::SUCCESS; } /** diff --git a/src/Console/Command/ProcessCommand.php b/src/Console/Command/ProcessCommand.php index c8202761d3a..1be8c9740cb 100644 --- a/src/Console/Command/ProcessCommand.php +++ b/src/Console/Command/ProcessCommand.php @@ -4,18 +4,19 @@ namespace Rector\Console\Command; +use Entropy\Console\Contract\CommandInterface; +use Entropy\Console\Contract\DefaultCommandInterface; use Rector\Application\ApplicationFileProcessor; use Rector\Autoloading\AdditionalAutoloader; use Rector\Caching\Detector\ChangedFilesDetector; +use Rector\ChangesReporting\Output\ConsoleOutputFormatter; use Rector\ChangesReporting\Output\JsonOutputFormatter; use Rector\Configuration\ConfigInitializer; use Rector\Configuration\ConfigurationFactory; use Rector\Configuration\ConfigurationRuleFilter; -use Rector\Configuration\Option; use Rector\Console\ExitCode; use Rector\Console\Output\OutputFormatterCollector; -use Rector\Console\ProcessConfigureDecorator; -use Rector\Exception\ShouldNotHappenException; +use Rector\Console\Style\RectorStyle; use Rector\Reporting\DeprecatedRulesReporter; use Rector\Reporting\MissConfigurationReporter; use Rector\Skipper\Skipper\Skipper; @@ -24,89 +25,133 @@ use Rector\ValueObject\Configuration; use Rector\ValueObject\Configuration\LevelOverflow; use Rector\ValueObject\ProcessResult; -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; -final class ProcessCommand extends Command +final readonly class ProcessCommand implements CommandInterface, DefaultCommandInterface { public function __construct( - private readonly AdditionalAutoloader $additionalAutoloader, - private readonly ChangedFilesDetector $changedFilesDetector, - private readonly ConfigInitializer $configInitializer, - private readonly ApplicationFileProcessor $applicationFileProcessor, - private readonly DynamicSourceLocatorDecorator $dynamicSourceLocatorDecorator, - private readonly OutputFormatterCollector $outputFormatterCollector, - private readonly SymfonyStyle $symfonyStyle, - private readonly MemoryLimiter $memoryLimiter, - private readonly ConfigurationFactory $configurationFactory, - private readonly DeprecatedRulesReporter $deprecatedRulesReporter, - private readonly MissConfigurationReporter $missConfigurationReporter, - private readonly ConfigurationRuleFilter $configurationRuleFilter, - private readonly Skipper $skipper, + private AdditionalAutoloader $additionalAutoloader, + private ChangedFilesDetector $changedFilesDetector, + private ConfigInitializer $configInitializer, + private ApplicationFileProcessor $applicationFileProcessor, + private DynamicSourceLocatorDecorator $dynamicSourceLocatorDecorator, + private OutputFormatterCollector $outputFormatterCollector, + private RectorStyle $rectorStyle, + private MemoryLimiter $memoryLimiter, + private ConfigurationFactory $configurationFactory, + private DeprecatedRulesReporter $deprecatedRulesReporter, + private MissConfigurationReporter $missConfigurationReporter, + private ConfigurationRuleFilter $configurationRuleFilter, + private Skipper $skipper, ) { - parent::__construct(); } - protected function configure(): void + public function getName(): string { - $this->setName('process'); - $this->setAliases(['p']); - $this->setDescription('Upgrades or refactors source code with provided Rector rules'); - $this->setHelp( - <<<'EOF' -The %command.name% command will run Rector main feature: - - vendor/bin/rector - -To specify a folder or a file, you can run: - - vendor/bin/rector src/Controller - -You can also dry run to see the changes that Rector will make with the --dry-run option: - - vendor/bin/rector src/Controller --dry-run - -It's also possible to get debug via the --debug option: - - vendor/bin/rector src/Controller --dry-run --debug -EOF - ); - - ProcessConfigureDecorator::decorate($this); - - parent::configure(); + return 'process'; } - protected function execute(InputInterface $input, OutputInterface $output): int + public function getDescription(): string { + return 'Upgrades or refactors source code with provided Rector rules'; + } + + /** + * @param string[] $only Fully qualified rule class name; repeat to run several rules + * @param string $config Path to config file + * @param string $autoloadFile Path to file with extra autoload (will be included) + * @param string $outputFormat Select output format + * @param string $onlySuffix Deprecated, use "--filter" instead + * @param string $filter Keep only files matching all comma-separated patterns + * @param string $memoryLimit Memory limit for process + * @param string $maxChanges Stop after this many changes are made + * @param string $port [INTERNAL] parallel TCP port + * @param string $identifier [INTERNAL] parallel identifier + * @param string ...$paths Files or directories to be upgraded + * + * @option $only + * @option $config + * @option $autoloadFile + * @option $outputFormat + * @option $onlySuffix + * @option $filter + * @option $memoryLimit + * @option $maxChanges + * @option $port + * @option $identifier + * + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run( + bool $dryRun = false, + bool $clearCache = false, + bool $noProgressBar = false, + bool $noDiffs = false, + bool $debug = false, + bool $composerBased = false, + bool $php = false, + bool $rulesSummary = false, + array $only = [], + string $config = '', + string $autoloadFile = '', + string $outputFormat = ConsoleOutputFormatter::NAME, + string $onlySuffix = '', + string $filter = '', + string $memoryLimit = '', + string $maxChanges = '', + string $port = '', + string $identifier = '', + string ...$paths, + ): int { // missing config? add it :) if (! $this->configInitializer->areSomeRectorsLoaded()) { - $this->configInitializer->createConfig(getcwd()); - return self::SUCCESS; + $this->configInitializer->createConfig((string) getcwd()); + return ExitCode::SUCCESS; } - $configuration = $this->configurationFactory->createFromInput($input); + $configuration = $this->configurationFactory->create( + array_values($paths), + $dryRun, + $clearCache, + $noProgressBar, + $noDiffs, + $outputFormat, + $only, + $composerBased, + $php, + $onlySuffix !== '' ? $onlySuffix : null, + $filter !== '' ? $filter : null, + $debug, + $memoryLimit !== '' ? $memoryLimit : null, + $port, + $identifier, + $maxChanges !== '' ? (int) $maxChanges : null, + $rulesSummary, + ); + + // clear cache in debug run, so nothing is skipped and errors show up clearly + if ($debug) { + $this->changedFilesDetector->clear(); + } $this->memoryLimiter->adjust($configuration); $this->configurationRuleFilter->setConfiguration($configuration); // disable console output in case of json output formatter if ($configuration->getOutputFormat() === JsonOutputFormatter::NAME) { - $this->symfonyStyle->setVerbosity(OutputInterface::VERBOSITY_QUIET); + $this->rectorStyle->setVerbosity(RectorStyle::VERBOSITY_QUIET); } // "--rules-summary" is a human-only table; the JSON format carries richer per-rule data if ($configuration->shouldShowRulesSummary()) { - $this->symfonyStyle->getErrorStyle() + $this->rectorStyle->getErrorStyle() ->warning( 'The "--rules-summary" option is deprecated and will be removed. Use "--output-format=json" instead, which reports every applied rule with its file and line.' ); } - $this->additionalAutoloader->autoloadInput($input); + $this->additionalAutoloader->autoloadFile(); $paths = $configuration->getPaths(); @@ -117,7 +162,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // 0. warn about skipped rules that are deprecated if ($this->skipper->resolveDeprecatedSkippedClasses() !== []) { - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'These rules are skipped, but are deprecated. Most likely you do not need to skip them anymore as not part of any set and remove them: %s%s', "\n\n", '* ' . implode("\n* ", $this->skipper->resolveDeprecatedSkippedClasses()) . "\n" @@ -127,7 +172,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // 1. warn about rules registered in both withRules() and sets to avoid bloated rector.php configs $setAndRulesDuplicatedRegistrations = $configuration->getBothSetAndRulesDuplicatedRegistrations(); if ($setAndRulesDuplicatedRegistrations !== []) { - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'These rules are registered in both sets and "withRules()". Remove them from "withRules()" to avoid duplications: %s* %s', "\n\n", implode(' * ', $setAndRulesDuplicatedRegistrations) . "\n" @@ -139,7 +184,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($this->dynamicSourceLocatorDecorator->arePathsEmpty()) { // read from rector.php, no paths definition needs withPaths() config if ($paths === []) { - $this->symfonyStyle->error( + $this->rectorStyle->error( 'Provide paths in Rector config. See ways: https://getrector.com/documentation/define-paths' ); @@ -148,7 +193,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // read from cli paths arguments, eg: vendor/bin/rector process A B C which A, B, and C not exists $isSingular = count($paths) === 1; - $this->symfonyStyle->error( + $this->rectorStyle->error( sprintf( 'The following given path%s do%s not match any file%s or director%s: %s%s', $isSingular ? '' : 's', @@ -172,7 +217,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // MAIN PHASE // 2. run Rector - $processResult = $this->applicationFileProcessor->run($configuration, $input); + $processResult = $this->applicationFileProcessor->run($configuration); // REPORTING PHASE // 3. reporting phaseRunning 2nd time with collectors data @@ -181,7 +226,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int // warn about deprecated static-analysis-oriented output formats, to be removed in next minor version if (in_array($outputFormat, ['github', 'gitlab'], true)) { - $this->symfonyStyle->getErrorStyle() + $this->rectorStyle->getErrorStyle() ->warning(sprintf( 'The "%s" output format is deprecated and will be removed in the next minor version, as Rector is not a static analysis tool.', $outputFormat @@ -207,21 +252,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int return $this->resolveReturnCode($processResult, $configuration); } - protected function initialize(InputInterface $input, OutputInterface $output): void - { - $application = $this->getApplication(); - if (! $application instanceof Application) { - throw new ShouldNotHappenException(); - } - - $optionDebug = (bool) $input->getOption(Option::DEBUG); - // clear cache - if ($optionDebug) { - $application->setCatchExceptions(false); - $this->changedFilesDetector->clear(); - } - } - /** * @return ExitCode::* */ @@ -255,7 +285,7 @@ private function reportLevelOverflow(LevelOverflow $levelOverflow): void $levelOverflow->getSuggestedRuleset() ) : sprintf('->withSets(SetList::%s)', $levelOverflow->getSuggestedSetListConstant()); - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'The "->%s()" level contains only %d rules, but you set level to %d.%sYou are using the full set now! Time to switch to more efficient "%s".', $levelOverflow->getConfigurationName(), $levelOverflow->getRuleCount(), diff --git a/src/Console/Command/SetupCICommand.php b/src/Console/Command/SetupCICommand.php index 38d5bc85db5..628aec77bf3 100644 --- a/src/Console/Command/SetupCICommand.php +++ b/src/Console/Command/SetupCICommand.php @@ -4,31 +4,38 @@ namespace Rector\Console\Command; -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 Entropy\Console\Contract\CommandInterface; +use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyle; -final class SetupCICommand extends Command +final readonly class SetupCICommand implements CommandInterface { public function __construct( - private readonly SymfonyStyle $symfonyStyle + private RectorStyle $rectorStyle ) { - parent::__construct(); } - protected function configure(): void + public function getName(): string { - $this->setName('setup-ci'); - $this->setDescription('[DEPRECATED] Add CI workflow to let Rector work for you'); + return 'setup-ci'; } - protected function execute(InputInterface $input, OutputInterface $output): int + public function getDescription(): string { - $this->symfonyStyle->error( + return '[DEPRECATED] Add CI workflow to let Rector work for you'; + } + + /** + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run(): int + { + $this->rectorStyle->error( 'The "setup-ci" command is deprecated and no longer generates files. Its generic template lacked caching and did not fit most setups. Add a CI workflow explicitly for your use case instead - an AI agent can tailor one to your repository.' ); - return Command::FAILURE; + return ExitCode::FAILURE; } } diff --git a/src/Console/Command/ValidateConfigCommand.php b/src/Console/Command/ValidateConfigCommand.php index 064cdab6ed2..dd49418a71d 100644 --- a/src/Console/Command/ValidateConfigCommand.php +++ b/src/Console/Command/ValidateConfigCommand.php @@ -4,55 +4,56 @@ namespace Rector\Console\Command; +use Entropy\Console\Contract\CommandInterface; use Nette\Utils\Json; use Rector\ChangesReporting\Output\ConsoleOutputFormatter; use Rector\ChangesReporting\Output\JsonOutputFormatter; -use Rector\Configuration\Option; use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyle; use Rector\Reporting\DeprecatedRulesReporter; use Rector\Reporting\MissConfigurationReporter; use Rector\Skipper\Skipper\Skipper; use Rector\ValueObject\Configuration; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; /** * @see \Rector\Tests\Console\Command\ValidateConfigCommandTest */ -final class ValidateConfigCommand extends Command +final readonly class ValidateConfigCommand implements CommandInterface { public function __construct( - private readonly SymfonyStyle $symfonyStyle, - private readonly DeprecatedRulesReporter $deprecatedRulesReporter, - private readonly MissConfigurationReporter $missConfigurationReporter, - private readonly Skipper $skipper, + private RectorStyle $rectorStyle, + private DeprecatedRulesReporter $deprecatedRulesReporter, + private MissConfigurationReporter $missConfigurationReporter, + private Skipper $skipper, ) { - parent::__construct(); } - protected function configure(): void + public function getName(): string { - $this->setName('validate-config'); - $this->setDescription('Report config hygiene issues without processing any files'); - $this->addOption( - Option::OUTPUT_FORMAT, - null, - InputOption::VALUE_REQUIRED, - sprintf('Output format: "%s" or "%s"', ConsoleOutputFormatter::NAME, JsonOutputFormatter::NAME), - ConsoleOutputFormatter::NAME - ); + return 'validate-config'; } - protected function execute(InputInterface $input, OutputInterface $output): int + public function getDescription(): string { - $isJsonOutput = $input->getOption(Option::OUTPUT_FORMAT) === JsonOutputFormatter::NAME; + return 'Report config hygiene issues without processing any files'; + } + + /** + * @param string $outputFormat Output format: "console" or "json" + * + * @option $outputFormat + * + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run(string $outputFormat = ConsoleOutputFormatter::NAME): int + { + $isJsonOutput = $outputFormat === JsonOutputFormatter::NAME; // silence the human-readable warnings, so only the JSON payload lands on stdout if ($isJsonOutput) { - $this->symfonyStyle->setVerbosity(OutputInterface::VERBOSITY_QUIET); + $this->rectorStyle->setVerbosity(RectorStyle::VERBOSITY_QUIET); } $issueCount = 0; @@ -79,11 +80,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int } if ($issueCount === 0) { - $this->symfonyStyle->success('Config is valid, no issues found'); + $this->rectorStyle->success('Config is valid, no issues found'); return ExitCode::SUCCESS; } - $this->symfonyStyle->error(sprintf( + $this->rectorStyle->error(sprintf( '%d config %s found, see the warnings above', $issueCount, $issueCount === 1 ? 'issue' : 'issues' @@ -99,7 +100,7 @@ private function reportDeprecatedSkippedClasses(): int return 0; } - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'These rules are skipped, but are deprecated. Most likely you do not need to skip them anymore, remove them: %s%s', "\n\n", '* ' . implode("\n* ", $deprecatedSkippedClasses) . "\n" @@ -116,7 +117,7 @@ private function reportSetAndRulesDuplicatedRegistrations(): int return 0; } - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'These rules are registered in both sets and "withRules()". Remove them from "withRules()" to avoid duplications: %s* %s', "\n\n", implode(' * ', $setAndRulesDuplicatedRegistrations) . "\n" diff --git a/src/Console/Command/WorkerCommand.php b/src/Console/Command/WorkerCommand.php index 8b7d4c1061c..b66fad41a42 100644 --- a/src/Console/Command/WorkerCommand.php +++ b/src/Console/Command/WorkerCommand.php @@ -6,15 +6,18 @@ use Clue\React\NDJson\Decoder; use Clue\React\NDJson\Encoder; +use Entropy\Console\Contract\CommandInterface; +use Entropy\Console\Contract\HiddenCommandInterface; use React\EventLoop\StreamSelectLoop; use React\Socket\ConnectionInterface; use React\Socket\TcpConnector; use Rector\Application\ApplicationFileProcessor; use Rector\Autoloading\AdditionalAutoloader; +use Rector\ChangesReporting\Output\ConsoleOutputFormatter; +use Rector\ChangesReporting\Output\JsonOutputFormatter; use Rector\Configuration\ConfigurationFactory; use Rector\Configuration\ConfigurationRuleFilter; -use Rector\Configuration\Option; -use Rector\Console\ProcessConfigureDecorator; +use Rector\Console\ExitCode; use Rector\Parallel\Enum\Action; use Rector\Parallel\Enum\ReactCommand; use Rector\Parallel\Enum\ReactEvent; @@ -24,9 +27,6 @@ use Rector\Util\MemoryLimiter; use Rector\ValueObject\Configuration; use Rector\ValueObject\Error\SystemError; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; use Throwable; use Webmozart\Assert\Assert; @@ -37,34 +37,99 @@ * ↓↓↓ * https://github.com/phpstan/phpstan-src/commit/b84acd2e3eadf66189a64fdbc6dd18ff76323f67#diff-7f625777f1ce5384046df08abffd6c911cfbb1cfc8fcb2bdeaf78f337689e3e2 */ -final class WorkerCommand extends Command +final readonly class WorkerCommand implements CommandInterface, HiddenCommandInterface { private const string RESULT = 'result'; public function __construct( - private readonly AdditionalAutoloader $additionalAutoloader, - private readonly DynamicSourceLocatorDecorator $dynamicSourceLocatorDecorator, - private readonly ApplicationFileProcessor $applicationFileProcessor, - private readonly MemoryLimiter $memoryLimiter, - private readonly ConfigurationFactory $configurationFactory, - private readonly ConfigurationRuleFilter $configurationRuleFilter, + private AdditionalAutoloader $additionalAutoloader, + private DynamicSourceLocatorDecorator $dynamicSourceLocatorDecorator, + private ApplicationFileProcessor $applicationFileProcessor, + private MemoryLimiter $memoryLimiter, + private ConfigurationFactory $configurationFactory, + private ConfigurationRuleFilter $configurationRuleFilter, ) { - parent::__construct(); } - protected function configure(): void + public function getName(): string { - $this->setName('worker'); - $this->setDescription('[INTERNAL] Support for parallel process'); - - ProcessConfigureDecorator::decorate($this); - - parent::configure(); + return 'worker'; } - protected function execute(InputInterface $input, OutputInterface $output): int + public function getDescription(): string { - $configuration = $this->configurationFactory->createFromInput($input); + return '[INTERNAL] Support for parallel process'; + } + + /** + * @param string[] $only Fully qualified rule class name; repeat to run several rules + * @param string $config Path to config file + * @param string $autoloadFile Path to file with extra autoload (will be included) + * @param string $outputFormat Select output format + * @param string $onlySuffix Deprecated, use "--filter" instead + * @param string $filter Keep only files matching all comma-separated patterns + * @param string $memoryLimit Memory limit for process + * @param string $maxChanges Stop after this many changes are made + * @param string $port [INTERNAL] parallel TCP port + * @param string $identifier [INTERNAL] parallel identifier + * @param string ...$paths Files or directories to be upgraded + * + * @option $only + * @option $config + * @option $autoloadFile + * @option $outputFormat + * @option $onlySuffix + * @option $filter + * @option $memoryLimit + * @option $maxChanges + * @option $port + * @option $identifier + * + * @api invoked via reflection by the Entropy console application + * + * @return ExitCode::* + */ + public function run( + bool $dryRun = false, + bool $clearCache = false, + bool $noProgressBar = false, + bool $noDiffs = false, + bool $debug = false, + bool $composerBased = false, + bool $php = false, + bool $rulesSummary = false, + array $only = [], + string $config = '', + string $autoloadFile = '', + string $outputFormat = ConsoleOutputFormatter::NAME, + string $onlySuffix = '', + string $filter = '', + string $memoryLimit = '', + string $maxChanges = '', + string $port = '', + string $identifier = '', + string ...$paths, + ): int { + $configuration = $this->configurationFactory->create( + array_values($paths), + $dryRun, + $clearCache, + $noProgressBar, + $noDiffs, + $outputFormat, + $only, + $composerBased, + $php, + $onlySuffix !== '' ? $onlySuffix : null, + $filter !== '' ? $filter : null, + $debug, + $memoryLimit !== '' ? $memoryLimit : null, + $port, + $identifier, + $maxChanges !== '' ? (int) $maxChanges : null, + $rulesSummary, + ); + $this->memoryLimiter->adjust($configuration); $this->configurationRuleFilter->setConfiguration($configuration); @@ -76,9 +141,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $promise = $tcpConnector->connect('127.0.0.1:' . $configuration->getParallelPort()); $promise->then(function (ConnectionInterface $connection) use ( $parallelIdentifier, - $configuration, - $input, - $output + $configuration ): void { $inDecoder = new Decoder($connection, true, StreamFormat::DEPTH, JSON_INVALID_UTF8_IGNORE, StreamFormat::MAX_LENGTH); $outEncoder = new Encoder($connection, JSON_INVALID_UTF8_IGNORE); @@ -88,27 +151,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int ReactCommand::IDENTIFIER => $parallelIdentifier, ]); - $this->runWorker($outEncoder, $inDecoder, $configuration, $input, $output); + $this->runWorker($outEncoder, $inDecoder, $configuration); }); $streamSelectLoop->run(); - return self::SUCCESS; + return ExitCode::SUCCESS; } private function runWorker( Encoder $encoder, Decoder $decoder, - Configuration $configuration, - InputInterface $input, - OutputInterface $output + Configuration $configuration ): void { + $this->additionalAutoloader->autoloadFile(); $this->additionalAutoloader->autoloadPaths(); + $this->dynamicSourceLocatorDecorator->addPaths($configuration->getPaths()); if ($configuration->isDebug()) { - $preFileCallback = static function (string $filePath) use ($output): void { - $output->writeln($filePath); + $preFileCallback = static function (string $filePath): void { + echo $filePath . PHP_EOL; }; } else { $preFileCallback = null; @@ -135,8 +198,7 @@ private function runWorker( $decoder->on(ReactEvent::DATA, function (array $json) use ( $preFileCallback, $encoder, - $configuration, - $input + $configuration ): void { $action = $json[ReactCommand::ACTION]; if ($action !== Action::MAIN) { @@ -161,7 +223,7 @@ private function runWorker( ReactCommand::ACTION => Action::RESULT, self::RESULT => [ Bridge::FILE_DIFFS => $processResult->getFileDiffs( - $input->getOption(Option::OUTPUT_FORMAT) !== 'json' + $configuration->getOutputFormat() !== JsonOutputFormatter::NAME ), Bridge::FILES_COUNT => count($filePaths), Bridge::SYSTEM_ERRORS => $processResult->getSystemErrors(), diff --git a/src/Console/ConsoleApplication.php b/src/Console/ConsoleApplication.php deleted file mode 100644 index 2fee6c57f73..00000000000 --- a/src/Console/ConsoleApplication.php +++ /dev/null @@ -1,165 +0,0 @@ -addCommands($commands); - - // run this command, if no command name is provided - $this->setDefaultCommand('process'); - } - - public function doRun(InputInterface $input, OutputInterface $output): int - { - // support "-v" as an alias for "--version", as "verbose" option is removed - if ($input->hasParameterOption('-v', true)) { - $output->writeln($this->getLongVersion()); - - return ExitCode::SUCCESS; - } - - $shouldFollowByNewline = false; - - // skip in this case, since generate content must be clear from meta-info - if ($this->shouldPrintMetaInformation($input)) { - $output->writeln($this->getLongVersion()); - $shouldFollowByNewline = true; - } - - if ($shouldFollowByNewline) { - $output->write(PHP_EOL); - } - - $commandName = $input->getFirstArgument(); - - if ($commandName === null) { - return parent::doRun($input, $output); - } - - // if paths exist or if the command name is not the first argument but with --option, eg: - // bin/rector src - // bin/rector --only "RemovePhpVersionIdCheckRector" - // file_exists() can check directory and file - if (( - file_exists($commandName) || isset($_SERVER['argv'][1]) - && $commandName !== $_SERVER['argv'][1] - // ensure verify has parameter option, eg: --only - && $input->hasParameterOption($_SERVER['argv'][1]) - ) - ) { - // prepend command name if implicit - $privatesAccessor = new PrivatesAccessor(); - $tokens = $privatesAccessor->getPrivateProperty($input, 'tokens'); - $tokens = array_merge(['process'], $tokens); - $privatesAccessor->setPrivateProperty($input, 'tokens', $tokens); - } elseif (! $this->has($commandName)) { - $this->symfonyStyle->error( - sprintf( - 'The following given path does not match any files or directories: %s%s', - "\n\n - ", - $commandName - ) - ); - - return ExitCode::FAILURE; - } - - return parent::doRun($input, $output); - } - - protected function getDefaultInputDefinition(): InputDefinition - { - $defaultInputDefinition = parent::getDefaultInputDefinition(); - - $this->removeUnusedOptions($defaultInputDefinition); - $this->addCustomOptions($defaultInputDefinition); - - return $defaultInputDefinition; - } - - private function shouldPrintMetaInformation(InputInterface $input): bool - { - $hasNoArguments = $input->getFirstArgument() === null; - if ($hasNoArguments) { - return false; - } - - $hasVersionOption = $input->hasParameterOption('--version'); - if ($hasVersionOption) { - return false; - } - - $outputFormat = $input->getParameterOption(['-o', '--output-format']); - return $outputFormat === ConsoleOutputFormatter::NAME; - } - - private function removeUnusedOptions(InputDefinition $inputDefinition): void - { - $options = $inputDefinition->getOptions(); - - unset($options['quiet'], $options['verbose'], $options['no-interaction']); - - $inputDefinition->setOptions($options); - } - - private function addCustomOptions(InputDefinition $inputDefinition): void - { - $inputDefinition->addOption(new InputOption( - Option::CONFIG, - 'c', - InputOption::VALUE_REQUIRED, - 'Path to config file' - )); - - $inputDefinition->addOption(new InputOption( - Option::DEBUG, - null, - InputOption::VALUE_NONE, - 'Enable debug verbosity' - )); - - $inputDefinition->addOption(new InputOption( - Option::XDEBUG, - null, - InputOption::VALUE_NONE, - 'Allow running xdebug' - )); - - $inputDefinition->addOption(new InputOption( - Option::CLEAR_CACHE, - null, - InputOption::VALUE_NONE, - 'Clear cache before starting the execution of the command' - )); - } -} diff --git a/src/Console/ExitCode.php b/src/Console/ExitCode.php index 046efbd7711..a995ca81816 100644 --- a/src/Console/ExitCode.php +++ b/src/Console/ExitCode.php @@ -4,16 +4,14 @@ namespace Rector\Console; -use Symfony\Component\Console\Command\Command; - /** * @api */ final class ExitCode { - public const int SUCCESS = Command::SUCCESS; + public const int SUCCESS = 0; - public const int FAILURE = Command::FAILURE; + public const int FAILURE = 1; public const int CHANGED_CODE = 2; } diff --git a/src/Console/Formatter/ColorConsoleDiffFormatter.php b/src/Console/Formatter/ColorConsoleDiffFormatter.php index 62788ea19dd..5e50967e524 100644 --- a/src/Console/Formatter/ColorConsoleDiffFormatter.php +++ b/src/Console/Formatter/ColorConsoleDiffFormatter.php @@ -6,7 +6,6 @@ use Nette\Utils\Strings; use Rector\Util\NewLineSplitter; -use Symfony\Component\Console\Formatter\OutputFormatter; /** * Inspired by @see https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/master/src/Differ/DiffConsoleFormatter.php to be @@ -54,9 +53,7 @@ public function format(string $diff): string private function formatWithTemplate(string $diff, string $template): string { - $escapedDiff = OutputFormatter::escape(rtrim($diff)); - - $escapedDiffLines = NewLineSplitter::split($escapedDiff); + $escapedDiffLines = NewLineSplitter::split(rtrim($diff)); // remove description of added + remove, obvious on diffs // decorize lines diff --git a/src/Console/Notifier.php b/src/Console/Notifier.php index 347c3505f17..b6b55408365 100644 --- a/src/Console/Notifier.php +++ b/src/Console/Notifier.php @@ -4,10 +4,8 @@ namespace Rector\Console; +use Rector\Console\Style\RectorStyleFactory; use Rector\Exception\Configuration\InvalidConfigurationException; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Style\SymfonyStyle; final class Notifier { @@ -22,8 +20,8 @@ public static function notifyNotSuitableMethodForPHP74(string $calledMethod): vo $calledMethod ); - $symfonyStyle = new SymfonyStyle(new ArgvInput(), new ConsoleOutput()); - $symfonyStyle->warning($message); + $rectorStyle = new RectorStyleFactory()->create(); + $rectorStyle->warning($message); sleep(3); } @@ -36,8 +34,8 @@ public static function notifyDeprecatedPhpSet(string $set): void $set ); - $symfonyStyle = new SymfonyStyle(new ArgvInput(), new ConsoleOutput()); - $symfonyStyle->warning($message); + $rectorStyle = new RectorStyleFactory()->create(); + $rectorStyle->warning($message); } public static function notifyDeprecatedNamingSet(): void @@ -45,8 +43,8 @@ public static function notifyDeprecatedNamingSet(): void $message = 'The "naming" prepared set is deprecated, as it often causes conflicting variable and param names and removes context. ' . 'Renaming to match types belongs to a coding standard tool rather than Rector.'; - $symfonyStyle = new SymfonyStyle(new ArgvInput(), new ConsoleOutput()); - $symfonyStyle->warning($message); + $rectorStyle = new RectorStyleFactory()->create(); + $rectorStyle->warning($message); } public static function errorWithPhpSetsNotSuitableForPHP74AndLower(): void diff --git a/src/Console/ProcessConfigureDecorator.php b/src/Console/ProcessConfigureDecorator.php deleted file mode 100644 index 800168aba89..00000000000 --- a/src/Console/ProcessConfigureDecorator.php +++ /dev/null @@ -1,118 +0,0 @@ -addArgument( - Option::SOURCE, - InputArgument::OPTIONAL | InputArgument::IS_ARRAY, - 'Files or directories to be upgraded.' - ); - - $command->addOption( - Option::DRY_RUN, - Option::DRY_RUN_SHORT, - InputOption::VALUE_NONE, - 'Only see the diff of changes, do not save them to files.' - ); - - $command->addOption( - Option::AUTOLOAD_FILE, - Option::AUTOLOAD_FILE_SHORT, - InputOption::VALUE_REQUIRED, - 'Path to file with extra autoload (will be included)' - ); - - $command->addOption( - Option::NO_PROGRESS_BAR, - null, - InputOption::VALUE_NONE, - 'Hide progress bar. Useful e.g. for nicer CI output.' - ); - - $command->addOption( - Option::NO_DIFFS, - null, - InputOption::VALUE_NONE, - 'Hide diffs of changed files. Useful e.g. for nicer CI output.' - ); - - $command->addOption( - Option::OUTPUT_FORMAT, - null, - InputOption::VALUE_REQUIRED, - 'Select output format', - ConsoleOutputFormatter::NAME - ); - - // filter by rule and path - $command->addOption( - Option::ONLY, - null, - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'Fully qualified rule class name; repeat to run several rules, e.g. --only=A --only=B' - ); - - $command->addOption( - Option::COMPOSER_BASED, - null, - InputOption::VALUE_NONE, - 'Run only rules bound to an installed composer package version' - ); - - $command->addOption( - Option::PHP, - null, - InputOption::VALUE_NONE, - 'Run only PHP rules, e.g. rules bound to a minimal PHP version' - ); - - $command->addOption( - Option::ONLY_SUFFIX, - null, - InputOption::VALUE_REQUIRED, - 'Deprecated, use "--filter" instead. Filter only files with specific suffix in name, e.g. "Controller"' - ); - - $command->addOption( - Option::FILTER, - null, - InputOption::VALUE_REQUIRED, - 'Keep only files matching all comma-separated patterns: "/Controller/" (path substring), "*Repository.php" (basename glob), "tests" (Test.php and TestCase.php files)' - ); - - $command->addOption(Option::DEBUG, null, InputOption::VALUE_NONE, 'Display debug output.'); - $command->addOption(Option::MEMORY_LIMIT, null, InputOption::VALUE_REQUIRED, 'Memory limit for process'); - $command->addOption(Option::CLEAR_CACHE, null, InputOption::VALUE_NONE, 'Clear unchanged files cache'); - - $command->addOption(Option::PARALLEL_PORT, null, InputOption::VALUE_REQUIRED); - $command->addOption(Option::PARALLEL_IDENTIFIER, null, InputOption::VALUE_REQUIRED); - - $command->addOption(Option::XDEBUG, null, InputOption::VALUE_NONE, 'Display xdebug output.'); - - $command->addOption( - Option::RULES_SUMMARY, - null, - InputOption::VALUE_NONE, - 'Show summary of rules applied during the run.' - ); - - $command->addOption( - Option::MAX_CHANGES, - null, - InputOption::VALUE_REQUIRED, - 'Stop after this many changes are made, leaving the rest untouched. Forces non-parallel run.' - ); - } -} diff --git a/src/Console/Style/RectorStyle.php b/src/Console/Style/RectorStyle.php index 0b8b5d7c4dc..3b33bc7daf9 100644 --- a/src/Console/Style/RectorStyle.php +++ b/src/Console/Style/RectorStyle.php @@ -4,81 +4,252 @@ namespace Rector\Console\Style; -use OndraM\CiDetector\CiDetector; -use Symfony\Component\Console\Exception\RuntimeException; -use Symfony\Component\Console\Helper\ProgressBar; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; - -final class RectorStyle extends SymfonyStyle +use Entropy\Console\ConsoleTable\ConsoleTable; +use Entropy\Console\Enum\Color; +use Entropy\Console\Output\OutputColorizer; +use Entropy\Console\Output\OutputPrinter; +use Entropy\Console\Output\ProgressBar; + +/** + * Entropy-backed console style, replacing the former symfony/console SymfonyStyle. + */ +final class RectorStyle { - private ?ProgressBar $progressBar = null; + public const int VERBOSITY_QUIET = 16; - private bool|null $isCiDetected = null; + public const int VERBOSITY_NORMAL = 32; - public function __construct(InputInterface $input, OutputInterface $output) + public const int VERBOSITY_VERBOSE = 64; + + public const int VERBOSITY_DEBUG = 256; + + private int $verbosity = self::VERBOSITY_NORMAL; + + private bool $isRecording = false; + + /** + * @var string[] + */ + private array $recordedMessages = []; + + public function __construct( + private readonly OutputPrinter $outputPrinter, + private readonly OutputColorizer $outputColorizer, + private readonly ProgressBar $progressBar, + ) { + } + + public function setVerbosity(int $verbosity): void + { + $this->verbosity = $verbosity; + } + + /** + * Collect emitted messages in memory, so tests can assert on them even though the + * underlying OutputPrinter stays silent during a PHPUnit run. + * + * @api used in tests + */ + public function startRecording(): void + { + $this->isRecording = true; + $this->recordedMessages = []; + } + + /** + * @api used in tests + */ + public function getRecordedOutput(): string + { + return implode("\n", $this->recordedMessages); + } + + public function isVerbose(): bool + { + return $this->verbosity >= self::VERBOSITY_VERBOSE; + } + + public function isDebug(): bool + { + return $this->verbosity >= self::VERBOSITY_DEBUG; + } + + public function writeln(string $message): void + { + $this->record($message); + + if ($this->isQuiet()) { + return; + } + + $this->outputPrinter->writeln($this->normalizeTags($message)); + } + + public function newLine(int $count = 1): void + { + if ($this->isQuiet()) { + return; + } + + $this->outputPrinter->newline($count); + } + + public function title(string $message): void + { + $this->record($message); + + if ($this->isQuiet()) { + return; + } + + $this->outputPrinter->title($message); + } + + public function section(string $message): void + { + $this->record($message); + + if ($this->isQuiet()) { + return; + } + + $this->outputPrinter->section($message); + } + + public function success(string $message): void + { + $this->record($message); + + if ($this->isQuiet()) { + return; + } + + $this->outputPrinter->success($message); + } + + public function warning(string $message): void { - parent::__construct($input, $output); + $this->record($message); - // silent output in tests - if (defined('PHPUNIT_COMPOSER_INSTALL')) { - $this->setVerbosity(OutputInterface::VERBOSITY_QUIET); + if ($this->isQuiet()) { + return; } + + $this->outputPrinter->warning($message); + } + + public function note(string $message): void + { + $this->record($message); + + if ($this->isQuiet()) { + return; + } + + $this->outputPrinter->writeln($this->normalizeTags($message)); + } + + public function error(string $message): void + { + $this->record($message); + + // errors are shown even in quiet mode, so failures are never swallowed + $this->outputPrinter->error($message); } /** - * @see https://github.com/phpstan/phpstan-src/commit/0993d180e5a15a17631d525909356081be59ffeb + * @param string[] $items */ - public function createProgressBar(int $max = 0): ProgressBar - { - $progressBar = parent::createProgressBar($max); - $progressBar->setOverwrite(! $this->isCiDetected()); - - $isCiDetected = $this->isCiDetected(); - $progressBar->setOverwrite(! $isCiDetected); - - if ($isCiDetected) { - $progressBar->minSecondsBetweenRedraws(15); - $progressBar->maxSecondsBetweenRedraws(30); - } elseif (DIRECTORY_SEPARATOR === '\\') { - // windows - $progressBar->minSecondsBetweenRedraws(0.5); - $progressBar->maxSecondsBetweenRedraws(2); - } else { - // *nix - $progressBar->minSecondsBetweenRedraws(0.1); - $progressBar->maxSecondsBetweenRedraws(0.5); + public function listing(array $items): void + { + foreach ($items as $item) { + $this->record($item); } - $this->progressBar = $progressBar; + if ($this->isQuiet()) { + return; + } - return $progressBar; + $this->outputPrinter->listing($items); + } + + /** + * @param string[] $headers + * @param array> $rows + */ + public function table(array $headers, array $rows): void + { + foreach ($rows as $row) { + $this->record(implode(' ', $row)); + } + + if ($this->isQuiet()) { + return; + } + + $consoleTable = new ConsoleTable($this->outputPrinter); + $consoleTable->render($headers, $rows); + } + + public function ask(string $question, ?string $default = null): ?string + { + return $this->outputPrinter->ask($question, $default); + } + + public function getErrorStyle(): self + { + return $this; + } + + public function progressStart(int $max): void + { + if ($this->isQuiet()) { + return; + } + + $this->progressBar->start($max); } public function progressAdvance(int $step = 1): void { - // hide progress bar in tests - if (defined('PHPUNIT_COMPOSER_INSTALL')) { + if ($this->isQuiet()) { return; } - $progressBar = $this->getProgressBar(); - $progressBar->advance($step); + $this->progressBar->advance($step); } - private function isCiDetected(): bool + private function isQuiet(): bool { - if ($this->isCiDetected === null) { - $ciDetector = new CiDetector(); - $this->isCiDetected = $ciDetector->isCiDetected(); + return $this->verbosity <= self::VERBOSITY_QUIET; + } + + private function record(string $message): void + { + if (! $this->isRecording) { + return; } - return $this->isCiDetected; + $this->recordedMessages[] = $message; } - private function getProgressBar(): ProgressBar + /** + * Translate the former symfony/console tags still emitted by the reporters into + * the smaller tag vocabulary understood by Entropy's OutputColorizer. + */ + private function normalizeTags(string $text): string { - return $this->progressBar ?? throw new RuntimeException('The ProgressBar is not started.'); + $text = (string) preg_replace('#(.*?)#su', '$1', $text); + $text = (string) preg_replace('#(.*?)#su', '$1', $text); + + // grey is not handled by OutputColorizer::colorize(), apply it directly + $text = (string) preg_replace_callback( + '#(.*?)#su', + fn (array $matches): string => $this->outputColorizer->color($matches[1], Color::GREY), + $text + ); + + // normalize explicit closing tags (e.g. ) to the generic closing tag + return (string) preg_replace('##', '', $text); } } diff --git a/src/Console/Style/RectorStyleFactory.php b/src/Console/Style/RectorStyleFactory.php new file mode 100644 index 00000000000..a64961c2a00 --- /dev/null +++ b/src/Console/Style/RectorStyleFactory.php @@ -0,0 +1,46 @@ +setVerbosity(RectorStyle::VERBOSITY_DEBUG); + } + + // disable output for tests + if ($this->isPHPUnitRun()) { + $rectorStyle->setVerbosity(RectorStyle::VERBOSITY_QUIET); + } + + return $rectorStyle; + } + + /** + * Never ever used static methods if not necessary, this is just handy for tests + src to prevent duplication. + */ + private function isPHPUnitRun(): bool + { + return defined('PHPUNIT_COMPOSER_INSTALL') || defined('__PHPUNIT_PHAR__'); + } +} diff --git a/src/Console/Style/SymfonyStyleFactory.php b/src/Console/Style/SymfonyStyleFactory.php deleted file mode 100644 index 55fc6e36be4..00000000000 --- a/src/Console/Style/SymfonyStyleFactory.php +++ /dev/null @@ -1,59 +0,0 @@ -privatesAccessor->callPrivateMethod(new Application(), 'configureIO', [$argvInput, $consoleOutput]); - - // --debug is called - if ($argvInput->hasParameterOption('--debug')) { - $consoleOutput->setVerbosity(OutputInterface::VERBOSITY_DEBUG); - } - - // disable output for tests - if ($this->isPHPUnitRun()) { - $consoleOutput->setVerbosity(OutputInterface::VERBOSITY_QUIET); - } - - // no interactive terminal, e.g. piped output, CI or an agent - never emit ANSI, even if forced via --ansi - if (! TerminalDetector::isOutputTty()) { - $consoleOutput->setDecorated(false); - } - - return new RectorStyle($argvInput, $consoleOutput); - } - - /** - * Never ever used static methods if not necessary, this is just handy for tests + src to prevent duplication. - */ - private function isPHPUnitRun(): bool - { - return defined('PHPUNIT_COMPOSER_INSTALL') || defined('__PHPUNIT_PHAR__'); - } -} diff --git a/src/DependencyInjection/LazyContainerFactory.php b/src/DependencyInjection/LazyContainerFactory.php index 37af84700bc..2b40480d757 100644 --- a/src/DependencyInjection/LazyContainerFactory.php +++ b/src/DependencyInjection/LazyContainerFactory.php @@ -41,9 +41,9 @@ use Rector\Console\Command\SetupCICommand; use Rector\Console\Command\ValidateConfigCommand; use Rector\Console\Command\WorkerCommand; -use Rector\Console\ConsoleApplication; use Rector\Console\Rule\AllRectorRulesProvider; -use Rector\Console\Style\SymfonyStyleFactory; +use Rector\Console\Style\RectorStyle; +use Rector\Console\Style\RectorStyleFactory; use Rector\Contract\PhpParser\DecoratingNodeVisitorInterface; use Rector\NodeNameResolver\NodeNameResolver; use Rector\NodeTypeResolver\DependencyInjection\PHPStanServicesFactory; @@ -72,8 +72,6 @@ use Rector\PHPStanStaticTypeMapper\TypeMapper\UnionTypeMapper; use Rector\PostRector\Application\PostFileProcessor; use Rector\Rector\AbstractRector; -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Style\SymfonyStyle; use Webmozart\Assert\Assert; final class LazyContainerFactory @@ -140,18 +138,6 @@ public function create(): RectorConfig private function registerConsole(RectorConfig $rectorConfig): void { - $rectorConfig->singleton(Application::class, static function (RectorConfig $rectorConfig): Application { - $consoleApplication = $rectorConfig->make(ConsoleApplication::class); - - $commandNamesToHide = ['list', 'completion', 'help', 'worker']; - foreach ($commandNamesToHide as $commandNameToHide) { - $commandToHide = $consoleApplication->get($commandNameToHide); - $commandToHide->setHidden(); - } - - return $consoleApplication; - }); - $rectorConfig->singleton(Inflector::class, static function (): Inflector { $inflectorFactory = new InflectorFactory(); return $inflectorFactory->build(); @@ -280,10 +266,10 @@ private function registerTaggedServices(RectorConfig $rectorConfig): void $rectorConfig->autodiscover(__DIR__ . '/../ChangesReporting/Output'); $rectorConfig->singleton( - SymfonyStyle::class, - static function (RectorConfig $rectorConfig): SymfonyStyle { - $symfonyStyleFactory = $rectorConfig->make(SymfonyStyleFactory::class); - return $symfonyStyleFactory->create(); + RectorStyle::class, + static function (RectorConfig $rectorConfig): RectorStyle { + $rectorStyleFactory = $rectorConfig->make(RectorStyleFactory::class); + return $rectorStyleFactory->create(); } ); diff --git a/src/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php b/src/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php index 8755e439efa..00a3df96c9a 100644 --- a/src/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php +++ b/src/NodeTypeResolver/DependencyInjection/PHPStanServicesFactory.php @@ -15,10 +15,8 @@ use PHPStan\Reflection\ReflectionProvider; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; +use Rector\Console\Style\RectorStyleFactory; use Rector\NodeTypeResolver\Reflection\BetterReflection\SourceLocatorProvider\DynamicSourceLocatorProvider; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Style\SymfonyStyle; use Throwable; use Webmozart\Assert\Assert; @@ -54,8 +52,8 @@ public function __construct() ); } catch (Throwable $throwable) { if ($throwable->getMessage() === "File 'phar://phpstan.phar/conf/bleedingEdge.neon' is missing or is not readable.") { - $symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput()); - $symfonyStyle->error( + $rectorStyle = new RectorStyleFactory()->create(); + $rectorStyle->error( str_replace("\r\n", "\n", sprintf( self::INVALID_BLEEDING_EDGE_PATH_MESSAGE, $throwable->getMessage() diff --git a/src/Parallel/Application/ParallelFileProcessor.php b/src/Parallel/Application/ParallelFileProcessor.php index ccce30cc841..0e23efc6bc0 100644 --- a/src/Parallel/Application/ParallelFileProcessor.php +++ b/src/Parallel/Application/ParallelFileProcessor.php @@ -12,7 +12,7 @@ use React\Socket\TcpServer; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; -use Rector\Console\Command\ProcessCommand; +use Rector\Console\ExitCode; use Rector\Parallel\Command\WorkerCommandLineFactory; use Rector\Parallel\Enum\Action; use Rector\Parallel\Enum\Content; @@ -23,10 +23,9 @@ use Rector\Parallel\ValueObject\ParallelProcess; use Rector\Parallel\ValueObject\ProcessPool; use Rector\Parallel\ValueObject\Schedule; +use Rector\ValueObject\Configuration; use Rector\ValueObject\Error\SystemError; use Rector\ValueObject\ProcessResult; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; use Throwable; /** @@ -52,7 +51,7 @@ public function process( Schedule $schedule, string $mainScript, callable $postFileCallback, - InputInterface $input + Configuration $configuration ): ProcessResult { // reversed, so that array_pop() hands out the biggest files first $jobsPerWorker = array_map( @@ -150,7 +149,7 @@ public function process( &$systemErrorsCount, &$reachedSystemErrorsCountLimit, $mainScript, - $input, + $configuration, $serverPort, $streamSelectLoop, $timeoutInSeconds, @@ -162,9 +161,8 @@ public function process( $workerCommandLine = $this->workerCommandLineFactory->create( $mainScript, - ProcessCommand::class, 'worker', - $input, + $configuration, $processIdentifier, $serverPort, ); @@ -222,7 +220,7 @@ function (array $json) use ( // 3. callable on exit function ($exitCode, string $stdErr) use ($parallelResultCollector, $processIdentifier): void { $this->processPool->tryQuitProcess($processIdentifier); - if ($exitCode === Command::SUCCESS) { + if ($exitCode === ExitCode::SUCCESS) { return; } diff --git a/src/Parallel/Command/WorkerCommandLineFactory.php b/src/Parallel/Command/WorkerCommandLineFactory.php index 87293be531f..e7e5e142921 100644 --- a/src/Parallel/Command/WorkerCommandLineFactory.php +++ b/src/Parallel/Command/WorkerCommandLineFactory.php @@ -7,86 +7,74 @@ use Rector\ChangesReporting\Output\JsonOutputFormatter; use Rector\Configuration\Option; use Rector\FileSystem\FilePathHelper; -use Rector\Parallel\Exception\ParallelShouldNotHappenException; -use Rector\Parallel\Reflection\CommandFromReflectionFactory; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputInterface; +use Rector\ValueObject\Configuration; /** * @see \Rector\Tests\Parallel\Command\WorkerCommandLineFactoryTest - * @todo possibly extract to symplify/easy-parallel */ final readonly class WorkerCommandLineFactory { private const string OPTION_DASHES = '--'; public function __construct( - private CommandFromReflectionFactory $commandFromReflectionFactory, private FilePathHelper $filePathHelper ) { } /** - * @param class-string $mainCommandClass + * Build the worker subprocess command line from the resolved Configuration. + * Options mirror the Entropy run() contract: a flag is "--name", a value option "--name value". */ public function create( string $mainScript, - string $mainCommandClass, string $workerCommandName, - InputInterface $input, + Configuration $configuration, string $identifier, - int $port + int $port, ): string { - $commandArguments = array_slice($_SERVER['argv'], 1); + $workerCommandArray = [escapeshellarg(PHP_BINARY), escapeshellarg($mainScript), $workerCommandName]; - // add implicit "process" command name if missing - if ($commandArguments !== [] && ($commandArguments[0] !== 'process' && $commandArguments[0] !== 'p') && ! defined( - 'PHPUNIT_COMPOSER_INSTALL' - )) { - $commandArguments = array_merge(['process'], $commandArguments); + if ($configuration->isDryRun()) { + $workerCommandArray[] = self::OPTION_DASHES . Option::DRY_RUN; } - $args = [PHP_BINARY, $mainScript, ...$commandArguments]; - $workerCommandArray = []; - - $mainCommand = $this->commandFromReflectionFactory->create($mainCommandClass); - - if ($mainCommand->getName() === null) { - $errorMessage = sprintf('The command name for "%s" is missing', $mainCommand::class); - throw new ParallelShouldNotHappenException($errorMessage); + if ($configuration->shouldClearCache()) { + $workerCommandArray[] = self::OPTION_DASHES . Option::CLEAR_CACHE; } - $mainCommandName = $mainCommand->getName(); - $mainCommandNames = [$mainCommandName, $mainCommandName[0]]; + if ($configuration->isComposerBased()) { + $workerCommandArray[] = self::OPTION_DASHES . Option::COMPOSER_BASED; + } - foreach ($args as $arg) { - // skip command name - if (in_array($arg, $mainCommandNames, true)) { - break; - } + if ($configuration->isPhpOnly()) { + $workerCommandArray[] = self::OPTION_DASHES . Option::PHP; + } - $workerCommandArray[] = escapeshellarg((string) $arg); + // narrow the run to the same rules + foreach ($configuration->getOnlyRules() as $onlyRule) { + $workerCommandArray[] = self::OPTION_DASHES . Option::ONLY; + $workerCommandArray[] = escapeshellarg($onlyRule); } - $workerCommandArray[] = $workerCommandName; + $onlySuffix = $configuration->getOnlySuffix(); + if ($onlySuffix !== null && $onlySuffix !== '') { + $workerCommandArray[] = self::OPTION_DASHES . Option::ONLY_SUFFIX; + $workerCommandArray[] = escapeshellarg($onlySuffix); + } - $mainCommandOptionNames = $this->getCommandOptionNames($mainCommand); - $workerCommandOptions = $this->mirrorCommandOptions($input, $mainCommandOptionNames); - $workerCommandArray = array_merge($workerCommandArray, $workerCommandOptions); + $memoryLimit = $configuration->getMemoryLimit(); + if ($memoryLimit !== null && $memoryLimit !== '') { + // "=" form, so a negative value like -1 is not parsed as another option + $workerCommandArray[] = self::OPTION_DASHES . Option::MEMORY_LIMIT . '=' . escapeshellarg($memoryLimit); + } // for TCP local server - $workerCommandArray[] = '--port'; - $workerCommandArray[] = $port; + $workerCommandArray[] = self::OPTION_DASHES . Option::PARALLEL_PORT; + $workerCommandArray[] = (string) $port; - $workerCommandArray[] = '--identifier'; + $workerCommandArray[] = self::OPTION_DASHES . Option::PARALLEL_IDENTIFIER; $workerCommandArray[] = escapeshellarg($identifier); - /** @var string[] $paths */ - $paths = $input->getArgument(Option::SOURCE); - foreach ($paths as $path) { - $workerCommandArray[] = escapeshellarg($path); - } - // set json output $workerCommandArray[] = self::OPTION_DASHES . Option::OUTPUT_FORMAT; $workerCommandArray[] = escapeshellarg(JsonOutputFormatter::NAME); @@ -95,118 +83,44 @@ public function create( // @see https://github.com/symfony/symfony/issues/1238 $workerCommandArray[] = '--no-ansi'; - // Only pass --config if explicitly set via command line - // If not set, the worker will resolve config using RectorConfigsResolver fallback mechanism - if ($input->hasOption(Option::CONFIG)) { - $configValue = $input->getOption(Option::CONFIG); - if (is_string($configValue) && $configValue !== '') { - $workerCommandArray[] = '--config'; - /** - * On parallel, the command is generated with `--config` addition - * Using escapeshellarg() to ensure the --config path escaped, even when it has a space. - * - * eg: - * --config /path/e2e/parallel with space/rector.php - * - * that can cause error: - * - * File /rector-src/e2e/parallel\" was not found - * - * the escaped result is: - * - * --config '/path/e2e/parallel with space/rector.php' - * - * tested in macOS and Ubuntu (github action) - */ - $config = $configValue; - $workerCommandArray[] = escapeshellarg($this->filePathHelper->relativePath($config)); - } + // only pass --config if explicitly set via command line; otherwise the worker resolves it + // through the RectorConfigsResolver fallback, same as the main process + $config = $this->resolveConfigFromArgv(); + if ($config !== null) { + $workerCommandArray[] = self::OPTION_DASHES . Option::CONFIG; + $workerCommandArray[] = escapeshellarg($this->filePathHelper->relativePath($config)); } - if ((bool) $input->getOption(Option::COMPOSER_BASED)) { - $workerCommandArray[] = self::OPTION_DASHES . Option::COMPOSER_BASED; + // paths last, as positional arguments + foreach ($configuration->getPaths() as $path) { + $workerCommandArray[] = escapeshellarg($path); } return implode(' ', $workerCommandArray); } - private function shouldSkipOption(InputInterface $input, string $optionName): bool - { - if (! $input->hasOption($optionName)) { - return true; - } - - // skip output format and config, handled separately in create() - return $optionName === Option::OUTPUT_FORMAT || $optionName === Option::CONFIG; - } - /** - * @return string[] + * Resolve "--config ", "--config=", "-c " or "-c=" from raw argv. */ - private function getCommandOptionNames(Command $command): array + private function resolveConfigFromArgv(): ?string { - $inputDefinition = $command->getDefinition(); + $argv = $_SERVER['argv'] ?? []; - $optionNames = []; - foreach ($inputDefinition->getOptions() as $inputOption) { - $optionNames[] = $inputOption->getName(); - } - - return $optionNames; - } - - /** - * Keeps all options that are allowed in check command options - * - * @param string[] $mainCommandOptionNames - * @return string[] - */ - private function mirrorCommandOptions(InputInterface $input, array $mainCommandOptionNames): array - { - $workerCommandOptions = []; - - foreach ($mainCommandOptionNames as $mainCommandOptionName) { - if ($this->shouldSkipOption($input, $mainCommandOptionName)) { - continue; - } - - /** @var bool|string|string[]|null $optionValue */ - $optionValue = $input->getOption($mainCommandOptionName); - - // skip clutter - if ($optionValue === null) { - continue; + foreach ($argv as $index => $arg) { + if ($arg === self::OPTION_DASHES . Option::CONFIG || $arg === '-c') { + $nextArg = $argv[$index + 1] ?? null; + return is_string($nextArg) ? $nextArg : null; } - if (is_bool($optionValue)) { - if ($optionValue) { - $workerCommandOptions[] = self::OPTION_DASHES . $mainCommandOptionName; - } - - continue; - } - - // array options (e.g. repeated --only) are mirrored one flag per value - if (is_array($optionValue)) { - foreach ($optionValue as $singleOptionValue) { - $workerCommandOptions[] = self::OPTION_DASHES . $mainCommandOptionName; - $workerCommandOptions[] = \escapeshellarg($singleOptionValue); - } - - continue; + if (is_string($arg) && str_starts_with($arg, self::OPTION_DASHES . Option::CONFIG . '=')) { + return substr($arg, strlen(self::OPTION_DASHES . Option::CONFIG . '=')); } - if ($mainCommandOptionName === 'memory-limit') { - // symfony/console does not accept -1 as value without assign - $workerCommandOptions[] = self::OPTION_DASHES . $mainCommandOptionName . '=' . \escapeshellarg( - $optionValue - ); - } else { - $workerCommandOptions[] = self::OPTION_DASHES . $mainCommandOptionName; - $workerCommandOptions[] = \escapeshellarg($optionValue); + if (is_string($arg) && str_starts_with($arg, '-c=')) { + return substr($arg, strlen('-c=')); } } - return $workerCommandOptions; + return null; } } diff --git a/src/Parallel/Reflection/CommandFromReflectionFactory.php b/src/Parallel/Reflection/CommandFromReflectionFactory.php deleted file mode 100644 index f233ad09714..00000000000 --- a/src/Parallel/Reflection/CommandFromReflectionFactory.php +++ /dev/null @@ -1,37 +0,0 @@ - $className - */ - public function create(string $className): Command - { - $commandReflectionClass = new ReflectionClass($className); - - $command = $commandReflectionClass->newInstanceWithoutConstructor(); - $parentClassReflection = $commandReflectionClass->getParentClass(); - - if (! $parentClassReflection instanceof ReflectionClass) { - throw new ParallelShouldNotHappenException(); - } - - $parentConstructorReflectionMethod = $parentClassReflection->getConstructor(); - if (! $parentConstructorReflectionMethod instanceof ReflectionMethod) { - throw new ParallelShouldNotHappenException(); - } - - $parentConstructorReflectionMethod->invoke($command); - - return $command; - } -} diff --git a/src/Reporting/DeprecatedRulesReporter.php b/src/Reporting/DeprecatedRulesReporter.php index 36d64aee3b0..f96f868c7b7 100644 --- a/src/Reporting/DeprecatedRulesReporter.php +++ b/src/Reporting/DeprecatedRulesReporter.php @@ -7,12 +7,12 @@ use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; -use Symfony\Component\Console\Style\SymfonyStyle; +use Rector\Console\Style\RectorStyle; final readonly class DeprecatedRulesReporter { public function __construct( - private SymfonyStyle $symfonyStyle + private RectorStyle $rectorStyle ) { } @@ -27,7 +27,7 @@ public function reportDeprecatedRules(): int continue; } - $this->symfonyStyle->warning( + $this->rectorStyle->warning( sprintf( 'Registered rule "%s" is deprecated and will be removed. Upgrade your config to use another rule or remove it', $registeredRectorRule @@ -50,7 +50,7 @@ public function reportDeprecatedSkippedRules(): int continue; } - $this->symfonyStyle->warning(sprintf('Skipped rule "%s" is deprecated', $skippedRectorRule)); + $this->rectorStyle->warning(sprintf('Skipped rule "%s" is deprecated', $skippedRectorRule)); ++$reportedCount; } @@ -64,7 +64,7 @@ public function reportDeprecatedCacheMetaExtensions(): int $reportedCount = 0; foreach ($cacheMetaExtensions as $cacheMetumExtension) { - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'Cache meta extension "%s" is deprecated and no longer applied. It is a niche mechanism, let Rector handle cache on its own. If custom invalidation is needed, handle it in CI in a more generic way, e.g. by clearing the cache directory.', $cacheMetumExtension )); @@ -83,7 +83,7 @@ public function reportDeprecatedPhpSetsMethods(): int $reportedCount = 0; foreach (array_unique($deprecatedPhpSetsMethods) as $deprecatedPhpSetsMethod) { - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'The "->%s()" method is deprecated and no longer applied. Use "->withPhpLevel()" instead, to raise PHP level one rule at a time.', $deprecatedPhpSetsMethod )); @@ -102,7 +102,7 @@ public function reportDeprecatedAttributesSetsArgs(): int $reportedCount = 0; foreach (array_unique($deprecatedAttributesSetsArgs) as $deprecatedAttributesSetsArg) { - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'The "->withAttributesSets(%s: true)" argument is deprecated and no longer applied. It is already included in the "symfony: true" argument, use it instead.', $deprecatedAttributesSetsArg )); @@ -121,7 +121,7 @@ public function reportDeprecatedComposerBasedArgs(): int $reportedCount = 0; foreach (array_unique($deprecatedComposerBasedArgs) as $deprecatedComposerBasedArg) { - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'The "->withComposerBased(%s: true)" argument is deprecated and no longer applied. It only added named args to 2 methods of a single package, register the rule directly if needed.', $deprecatedComposerBasedArg )); diff --git a/src/Reporting/MissConfigurationReporter.php b/src/Reporting/MissConfigurationReporter.php index 3a137588b65..95b449fe537 100644 --- a/src/Reporting/MissConfigurationReporter.php +++ b/src/Reporting/MissConfigurationReporter.php @@ -7,10 +7,10 @@ use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\Configuration\VendorMissAnalyseGuard; +use Rector\Console\Style\RectorStyle; use Rector\Contract\Rector\RectorInterface; use Rector\PostRector\Contract\Rector\PostRectorInterface; use Rector\ValueObject\ProcessResult; -use Symfony\Component\Console\Style\SymfonyStyle; /** * @see \Rector\Tests\Reporting\MissConfigurationReporterTest @@ -18,7 +18,7 @@ final readonly class MissConfigurationReporter { public function __construct( - private SymfonyStyle $symfonyStyle, + private RectorStyle $rectorStyle, private VendorMissAnalyseGuard $vendorMissAnalyseGuard, private UnusedSkipResolver $unusedSkipResolver, ) { @@ -35,7 +35,7 @@ public function reportUnusedSkips(ProcessResult $processResult): void return; } - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( '%s never matched any element. You can remove %s from "->withSkip()"', count($unusedSkips) > 1 ? 'These skips are unused, they' : 'This skip is unused, it', count($unusedSkips) > 1 ? 'them' : 'it' @@ -44,7 +44,7 @@ public function reportUnusedSkips(ProcessResult $processResult): void // add a blank line between items, so grouped rule skips stay visually separated $spacedUnusedSkips = array_map(static fn (string $unusedSkip): string => $unusedSkip . "\n", $unusedSkips); - $this->symfonyStyle->listing($spacedUnusedSkips); + $this->rectorStyle->listing($spacedUnusedSkips); } public function reportSkippedNeverRegisteredRules(): int @@ -64,13 +64,13 @@ public function reportSkippedNeverRegisteredRules(): int return 0; } - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( '%s never registered. You can remove %s from "->withSkip()"', count($neverRegisteredSkippedRules) > 1 ? 'These skipped rules are' : 'This skipped rule is', count($neverRegisteredSkippedRules) > 1 ? 'them' : 'it' )); - $this->symfonyStyle->listing($neverRegisteredSkippedRules); + $this->rectorStyle->listing($neverRegisteredSkippedRules); return count($neverRegisteredSkippedRules); } @@ -85,7 +85,7 @@ public function reportSkippedNonRectorClasses(): int return 0; } - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( '%s "%s" %s not a Rector rule, so %s never be skipped. Only classes that implement "%s" can be used in "->withSkip()"', count($skippedNonRectorClasses) > 1 ? 'These skipped classes' : 'This skipped class', implode('", "', $skippedNonRectorClasses), @@ -106,7 +106,7 @@ public function reportVendorInPaths(array $filePaths): void return; } - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'Rector has detected a "/vendor" directory in your configured paths. If this is Composer\'s vendor directory, this is not necessary as it will be autoloaded. Scanning the Composer /vendor directory will cause Rector to run much slower and possibly with errors.%sRemove "/vendor" from Rector paths and run again.', "\n\n" )); @@ -124,7 +124,7 @@ public function reportStartWithShortOpenTag(): void $suffix = count($files) > 1 ? 's were' : ' was'; $fileList = implode("\n", $files); - $this->symfonyStyle->warning(sprintf( + $this->rectorStyle->warning(sprintf( 'The following file%s skipped as starting with short open tag. Migrate to long open PHP tag first: %s%s', $suffix, "\n\n", diff --git a/src/Util/NodePrinter.php b/src/Util/NodePrinter.php index e914692dcc4..91ca51be2e7 100644 --- a/src/Util/NodePrinter.php +++ b/src/Util/NodePrinter.php @@ -6,8 +6,8 @@ use Nette\Utils\Strings; use PhpParser\Node; +use Rector\Console\Style\RectorStyle; use Rector\CustomRules\SimpleNodeDumper; -use Symfony\Component\Console\Style\SymfonyStyle; final readonly class NodePrinter { @@ -22,7 +22,7 @@ private const string PROPERTY_KEY_REGEX = '#(?[\w\d]+)\:#'; public function __construct( - private SymfonyStyle $symfonyStyle + private RectorStyle $rectorStyle ) { } @@ -35,9 +35,9 @@ public function printNodes(Node|array $nodes): void // colorize $colorContents = $this->addConsoleColors($dumpedNodesContents); - $this->symfonyStyle->writeln($colorContents); + $this->rectorStyle->writeln($colorContents); - $this->symfonyStyle->newLine(); + $this->rectorStyle->newLine(); } private function addConsoleColors(string $contents): string diff --git a/src/functions/node_helper.php b/src/functions/node_helper.php index d0d69a2ca36..d0d11a67bdf 100644 --- a/src/functions/node_helper.php +++ b/src/functions/node_helper.php @@ -4,11 +4,10 @@ use PhpParser\Node; use PhpParser\PrettyPrinter\Standard; -use Rector\Console\Style\SymfonyStyleFactory; +use Rector\Console\Style\RectorStyle; +use Rector\Console\Style\RectorStyleFactory; use Rector\PhpParser\Node\FileNode; use Rector\Util\NodePrinter; -use Rector\Util\Reflection\PrivatesAccessor; -use Symfony\Component\Console\Output\OutputInterface; if (! function_exists('print_node')) { /** @@ -36,12 +35,12 @@ function print_node(Node|array $node): void */ function dump_node(Node|array $node): void { - $symfonyStyleFactory = new SymfonyStyleFactory(new PrivatesAccessor()); - $rectorStyle = $symfonyStyleFactory->create(); + $rectorStyleFactory = new RectorStyleFactory(); + $rectorStyle = $rectorStyleFactory->create(); // we turn up the verbosity so it's visible in tests overriding the // default which is to be quite during tests - $rectorStyle->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + $rectorStyle->setVerbosity(RectorStyle::VERBOSITY_VERBOSE); $rectorStyle->newLine(); $nodePrinter = new NodePrinter($rectorStyle); diff --git a/tests/Bin/RectorTest.php b/tests/Bin/RectorTest.php index 44658f11723..a00ac8f3c94 100644 --- a/tests/Bin/RectorTest.php +++ b/tests/Bin/RectorTest.php @@ -22,7 +22,7 @@ public static function outputProvider(): Iterator ]; yield 'Exception with previous console output' => [ 'command' => PHP_BINARY . ' bin/rector -c tests/Bin/config/incorrect-phpstan-files.php', - 'expectedOutput' => PHP_EOL . " [ERROR] Unexpected item 'parameters › invalidParameters'. " . PHP_EOL . PHP_EOL, + 'expectedOutput' => PHP_EOL . "[ERROR] Unexpected item 'parameters › invalidParameters'." . PHP_EOL, ]; yield 'Exception with previous console output in JSON format' => [ 'command' => PHP_BINARY . ' bin/rector -c tests/Bin/config/incorrect-phpstan-files.php --output-format json', diff --git a/tests/Configuration/ConfigurationFactoryTest.php b/tests/Configuration/ConfigurationFactoryTest.php index adae31167c1..1cf35a33a45 100644 --- a/tests/Configuration/ConfigurationFactoryTest.php +++ b/tests/Configuration/ConfigurationFactoryTest.php @@ -4,14 +4,13 @@ namespace Rector\Tests\Configuration; +use Rector\ChangesReporting\Output\ConsoleOutputFormatter; use Rector\Configuration\ConfigurationFactory; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; -use Rector\Console\ProcessConfigureDecorator; use Rector\FileSystem\FilesFinder; use Rector\Testing\PHPUnit\AbstractLazyTestCase; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\ArrayInput; +use Rector\ValueObject\Configuration; final class ConfigurationFactoryTest extends AbstractLazyTestCase { @@ -59,8 +58,7 @@ public function testPublishesCommandLinePathsAsSourceParameter(): void { SimpleParameterProvider::setParameter(Option::PATHS, [__DIR__ . '/config']); - $this->make(ConfigurationFactory::class) - ->createFromInput($this->createInput([__DIR__ . '/Source'])); + $this->createConfiguration([__DIR__ . '/Source']); $this->assertSame( [__DIR__ . '/Source'], @@ -72,8 +70,7 @@ public function testPublishesConfiguredPathsAsSourceParameterWhenNoneGivenOnTheC { SimpleParameterProvider::setParameter(Option::PATHS, [__DIR__ . '/config']); - $this->make(ConfigurationFactory::class) - ->createFromInput($this->createInput([])); + $this->createConfiguration([]); $this->assertSame( [__DIR__ . '/config'], @@ -96,13 +93,27 @@ public function testSourceParameterIsOutsideTheCacheInvalidationHash(): void /** * @param string[] $paths */ - private function createInput(array $paths): ArrayInput + private function createConfiguration(array $paths): Configuration { - $command = new Command('process'); - ProcessConfigureDecorator::decorate($command); - - return new ArrayInput([ - Option::SOURCE => $paths, - ], $command->getDefinition()); + return $this->make(ConfigurationFactory::class) + ->create( + $paths, + false, + false, + false, + false, + ConsoleOutputFormatter::NAME, + [], + false, + false, + null, + null, + false, + null, + '', + '', + null, + false, + ); } } diff --git a/tests/Console/Command/ComposerBasedCommandTest.php b/tests/Console/Command/ComposerBasedCommandTest.php index ec9448d7861..09abb502d2a 100644 --- a/tests/Console/Command/ComposerBasedCommandTest.php +++ b/tests/Console/Command/ComposerBasedCommandTest.php @@ -10,20 +10,17 @@ use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\Console\Command\ComposerBasedCommand; +use Rector\Console\Style\RectorStyle; +use Rector\Console\Style\RectorStyleFactory; use Rector\Renaming\Rector\Name\RenameClassRector; use Rector\Tests\Console\Command\Source\ComposerBoundRector; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\Console\Style\SymfonyStyle; final class ComposerBasedCommandTest extends TestCase { - private BufferedOutput $bufferedOutput; + private RectorStyle $rectorStyle; protected function setUp(): void { - $this->bufferedOutput = new BufferedOutput(); - // the parameter provider is static, reset it between tests SimpleParameterProvider::setParameter(Option::COMPOSER_BOUND_RULE_CONFIGURATIONS, []); } @@ -38,9 +35,9 @@ public function testName(): void public function testSkipWithoutComposerBoundRules(): void { $composerBasedCommand = $this->createComposerBasedCommand([]); - $composerBasedCommand->run(new ArrayInput([]), $this->bufferedOutput); + $composerBasedCommand->run(); - $this->assertStringContainsString('No composer package bound rule is loaded', $this->bufferedOutput->fetch()); + $this->assertStringContainsString('No composer package bound rule is loaded', $this->rectorStyle->getRecordedOutput()); } public function testActiveRule(): void @@ -49,9 +46,9 @@ public function testActiveRule(): void $composerBoundRector = new ComposerBoundRector('phpunit/phpunit', '>=9.0'); $composerBasedCommand = $this->createComposerBasedCommand([$composerBoundRector]); - $composerBasedCommand->run(new ArrayInput([]), $this->bufferedOutput); + $composerBasedCommand->run(); - $output = $this->bufferedOutput->fetch(); + $output = $this->rectorStyle->getRecordedOutput(); $this->assertStringContainsString('phpunit/phpunit', $output); $this->assertStringContainsString('>=9.0', $output); @@ -63,9 +60,9 @@ public function testNotInstalledPackage(): void $composerBoundRector = new ComposerBoundRector('not-installed/package', '>=1.0'); $composerBasedCommand = $this->createComposerBasedCommand([$composerBoundRector]); - $composerBasedCommand->run(new ArrayInput([]), $this->bufferedOutput); + $composerBasedCommand->run(); - $output = $this->bufferedOutput->fetch(); + $output = $this->rectorStyle->getRecordedOutput(); $this->assertStringContainsString('not-installed/package', $output); $this->assertStringContainsString('0 of 1 composer package bound items are active', $output); @@ -86,9 +83,9 @@ public function testRuleConfiguration(): void ); $composerBasedCommand = $this->createComposerBasedCommand([]); - $composerBasedCommand->run(new ArrayInput([]), $this->bufferedOutput); + $composerBasedCommand->run(); - $output = $this->bufferedOutput->fetch(); + $output = $this->rectorStyle->getRecordedOutput(); $this->assertStringContainsString('Composer package bound rule configuration', $output); $this->assertStringContainsString('SomeOldClass: SomeNewClass', $output); @@ -109,9 +106,9 @@ public function testInactiveRuleConfigurationIsReported(): void ); $composerBasedCommand = $this->createComposerBasedCommand([]); - $composerBasedCommand->run(new ArrayInput([]), $this->bufferedOutput); + $composerBasedCommand->run(); - $output = $this->bufferedOutput->fetch(); + $output = $this->rectorStyle->getRecordedOutput(); $this->assertStringContainsString('not-installed/package', $output); $this->assertStringContainsString('0 of 1 composer package bound items are active', $output); @@ -122,8 +119,9 @@ public function testInactiveRuleConfigurationIsReported(): void */ private function createComposerBasedCommand(array $rectors): ComposerBasedCommand { - $symfonyStyle = new SymfonyStyle(new ArrayInput([]), $this->bufferedOutput); + $this->rectorStyle = new RectorStyleFactory()->create(); + $this->rectorStyle->startRecording(); - return new ComposerBasedCommand($symfonyStyle, new InstalledPackageResolver(getcwd()), $rectors); + return new ComposerBasedCommand($this->rectorStyle, new InstalledPackageResolver(getcwd()), $rectors); } } diff --git a/tests/Console/Command/ValidateConfigCommandTest.php b/tests/Console/Command/ValidateConfigCommandTest.php index c2b2cf61550..5bba38a52c9 100644 --- a/tests/Console/Command/ValidateConfigCommandTest.php +++ b/tests/Console/Command/ValidateConfigCommandTest.php @@ -10,6 +10,7 @@ use Rector\Configuration\VendorMissAnalyseGuard; use Rector\Console\Command\ValidateConfigCommand; use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyleFactory; use Rector\Php85\Rector\FuncCall\OrdSingleByteRector; use Rector\Reporting\DeprecatedRulesReporter; use Rector\Reporting\MissConfigurationReporter; @@ -17,14 +18,10 @@ use Rector\Skipper\Skipper\Skipper; use Rector\Testing\PHPUnit\AbstractLazyTestCase; use Rector\Tests\Console\Command\Source\DeprecatedFixtureRule; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Console\Tester\CommandTester; final class ValidateConfigCommandTest extends AbstractLazyTestCase { - private CommandTester $commandTester; + private ValidateConfigCommand $validateConfigCommand; protected function setUp(): void { @@ -46,20 +43,18 @@ protected function setUp(): void SimpleParameterProvider::setParameter($optionName, []); } - $symfonyStyle = new SymfonyStyle(new ArrayInput([]), new BufferedOutput()); + $rectorStyle = new RectorStyleFactory()->create(); - $validateConfigCommand = new ValidateConfigCommand( - $symfonyStyle, - new DeprecatedRulesReporter($symfonyStyle), + $this->validateConfigCommand = new ValidateConfigCommand( + $rectorStyle, + new DeprecatedRulesReporter($rectorStyle), new MissConfigurationReporter( - $symfonyStyle, + $rectorStyle, new VendorMissAnalyseGuard(), $this->make(UnusedSkipResolver::class), ), $this->make(Skipper::class), ); - - $this->commandTester = new CommandTester($validateConfigCommand); } protected function tearDown(): void @@ -71,14 +66,14 @@ public function testFailsOnDeprecatedRegisteredRule(): void { SimpleParameterProvider::setParameter(Option::REGISTERED_RECTOR_RULES, [DeprecatedFixtureRule::class]); - $this->assertSame(ExitCode::FAILURE, $this->commandTester->execute([])); + $this->assertSame(ExitCode::FAILURE, $this->validateConfigCommand->run()); } public function testSucceedsOnCleanConfig(): void { SimpleParameterProvider::setParameter(Option::REGISTERED_RECTOR_RULES, [OrdSingleByteRector::class]); - $this->assertSame(ExitCode::SUCCESS, $this->commandTester->execute([])); + $this->assertSame(ExitCode::SUCCESS, $this->validateConfigCommand->run()); } public function testJsonOutputOnCleanConfig(): void @@ -86,9 +81,7 @@ public function testJsonOutputOnCleanConfig(): void SimpleParameterProvider::setParameter(Option::REGISTERED_RECTOR_RULES, [OrdSingleByteRector::class]); ob_start(); - $exitCode = $this->commandTester->execute([ - '--output-format' => 'json', - ]); + $exitCode = $this->validateConfigCommand->run(outputFormat: 'json'); $json = (string) ob_get_clean(); $this->assertSame(ExitCode::SUCCESS, $exitCode); @@ -103,9 +96,7 @@ public function testJsonOutputOnDeprecatedRegisteredRule(): void SimpleParameterProvider::setParameter(Option::REGISTERED_RECTOR_RULES, [DeprecatedFixtureRule::class]); ob_start(); - $exitCode = $this->commandTester->execute([ - '--output-format' => 'json', - ]); + $exitCode = $this->validateConfigCommand->run(outputFormat: 'json'); $json = (string) ob_get_clean(); $this->assertSame(ExitCode::FAILURE, $exitCode); diff --git a/tests/Parallel/Command/WorkerCommandLineFactoryTest.php b/tests/Parallel/Command/WorkerCommandLineFactoryTest.php index 3f12115eedd..f61618eefc8 100644 --- a/tests/Parallel/Command/WorkerCommandLineFactoryTest.php +++ b/tests/Parallel/Command/WorkerCommandLineFactoryTest.php @@ -7,226 +7,137 @@ use Iterator; use PHPUnit\Framework\Attributes\DataProvider; use Rector\ChangesReporting\Output\ConsoleOutputFormatter; +use Rector\Configuration\ConfigurationFactory; use Rector\Configuration\Option; -use Rector\Console\Command\ProcessCommand; +use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\Parallel\Command\WorkerCommandLineFactory; use Rector\Testing\PHPUnit\AbstractLazyTestCase; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputDefinition; +use Rector\ValueObject\Configuration; final class WorkerCommandLineFactoryTest extends AbstractLazyTestCase { - private const string COMMAND = 'command'; - private const string DUMMY_MAIN_SCRIPT = 'main_script'; private const string SPACED_DUMMY_MAIN_SCRIPT = 'C:\Users\P\Desktop\Web Dev\vendor\bin\rector'; private WorkerCommandLineFactory $workerCommandLineFactory; - private ProcessCommand $processCommand; + private ConfigurationFactory $configurationFactory; protected function setUp(): void { parent::setUp(); $this->workerCommandLineFactory = $this->make(WorkerCommandLineFactory::class); - $this->processCommand = $this->make(ProcessCommand::class); + $this->configurationFactory = $this->make(ConfigurationFactory::class); } - /** - * @param array $inputParameters - */ - #[DataProvider('provideDataSpacedMainScript')] - public function testSpacedMainScript(array $inputParameters, string $expectedCommand): void + protected function tearDown(): void + { + SimpleParameterProvider::setParameter(Option::IS_RUN_NARROWED, false); + SimpleParameterProvider::setParameter(Option::SOURCE, []); + } + + public function testSpacedMainScript(): void { - $inputDefinition = $this->prepareProcessCommandDefinition(); - $arrayInput = new ArrayInput($inputParameters, $inputDefinition); + $configuration = $this->createConfiguration(); $workerCommandLine = $this->workerCommandLineFactory->create( self::SPACED_DUMMY_MAIN_SCRIPT, - ProcessCommand::class, 'worker', - $arrayInput, + $configuration, 'identifier', 2000 ); - $expectedCommand = $this->normalizeExpectedCommandOutput($expectedCommand); - $expectedCommand = $this->cleanUpEmptyQuoteExpectedCommandOutput($expectedCommand); + $expectedCommand = $this->normalizeExpectedCommandOutput(sprintf( + "'%s' '%s' worker --port 2000 --identifier 'identifier' --output-format 'json' --no-ansi 'src'", + PHP_BINARY, + self::SPACED_DUMMY_MAIN_SCRIPT + )); $this->assertSame($expectedCommand, $workerCommandLine); } /** - * @return Iterator>|string[]> - */ - public static function provideDataSpacedMainScript(): Iterator - { - $cliInputOptions = array_slice($_SERVER['argv'], 1); - $cliInputOptionsAsString = implode("' '", $cliInputOptions); - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - ], - "'" . PHP_BINARY . "' '" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - '--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME, - ], - "'" . PHP_BINARY . "' '" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - '--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME, - '--' . Option::DEBUG => true, - ], - "'" . PHP_BINARY . "' '" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - '--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME, - '--' . Option::DEBUG => true, - '--' . Option::XDEBUG => true, - ], - "'" . PHP_BINARY . "' '" . self::SPACED_DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --xdebug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; - } - - /** - * @param array $inputParameters + * @param array{dryRun?: bool, php?: bool, memoryLimit?: ?string} $options */ #[DataProvider('provideData')] - public function test(array $inputParameters, string $expectedCommand): void + public function test(array $options, string $expectedOptionsSegment): void { - $inputDefinition = $this->prepareProcessCommandDefinition(); - $arrayInput = new ArrayInput($inputParameters, $inputDefinition); + $configuration = $this->createConfiguration( + $options['dryRun'] ?? false, + $options['php'] ?? false, + $options['memoryLimit'] ?? null + ); $workerCommandLine = $this->workerCommandLineFactory->create( self::DUMMY_MAIN_SCRIPT, - ProcessCommand::class, 'worker', - $arrayInput, + $configuration, 'identifier', 2000 ); - $expectedCommand = $this->normalizeExpectedCommandOutput($expectedCommand); - $expectedCommand = $this->cleanUpEmptyQuoteExpectedCommandOutput($expectedCommand); + $expectedCommand = $this->normalizeExpectedCommandOutput(sprintf( + "'%s' '%s' worker%s --port 2000 --identifier 'identifier' --output-format 'json' --no-ansi 'src'", + PHP_BINARY, + self::DUMMY_MAIN_SCRIPT, + $expectedOptionsSegment + )); $this->assertSame($expectedCommand, $workerCommandLine); } /** - * @return Iterator>|string[]> + * @return Iterator, string}> */ public static function provideData(): Iterator { - $cliInputOptions = array_slice($_SERVER['argv'], 1); - $cliInputOptionsAsString = implode("' '", $cliInputOptions); - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - ], - "'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - '--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME, - ], - "'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - '--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME, - '--' . Option::DEBUG => true, - ], - "'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - '--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME, - '--' . Option::DEBUG => true, - '--' . Option::XDEBUG => true, - ], - "'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --debug --xdebug --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - '--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME, - '--' . Option::MEMORY_LIMIT => '-1', - ], - "'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --memory-limit='-1' --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; - - yield [ - [ - self::COMMAND => 'process', - Option::SOURCE => ['src'], - '--' . Option::OUTPUT_FORMAT => ConsoleOutputFormatter::NAME, - '--' . Option::PHP => true, - ], - "'" . PHP_BINARY . "' '" . self::DUMMY_MAIN_SCRIPT . "' '" . $cliInputOptionsAsString . "' worker --php --port 2000 --identifier 'identifier' 'src' --output-format 'json' --no-ansi", - ]; + yield [[], '']; + yield [[ + 'dryRun' => true, + ], ' --dry-run']; + yield [[ + 'php' => true, + ], ' --php']; + yield [[ + 'memoryLimit' => '-1', + ], " --memory-limit='-1'"]; } - private function cleanUpEmptyQuoteExpectedCommandOutput(string $result): string - { - if ($this->isWindows()) { - return str_replace(' "" ', ' ', $result); - } - - return str_replace(" '' ", ' ', $result); + private function createConfiguration( + bool $dryRun = false, + bool $php = false, + ?string $memoryLimit = null + ): Configuration { + return $this->configurationFactory->create( + ['src'], + $dryRun, + false, + false, + false, + ConsoleOutputFormatter::NAME, + [], + false, + $php, + null, + null, + false, + $memoryLimit, + '', + '', + null, + false, + ); } private function normalizeExpectedCommandOutput(string $command): string { - if ($this->isWindows()) { + if (DIRECTORY_SEPARATOR === '\\') { return str_replace("'", '"', $command); } return $command; } - - private function prepareProcessCommandDefinition(): InputDefinition - { - // clone the object as we should not modify a object taken from the DI container - $inputDefinition = clone $this->processCommand->getDefinition(); - - // not sure why, but the 1st argument "command" is missing; this is needed for a command name - $arguments = $inputDefinition->getArguments(); - $commandInputArgument = new InputArgument(self::COMMAND, InputArgument::REQUIRED); - $arguments = [$commandInputArgument, ...$arguments]; - - $inputDefinition->setArguments($arguments); - - return $inputDefinition; - } } diff --git a/tests/Reporting/MissConfigurationReporterTest.php b/tests/Reporting/MissConfigurationReporterTest.php index 50c1a8cdcc5..18541f110b7 100644 --- a/tests/Reporting/MissConfigurationReporterTest.php +++ b/tests/Reporting/MissConfigurationReporterTest.php @@ -7,6 +7,8 @@ use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\Configuration\VendorMissAnalyseGuard; +use Rector\Console\Style\RectorStyle; +use Rector\Console\Style\RectorStyleFactory; use Rector\Php85\Rector\FuncCall\OrdSingleByteRector; use Rector\PostRector\Rector\NameImportingPostRector; use Rector\Reporting\MissConfigurationReporter; @@ -18,9 +20,6 @@ use Rector\Tests\Skipper\Skipper\Source\NotSkippedClass; use Rector\Validation\RectorConfigValidator; use Rector\ValueObject\ProcessResult; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\Console\Style\SymfonyStyle; final class MissConfigurationReporterTest extends AbstractLazyTestCase { @@ -30,7 +29,7 @@ final class MissConfigurationReporterTest extends AbstractLazyTestCase private const string GLOBAL_MASK = '*/global-mask/*'; - private BufferedOutput $bufferedOutput; + private RectorStyle $rectorStyle; private MissConfigurationReporter $missConfigurationReporter; @@ -38,11 +37,11 @@ protected function setUp(): void { parent::setUp(); - $this->bufferedOutput = new BufferedOutput(); - $symfonyStyle = new SymfonyStyle(new ArrayInput([]), $this->bufferedOutput); + $this->rectorStyle = new RectorStyleFactory()->create(); + $this->rectorStyle->startRecording(); $this->missConfigurationReporter = new MissConfigurationReporter( - $symfonyStyle, + $this->rectorStyle, new VendorMissAnalyseGuard(), $this->make(UnusedSkipResolver::class), ); @@ -79,7 +78,7 @@ public function testReportsSkippedNonRectorClass(): void $this->missConfigurationReporter->reportSkippedNonRectorClasses(); - $output = $this->bufferedOutput->fetch(); + $output = $this->rectorStyle->getRecordedOutput(); $this->assertStringContainsString('NotSkippedClass', $output); @@ -95,7 +94,7 @@ public function testReportsNothingOnRectorClassesOnly(): void $this->missConfigurationReporter->reportSkippedNonRectorClasses(); - $this->assertSame('', $this->bufferedOutput->fetch()); + $this->assertSame('', $this->rectorStyle->getRecordedOutput()); } public function testReportsOnlyTrackableUnusedSkips(): void @@ -108,7 +107,7 @@ public function testReportsOnlyTrackableUnusedSkips(): void ]); $this->missConfigurationReporter->reportUnusedSkips($processResult); - $output = $this->bufferedOutput->fetch(); + $output = $this->rectorStyle->getRecordedOutput(); // unused rule-scoped path is reported as "rule => path", so both are shown $this->assertStringContainsString('dead-rule', $output); @@ -132,6 +131,6 @@ public function testReportsNothingWhenDisabled(): void $processResult = new ProcessResult([], [], 0, []); $this->missConfigurationReporter->reportUnusedSkips($processResult); - $this->assertSame('', $this->bufferedOutput->fetch()); + $this->assertSame('', $this->rectorStyle->getRecordedOutput()); } } From 6f07f501c667d11419e55afbaf65fdfbaaa86815 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 23 Oct 2026 12:44:31 +0000 Subject: [PATCH 2/5] [ci-review] Rector Rectify --- scripts/check-before-after-same-fixtures.php | 1 - scripts/no-php-file-in-fixtures.php | 1 - 2 files changed, 2 deletions(-) diff --git a/scripts/check-before-after-same-fixtures.php b/scripts/check-before-after-same-fixtures.php index 8127038d1bc..2328301433b 100644 --- a/scripts/check-before-after-same-fixtures.php +++ b/scripts/check-before-after-same-fixtures.php @@ -23,7 +23,6 @@ public function __construct() /** * @param string[] $testDirectories - * @return Command::SUCCESS|Command::FAILURE */ public function run(array $testDirectories): int { diff --git a/scripts/no-php-file-in-fixtures.php b/scripts/no-php-file-in-fixtures.php index 59107888ad6..57f8b905c44 100644 --- a/scripts/no-php-file-in-fixtures.php +++ b/scripts/no-php-file-in-fixtures.php @@ -32,7 +32,6 @@ public function __construct() /** * @param string[] $ruleTestDirectories * @param string[] $testDirectories - * @return Command::SUCCESS|Command::FAILURE */ public function run(array $ruleTestDirectories, array $testDirectories): int { From 84f853600be1ae14bb57f4de7d71e02e4a204749 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 23 Oct 2026 15:13:13 +0200 Subject: [PATCH 3/5] Migrate dev scripts to entropy console, update e2e fixtures and package-count floor --- bin/rector.php | 5 + e2e/applied-auto-import/expected-output.diff | 4 +- .../expected-output.diff | 4 +- e2e/config-dist-fallback/expected-output.diff | 4 +- e2e/config-file-priority/expected-output.diff | 4 +- e2e/only-option/expected-output.diff | 4 +- .../expected-output.diff | 4 +- .../expected-output.diff | 7 +- .../expected-output.diff | 207 +++++++++--------- .../expected-output.diff | 14 +- .../expected-output.diff | 6 +- .../expected-output.diff | 37 ++-- e2e/rules-summary-option/expected-output.diff | 14 +- .../avoid-short-node-names-in-fixtures.php | 8 +- scripts/check-before-after-same-fixtures.php | 21 +- scripts/list-non-composer-based-rules.php | 24 +- scripts/list-unused-rules.php | 26 +-- scripts/no-php-file-in-fixtures.php | 21 +- scripts/validate-phpstan-version.php | 6 +- scripts/validate-rule-definitions.php | 16 +- .../Composer/InstalledPackageResolverTest.php | 4 +- 21 files changed, 209 insertions(+), 231 deletions(-) diff --git a/bin/rector.php b/bin/rector.php index ea3837663e3..10078690b4d 100755 --- a/bin/rector.php +++ b/bin/rector.php @@ -231,6 +231,11 @@ function rector_normalize_argv(array $argv): array $normalized[1] = 'process'; } + // a leading "help" token shows the application help, matching the former symfony/console behavior + if (isset($normalized[1]) && $normalized[1] === 'help') { + return [$normalized[0], '--help']; + } + // first real argument is an option or a path - inject the default "process" command, so the parser // does not mistake a leading "--option" or a path for an unknown command $helpFlags = ['-h', '--help']; diff --git a/e2e/applied-auto-import/expected-output.diff b/e2e/applied-auto-import/expected-output.diff index 9233afa20c6..4cf7dfd0d73 100644 --- a/e2e/applied-auto-import/expected-output.diff +++ b/e2e/applied-auto-import/expected-output.diff @@ -22,7 +22,7 @@ ----------- end diff ----------- Applied rules: - * RenameClassRector +* RenameClassRector - [OK] 1 file would have been changed (dry-run) by Rector +[OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/applied-polyfill-php80/expected-output.diff b/e2e/applied-polyfill-php80/expected-output.diff index 6a52de26b30..fce3165fd5a 100644 --- a/e2e/applied-polyfill-php80/expected-output.diff +++ b/e2e/applied-polyfill-php80/expected-output.diff @@ -15,7 +15,7 @@ ----------- end diff ----------- Applied rules: - * StrStartsWithRector +* StrStartsWithRector - [OK] 1 file would have been changed (dry-run) by Rector +[OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/config-dist-fallback/expected-output.diff b/e2e/config-dist-fallback/expected-output.diff index be7387f8764..98ece7e6ba7 100644 --- a/e2e/config-dist-fallback/expected-output.diff +++ b/e2e/config-dist-fallback/expected-output.diff @@ -16,7 +16,7 @@ ----------- end diff ----------- Applied rules: - * RemoveUselessVarTagRector +* RemoveUselessVarTagRector - [OK] 1 file would have been changed (dry-run) by Rector +[OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/config-file-priority/expected-output.diff b/e2e/config-file-priority/expected-output.diff index be7387f8764..98ece7e6ba7 100644 --- a/e2e/config-file-priority/expected-output.diff +++ b/e2e/config-file-priority/expected-output.diff @@ -16,7 +16,7 @@ ----------- end diff ----------- Applied rules: - * RemoveUselessVarTagRector +* RemoveUselessVarTagRector - [OK] 1 file would have been changed (dry-run) by Rector +[OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/only-option/expected-output.diff b/e2e/only-option/expected-output.diff index 7bb469c4f6d..51833f856b2 100644 --- a/e2e/only-option/expected-output.diff +++ b/e2e/only-option/expected-output.diff @@ -16,7 +16,7 @@ ----------- end diff ----------- Applied rules: - * RemoveUnusedPrivateMethodRector +* RemoveUnusedPrivateMethodRector - [OK] 1 file would have been changed (dry-run) by Rector +[OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/parallel-custom-config/expected-output.diff b/e2e/parallel-custom-config/expected-output.diff index b29db192fee..d8e5ce14630 100644 --- a/e2e/parallel-custom-config/expected-output.diff +++ b/e2e/parallel-custom-config/expected-output.diff @@ -19,7 +19,7 @@ ----------- end diff ----------- Applied rules: - * DowngradeReadonlyPropertyRector +* DowngradeReadonlyPropertyRector - [OK] 1 file would have been changed (dry-run) by Rector +[OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/parallel-reflection-resolver/expected-output.diff b/e2e/parallel-reflection-resolver/expected-output.diff index a9e4b1fbc8f..d4294dbe53e 100644 --- a/e2e/parallel-reflection-resolver/expected-output.diff +++ b/e2e/parallel-reflection-resolver/expected-output.diff @@ -13,8 +13,7 @@ ----------- end diff ----------- Applied rules: - * RemoveUnusedPrivatePropertyRector - +* RemoveUnusedPrivatePropertyRector 2) src/SomeClassNotFound.php:4 @@ -28,7 +27,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * RemoveUnusedPrivatePropertyRector +* RemoveUnusedPrivatePropertyRector - [OK] 2 files would have been changed (dry-run) by Rector +[OK] 2 files would have been changed (dry-run) by Rector diff --git a/e2e/parallel-system-errors-limit/expected-output.diff b/e2e/parallel-system-errors-limit/expected-output.diff index ef787becca4..85e6004de00 100644 --- a/e2e/parallel-system-errors-limit/expected-output.diff +++ b/e2e/parallel-system-errors-limit/expected-output.diff @@ -1,156 +1,155 @@ [ERROR] Could not process "src/File05.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File06.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File06.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File07.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File07.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File08.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File08.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File09.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File09.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File10.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File10.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File11.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File11.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File12.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File12.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File13.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File13.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File14.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File14.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File15.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File15.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File16.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File16.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File17.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File17.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File18.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File18.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File19.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File19.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File20.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File20.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File21.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File21.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File22.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File22.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File23.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File23.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File24.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File24.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File25.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File25.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File26.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File26.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File27.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File27.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File28.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File28.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File29.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File29.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File30.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File30.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File31.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File31.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File32.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File32.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File33.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File33.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File34.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File34.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File35.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File35.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File36.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File36.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File37.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File37.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File38.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File38.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File39.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File39.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File40.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File40.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File41.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File41.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File42.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File42.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File43.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File43.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File44.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File44.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File45.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File45.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File46.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File46.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File47.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File47.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File48.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File48.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File49.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File49.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File50.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File50.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File51.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File51.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File52.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File52.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File53.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File53.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process "src/File54.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File54.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process some files, due to: - "Reached system errors count limit of 50, exiting...". +[ERROR] Could not process some files, due to: +"Reached system errors count limit of 50, exiting...". - [ERROR] Could not process some files, due to: - "Some parallel jobs have not finished, results for 1 of 55 files are - missing". +[ERROR] Could not process some files, due to: +"Some parallel jobs have not finished, results for 1 of 55 files are missing". diff --git a/e2e/parallel-unfinished-jobs/expected-output.diff b/e2e/parallel-unfinished-jobs/expected-output.diff index 9cc3a503e90..626b41e646b 100644 --- a/e2e/parallel-unfinished-jobs/expected-output.diff +++ b/e2e/parallel-unfinished-jobs/expected-output.diff @@ -12,8 +12,7 @@ ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 2) src/File4.php:1 @@ -26,12 +25,11 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector +* LongArrayToShortArrayRector - [ERROR] Could not process "src/File2.php" file, due to: - "Syntax error, unexpected ';'". On line: 3 +[ERROR] Could not process "src/File2.php" file, due to: +"Syntax error, unexpected ';'". On line: 3 - [ERROR] Could not process some files, due to: - "Some parallel jobs have not finished, results for 1 of 4 files are - missing". +[ERROR] Could not process some files, due to: +"Some parallel jobs have not finished, results for 1 of 4 files are missing". diff --git a/e2e/parallel-unused-skips/expected-output.diff b/e2e/parallel-unused-skips/expected-output.diff index adbcf14bc47..a67ce5fce7e 100644 --- a/e2e/parallel-unused-skips/expected-output.diff +++ b/e2e/parallel-unused-skips/expected-output.diff @@ -1,7 +1,5 @@ [OK] Rector is done! - [WARNING] This skip is unused, it never matched any element. You can remove it - from "->withSkip()" - - * Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector: +[WARNING] This skip is unused, it never matched any element. You can remove it from "->withSkip()" +* Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector: * */NonexistentUnused/* diff --git a/e2e/parallel-worker-respawn/expected-output.diff b/e2e/parallel-worker-respawn/expected-output.diff index 42a3cc729de..bcc42438c9a 100644 --- a/e2e/parallel-worker-respawn/expected-output.diff +++ b/e2e/parallel-worker-respawn/expected-output.diff @@ -12,8 +12,7 @@ ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 2) src/File11.php:1 @@ -26,8 +25,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 3) src/File12.php:1 @@ -40,8 +38,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 4) src/File01.php:1 @@ -54,8 +51,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 5) src/File02.php:1 @@ -68,8 +64,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 6) src/File03.php:1 @@ -82,8 +77,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 7) src/File04.php:1 @@ -96,8 +90,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 8) src/File05.php:1 @@ -110,8 +103,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 9) src/File06.php:1 @@ -124,8 +116,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 10) src/File07.php:1 @@ -138,8 +129,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 11) src/File08.php:1 @@ -152,8 +142,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector - +* LongArrayToShortArrayRector 12) src/File09.php:1 @@ -166,7 +155,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * LongArrayToShortArrayRector +* LongArrayToShortArrayRector - [OK] 12 files would have been changed (dry-run) by Rector +[OK] 12 files would have been changed (dry-run) by Rector diff --git a/e2e/rules-summary-option/expected-output.diff b/e2e/rules-summary-option/expected-output.diff index 15dfdaa0ad6..a9ae1da2f89 100644 --- a/e2e/rules-summary-option/expected-output.diff +++ b/e2e/rules-summary-option/expected-output.diff @@ -1,3 +1,5 @@ +[WARNING] The "--rules-summary" option is deprecated and will be removed. Use "--output-format=json" instead, which reports every applied rule with its file and line. + 3 files with changes ==================== @@ -20,8 +22,7 @@ ----------- end diff ----------- Applied rules: - * RemoveAlwaysTrueIfConditionRector - +* RemoveAlwaysTrueIfConditionRector 2) src/AnotherAlwaysTrue.php:4 @@ -39,8 +40,7 @@ Applied rules: ----------- end diff ----------- Applied rules: - * RemoveAlwaysTrueIfConditionRector - +* RemoveAlwaysTrueIfConditionRector 3) src/DeadConstructor.php:2 @@ -56,13 +56,13 @@ Applied rules: ----------- end diff ----------- Applied rules: - * RemoveEmptyClassMethodRector +* RemoveEmptyClassMethodRector Rules Summary ------------- - * RemoveAlwaysTrueIfConditionRector would have been applied 2 times * RemoveEmptyClassMethodRector would have been applied 1 time - [OK] 3 files would have been changed (dry-run) by Rector \ No newline at end of file + +[OK] 3 files would have been changed (dry-run) by Rector diff --git a/scripts/avoid-short-node-names-in-fixtures.php b/scripts/avoid-short-node-names-in-fixtures.php index 3c488da66b4..65c96213d40 100644 --- a/scripts/avoid-short-node-names-in-fixtures.php +++ b/scripts/avoid-short-node-names-in-fixtures.php @@ -5,10 +5,8 @@ declare(strict_types=1); use Nette\Utils\Strings; +use Rector\Console\Style\RectorStyleFactory; use Rector\Scripts\Finder\FixtureFinder; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; @@ -31,7 +29,7 @@ $shortNodeClassNames[] = $nodeFileInfo->getBasename('.php'); } -$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput()); +$rectorStyle = new RectorStyleFactory()->create(); $hasErrors = false; @@ -49,7 +47,7 @@ continue; } - $symfonyStyle->writeln(sprintf( + $rectorStyle->writeln(sprintf( 'Fixture class name "%s" conflicts with native short node name.%sChange it to different to avoid IDE incorrect autocomplete:%s%s%s', $fixtureClassName, PHP_EOL, diff --git a/scripts/check-before-after-same-fixtures.php b/scripts/check-before-after-same-fixtures.php index 2328301433b..1d4f68e5a24 100644 --- a/scripts/check-before-after-same-fixtures.php +++ b/scripts/check-before-after-same-fixtures.php @@ -6,19 +6,18 @@ use Rector\Scripts\Finder\FixtureFinder; require __DIR__ . '/../vendor/autoload.php'; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Style\SymfonyStyle; +use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyle; +use Rector\Console\Style\RectorStyleFactory; use Symfony\Component\Finder\SplFileInfo; final readonly class SameBeforeAfterFixtureDetector { - private SymfonyStyle $symfonyStyle; + private RectorStyle $rectorStyle; public function __construct() { - $this->symfonyStyle = new SymfonyStyle(new ArgvInput(), new ConsoleOutput()); + $this->rectorStyle = new RectorStyleFactory()->create(); } /** @@ -38,16 +37,16 @@ public function run(array $testDirectories): int } if ($invalidFixturePaths === []) { - $this->symfonyStyle->success(sprintf('All %d fixtures are valid', count($fixtureFiles))); - return Command::SUCCESS; + $this->rectorStyle->success(sprintf('All %d fixtures are valid', count($fixtureFiles))); + return ExitCode::SUCCESS; } - $this->symfonyStyle->error( + $this->rectorStyle->error( 'The following fixtures have the same before and after content. Remove the part after "-----" to fix them' ); - $this->symfonyStyle->listing($invalidFixturePaths); + $this->rectorStyle->listing($invalidFixturePaths); - return Command::FAILURE; + return ExitCode::FAILURE; } private function hasFileSameBeforeAndAfterPart(SplFileInfo $fixtureFile): bool diff --git a/scripts/list-non-composer-based-rules.php b/scripts/list-non-composer-based-rules.php index 337187818ac..d3af4ee7b23 100644 --- a/scripts/list-non-composer-based-rules.php +++ b/scripts/list-non-composer-based-rules.php @@ -2,12 +2,10 @@ declare(strict_types=1); +use Rector\Console\Style\RectorStyleFactory; use Rector\Scripts\Finder\RectorClassFinder; use Rector\Scripts\Finder\RectorSetFilesFinder; use Rector\Scripts\Resolver\UsedRectorClassResolver; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Style\SymfonyStyle; require __DIR__ . '/../vendor/autoload.php'; @@ -20,8 +18,8 @@ __DIR__ . '/../vendor/rector/rector-symfony', ]); -$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput()); -$symfonyStyle->writeln(sprintf('Found Rector %d rules', count($rectorClasses))); +$rectorStyle = new RectorStyleFactory()->create(); +$rectorStyle->writeln(sprintf('Found Rector %d rules', count($rectorClasses))); // 2. find "composer-based.php" sets, that bind rules to the installed package version $rectorSetFilesFinder = new RectorSetFilesFinder(); @@ -37,13 +35,13 @@ static fn (string $rectorSetFile): bool => basename($rectorSetFile) === 'composer-based.php' ); -$symfonyStyle->writeln(sprintf('Found %d composer-based sets', count($composerBasedSetFiles))); -$symfonyStyle->listing($composerBasedSetFiles); +$rectorStyle->writeln(sprintf('Found %d composer-based sets', count($composerBasedSetFiles))); +$rectorStyle->listing($composerBasedSetFiles); $usedRectorClassResolver = new UsedRectorClassResolver(); $usedRectorRules = $usedRectorClassResolver->resolve($composerBasedSetFiles); -$symfonyStyle->writeln( +$rectorStyle->writeln( sprintf('Found %d Rector rules used in composer-based sets', count($usedRectorRules)) ); @@ -54,7 +52,7 @@ $versionAgnosticRectorRules = $usedRectorClassResolver->resolve($versionAgnosticSetFiles); -$symfonyStyle->writeln( +$rectorStyle->writeln( sprintf( 'Found %d Rector rules used in version-agnostic sets', count($versionAgnosticRectorRules) @@ -63,13 +61,13 @@ $nonComposerBasedRectorRules = array_diff($rectorClasses, $usedRectorRules, $versionAgnosticRectorRules); -$symfonyStyle->newLine(); -$symfonyStyle->listing($nonComposerBasedRectorRules); +$rectorStyle->newLine(); +$rectorStyle->listing($nonComposerBasedRectorRules); -$symfonyStyle->writeln( +$rectorStyle->writeln( sprintf( 'Found %d Rector rules not in any composer-based set, likely dead', count($nonComposerBasedRectorRules) ) ); -$symfonyStyle->newLine(); +$rectorStyle->newLine(); diff --git a/scripts/list-unused-rules.php b/scripts/list-unused-rules.php index 55bfee76e10..b45b096ddc5 100644 --- a/scripts/list-unused-rules.php +++ b/scripts/list-unused-rules.php @@ -3,13 +3,11 @@ declare(strict_types=1); use Rector\Configuration\Deprecation\Contract\DeprecatedInterface; +use Rector\Console\Style\RectorStyleFactory; use Rector\Contract\Rector\ConfigurableRectorInterface; use Rector\Scripts\Finder\RectorClassFinder; use Rector\Scripts\Finder\RectorSetFilesFinder; use Rector\Scripts\Resolver\UsedRectorClassResolver; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Style\SymfonyStyle; require __DIR__ . '/../vendor/autoload.php'; @@ -24,8 +22,8 @@ __DIR__ . '/../vendor/rector/rector-downgrade-php', ]); -$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput()); -$symfonyStyle->writeln(sprintf('Found Rector %d rules', count($rectorClasses))); +$rectorStyle = new RectorStyleFactory()->create(); +$rectorStyle->writeln(sprintf('Found Rector %d rules', count($rectorClasses))); $rectorSeFinder = new RectorSetFilesFinder(); @@ -37,13 +35,13 @@ __DIR__ . '/../vendor/rector/rector-downgrade-php/config/set', ]); -$symfonyStyle->writeln(sprintf('Found %d sets', count($rectorSetFiles))); +$rectorStyle->writeln(sprintf('Found %d sets', count($rectorSetFiles))); $usedRectorClassResolver = new UsedRectorClassResolver(); $usedRectorRules = $usedRectorClassResolver->resolve($rectorSetFiles); -$symfonyStyle->newLine(); -$symfonyStyle->writeln(sprintf('Found %d used Rector rules in sets', count($usedRectorRules))); +$rectorStyle->newLine(); +$rectorStyle->writeln(sprintf('Found %d used Rector rules in sets', count($usedRectorRules))); $unusedRectorRules = array_diff($rectorClasses, $usedRectorRules); @@ -63,16 +61,16 @@ $unusedRectorRules = array_diff($unusedRectorRules, $deprecatedRectorRules); -$symfonyStyle->newLine(); -$symfonyStyle->listing($unusedRectorRules); +$rectorStyle->newLine(); +$rectorStyle->listing($unusedRectorRules); -$symfonyStyle->writeln( +$rectorStyle->writeln( sprintf('Found %d Rector rules not in any set', count($unusedRectorRules)) ); -$symfonyStyle->writeln( +$rectorStyle->writeln( sprintf('Skipped %d configurable Rector rules', count($configurableRectorRules)) ); -$symfonyStyle->writeln( +$rectorStyle->writeln( sprintf('Skipped %d deprecated Rector rules', count($deprecatedRectorRules)) ); -$symfonyStyle->newLine(); +$rectorStyle->newLine(); diff --git a/scripts/no-php-file-in-fixtures.php b/scripts/no-php-file-in-fixtures.php index 57f8b905c44..7b5b1b37990 100644 --- a/scripts/no-php-file-in-fixtures.php +++ b/scripts/no-php-file-in-fixtures.php @@ -4,10 +4,9 @@ require __DIR__ . '/../vendor/autoload.php'; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Style\SymfonyStyle; +use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyle; +use Rector\Console\Style\RectorStyleFactory; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; use Webmozart\Assert\Assert; @@ -22,11 +21,11 @@ 'rules-tests/Renaming/Rector/Name/RenameClassRector/FixtureAutoImportNames/SomeShort.php', ]; - private SymfonyStyle $symfonyStyle; + private RectorStyle $rectorStyle; public function __construct() { - $this->symfonyStyle = new SymfonyStyle(new ArgvInput(), new ConsoleOutput()); + $this->rectorStyle = new RectorStyleFactory()->create(); } /** @@ -56,16 +55,16 @@ public function run(array $ruleTestDirectories, array $testDirectories): int } if ($relativeInvalidFilePaths === []) { - $this->symfonyStyle->success(sprintf('All %d fixtures are valid', count($allFixtureFiles))); - return Command::SUCCESS; + $this->rectorStyle->success(sprintf('All %d fixtures are valid', count($allFixtureFiles))); + return ExitCode::SUCCESS; } - $this->symfonyStyle->error( + $this->rectorStyle->error( 'The following files were found in /Fixtures directory, but only "*.php.inc" files are picked up and allowed. Rename their suffix or remove them' ); - $this->symfonyStyle->listing($relativeInvalidFilePaths); + $this->rectorStyle->listing($relativeInvalidFilePaths); - return Command::FAILURE; + return ExitCode::FAILURE; } /** diff --git a/scripts/validate-phpstan-version.php b/scripts/validate-phpstan-version.php index 577dac124c2..a49df0094e1 100644 --- a/scripts/validate-phpstan-version.php +++ b/scripts/validate-phpstan-version.php @@ -2,9 +2,9 @@ declare(strict_types=1); +use Rector\Console\ExitCode; use Rector\Exception\ShouldNotHappenException; use Rector\FileSystem\JsonFileSystem; -use Symfony\Component\Console\Command\Command; require __DIR__ . '/../vendor/autoload.php'; @@ -18,7 +18,7 @@ if ($localPHPStanVersion === $downgradedPHPStanVersion) { echo '[OK] PHPStan version in local and downgraded composer.json are equal, good job!' . PHP_EOL; - exit(Command::SUCCESS); + exit(ExitCode::SUCCESS); } echo sprintf( @@ -27,7 +27,7 @@ $downgradedPHPStanVersion, PHP_EOL ) . PHP_EOL; -exit(Command::FAILURE); +exit(ExitCode::FAILURE); final class PackageVersionResolver { diff --git a/scripts/validate-rule-definitions.php b/scripts/validate-rule-definitions.php index 5908dcf19d2..875d51562e4 100644 --- a/scripts/validate-rule-definitions.php +++ b/scripts/validate-rule-definitions.php @@ -3,15 +3,13 @@ declare(strict_types=1); use Rector\Console\ExitCode; +use Rector\Console\Style\RectorStyleFactory; use Rector\RuleDoc\Contract\DocumentedRuleInterface; use Rector\Scripts\Finder\RectorClassFinder; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Style\SymfonyStyle; require __DIR__ . '/../vendor/autoload.php'; -$symfonyStyle = new SymfonyStyle(new ArrayInput([]), new ConsoleOutput()); +$rectorStyle = new RectorStyleFactory()->create(); // core rules + all installed rector extensions (rector-doctrine, rector-symfony, rector-phpunit, ...) $ruleDirectories = array_merge( @@ -54,15 +52,15 @@ } if ($errorMessages !== []) { - $symfonyStyle->listing($errorMessages); - $symfonyStyle->error(sprintf('Found %d rule definition error(s), see above', count($errorMessages))); + $rectorStyle->listing($errorMessages); + $rectorStyle->error(sprintf('Found %d rule definition error(s), see above', count($errorMessages))); exit(ExitCode::FAILURE); } -$symfonyStyle->writeln('Scanned paths:'); -$symfonyStyle->listing(array_map(realpath(...), $ruleDirectories)); +$rectorStyle->writeln('Scanned paths:'); +$rectorStyle->listing(array_map(realpath(...), $ruleDirectories)); -$symfonyStyle->success(sprintf('All %d Rector rule definitions are valid!', count($rectorClasses))); +$rectorStyle->success(sprintf('All %d Rector rule definitions are valid!', count($rectorClasses))); exit(ExitCode::SUCCESS); diff --git a/tests/Composer/InstalledPackageResolverTest.php b/tests/Composer/InstalledPackageResolverTest.php index 6ce3f23441a..4c66c9e5cc5 100644 --- a/tests/Composer/InstalledPackageResolverTest.php +++ b/tests/Composer/InstalledPackageResolverTest.php @@ -17,7 +17,7 @@ public function test(): void $installedPackages = $installedPackageResolver->resolve(); $this->assertContainsOnlyInstancesOf(InstalledPackage::class, $installedPackages); - $this->assertGreaterThan(77, count($installedPackages)); + $this->assertGreaterThan(70, count($installedPackages)); } public function testFallbackToCurrentWorkingDirectory(): void @@ -26,7 +26,7 @@ public function testFallbackToCurrentWorkingDirectory(): void $installedPackages = $installedPackageResolver->resolve(); $this->assertContainsOnlyInstancesOf(InstalledPackage::class, $installedPackages); - $this->assertGreaterThan(77, count($installedPackages)); + $this->assertGreaterThan(70, count($installedPackages)); } public function testComposerJsonHasPriorityOverOutdatedInstalledJson(): void From a1a338b0e7a10312396222fc743a0bbf2b42b549 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 23 Oct 2026 15:15:57 +0200 Subject: [PATCH 4/5] Support help and list commands in entropy bin entrypoint --- bin/rector.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/rector.php b/bin/rector.php index 10078690b4d..ce48d528129 100755 --- a/bin/rector.php +++ b/bin/rector.php @@ -231,8 +231,8 @@ function rector_normalize_argv(array $argv): array $normalized[1] = 'process'; } - // a leading "help" token shows the application help, matching the former symfony/console behavior - if (isset($normalized[1]) && $normalized[1] === 'help') { + // leading "help" or "list" tokens show the application help, matching the former symfony/console behavior + if (isset($normalized[1]) && ($normalized[1] === 'help' || $normalized[1] === 'list')) { return [$normalized[0], '--help']; } From bf214aa303880c3645ac45faaed83ba1998e268a Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 23 Oct 2026 15:56:50 +0200 Subject: [PATCH 5/5] Bump entropy/entropy to ^0.4.35, update e2e fixtures to its restored output format --- composer.json | 2 +- e2e/applied-auto-import/expected-output.diff | 4 +- .../expected-output.diff | 4 +- e2e/config-dist-fallback/expected-output.diff | 4 +- e2e/config-file-priority/expected-output.diff | 4 +- e2e/only-option/expected-output.diff | 4 +- .../expected-output.diff | 4 +- .../expected-output.diff | 6 +- .../expected-output.diff | 102 +++++++++--------- .../expected-output.diff | 8 +- .../expected-output.diff | 4 +- .../expected-output.diff | 26 ++--- e2e/rules-summary-option/expected-output.diff | 8 +- tests/Bin/RectorTest.php | 2 +- 14 files changed, 91 insertions(+), 91 deletions(-) diff --git a/composer.json b/composer.json index 4d211db4299..39a5fab0a56 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.5", "doctrine/inflector": "^2.1", - "entropy/entropy": "^0.4.12", + "entropy/entropy": "^0.4.35", "fidry/cpu-core-counter": "^1.1", "nette/utils": "^4.1.5", "nikic/php-parser": "^5.9", diff --git a/e2e/applied-auto-import/expected-output.diff b/e2e/applied-auto-import/expected-output.diff index 4cf7dfd0d73..9233afa20c6 100644 --- a/e2e/applied-auto-import/expected-output.diff +++ b/e2e/applied-auto-import/expected-output.diff @@ -22,7 +22,7 @@ ----------- end diff ----------- Applied rules: -* RenameClassRector + * RenameClassRector -[OK] 1 file would have been changed (dry-run) by Rector + [OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/applied-polyfill-php80/expected-output.diff b/e2e/applied-polyfill-php80/expected-output.diff index fce3165fd5a..6a52de26b30 100644 --- a/e2e/applied-polyfill-php80/expected-output.diff +++ b/e2e/applied-polyfill-php80/expected-output.diff @@ -15,7 +15,7 @@ ----------- end diff ----------- Applied rules: -* StrStartsWithRector + * StrStartsWithRector -[OK] 1 file would have been changed (dry-run) by Rector + [OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/config-dist-fallback/expected-output.diff b/e2e/config-dist-fallback/expected-output.diff index 98ece7e6ba7..be7387f8764 100644 --- a/e2e/config-dist-fallback/expected-output.diff +++ b/e2e/config-dist-fallback/expected-output.diff @@ -16,7 +16,7 @@ ----------- end diff ----------- Applied rules: -* RemoveUselessVarTagRector + * RemoveUselessVarTagRector -[OK] 1 file would have been changed (dry-run) by Rector + [OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/config-file-priority/expected-output.diff b/e2e/config-file-priority/expected-output.diff index 98ece7e6ba7..be7387f8764 100644 --- a/e2e/config-file-priority/expected-output.diff +++ b/e2e/config-file-priority/expected-output.diff @@ -16,7 +16,7 @@ ----------- end diff ----------- Applied rules: -* RemoveUselessVarTagRector + * RemoveUselessVarTagRector -[OK] 1 file would have been changed (dry-run) by Rector + [OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/only-option/expected-output.diff b/e2e/only-option/expected-output.diff index 51833f856b2..7bb469c4f6d 100644 --- a/e2e/only-option/expected-output.diff +++ b/e2e/only-option/expected-output.diff @@ -16,7 +16,7 @@ ----------- end diff ----------- Applied rules: -* RemoveUnusedPrivateMethodRector + * RemoveUnusedPrivateMethodRector -[OK] 1 file would have been changed (dry-run) by Rector + [OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/parallel-custom-config/expected-output.diff b/e2e/parallel-custom-config/expected-output.diff index d8e5ce14630..b29db192fee 100644 --- a/e2e/parallel-custom-config/expected-output.diff +++ b/e2e/parallel-custom-config/expected-output.diff @@ -19,7 +19,7 @@ ----------- end diff ----------- Applied rules: -* DowngradeReadonlyPropertyRector + * DowngradeReadonlyPropertyRector -[OK] 1 file would have been changed (dry-run) by Rector + [OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/parallel-reflection-resolver/expected-output.diff b/e2e/parallel-reflection-resolver/expected-output.diff index d4294dbe53e..ab74b6019ba 100644 --- a/e2e/parallel-reflection-resolver/expected-output.diff +++ b/e2e/parallel-reflection-resolver/expected-output.diff @@ -13,7 +13,7 @@ ----------- end diff ----------- Applied rules: -* RemoveUnusedPrivatePropertyRector + * RemoveUnusedPrivatePropertyRector 2) src/SomeClassNotFound.php:4 @@ -27,7 +27,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* RemoveUnusedPrivatePropertyRector + * RemoveUnusedPrivatePropertyRector -[OK] 2 files would have been changed (dry-run) by Rector + [OK] 2 files would have been changed (dry-run) by Rector diff --git a/e2e/parallel-system-errors-limit/expected-output.diff b/e2e/parallel-system-errors-limit/expected-output.diff index 85e6004de00..e158ce0d9dc 100644 --- a/e2e/parallel-system-errors-limit/expected-output.diff +++ b/e2e/parallel-system-errors-limit/expected-output.diff @@ -1,155 +1,155 @@ [ERROR] Could not process "src/File05.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File06.php" file, due to: + [ERROR] Could not process "src/File06.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File07.php" file, due to: + [ERROR] Could not process "src/File07.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File08.php" file, due to: + [ERROR] Could not process "src/File08.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File09.php" file, due to: + [ERROR] Could not process "src/File09.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File10.php" file, due to: + [ERROR] Could not process "src/File10.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File11.php" file, due to: + [ERROR] Could not process "src/File11.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File12.php" file, due to: + [ERROR] Could not process "src/File12.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File13.php" file, due to: + [ERROR] Could not process "src/File13.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File14.php" file, due to: + [ERROR] Could not process "src/File14.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File15.php" file, due to: + [ERROR] Could not process "src/File15.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File16.php" file, due to: + [ERROR] Could not process "src/File16.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File17.php" file, due to: + [ERROR] Could not process "src/File17.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File18.php" file, due to: + [ERROR] Could not process "src/File18.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File19.php" file, due to: + [ERROR] Could not process "src/File19.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File20.php" file, due to: + [ERROR] Could not process "src/File20.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File21.php" file, due to: + [ERROR] Could not process "src/File21.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File22.php" file, due to: + [ERROR] Could not process "src/File22.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File23.php" file, due to: + [ERROR] Could not process "src/File23.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File24.php" file, due to: + [ERROR] Could not process "src/File24.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File25.php" file, due to: + [ERROR] Could not process "src/File25.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File26.php" file, due to: + [ERROR] Could not process "src/File26.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File27.php" file, due to: + [ERROR] Could not process "src/File27.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File28.php" file, due to: + [ERROR] Could not process "src/File28.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File29.php" file, due to: + [ERROR] Could not process "src/File29.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File30.php" file, due to: + [ERROR] Could not process "src/File30.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File31.php" file, due to: + [ERROR] Could not process "src/File31.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File32.php" file, due to: + [ERROR] Could not process "src/File32.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File33.php" file, due to: + [ERROR] Could not process "src/File33.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File34.php" file, due to: + [ERROR] Could not process "src/File34.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File35.php" file, due to: + [ERROR] Could not process "src/File35.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File36.php" file, due to: + [ERROR] Could not process "src/File36.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File37.php" file, due to: + [ERROR] Could not process "src/File37.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File38.php" file, due to: + [ERROR] Could not process "src/File38.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File39.php" file, due to: + [ERROR] Could not process "src/File39.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File40.php" file, due to: + [ERROR] Could not process "src/File40.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File41.php" file, due to: + [ERROR] Could not process "src/File41.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File42.php" file, due to: + [ERROR] Could not process "src/File42.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File43.php" file, due to: + [ERROR] Could not process "src/File43.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File44.php" file, due to: + [ERROR] Could not process "src/File44.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File45.php" file, due to: + [ERROR] Could not process "src/File45.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File46.php" file, due to: + [ERROR] Could not process "src/File46.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File47.php" file, due to: + [ERROR] Could not process "src/File47.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File48.php" file, due to: + [ERROR] Could not process "src/File48.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File49.php" file, due to: + [ERROR] Could not process "src/File49.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File50.php" file, due to: + [ERROR] Could not process "src/File50.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File51.php" file, due to: + [ERROR] Could not process "src/File51.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File52.php" file, due to: + [ERROR] Could not process "src/File52.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File53.php" file, due to: + [ERROR] Could not process "src/File53.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process "src/File54.php" file, due to: + [ERROR] Could not process "src/File54.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process some files, due to: + [ERROR] Could not process some files, due to: "Reached system errors count limit of 50, exiting...". -[ERROR] Could not process some files, due to: + [ERROR] Could not process some files, due to: "Some parallel jobs have not finished, results for 1 of 55 files are missing". diff --git a/e2e/parallel-unfinished-jobs/expected-output.diff b/e2e/parallel-unfinished-jobs/expected-output.diff index 626b41e646b..0b27bd8f7f5 100644 --- a/e2e/parallel-unfinished-jobs/expected-output.diff +++ b/e2e/parallel-unfinished-jobs/expected-output.diff @@ -12,7 +12,7 @@ ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 2) src/File4.php:1 @@ -25,11 +25,11 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector -[ERROR] Could not process "src/File2.php" file, due to: + [ERROR] Could not process "src/File2.php" file, due to: "Syntax error, unexpected ';'". On line: 3 -[ERROR] Could not process some files, due to: + [ERROR] Could not process some files, due to: "Some parallel jobs have not finished, results for 1 of 4 files are missing". diff --git a/e2e/parallel-unused-skips/expected-output.diff b/e2e/parallel-unused-skips/expected-output.diff index a67ce5fce7e..8d9ec6e65e8 100644 --- a/e2e/parallel-unused-skips/expected-output.diff +++ b/e2e/parallel-unused-skips/expected-output.diff @@ -1,5 +1,5 @@ [OK] Rector is done! -[WARNING] This skip is unused, it never matched any element. You can remove it from "->withSkip()" -* Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector: + [WARNING] This skip is unused, it never matched any element. You can remove it from "->withSkip()" + * Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector: * */NonexistentUnused/* diff --git a/e2e/parallel-worker-respawn/expected-output.diff b/e2e/parallel-worker-respawn/expected-output.diff index bcc42438c9a..5309e147dfc 100644 --- a/e2e/parallel-worker-respawn/expected-output.diff +++ b/e2e/parallel-worker-respawn/expected-output.diff @@ -12,7 +12,7 @@ ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 2) src/File11.php:1 @@ -25,7 +25,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 3) src/File12.php:1 @@ -38,7 +38,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 4) src/File01.php:1 @@ -51,7 +51,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 5) src/File02.php:1 @@ -64,7 +64,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 6) src/File03.php:1 @@ -77,7 +77,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 7) src/File04.php:1 @@ -90,7 +90,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 8) src/File05.php:1 @@ -103,7 +103,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 9) src/File06.php:1 @@ -116,7 +116,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 10) src/File07.php:1 @@ -129,7 +129,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 11) src/File08.php:1 @@ -142,7 +142,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector 12) src/File09.php:1 @@ -155,7 +155,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* LongArrayToShortArrayRector + * LongArrayToShortArrayRector -[OK] 12 files would have been changed (dry-run) by Rector + [OK] 12 files would have been changed (dry-run) by Rector diff --git a/e2e/rules-summary-option/expected-output.diff b/e2e/rules-summary-option/expected-output.diff index a9ae1da2f89..6d430b2a993 100644 --- a/e2e/rules-summary-option/expected-output.diff +++ b/e2e/rules-summary-option/expected-output.diff @@ -22,7 +22,7 @@ ----------- end diff ----------- Applied rules: -* RemoveAlwaysTrueIfConditionRector + * RemoveAlwaysTrueIfConditionRector 2) src/AnotherAlwaysTrue.php:4 @@ -40,7 +40,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* RemoveAlwaysTrueIfConditionRector + * RemoveAlwaysTrueIfConditionRector 3) src/DeadConstructor.php:2 @@ -56,7 +56,7 @@ Applied rules: ----------- end diff ----------- Applied rules: -* RemoveEmptyClassMethodRector + * RemoveEmptyClassMethodRector Rules Summary @@ -65,4 +65,4 @@ Rules Summary * RemoveEmptyClassMethodRector would have been applied 1 time -[OK] 3 files would have been changed (dry-run) by Rector + [OK] 3 files would have been changed (dry-run) by Rector diff --git a/tests/Bin/RectorTest.php b/tests/Bin/RectorTest.php index a00ac8f3c94..7b28e8e96dd 100644 --- a/tests/Bin/RectorTest.php +++ b/tests/Bin/RectorTest.php @@ -22,7 +22,7 @@ public static function outputProvider(): Iterator ]; yield 'Exception with previous console output' => [ 'command' => PHP_BINARY . ' bin/rector -c tests/Bin/config/incorrect-phpstan-files.php', - 'expectedOutput' => PHP_EOL . "[ERROR] Unexpected item 'parameters › invalidParameters'." . PHP_EOL, + 'expectedOutput' => PHP_EOL . " [ERROR] Unexpected item 'parameters › invalidParameters'." . PHP_EOL, ]; yield 'Exception with previous console output in JSON format' => [ 'command' => PHP_BINARY . ' bin/rector -c tests/Bin/config/incorrect-phpstan-files.php --output-format json',