From 69625933d19cfa010eed7968d98f43229926d1fa Mon Sep 17 00:00:00 2001 From: Erik van Velzen Date: Fri, 21 Feb 2020 13:32:52 +0100 Subject: [PATCH 1/2] Add reformat command to transform json format into other formats --- bin/phpstan | 2 + .../JsonErrorFormatterDeserializer.php | 47 ++++++++++ src/Command/ReformatCommand.php | 92 +++++++++++++++++++ tests/PHPStan/Command/CaptureOutput.php | 36 ++++++++ .../JsonErrorFormatterDeserializerTest.php | 63 +++++++++++++ 5 files changed, 240 insertions(+) create mode 100644 src/Command/JsonErrorFormatterDeserializer.php create mode 100644 src/Command/ReformatCommand.php create mode 100644 tests/PHPStan/Command/CaptureOutput.php create mode 100644 tests/PHPStan/Command/JsonErrorFormatterDeserializerTest.php diff --git a/bin/phpstan b/bin/phpstan index b5dff81ecd..0ac9f04fdc 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -4,6 +4,7 @@ use PHPStan\Command\AnalyseCommand; use PHPStan\Command\ClearResultCacheCommand; use PHPStan\Command\DumpDependenciesCommand; +use PHPStan\Command\ReformatCommand; use PHPStan\Command\WorkerCommand; (function () { @@ -75,6 +76,7 @@ use PHPStan\Command\WorkerCommand; $reversedComposerAutoloaderProjectPaths = array_reverse($composerAutoloaderProjectPaths); $application->add(new AnalyseCommand($reversedComposerAutoloaderProjectPaths)); $application->add(new DumpDependenciesCommand($reversedComposerAutoloaderProjectPaths)); + $application->add(new ReformatCommand($reversedComposerAutoloaderProjectPaths)); $application->add(new WorkerCommand($reversedComposerAutoloaderProjectPaths)); $application->add(new ClearResultCacheCommand($reversedComposerAutoloaderProjectPaths)); $application->run(); diff --git a/src/Command/JsonErrorFormatterDeserializer.php b/src/Command/JsonErrorFormatterDeserializer.php new file mode 100644 index 0000000000..d90e2d1d61 --- /dev/null +++ b/src/Command/JsonErrorFormatterDeserializer.php @@ -0,0 +1,47 @@ + ['messages' => $messages]) { + foreach ($messages as $message) { + $fileSpecificErrors[] = new \PHPStan\Analyser\Error( + $message['message'], + $file, + $message['line'] ?? null, + $message['ignorable'] + ); + } + } + + return new AnalysisResult( + $fileSpecificErrors, + $notFileSpecificErrors, + [], + false, + false, + null + ); + } + +} diff --git a/src/Command/ReformatCommand.php b/src/Command/ReformatCommand.php new file mode 100644 index 0000000000..780fd8a683 --- /dev/null +++ b/src/Command/ReformatCommand.php @@ -0,0 +1,92 @@ +setName(self::NAME) + ->setDescription('Read a previously generated analysis result from STDIN in JSON format and converts it to a different format') + ->setDefinition([ + new InputOption('error-format', null, InputOption::VALUE_REQUIRED, 'Format in which to print the result of the analysis', 'table'), + new InputOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Memory limit for the run'), + new InputOption('xdebug', null, InputOption::VALUE_NONE, 'Allow running with XDebug for debugging purposes'), + ]); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $memoryLimit = $input->getOption('memory-limit'); + $allowXdebug = $input->getOption('xdebug'); + + if ((!is_string($memoryLimit) && $memoryLimit !== null) + || (!is_bool($allowXdebug)) + ) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $inceptionResult = CommandHelper::begin( + $input, + $output, + ['.'], + null, + $memoryLimit, + null, + [], + null, + null, + $allowXdebug + ); + + $errorFormat = $input->getOption('error-format'); + if (!is_string($errorFormat)) { + throw new \PHPStan\ShouldNotHappenException(); + } + + $errorOutput = $inceptionResult->getErrorOutput(); + $container = $inceptionResult->getContainer(); + + $errorFormatterServiceName = sprintf('errorFormatter.%s', $errorFormat); + if (!$container->hasService($errorFormatterServiceName)) { + $errorOutput->writeLineFormatted(sprintf( + 'Error formatter "%s" not found. Available error formatters are: %s', + $errorFormat, + implode(', ', array_map(static function (string $name): string { + return substr($name, strlen('errorFormatter.')); + }, $container->findServiceNamesByType(ErrorFormatter::class))) + )); + return 1; + } + + /** @var ErrorFormatter $errorFormatter */ + $errorFormatter = $container->getService($errorFormatterServiceName); + + if (!defined('STDIN')) { + $errorOutput->writeLineFormatted('STDIN is not defined'); + return 1; + } + $jsonString = stream_get_contents(STDIN); + if ($jsonString === false) { + $errorOutput->writeLineFormatted('reading from STDIN failed'); + return 1; + } + $analysisResult = JsonErrorFormatterDeserializer::deserializeErrors($jsonString); + return $errorFormatter->formatErrors($analysisResult, $inceptionResult->getStdOutput()); + } + +} diff --git a/tests/PHPStan/Command/CaptureOutput.php b/tests/PHPStan/Command/CaptureOutput.php new file mode 100644 index 0000000000..ed460b45f2 --- /dev/null +++ b/tests/PHPStan/Command/CaptureOutput.php @@ -0,0 +1,36 @@ +result .= $message; + } + + public function getStyle(): OutputStyle + { + throw new \Exception('Not implemented'); + } + + public function getResult(): string + { + return $this->result; + } + +} diff --git a/tests/PHPStan/Command/JsonErrorFormatterDeserializerTest.php b/tests/PHPStan/Command/JsonErrorFormatterDeserializerTest.php new file mode 100644 index 0000000000..08ae984d51 --- /dev/null +++ b/tests/PHPStan/Command/JsonErrorFormatterDeserializerTest.php @@ -0,0 +1,63 @@ +formatErrors(self::createSampleAnalysisResult(), $firstSerialization); + + $parsedAnalysisResult = JsonErrorFormatterDeserializer::deserializeErrors($firstSerialization->getResult()); + + $secondSerialization = new CaptureOutput(); + $jsonFormatter->formatErrors($parsedAnalysisResult, $secondSerialization); + + self::assertEquals('{', $firstSerialization->getResult()[0]); + self::assertEquals($firstSerialization->getResult(), $secondSerialization->getResult()); + } + + private static function createSampleAnalysisResult(): AnalysisResult + { + $fileSpecificErrors = [ + new Error( + 'Cushioned proxy should only be used untethered', + 'src/Code.php', + 42, + true + ), + new Error( + 'Liaised mutex found to be grandfathered', + 'src/MoreCode.php', + 43, + false + ), + ]; + + $notFileSpecificErrors = [ + 'Orchestration was under quarantine', + ]; + + $warnings = [ + 'Technobabble detected', + ]; + + return new AnalysisResult( + $fileSpecificErrors, + $notFileSpecificErrors, + $warnings, + false, + false, + './jsontest.neon' + ); + } + +} From c501c617d6cfe5f6105afd47c52e16df7cda194f Mon Sep 17 00:00:00 2001 From: Erik van Velzen Date: Fri, 13 Mar 2020 13:30:08 +0100 Subject: [PATCH 2/2] Ondrej's feedback --- bin/phpstan | 2 +- .../JsonErrorFormatterDeserializer.php | 3 +- src/Command/ReformatCommand.php | 5 -- tests/PHPStan/Command/CaptureOutput.php | 63 ++++++++++++++++++- 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/bin/phpstan b/bin/phpstan index 0ac9f04fdc..a73ce1646b 100755 --- a/bin/phpstan +++ b/bin/phpstan @@ -76,7 +76,7 @@ use PHPStan\Command\WorkerCommand; $reversedComposerAutoloaderProjectPaths = array_reverse($composerAutoloaderProjectPaths); $application->add(new AnalyseCommand($reversedComposerAutoloaderProjectPaths)); $application->add(new DumpDependenciesCommand($reversedComposerAutoloaderProjectPaths)); - $application->add(new ReformatCommand($reversedComposerAutoloaderProjectPaths)); + $application->add(new ReformatCommand()); $application->add(new WorkerCommand($reversedComposerAutoloaderProjectPaths)); $application->add(new ClearResultCacheCommand($reversedComposerAutoloaderProjectPaths)); $application->run(); diff --git a/src/Command/JsonErrorFormatterDeserializer.php b/src/Command/JsonErrorFormatterDeserializer.php index d90e2d1d61..ab087add0c 100644 --- a/src/Command/JsonErrorFormatterDeserializer.php +++ b/src/Command/JsonErrorFormatterDeserializer.php @@ -3,9 +3,8 @@ namespace PHPStan\Command; use Nette\Utils\Json; -use Symfony\Component\Console\Command\Command; -class JsonErrorFormatterDeserializer extends Command +class JsonErrorFormatterDeserializer { /** diff --git a/src/Command/ReformatCommand.php b/src/Command/ReformatCommand.php index 780fd8a683..4a0f7c605d 100644 --- a/src/Command/ReformatCommand.php +++ b/src/Command/ReformatCommand.php @@ -13,11 +13,6 @@ class ReformatCommand extends Command private const NAME = 'reformat'; - public function __construct() - { - parent::__construct(); - } - protected function configure(): void { $this->setName(self::NAME) diff --git a/tests/PHPStan/Command/CaptureOutput.php b/tests/PHPStan/Command/CaptureOutput.php index ed460b45f2..317a73d410 100644 --- a/tests/PHPStan/Command/CaptureOutput.php +++ b/tests/PHPStan/Command/CaptureOutput.php @@ -2,6 +2,9 @@ namespace PHPStan\Command; +/** + * Test helper to capture and verify output + */ class CaptureOutput implements Output { @@ -10,12 +13,12 @@ class CaptureOutput implements Output public function writeFormatted(string $message): void { - throw new \Exception('Not implemented'); + $this->result .= $message; } public function writeLineFormatted(string $message): void { - throw new \Exception('Not implemented'); + $this->result .= $message . "\n"; } public function writeRaw(string $message): void @@ -25,7 +28,61 @@ public function writeRaw(string $message): void public function getStyle(): OutputStyle { - throw new \Exception('Not implemented'); + return new class implements OutputStyle { + + public function title(string $message): void + { + } + + public function section(string $message): void + { + } + + public function listing(array $elements): void + { + } + + public function success(string $message): void + { + } + + public function error(string $message): void + { + } + + public function warning(string $message): void + { + } + + public function note(string $message): void + { + } + + public function caution(string $message): void + { + } + + public function table(array $headers, array $rows): void + { + } + + public function newLine(int $count = 1): void + { + } + + public function progressStart(int $max = 0): void + { + } + + public function progressAdvance(int $step = 1): void + { + } + + public function progressFinish(): void + { + } + + }; } public function getResult(): string