From d099c24f9de731b82411a2673669cc5ffac9a94e Mon Sep 17 00:00:00 2001 From: Carlos Granados Date: Tue, 3 Feb 2026 10:18:48 +0000 Subject: [PATCH] Add --rules-summary option to display applied rules summary with count --- e2e/rules-summary-option/.gitignore | 1 + e2e/rules-summary-option/cli-options.txt | 1 + e2e/rules-summary-option/composer.json | 7 ++ e2e/rules-summary-option/expected-output.diff | 68 +++++++++++++++++++ e2e/rules-summary-option/rector.php | 16 +++++ e2e/rules-summary-option/src/AlwaysTrue.php | 15 ++++ .../src/AnotherAlwaysTrue.php | 12 ++++ .../src/DeadConstructor.php | 8 +++ .../Output/ConsoleOutputFormatter.php | 29 ++++++++ src/Configuration/ConfigurationFactory.php | 3 + src/Configuration/Option.php | 2 + src/Console/ProcessConfigureDecorator.php | 7 ++ src/ValueObject/Configuration.php | 6 ++ src/ValueObject/ProcessResult.php | 22 ++++++ 14 files changed, 197 insertions(+) create mode 100644 e2e/rules-summary-option/.gitignore create mode 100644 e2e/rules-summary-option/cli-options.txt create mode 100644 e2e/rules-summary-option/composer.json create mode 100644 e2e/rules-summary-option/expected-output.diff create mode 100644 e2e/rules-summary-option/rector.php create mode 100644 e2e/rules-summary-option/src/AlwaysTrue.php create mode 100644 e2e/rules-summary-option/src/AnotherAlwaysTrue.php create mode 100644 e2e/rules-summary-option/src/DeadConstructor.php diff --git a/e2e/rules-summary-option/.gitignore b/e2e/rules-summary-option/.gitignore new file mode 100644 index 00000000000..61ead86667c --- /dev/null +++ b/e2e/rules-summary-option/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/e2e/rules-summary-option/cli-options.txt b/e2e/rules-summary-option/cli-options.txt new file mode 100644 index 00000000000..bd1fd3cbf1f --- /dev/null +++ b/e2e/rules-summary-option/cli-options.txt @@ -0,0 +1 @@ +--rules-summary diff --git a/e2e/rules-summary-option/composer.json b/e2e/rules-summary-option/composer.json new file mode 100644 index 00000000000..5468cd74606 --- /dev/null +++ b/e2e/rules-summary-option/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "php": "^8.1" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/e2e/rules-summary-option/expected-output.diff b/e2e/rules-summary-option/expected-output.diff new file mode 100644 index 00000000000..d6af215e6c6 --- /dev/null +++ b/e2e/rules-summary-option/expected-output.diff @@ -0,0 +1,68 @@ +3 files with changes +==================== + +1) src/AlwaysTrue.php:4 + + ---------- begin diff ---------- +@@ @@ + { + public function run() + { +- if (1 === 1) { +- } +- +- if (2 === 2) { +- } +- + return 'no'; + } + } + ----------- end diff ----------- + +Applied rules: + * RemoveAlwaysTrueIfConditionRector + + +2) src/AnotherAlwaysTrue.php:4 + + ---------- begin diff ---------- +@@ @@ + { + public function run() + { +- if (3 === 3) { +- } +- + return 'yes'; + } + } + ----------- end diff ----------- + +Applied rules: + * RemoveAlwaysTrueIfConditionRector + + +3) src/DeadConstructor.php:2 + + ---------- begin diff ---------- +@@ @@ + + final class DeadConstructor + { +- public function __construct() +- { +- } + } + ----------- end diff ----------- + +Applied rules: + * 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 diff --git a/e2e/rules-summary-option/rector.php b/e2e/rules-summary-option/rector.php new file mode 100644 index 00000000000..4143a148053 --- /dev/null +++ b/e2e/rules-summary-option/rector.php @@ -0,0 +1,16 @@ +paths([ + __DIR__ . '/src', + ]); + + $rectorConfig->rule(RemoveEmptyClassMethodRector::class); + $rectorConfig->rule(RemoveAlwaysTrueIfConditionRector::class); +}; diff --git a/e2e/rules-summary-option/src/AlwaysTrue.php b/e2e/rules-summary-option/src/AlwaysTrue.php new file mode 100644 index 00000000000..c7fc2ce126c --- /dev/null +++ b/e2e/rules-summary-option/src/AlwaysTrue.php @@ -0,0 +1,15 @@ +symfonyStyle->newLine(); } + if ($configuration->shouldShowRulesSummary()) { + $this->reportRulesSummary($processResult, $configuration); + } + $message = $this->createSuccessMessage($processResult, $configuration); $this->symfonyStyle->success($message); } @@ -131,6 +135,31 @@ private function reportErrors(array $errors, bool $absoluteFilePath): void } } + private function reportRulesSummary(ProcessResult $processResult, Configuration $configuration): void + { + $ruleApplicationCounts = $processResult->getRuleApplicationCounts(); + if ($ruleApplicationCounts === []) { + return; + } + + $verb = $configuration->isDryRun() ? 'would have been applied' : 'was applied'; + + $this->symfonyStyle->section('Rules Summary'); + + foreach ($ruleApplicationCounts as $ruleClass => $count) { + $ruleShortClass = (string) Strings::after($ruleClass, '\\', -1); + $this->symfonyStyle->writeln(sprintf( + ' * %s %s %d time%s', + $ruleShortClass, + $verb, + $count, + $count > 1 ? 's' : '' + )); + } + + $this->symfonyStyle->newLine(); + } + private function normalizePathsToRelativeWithLine(string $errorMessage): string { $regex = '#' . preg_quote(getcwd(), '#') . '/#'; diff --git a/src/Configuration/ConfigurationFactory.php b/src/Configuration/ConfigurationFactory.php index ee256294bcb..2128b23f11d 100644 --- a/src/Configuration/ConfigurationFactory.php +++ b/src/Configuration/ConfigurationFactory.php @@ -87,6 +87,8 @@ public function createFromInput(InputInterface $input): Configuration $levelOverflows = SimpleParameterProvider::provideArrayParameter(Option::LEVEL_OVERFLOWS); + $showRulesSummary = (bool) $input->getOption(Option::RULES_SUMMARY); + return new Configuration( $isDryRun, $showProgressBar, @@ -104,6 +106,7 @@ public function createFromInput(InputInterface $input): Configuration $onlyRule, $onlySuffix, $levelOverflows, + $showRulesSummary, ); } diff --git a/src/Configuration/Option.php b/src/Configuration/Option.php index c815eeaa88e..a0021391dbc 100644 --- a/src/Configuration/Option.php +++ b/src/Configuration/Option.php @@ -126,6 +126,8 @@ final class Option public const string XDEBUG = 'xdebug'; + public const string RULES_SUMMARY = 'rules-summary'; + public const string CONFIG = 'config'; /** diff --git a/src/Console/ProcessConfigureDecorator.php b/src/Console/ProcessConfigureDecorator.php index 7f5d3dab9bc..7b3f20f107e 100644 --- a/src/Console/ProcessConfigureDecorator.php +++ b/src/Console/ProcessConfigureDecorator.php @@ -74,5 +74,12 @@ public static function decorate(Command $command): void $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.' + ); } } diff --git a/src/ValueObject/Configuration.php b/src/ValueObject/Configuration.php index fbafc385f6f..e1905416cac 100644 --- a/src/ValueObject/Configuration.php +++ b/src/ValueObject/Configuration.php @@ -34,6 +34,7 @@ public function __construct( private ?string $onlyRule = null, private ?string $onlySuffix = null, private array $levelOverflows = [], + private bool $showRulesSummary = false, ) { } @@ -141,4 +142,9 @@ public function getBothSetAndRulesDuplicatedRegistrations(): array return array_unique($ruleDuplicatedRegistrations); } + + public function shouldShowRulesSummary(): bool + { + return $this->showRulesSummary; + } } diff --git a/src/ValueObject/ProcessResult.php b/src/ValueObject/ProcessResult.php index 9f562735ab2..4d2c60a7875 100644 --- a/src/ValueObject/ProcessResult.php +++ b/src/ValueObject/ProcessResult.php @@ -4,6 +4,7 @@ namespace Rector\ValueObject; +use Rector\Contract\Rector\RectorInterface; use Rector\ValueObject\Error\SystemError; use Rector\ValueObject\Reporting\FileDiff; use Webmozart\Assert\Assert; @@ -57,4 +58,25 @@ public function getTotalChanged(): int { return $this->totalChanged; } + + /** + * @return array, int> + */ + public function getRuleApplicationCounts(): array + { + $ruleCounts = []; + + foreach ($this->fileDiffs as $fileDiff) { + foreach ($fileDiff->getRectorClasses() as $rectorClass) { + if (! isset($ruleCounts[$rectorClass])) { + $ruleCounts[$rectorClass] = 0; + } + + ++$ruleCounts[$rectorClass]; + } + } + + arsort($ruleCounts); + return $ruleCounts; + } }