diff --git a/src/Console/Command/ValidateConfigCommand.php b/src/Console/Command/ValidateConfigCommand.php index 2e421f43c44..7e193f92ae0 100644 --- a/src/Console/Command/ValidateConfigCommand.php +++ b/src/Console/Command/ValidateConfigCommand.php @@ -4,6 +4,10 @@ namespace Rector\Console\Command; +use Nette\Utils\Json; +use Rector\ChangesReporting\Output\ConsoleOutputFormatter; +use Rector\ChangesReporting\Output\JsonOutputFormatter; +use Rector\Configuration\Option; use Rector\Console\ExitCode; use Rector\Reporting\DeprecatedRulesReporter; use Rector\Reporting\MissConfigurationReporter; @@ -11,6 +15,7 @@ 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; @@ -32,10 +37,24 @@ protected function configure(): void { $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 + ); } protected function execute(InputInterface $input, OutputInterface $output): int { + $isJsonOutput = $input->getOption(Option::OUTPUT_FORMAT) === JsonOutputFormatter::NAME; + + // silence the human-readable warnings, so only the JSON payload lands on stdout + if ($isJsonOutput) { + $this->symfonyStyle->setVerbosity(OutputInterface::VERBOSITY_QUIET); + } + $issueCount = 0; $issueCount += $this->deprecatedRulesReporter->reportDeprecatedRules(); @@ -52,6 +71,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int $issueCount += $this->reportDeprecatedSkippedClasses(); $issueCount += $this->reportSetAndRulesDuplicatedRegistrations(); + if ($isJsonOutput) { + echo Json::encode([ + 'valid' => $issueCount === 0, + 'issue_count' => $issueCount, + ], pretty: true) . PHP_EOL; + return $issueCount === 0 ? ExitCode::SUCCESS : ExitCode::FAILURE; + } + if ($issueCount === 0) { $this->symfonyStyle->success('Config is valid, no issues found'); return ExitCode::SUCCESS; diff --git a/tests/Console/Command/ValidateConfigCommandTest.php b/tests/Console/Command/ValidateConfigCommandTest.php index b630287ecf5..5aba86ff3fc 100644 --- a/tests/Console/Command/ValidateConfigCommandTest.php +++ b/tests/Console/Command/ValidateConfigCommandTest.php @@ -4,6 +4,7 @@ namespace Rector\Tests\Console\Command; +use Nette\Utils\Json; use Rector\Configuration\Option; use Rector\Configuration\Parameter\SimpleParameterProvider; use Rector\Configuration\VendorMissAnalyseGuard; @@ -63,4 +64,38 @@ public function testSucceedsOnCleanConfig(): void $this->assertSame(ExitCode::SUCCESS, $this->commandTester->execute([])); } + + public function testJsonOutputOnCleanConfig(): void + { + SimpleParameterProvider::setParameter(Option::REGISTERED_RECTOR_RULES, [OrdSingleByteRector::class]); + + ob_start(); + $exitCode = $this->commandTester->execute([ + '--output-format' => 'json', + ]); + $json = (string) ob_get_clean(); + + $this->assertSame(ExitCode::SUCCESS, $exitCode); + $this->assertSame([ + 'valid' => true, + 'issue_count' => 0, + ], Json::decode($json, forceArrays: true)); + } + + public function testJsonOutputOnDeprecatedRegisteredRule(): void + { + SimpleParameterProvider::setParameter(Option::REGISTERED_RECTOR_RULES, [DeprecatedFixtureRule::class]); + + ob_start(); + $exitCode = $this->commandTester->execute([ + '--output-format' => 'json', + ]); + $json = (string) ob_get_clean(); + + $this->assertSame(ExitCode::FAILURE, $exitCode); + $this->assertSame([ + 'valid' => false, + 'issue_count' => 1, + ], Json::decode($json, forceArrays: true)); + } }