From f2cce36138fb4434c8cab084240295a64b974a14 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 18 Apr 2021 15:51:52 +0200 Subject: [PATCH] Add list-sets command --- src/Console/Application.php | 2 + src/Console/Command/ListSetsCommand.php | 89 ++++++++++++++ .../Report/ListSetsReport/JsonReporter.php | 54 ++++++++ .../Report/ListSetsReport/ReportSummary.php | 43 +++++++ .../Report/ListSetsReport/ReporterFactory.php | 95 +++++++++++++++ .../ListSetsReport/ReporterInterface.php | 33 +++++ .../Report/ListSetsReport/TextReporter.php | 56 +++++++++ .../AbstractReporterTestCase.php | 101 +++++++++++++++ .../ListSetsReport/JsonReporterTest.php | 89 ++++++++++++++ .../ListSetsReport/ReportSummaryTest.php | 39 ++++++ .../ListSetsReport/ReporterFactoryTest.php | 115 ++++++++++++++++++ .../ListSetsReport/TextReporterTest.php | 53 ++++++++ 12 files changed, 769 insertions(+) create mode 100644 src/Console/Command/ListSetsCommand.php create mode 100644 src/Console/Report/ListSetsReport/JsonReporter.php create mode 100644 src/Console/Report/ListSetsReport/ReportSummary.php create mode 100644 src/Console/Report/ListSetsReport/ReporterFactory.php create mode 100644 src/Console/Report/ListSetsReport/ReporterInterface.php create mode 100644 src/Console/Report/ListSetsReport/TextReporter.php create mode 100644 tests/Console/Report/ListSetsReport/AbstractReporterTestCase.php create mode 100644 tests/Console/Report/ListSetsReport/JsonReporterTest.php create mode 100644 tests/Console/Report/ListSetsReport/ReportSummaryTest.php create mode 100644 tests/Console/Report/ListSetsReport/ReporterFactoryTest.php create mode 100644 tests/Console/Report/ListSetsReport/TextReporterTest.php diff --git a/src/Console/Application.php b/src/Console/Application.php index b5b7f8e56cd..a93d8a1a591 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -16,6 +16,7 @@ use PhpCsFixer\Console\Command\FixCommand; use PhpCsFixer\Console\Command\HelpCommand; use PhpCsFixer\Console\Command\ListFilesCommand; +use PhpCsFixer\Console\Command\ListSetsCommand; use PhpCsFixer\Console\Command\SelfUpdateCommand; use PhpCsFixer\Console\SelfUpdate\GithubClient; use PhpCsFixer\Console\SelfUpdate\NewVersionChecker; @@ -57,6 +58,7 @@ public function __construct() $this->add(new DescribeCommand()); $this->add(new FixCommand($this->toolInfo)); $this->add(new ListFilesCommand($this->toolInfo)); + $this->add(new ListSetsCommand()); $this->add(new SelfUpdateCommand( new NewVersionChecker(new GithubClient()), $this->toolInfo, diff --git a/src/Console/Command/ListSetsCommand.php b/src/Console/Command/ListSetsCommand.php new file mode 100644 index 00000000000..5695cbcc49b --- /dev/null +++ b/src/Console/Command/ListSetsCommand.php @@ -0,0 +1,89 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Command; + +use PhpCsFixer\Config; +use PhpCsFixer\ConfigInterface; +use PhpCsFixer\ConfigurationException\InvalidConfigurationException; +use PhpCsFixer\Console\Report\ListSetsReport\ReporterFactory; +use PhpCsFixer\Console\Report\ListSetsReport\ReportSummary; +use PhpCsFixer\Console\Report\ListSetsReport\TextReporter; +use PhpCsFixer\RuleSet\RuleSets; +use PhpCsFixer\ToolInfoInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ListSetsCommand extends Command +{ + protected static $defaultName = 'list-sets'; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setDefinition( + [ + new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.', (new TextReporter())->getFormat()), + ] + ) + ->setDescription('List all available RuleSets.') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $reporter = $this->resolveReporterWithFactory( + $input->getOption('format'), + new ReporterFactory() + ); + $reportSummary = new ReportSummary( + array_values(RuleSets::getSetDefinitions()) + ); + $report = $reporter->generate($reportSummary); + + $output->isDecorated() + ? $output->write(OutputFormatter::escape($report)) + : $output->write($report, false, OutputInterface::OUTPUT_RAW) + ; + + return 0; + } + + /** + * @param string $format + */ + private function resolveReporterWithFactory($format, ReporterFactory $factory) + { + try { + $factory->registerBuiltInReporters(); + $reporter = $factory->getReporter($format); + } catch (\UnexpectedValueException $e) { + $formats = $factory->getFormats(); + sort($formats); + + throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats))); + } + + return $reporter; + } +} diff --git a/src/Console/Report/ListSetsReport/JsonReporter.php b/src/Console/Report/ListSetsReport/JsonReporter.php new file mode 100644 index 00000000000..efc3d06dd7c --- /dev/null +++ b/src/Console/Report/ListSetsReport/JsonReporter.php @@ -0,0 +1,54 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +use PhpCsFixer\RuleSet\RuleSetDescriptionInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class JsonReporter implements ReporterInterface +{ + /** + * {@inheritdoc} + */ + public function getFormat() + { + return 'json'; + } + + /** + * {@inheritdoc} + */ + public function generate(ReportSummary $reportSummary) + { + $json = ['sets' => []]; + + $sets = $reportSummary->getSets(); + usort($sets, function (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b) { + return $a->getName() > $b->getName(); + }); + + foreach ($sets as $set) { + $json['sets'][$set->getName()] = [ + 'description' => $set->getDescription(), + 'isRisky' => $set->isRisky(), + 'name' => $set->getName(), + ]; + } + + return json_encode($json, JSON_PRETTY_PRINT); + } +} diff --git a/src/Console/Report/ListSetsReport/ReportSummary.php b/src/Console/Report/ListSetsReport/ReportSummary.php new file mode 100644 index 00000000000..a9abc83ddb6 --- /dev/null +++ b/src/Console/Report/ListSetsReport/ReportSummary.php @@ -0,0 +1,43 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class ReportSummary +{ + /** + * @var RuleSetDescriptionInterface[] + */ + private $sets; + + /** + * @param RuleSetDescriptionInterface[] $sets + */ + public function __construct( + array $sets + ) { + $this->sets = $sets; + } + + /** + * @return RuleSetDescriptionInterface[] + */ + public function getSets() + { + return $this->sets; + } +} diff --git a/src/Console/Report/ListSetsReport/ReporterFactory.php b/src/Console/Report/ListSetsReport/ReporterFactory.php new file mode 100644 index 00000000000..c5207c52d3f --- /dev/null +++ b/src/Console/Report/ListSetsReport/ReporterFactory.php @@ -0,0 +1,95 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +use Symfony\Component\Finder\Finder as SymfonyFinder; +use Symfony\Component\Finder\SplFileInfo; + +/** + * @author Boris Gorbylev + * + * @internal + */ +final class ReporterFactory +{ + /** @var ReporterInterface[] */ + private $reporters = []; + + public function registerBuiltInReporters() + { + /** @var null|string[] $builtInReporters */ + static $builtInReporters; + + if (null === $builtInReporters) { + $builtInReporters = []; + + /** @var SplFileInfo $file */ + foreach (SymfonyFinder::create()->files()->name('*Reporter.php')->in(__DIR__) as $file) { + $relativeNamespace = $file->getRelativePath(); + $builtInReporters[] = sprintf( + '%s\\%s%s', + __NAMESPACE__, + $relativeNamespace ? $relativeNamespace.'\\' : '', + $file->getBasename('.php') + ); + } + } + + foreach ($builtInReporters as $reporterClass) { + $this->registerReporter(new $reporterClass()); + } + + return $this; + } + + /** + * @return $this + */ + public function registerReporter(ReporterInterface $reporter) + { + $format = $reporter->getFormat(); + + if (isset($this->reporters[$format])) { + throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is already registered.', $format)); + } + + $this->reporters[$format] = $reporter; + + return $this; + } + + /** + * @return string[] + */ + public function getFormats() + { + $formats = array_keys($this->reporters); + sort($formats); + + return $formats; + } + + /** + * @param string $format + * + * @return ReporterInterface + */ + public function getReporter($format) + { + if (!isset($this->reporters[$format])) { + throw new \UnexpectedValueException(sprintf('Reporter for format "%s" is not registered.', $format)); + } + + return $this->reporters[$format]; + } +} diff --git a/src/Console/Report/ListSetsReport/ReporterInterface.php b/src/Console/Report/ListSetsReport/ReporterInterface.php new file mode 100644 index 00000000000..b074f50bfde --- /dev/null +++ b/src/Console/Report/ListSetsReport/ReporterInterface.php @@ -0,0 +1,33 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +interface ReporterInterface +{ + /** + * @return string + */ + public function getFormat(); + + /** + * Process changed files array. Returns generated report. + * + * @return string + */ + public function generate(ReportSummary $reportSummary); +} diff --git a/src/Console/Report/ListSetsReport/TextReporter.php b/src/Console/Report/ListSetsReport/TextReporter.php new file mode 100644 index 00000000000..275dad5aca6 --- /dev/null +++ b/src/Console/Report/ListSetsReport/TextReporter.php @@ -0,0 +1,56 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Console\Report\ListSetsReport; + +use PhpCsFixer\RuleSet\RuleSetDescriptionInterface; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +final class TextReporter implements ReporterInterface +{ + /** + * {@inheritdoc} + */ + public function getFormat() + { + return 'txt'; + } + + /** + * {@inheritdoc} + */ + public function generate(ReportSummary $reportSummary) + { + $output = ''; + $i = 0; + + $sets = $reportSummary->getSets(); + usort($sets, function (RuleSetDescriptionInterface $a, RuleSetDescriptionInterface $b) { + return $a->getName() > $b->getName(); + }); + + foreach ($sets as $set) { + ++$i; + $output .= sprintf('%2d) %s', $i, $set->getName()).PHP_EOL.' '.$set->getDescription().PHP_EOL; + + if ($set->isRisky()) { + $output .= ' Set contains risky rules.'.PHP_EOL; + } + } + + return $output; + } +} diff --git a/tests/Console/Report/ListSetsReport/AbstractReporterTestCase.php b/tests/Console/Report/ListSetsReport/AbstractReporterTestCase.php new file mode 100644 index 00000000000..70c36f12fb9 --- /dev/null +++ b/tests/Console/Report/ListSetsReport/AbstractReporterTestCase.php @@ -0,0 +1,101 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Console\Report\ListSetsReport; + +use PhpCsFixer\Console\Report\ListSetsReport\ReporterInterface; +use PhpCsFixer\Console\Report\ListSetsReport\ReportSummary; +use PhpCsFixer\RuleSet\Sets\PhpCsFixerSet; +use PhpCsFixer\RuleSet\Sets\SymfonyRiskySet; +use PhpCsFixer\Tests\TestCase; + +/** + * @author Dariusz Rumiński + * + * @internal + */ +abstract class AbstractReporterTestCase extends TestCase +{ + /** + * @var null|ReporterInterface + */ + protected $reporter; + + protected function doSetUp() + { + parent::doSetUp(); + + $this->reporter = $this->createReporter(); + } + + protected function doTearDown() + { + parent::doTearDown(); + + $this->reporter = null; + } + + final public function testGetFormat() + { + static::assertSame( + $this->getFormat(), + $this->reporter->getFormat() + ); + } + + /** + * @param string $expectedReport + * + * @dataProvider provideGenerateCases + */ + final public function testGenerate($expectedReport, ReportSummary $reportSummary) + { + $actualReport = $this->reporter->generate($reportSummary); + + $this->assertFormat($expectedReport, $actualReport); + } + + /** + * @return array + */ + final public function provideGenerateCases() + { + yield 'example' => [ + $this->createSimpleReport(), + new ReportSummary([ + new SymfonyRiskySet(), + new PhpCsFixerSet(), + ]), + ]; + } + + /** + * @return ReporterInterface + */ + abstract protected function createReporter(); + + /** + * @return string + */ + abstract protected function getFormat(); + + /** + * @param string $expected + * @param string $input + */ + abstract protected function assertFormat($expected, $input); + + /** + * @return string + */ + abstract protected function createSimpleReport(); +} diff --git a/tests/Console/Report/ListSetsReport/JsonReporterTest.php b/tests/Console/Report/ListSetsReport/JsonReporterTest.php new file mode 100644 index 00000000000..999fee553cb --- /dev/null +++ b/tests/Console/Report/ListSetsReport/JsonReporterTest.php @@ -0,0 +1,89 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Console\Report\ListSetsReport; + +use PhpCsFixer\Console\Report\ListSetsReport\JsonReporter; + +/** + * @author Dariusz Rumiński + * + * @internal + * + * @covers \PhpCsFixer\Console\Report\ListSetsReport\JsonReporter + */ +final class JsonReporterTest extends AbstractReporterTestCase +{ + protected function createReporter() + { + return new JsonReporter(); + } + + protected function getFormat() + { + return 'json'; + } + + protected function assertFormat($expected, $input) + { + static::assertJsonSchema($input); + static::assertJsonStringEqualsJsonString($expected, $input); + } + + /** + * @return string + */ + protected function createSimpleReport() + { + return '{ + "sets": { + "@PhpCsFixer": { + "description": "Rule set as used by the PHP-CS-Fixer development team, highly opinionated.", + "isRisky": false, + "name": "@PhpCsFixer" + }, + "@Symfony:risky": { + "description": "Rules that follow the official `Symfony Coding Standards `_.", + "isRisky": true, + "name": "@Symfony:risky" + } + } +}'; + } + + /** + * @param string $json + */ + private static function assertJsonSchema($json) + { + $jsonPath = __DIR__.'/../../../../doc/schemas/list-sets/schema.json'; + + $data = json_decode($json); + + $validator = new \JsonSchema\Validator(); + $validator->validate( + $data, + (object) ['$ref' => 'file://'.realpath($jsonPath)] + ); + + static::assertTrue( + $validator->isValid(), + implode( + "\n", + array_map( + static function (array $item) { return sprintf('Property `%s`: %s.', $item['property'], $item['message']); }, + $validator->getErrors() + ) + ) + ); + } +} diff --git a/tests/Console/Report/ListSetsReport/ReportSummaryTest.php b/tests/Console/Report/ListSetsReport/ReportSummaryTest.php new file mode 100644 index 00000000000..59ca4e5147a --- /dev/null +++ b/tests/Console/Report/ListSetsReport/ReportSummaryTest.php @@ -0,0 +1,39 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Console\Report\ListSetsReport; + +use PhpCsFixer\Console\Report\ListSetsReport\ReportSummary; +use PhpCsFixer\RuleSet\Sets\PhpCsFixerSet; +use PhpCsFixer\Tests\TestCase; + +/** + * @author Dariusz Rumiński + * + * @internal + * + * @covers \PhpCsFixer\Console\Report\ListSetsReport\ReportSummary + */ +final class ReportSummaryTest extends TestCase +{ + public function testReportSummary() + { + $sets = [ + new PhpCsFixerSet(), + ]; + $reportSummary = new ReportSummary( + $sets + ); + + static::assertSame($sets, $reportSummary->getSets()); + } +} diff --git a/tests/Console/Report/ListSetsReport/ReporterFactoryTest.php b/tests/Console/Report/ListSetsReport/ReporterFactoryTest.php new file mode 100644 index 00000000000..4f0ed3635b4 --- /dev/null +++ b/tests/Console/Report/ListSetsReport/ReporterFactoryTest.php @@ -0,0 +1,115 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Console\Report\ListSetsReport; + +use PhpCsFixer\Console\Report\ListSetsReport\ReporterFactory; +use PhpCsFixer\Tests\TestCase; + +/** + * @author Boris Gorbylev + * @author Dariusz Rumiński + * + * @internal + * + * @covers \PhpCsFixer\Console\Report\ListSetsReport\ReporterFactory + */ +final class ReporterFactoryTest extends TestCase +{ + public function testInterfaceIsFluent() + { + $builder = new ReporterFactory(); + + $testInstance = $builder->registerBuiltInReporters(); + static::assertSame($builder, $testInstance); + + $double = $this->createReporterDouble('r1'); + $testInstance = $builder->registerReporter($double); + static::assertSame($builder, $testInstance); + } + + public function testRegisterBuiltInReports() + { + $builder = new ReporterFactory(); + + static::assertCount(0, $builder->getFormats()); + + $builder->registerBuiltInReporters(); + static::assertSame( + ['json', 'txt'], + $builder->getFormats() + ); + } + + public function testThatCanRegisterAndGetReports() + { + $builder = new ReporterFactory(); + + $r1 = $this->createReporterDouble('r1'); + $r2 = $this->createReporterDouble('r2'); + $r3 = $this->createReporterDouble('r3'); + + $builder->registerReporter($r1); + $builder->registerReporter($r2); + $builder->registerReporter($r3); + + static::assertSame($r1, $builder->getReporter('r1')); + static::assertSame($r2, $builder->getReporter('r2')); + static::assertSame($r3, $builder->getReporter('r3')); + } + + public function testGetFormats() + { + $builder = new ReporterFactory(); + + $r1 = $this->createReporterDouble('r1'); + $r2 = $this->createReporterDouble('r2'); + $r3 = $this->createReporterDouble('r3'); + + $builder->registerReporter($r1); + $builder->registerReporter($r2); + $builder->registerReporter($r3); + + static::assertSame(['r1', 'r2', 'r3'], $builder->getFormats()); + } + + public function testRegisterReportWithOccupiedFormat() + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Reporter for format "non_unique_name" is already registered.'); + + $factory = new ReporterFactory(); + + $r1 = $this->createReporterDouble('non_unique_name'); + $r2 = $this->createReporterDouble('non_unique_name'); + $factory->registerReporter($r1); + $factory->registerReporter($r2); + } + + public function testGetNonRegisteredReport() + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Reporter for format "non_registered_format" is not registered.'); + + $builder = new ReporterFactory(); + + $builder->getReporter('non_registered_format'); + } + + private function createReporterDouble($format) + { + $reporter = $this->prophesize(\PhpCsFixer\Console\Report\ListSetsReport\ReporterInterface::class); + $reporter->getFormat()->willReturn($format); + + return $reporter->reveal(); + } +} diff --git a/tests/Console/Report/ListSetsReport/TextReporterTest.php b/tests/Console/Report/ListSetsReport/TextReporterTest.php new file mode 100644 index 00000000000..19558f69f54 --- /dev/null +++ b/tests/Console/Report/ListSetsReport/TextReporterTest.php @@ -0,0 +1,53 @@ + + * Dariusz Rumiński + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace PhpCsFixer\Tests\Console\Report\ListSetsReport; + +use PhpCsFixer\Console\Report\ListSetsReport\TextReporter; + +/** + * @author Dariusz Rumiński + * + * @internal + * + * @covers \PhpCsFixer\Console\Report\ListSetsReport\TextReporter + */ +final class TextReporterTest extends AbstractReporterTestCase +{ + protected function createReporter() + { + return new TextReporter(); + } + + protected function getFormat() + { + return 'txt'; + } + + protected function assertFormat($expected, $input) + { + static::assertSame($expected, $input); + } + + /** + * @return string + */ + protected function createSimpleReport() + { + return ' 1) @PhpCsFixer + Rule set as used by the PHP-CS-Fixer development team, highly opinionated. + 2) @Symfony:risky + Rules that follow the official `Symfony Coding Standards `_. + Set contains risky rules. +'; + } +}