diff --git a/e2e/rules-summary/cli-options.txt b/e2e/rules-summary/cli-options.txt
new file mode 100644
index 00000000..bd1fd3cb
--- /dev/null
+++ b/e2e/rules-summary/cli-options.txt
@@ -0,0 +1 @@
+--rules-summary
diff --git a/e2e/rules-summary/expected-output.txt b/e2e/rules-summary/expected-output.txt
new file mode 100644
index 00000000..f148a58c
--- /dev/null
+++ b/e2e/rules-summary/expected-output.txt
@@ -0,0 +1,26 @@
+
+1 file with changes
+===================
+
+1) rules-summary/result/fixture.typoscript:1
+
+ ---------- begin diff ----------
+@@ @@
+-config.xhtmlDoctype = 1
+-config.metaCharset = utf-8
++config.doctype = 1
+ ----------- end diff -----------
+
+Applied rules:
+ * RenameConfigXhtmlDoctypeToDoctypeFractor
+ * RemoveConfigMetaCharsetFractor
+
+
+Rules Summary
+-------------
+
+ * RenameConfigXhtmlDoctypeToDoctypeFractor was applied 1 time
+ * RemoveConfigMetaCharsetFractor was applied 1 time
+
+ [OK] 1 file has been changed by Fractor
+
diff --git a/e2e/rules-summary/expected-result/fixture.typoscript b/e2e/rules-summary/expected-result/fixture.typoscript
new file mode 100644
index 00000000..dfc3a281
--- /dev/null
+++ b/e2e/rules-summary/expected-result/fixture.typoscript
@@ -0,0 +1 @@
+config.doctype = 1
diff --git a/e2e/rules-summary/fixtures/fixture.typoscript b/e2e/rules-summary/fixtures/fixture.typoscript
new file mode 100644
index 00000000..efd7b323
--- /dev/null
+++ b/e2e/rules-summary/fixtures/fixture.typoscript
@@ -0,0 +1,2 @@
+config.xhtmlDoctype = 1
+config.metaCharset = utf-8
diff --git a/e2e/rules-summary/fractor.php b/e2e/rules-summary/fractor.php
new file mode 100644
index 00000000..882239ae
--- /dev/null
+++ b/e2e/rules-summary/fractor.php
@@ -0,0 +1,10 @@
+withPaths([__DIR__ . '/result/'])
+ ->withSets([Typo3LevelSetList::UP_TO_TYPO3_14]);
diff --git a/e2e/rules-summary/result/fixture.typoscript b/e2e/rules-summary/result/fixture.typoscript
new file mode 100644
index 00000000..dfc3a281
--- /dev/null
+++ b/e2e/rules-summary/result/fixture.typoscript
@@ -0,0 +1 @@
+config.doctype = 1
diff --git a/e2e/run-test.sh b/e2e/run-test.sh
index 1777d566..6ce62e6c 100755
--- a/e2e/run-test.sh
+++ b/e2e/run-test.sh
@@ -10,7 +10,7 @@ cd $TESTS_BASE_DIR
rm -r composer.lock vendor || true
composer install
-for TEST_DIR in only-option typo3-extension typo3-typoscript typo3-xml typo3-yaml
+for TEST_DIR in only-option rules-summary typo3-extension typo3-typoscript typo3-xml typo3-yaml
do
set +x
echo
diff --git a/packages/fractor/src/ChangesReporting/Output/ConsoleOutputFormatter.php b/packages/fractor/src/ChangesReporting/Output/ConsoleOutputFormatter.php
index 97e96d18..4f7c500f 100644
--- a/packages/fractor/src/ChangesReporting/Output/ConsoleOutputFormatter.php
+++ b/packages/fractor/src/ChangesReporting/Output/ConsoleOutputFormatter.php
@@ -8,6 +8,7 @@
use a9f\Fractor\Configuration\ValueObject\Configuration;
use a9f\Fractor\Differ\ValueObject\FileDiff;
use a9f\Fractor\ValueObject\ProcessResult;
+use Nette\Utils\Strings;
use Symfony\Component\Console\Style\SymfonyStyle;
final readonly class ConsoleOutputFormatter implements OutputFormatterInterface
@@ -38,6 +39,10 @@ public function report(ProcessResult $processResult, Configuration $configuratio
$this->symfonyStyle->newLine();
}
+ if ($configuration->shouldShowRulesSummary()) {
+ $this->reportRulesSummary($processResult, $configuration);
+ }
+
$message = $this->createSuccessMessage($processResult, $configuration);
$this->symfonyStyle->success($message);
}
@@ -93,4 +98,29 @@ private function createSuccessMessage(ProcessResult $processResult, Configuratio
: ($changeCount === 1 ? 'has' : 'have') . ' been changed'
);
}
+
+ 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();
+ }
}
diff --git a/packages/fractor/src/Configuration/ConfigurationFactory.php b/packages/fractor/src/Configuration/ConfigurationFactory.php
index 67702217..3848c06d 100644
--- a/packages/fractor/src/Configuration/ConfigurationFactory.php
+++ b/packages/fractor/src/Configuration/ConfigurationFactory.php
@@ -42,6 +42,8 @@ public function createFromInput(InputInterface $input): Configuration
$memoryLimit = $this->resolveMemoryLimit($input);
+ $showRulesSummary = (bool) $input->getOption(Option::RULES_SUMMARY);
+
return new Configuration(
$dryRun,
$showProgressBar,
@@ -51,6 +53,7 @@ public function createFromInput(InputInterface $input): Configuration
$showDiffs,
$memoryLimit,
$onlyRule,
+ $showRulesSummary,
$showChangelog
);
}
diff --git a/packages/fractor/src/Configuration/Option.php b/packages/fractor/src/Configuration/Option.php
index 11b4997a..d0732c5a 100644
--- a/packages/fractor/src/Configuration/Option.php
+++ b/packages/fractor/src/Configuration/Option.php
@@ -89,4 +89,9 @@ final class Option
* @var string
*/
public const MEMORY_LIMIT = 'memory-limit';
+
+ /**
+ * @var string
+ */
+ public const RULES_SUMMARY = 'rules-summary';
}
diff --git a/packages/fractor/src/Configuration/ValueObject/Configuration.php b/packages/fractor/src/Configuration/ValueObject/Configuration.php
index e7ce7153..36d55487 100644
--- a/packages/fractor/src/Configuration/ValueObject/Configuration.php
+++ b/packages/fractor/src/Configuration/ValueObject/Configuration.php
@@ -25,6 +25,7 @@ public function __construct(
private bool $showDiffs = true,
private string|null $memoryLimit = null,
private ?string $onlyRule = null,
+ private bool $showRulesSummary = false,
private bool $showChangelog = false,
) {
Assert::allStringNotEmpty($this->paths, 'No directories given');
@@ -76,6 +77,11 @@ public function getOnlyRule(): ?string
return $this->onlyRule;
}
+ public function shouldShowRulesSummary(): bool
+ {
+ return $this->showRulesSummary;
+ }
+
public function shouldShowChangelog(): bool
{
return $this->showChangelog;
diff --git a/packages/fractor/src/Console/Command/ProcessCommand.php b/packages/fractor/src/Console/Command/ProcessCommand.php
index c869d404..58433caa 100644
--- a/packages/fractor/src/Console/Command/ProcessCommand.php
+++ b/packages/fractor/src/Console/Command/ProcessCommand.php
@@ -92,6 +92,13 @@ protected function configure(): void
// filter by rule and path
$this->addOption(Option::ONLY, null, InputOption::VALUE_REQUIRED, 'Fully qualified rule class name');
+
+ $this->addOption(
+ Option::RULES_SUMMARY,
+ null,
+ InputOption::VALUE_NONE,
+ 'Show summary of rules applied during the run.'
+ );
}
protected function execute(InputInterface $input, OutputInterface $output): int
diff --git a/packages/fractor/src/ValueObject/ProcessResult.php b/packages/fractor/src/ValueObject/ProcessResult.php
index c40b3ae1..304f7e22 100644
--- a/packages/fractor/src/ValueObject/ProcessResult.php
+++ b/packages/fractor/src/ValueObject/ProcessResult.php
@@ -4,6 +4,7 @@
namespace a9f\Fractor\ValueObject;
+use a9f\Fractor\Application\Contract\FractorRule;
use a9f\Fractor\Differ\ValueObject\FileDiff;
use Webmozart\Assert\Assert;
@@ -34,4 +35,25 @@ public function getTotalChanged(): int
{
return $this->totalChanged;
}
+
+ /**
+ * @return array, int>
+ */
+ public function getRuleApplicationCounts(): array
+ {
+ $ruleCounts = [];
+
+ foreach ($this->fileDiffs as $fileDiff) {
+ foreach ($fileDiff->getFractorClasses() as $fractorClass) {
+ if (! isset($ruleCounts[$fractorClass])) {
+ $ruleCounts[$fractorClass] = 0;
+ }
+
+ ++$ruleCounts[$fractorClass];
+ }
+ }
+
+ arsort($ruleCounts);
+ return $ruleCounts;
+ }
}