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;
+ }
}