From 29704fc249124d06f8d0157a8b81ae733e3477ee Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Tue, 20 Jan 2026 09:05:01 +0100 Subject: [PATCH] Allow using a canonical dist file --- .github/workflows/e2e.yaml | 2 + e2e/config-dist-fallback/.gitignore | 1 + e2e/config-dist-fallback/composer.json | 7 +++ e2e/config-dist-fallback/expected-output.diff | 22 +++++++++ e2e/config-dist-fallback/rector.dist.php | 15 ++++++ e2e/config-dist-fallback/src/SomeClass.php | 11 +++++ e2e/config-file-priority/.gitignore | 1 + e2e/config-file-priority/composer.json | 7 +++ e2e/config-file-priority/expected-output.diff | 22 +++++++++ e2e/config-file-priority/rector.dist.php | 15 ++++++ e2e/config-file-priority/rector.php | 15 ++++++ e2e/config-file-priority/src/SomeClass.php | 11 +++++ src/Bootstrap/RectorConfigsResolver.php | 16 ++++-- src/Configuration/ConfigInitializer.php | 20 ++++++-- src/Console/ConsoleApplication.php | 8 +-- .../Command/WorkerCommandLineFactory.php | 49 ++++++++++--------- 16 files changed, 187 insertions(+), 35 deletions(-) create mode 100644 e2e/config-dist-fallback/.gitignore create mode 100644 e2e/config-dist-fallback/composer.json create mode 100644 e2e/config-dist-fallback/expected-output.diff create mode 100644 e2e/config-dist-fallback/rector.dist.php create mode 100644 e2e/config-dist-fallback/src/SomeClass.php create mode 100644 e2e/config-file-priority/.gitignore create mode 100644 e2e/config-file-priority/composer.json create mode 100644 e2e/config-file-priority/expected-output.diff create mode 100644 e2e/config-file-priority/rector.dist.php create mode 100644 e2e/config-file-priority/rector.php create mode 100644 e2e/config-file-priority/src/SomeClass.php diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index d69ffcaa76a..be970e32f7d 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -29,6 +29,8 @@ jobs: - 'e2e/applied-rule-change-docblock' - 'e2e/applied-rule-removed-node' - 'e2e/applied-rule-return-array-nodes' + - 'e2e/config-dist-fallback' + - 'e2e/config-file-priority' - 'e2e/different-path-over-skip-config' - 'e2e/invalid-paths' - 'e2e/no-parallel-reflection-resolver' diff --git a/e2e/config-dist-fallback/.gitignore b/e2e/config-dist-fallback/.gitignore new file mode 100644 index 00000000000..61ead86667c --- /dev/null +++ b/e2e/config-dist-fallback/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/e2e/config-dist-fallback/composer.json b/e2e/config-dist-fallback/composer.json new file mode 100644 index 00000000000..5468cd74606 --- /dev/null +++ b/e2e/config-dist-fallback/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "php": "^8.1" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/e2e/config-dist-fallback/expected-output.diff b/e2e/config-dist-fallback/expected-output.diff new file mode 100644 index 00000000000..1bc6840a60b --- /dev/null +++ b/e2e/config-dist-fallback/expected-output.diff @@ -0,0 +1,22 @@ +1 file with changes +=================== + +1) src/SomeClass.php:4 + + ---------- begin diff ---------- +@@ @@ + + final class SomeClass + { +- /** +- * @var string +- */ + public string $name = 'name'; + } + ----------- end diff ----------- + +Applied rules: + * RemoveUselessVarTagRector + + + [OK] 1 file would have been changed (dry-run) by Rector diff --git a/e2e/config-dist-fallback/rector.dist.php b/e2e/config-dist-fallback/rector.dist.php new file mode 100644 index 00000000000..c6ea8754079 --- /dev/null +++ b/e2e/config-dist-fallback/rector.dist.php @@ -0,0 +1,15 @@ +paths([ + __DIR__ . '/src', + ]); + + // This rule should be applied when rector.dist.php is used as fallback + $rectorConfig->rule(RemoveUselessVarTagRector::class); +}; diff --git a/e2e/config-dist-fallback/src/SomeClass.php b/e2e/config-dist-fallback/src/SomeClass.php new file mode 100644 index 00000000000..84d5c95be59 --- /dev/null +++ b/e2e/config-dist-fallback/src/SomeClass.php @@ -0,0 +1,11 @@ +paths([ + __DIR__ . '/src', + ]); + + // This rule should NOT be applied because rector.php takes priority + $rectorConfig->rule(ClosureToArrowFunctionRector::class); +}; diff --git a/e2e/config-file-priority/rector.php b/e2e/config-file-priority/rector.php new file mode 100644 index 00000000000..c69ea6e0e55 --- /dev/null +++ b/e2e/config-file-priority/rector.php @@ -0,0 +1,15 @@ +paths([ + __DIR__ . '/src', + ]); + + // This rule should be applied when rector.php is used + $rectorConfig->rule(RemoveUselessVarTagRector::class); +}; diff --git a/e2e/config-file-priority/src/SomeClass.php b/e2e/config-file-priority/src/SomeClass.php new file mode 100644 index 00000000000..84d5c95be59 --- /dev/null +++ b/e2e/config-file-priority/src/SomeClass.php @@ -0,0 +1,11 @@ +resolveFromInputWithFallback($argvInput, 'rector.php'); + $mainConfigFile = $this->resolveFromInputWithFallback($argvInput); return new BootstrapConfigs($mainConfigFile, []); } @@ -30,14 +34,20 @@ private function resolveFromInput(ArgvInput $argvInput): ?string return realpath($configFile); } - private function resolveFromInputWithFallback(ArgvInput $argvInput, string $fallbackFile): ?string + private function resolveFromInputWithFallback(ArgvInput $argvInput): ?string { $configFile = $this->resolveFromInput($argvInput); if ($configFile !== null) { return $configFile; } - return $this->createFallbackFileInfoIfFound($fallbackFile); + // Try rector.php first, then fall back to rector.dist.php + $rectorConfigFile = $this->createFallbackFileInfoIfFound(self::DEFAULT_CONFIG_FILE); + if ($rectorConfigFile !== null) { + return $rectorConfigFile; + } + + return $this->createFallbackFileInfoIfFound(self::DEFAULT_DIST_CONFIG_FILE); } private function createFallbackFileInfoIfFound(string $fallbackFile): ?string diff --git a/src/Configuration/ConfigInitializer.php b/src/Configuration/ConfigInitializer.php index 2c290a9927c..339923a6b45 100644 --- a/src/Configuration/ConfigInitializer.php +++ b/src/Configuration/ConfigInitializer.php @@ -5,6 +5,7 @@ namespace Rector\Configuration; use Nette\Utils\FileSystem; +use Rector\Bootstrap\RectorConfigsResolver; use Rector\Contract\Rector\RectorInterface; use Rector\FileSystem\InitFilePathsResolver; use Rector\PostRector\Contract\Rector\PostRectorInterface; @@ -24,14 +25,27 @@ public function __construct( public function createConfig(string $projectDirectory): void { - $commonRectorConfigPath = $projectDirectory . '/rector.php'; + $commonRectorConfigPath = $projectDirectory . '/' . RectorConfigsResolver::DEFAULT_CONFIG_FILE; + $distRectorConfigPath = $projectDirectory . '/' . RectorConfigsResolver::DEFAULT_DIST_CONFIG_FILE; if (file_exists($commonRectorConfigPath)) { - $this->symfonyStyle->warning('Register rules or sets in your "rector.php" config'); + $this->symfonyStyle->warning( + 'Register rules or sets in your "' . RectorConfigsResolver::DEFAULT_CONFIG_FILE . '" config' + ); return; } - $response = $this->symfonyStyle->ask('No "rector.php" config found. Should we generate it for you?', 'yes'); + if (file_exists($distRectorConfigPath)) { + $this->symfonyStyle->warning( + 'Register rules or sets in your "' . RectorConfigsResolver::DEFAULT_DIST_CONFIG_FILE . '" config' + ); + return; + } + + $response = $this->symfonyStyle->ask( + 'No "' . RectorConfigsResolver::DEFAULT_CONFIG_FILE . '" config found. Should we generate it for you?', + 'yes' + ); // be tolerant about input if (! in_array($response, ['yes', 'YES', 'y', 'Y'], true)) { // okay, nothing we can do diff --git a/src/Console/ConsoleApplication.php b/src/Console/ConsoleApplication.php index 954c0645c12..8b637b16f14 100644 --- a/src/Console/ConsoleApplication.php +++ b/src/Console/ConsoleApplication.php @@ -120,8 +120,7 @@ private function addCustomOptions(InputDefinition $inputDefinition): void Option::CONFIG, 'c', InputOption::VALUE_REQUIRED, - 'Path to config file', - $this->getDefaultConfigPath() + 'Path to config file' )); $inputDefinition->addOption(new InputOption( @@ -146,11 +145,6 @@ private function addCustomOptions(InputDefinition $inputDefinition): void )); } - private function getDefaultConfigPath(): string - { - return getcwd() . '/rector.php'; - } - private function enableXdebug(InputInterface $input): void { $isXdebugAllowed = $input->hasParameterOption('--xdebug'); diff --git a/src/Parallel/Command/WorkerCommandLineFactory.php b/src/Parallel/Command/WorkerCommandLineFactory.php index eb3ae448531..34dc91e5010 100644 --- a/src/Parallel/Command/WorkerCommandLineFactory.php +++ b/src/Parallel/Command/WorkerCommandLineFactory.php @@ -95,27 +95,32 @@ 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)) { - $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 = (string) $input->getOption(Option::CONFIG); - $workerCommandArray[] = escapeshellarg($this->filePathHelper->relativePath($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)); + } } if ($input->getOption(Option::ONLY) !== null) { @@ -132,8 +137,8 @@ private function shouldSkipOption(InputInterface $input, string $optionName): bo return true; } - // skip output format, not relevant in parallel worker command - return $optionName === Option::OUTPUT_FORMAT; + // skip output format and config, handled separately in create() + return $optionName === Option::OUTPUT_FORMAT || $optionName === Option::CONFIG; } /**