From 87b94d1b7ef95418e8d143212c1264fd13378f1b Mon Sep 17 00:00:00 2001 From: Jack Worman Date: Sat, 17 Dec 2022 15:05:27 -0600 Subject: [PATCH] Json baseline formatter --- composer.json | 3 +- psalm-baseline.xml | 24 +- src/Psalm/ErrorBaseline.php | 234 +++++++----------- .../BaselineFormatterFactory.php | 49 ++++ .../BaselineFormatterInterface.php | 29 +++ .../JsonBaselineFormatter.php | 73 ++++++ .../XmlBaselineFormatter.php | 150 +++++++++++ src/Psalm/Internal/Cli/Psalm.php | 55 ++-- tests/ErrorBaselineTest.php | 14 +- .../BaselineFormatterFactoryTest.php | 92 +++++++ .../JsonBaselineFormatterTest.php | 129 ++++++++++ .../XmlBaselineFormatterTest.php | 142 +++++++++++ 12 files changed, 808 insertions(+), 186 deletions(-) create mode 100644 src/Psalm/Internal/BaselineFormatter/BaselineFormatterFactory.php create mode 100644 src/Psalm/Internal/BaselineFormatter/BaselineFormatterInterface.php create mode 100644 src/Psalm/Internal/BaselineFormatter/JsonBaselineFormatter.php create mode 100644 src/Psalm/Internal/BaselineFormatter/XmlBaselineFormatter.php create mode 100644 tests/Internal/BaselineFormatter/BaselineFormatterFactoryTest.php create mode 100644 tests/Internal/BaselineFormatter/JsonBaselineFormatterTest.php create mode 100644 tests/Internal/BaselineFormatter/XmlBaselineFormatterTest.php diff --git a/composer.json b/composer.json index 30fefaeda0a..d36bd74e23c 100644 --- a/composer.json +++ b/composer.json @@ -107,12 +107,13 @@ ], "scripts": { "cs": "phpcs -ps", - "cs-fix": "phpcbf -ps", + "cs-fix": "phpcbf -p", "lint": "parallel-lint ./src ./tests", "phpunit": "paratest --runner=WrapperRunner", "phpunit-std": "phpunit", "verify-callmap": "phpunit tests/Internal/Codebase/InternalCallMapHandlerTest.php", "psalm": "@php ./psalm --find-dead-code", + "psalm-set-baseline": "@php ./psalm --find-dead-code --set-baseline=psalm-baseline.xml", "tests": [ "@lint", "@cs", diff --git a/psalm-baseline.xml b/psalm-baseline.xml index c8dee018001..9994d553d2a 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $comment_block->tags['variablesfrom'][0] @@ -27,13 +27,6 @@ explode('::', $method_id)[1] - - - $matches[1] - $matches[2] - $matches[3] - - $comments[0] @@ -231,6 +224,18 @@ $check_type_string + + + $options['set-baseline'] + + + + + $matches[1] + $matches[2] + $matches[3] + + $identifier_name @@ -391,6 +396,9 @@ $class_strings ?: null + !$type->possibly_undefined + && !$unpacking_possibly_empty + && $is_replace $is_replace diff --git a/src/Psalm/ErrorBaseline.php b/src/Psalm/ErrorBaseline.php index e93b7f14220..8ffe5d6daaf 100644 --- a/src/Psalm/ErrorBaseline.php +++ b/src/Psalm/ErrorBaseline.php @@ -2,38 +2,32 @@ namespace Psalm; -use DOMDocument; -use DOMElement; use Psalm\Exception\ConfigException; use Psalm\Internal\Analyzer\IssueData; +use Psalm\Internal\BaselineFormatter\BaselineFormatterInterface; +use Psalm\Internal\BaselineFormatter\XmlBaselineFormatter; use Psalm\Internal\Provider\FileProvider; -use RuntimeException; use function array_filter; use function array_intersect; -use function array_map; use function array_merge; use function array_reduce; use function array_values; -use function get_loaded_extensions; -use function implode; use function ksort; use function min; -use function phpversion; -use function preg_replace_callback; -use function sort; +use function sprintf; use function str_replace; -use function strpos; -use function trim; -use function usort; +use function trigger_error; -use const LIBXML_NOBLANKS; -use const PHP_VERSION; +use const E_USER_DEPRECATED; +/** + * @psalm-type psalmFormattedBaseline = array}>> + */ final class ErrorBaseline { /** - * @param array}>> $existingIssues + * @param psalmFormattedBaseline $existingIssues * @psalm-pure */ public static function countTotalIssues(array $existingIssues): int @@ -56,89 +50,91 @@ public static function countTotalIssues(array $existingIssues): int /** * @param array> $issues + * @return psalmFormattedBaseline */ public static function create( FileProvider $fileProvider, string $baselineFile, array $issues, - bool $include_php_versions - ): void { + bool $include_php_versions, + ?BaselineFormatterInterface $baseline_formatter = null + ): array { + if ($baseline_formatter === null) { + trigger_error( + sprintf( + 'Not passing in a "%s" explicitly to "%s" is deprecated.', + BaselineFormatterInterface::class, + __METHOD__ + ), + E_USER_DEPRECATED, + ); + $baseline_formatter = new XmlBaselineFormatter(); + } $groupedIssues = self::countIssueTypesByFile($issues); - - self::writeToFile($fileProvider, $baselineFile, $groupedIssues, $include_php_versions); + self::writeToFile( + $fileProvider, + $baselineFile, + $groupedIssues, + $include_php_versions, + $baseline_formatter, + ); + return $groupedIssues; } /** - * @return array}>> + * @return psalmFormattedBaseline * @throws ConfigException */ - public static function read(FileProvider $fileProvider, string $baselineFile): array - { - if (!$fileProvider->fileExists($baselineFile)) { - throw new ConfigException("{$baselineFile} does not exist or is not readable"); - } - - $xmlSource = $fileProvider->getContents($baselineFile); - - if ($xmlSource === '') { - throw new ConfigException('Baseline file is empty'); - } - - $baselineDoc = new DOMDocument(); - $baselineDoc->loadXML($xmlSource, LIBXML_NOBLANKS); - - $filesElement = $baselineDoc->getElementsByTagName('files'); - - if ($filesElement->length === 0) { - throw new ConfigException('Baseline file does not contain '); + public static function read( + FileProvider $fileProvider, + string $baselineFile, + ?BaselineFormatterInterface $baseline_formatter = null + ): array { + if ($baseline_formatter === null) { + trigger_error( + sprintf( + 'Not passing in a "%s" explicitly to "%s" is deprecated.', + BaselineFormatterInterface::class, + __METHOD__ + ), + E_USER_DEPRECATED, + ); + $baseline_formatter = new XmlBaselineFormatter(); } - $files = []; - - /** @var DOMElement $filesElement */ - $filesElement = $filesElement[0]; - - foreach ($filesElement->getElementsByTagName('file') as $file) { - $fileName = $file->getAttribute('src'); - - $fileName = str_replace('\\', '/', $fileName); - - $files[$fileName] = []; - - foreach ($file->childNodes as $issue) { - if (!$issue instanceof DOMElement) { - continue; - } - - $issueType = $issue->tagName; - - $files[$fileName][$issueType] = [ - 'o' => (int)$issue->getAttribute('occurrences'), - 's' => [], - ]; - $codeSamples = $issue->getElementsByTagName('code'); - - foreach ($codeSamples as $codeSample) { - $files[$fileName][$issueType]['s'][] = trim($codeSample->textContent); - } - } + if (!$fileProvider->fileExists($baselineFile)) { + throw new ConfigException("{$baselineFile} does not exist or is not readable"); } - return $files; + $content = $fileProvider->getContents($baselineFile); + return $baseline_formatter->read($content); } /** * @param array> $issues - * @return array}>> + * @return psalmFormattedBaseline * @throws ConfigException */ public static function update( FileProvider $fileProvider, string $baselineFile, array $issues, - bool $include_php_versions + bool $include_php_versions, + ?BaselineFormatterInterface $baseline_formatter = null ): array { - $existingIssues = self::read($fileProvider, $baselineFile); + if ($baseline_formatter === null) { + trigger_error( + sprintf( + 'Not passing in a "%s" explicitly to "%s" is deprecated.', + BaselineFormatterInterface::class, + __METHOD__ + ), + E_USER_DEPRECATED, + ); + $baseline_formatter = new XmlBaselineFormatter(); + } + + $existingIssues = self::read($fileProvider, $baselineFile, $baseline_formatter); $newIssues = self::countIssueTypesByFile($issues); foreach ($existingIssues as $file => &$existingIssuesCount) { @@ -168,14 +164,20 @@ public static function update( $groupedIssues = array_filter($existingIssues); - self::writeToFile($fileProvider, $baselineFile, $groupedIssues, $include_php_versions); + self::writeToFile( + $fileProvider, + $baselineFile, + $groupedIssues, + $include_php_versions, + $baseline_formatter, + ); return $groupedIssues; } /** * @param array> $issues - * @return array}>> + * @return psalmFormattedBaseline */ private static function countIssueTypesByFile(array $issues): array { @@ -185,8 +187,8 @@ private static function countIssueTypesByFile(array $issues): array $groupedIssues = array_reduce( array_merge(...array_values($issues)), /** - * @param array}>> $carry - * @return array}>> + * @param psalmFormattedBaseline $carry + * @return psalmFormattedBaseline */ static function (array $carry, IssueData $issue): array { if ($issue->severity !== Config::REPORT_ERROR) { @@ -206,10 +208,7 @@ static function (array $carry, IssueData $issue): array { } ++$carry[$fileName][$issueType]['o']; - - if (!strpos($issue->selected_text, "\n")) { - $carry[$fileName][$issueType]['s'][] = $issue->selected_text; - } + $carry[$fileName][$issueType]['s'][] = $issue->selected_text; return $carry; }, @@ -228,81 +227,18 @@ static function (array $carry, IssueData $issue): array { } /** - * @param array}>> $groupedIssues + * @param psalmFormattedBaseline $groupedIssues */ private static function writeToFile( FileProvider $fileProvider, string $baselineFile, array $groupedIssues, - bool $include_php_versions + bool $include_php_versions, + BaselineFormatterInterface $baseline_formatter ): void { - $baselineDoc = new DOMDocument('1.0', 'UTF-8'); - $filesNode = $baselineDoc->createElement('files'); - $filesNode->setAttribute('psalm-version', PSALM_VERSION); - - if ($include_php_versions) { - $extensions = [...get_loaded_extensions(), ...get_loaded_extensions(true)]; - - usort($extensions, 'strnatcasecmp'); - - $filesNode->setAttribute('php-version', implode(';' . "\n\t", [...[ - ('php:' . PHP_VERSION), - ], ...array_map( - static fn(string $extension): string => $extension . ':' . phpversion($extension), - $extensions - )])); - } - - foreach ($groupedIssues as $file => $issueTypes) { - $fileNode = $baselineDoc->createElement('file'); - - $fileNode->setAttribute('src', $file); - - foreach ($issueTypes as $issueType => $existingIssueType) { - $issueNode = $baselineDoc->createElement($issueType); - - $issueNode->setAttribute('occurrences', (string)$existingIssueType['o']); - - sort($existingIssueType['s']); - - foreach ($existingIssueType['s'] as $selection) { - $codeNode = $baselineDoc->createElement('code'); - $codeNode->textContent = trim($selection); - $issueNode->appendChild($codeNode); - } - $fileNode->appendChild($issueNode); - } - - $filesNode->appendChild($fileNode); - } - - $baselineDoc->appendChild($filesNode); - $baselineDoc->formatOutput = true; - - $xml = preg_replace_callback( - '/)\n)/', - /** - * @param string[] $matches - */ - static fn(array $matches): string => 'saveXML() + $fileProvider->setContents( + $baselineFile, + $baseline_formatter->format($groupedIssues, $include_php_versions), ); - - if ($xml === null) { - throw new RuntimeException('Failed to reformat opening attributes!'); - } - - $fileProvider->setContents($baselineFile, $xml); } } diff --git a/src/Psalm/Internal/BaselineFormatter/BaselineFormatterFactory.php b/src/Psalm/Internal/BaselineFormatter/BaselineFormatterFactory.php new file mode 100644 index 00000000000..3dc5646d82d --- /dev/null +++ b/src/Psalm/Internal/BaselineFormatter/BaselineFormatterFactory.php @@ -0,0 +1,49 @@ +error_baseline) { + $extension = pathinfo($config->error_baseline, PATHINFO_EXTENSION); + $key = $extension !== '' ? $extension : XmlBaselineFormatter::getKey(); + } else { + $key = XmlBaselineFormatter::getKey(); + } + return $this->fromKey($key); + } +} diff --git a/src/Psalm/Internal/BaselineFormatter/BaselineFormatterInterface.php b/src/Psalm/Internal/BaselineFormatter/BaselineFormatterInterface.php new file mode 100644 index 00000000000..95e7c26c70a --- /dev/null +++ b/src/Psalm/Internal/BaselineFormatter/BaselineFormatterInterface.php @@ -0,0 +1,29 @@ + PSALM_VERSION]; + if ($include_php_versions) { + $extensions = [...get_loaded_extensions(), ...get_loaded_extensions(true)]; + usort($extensions, 'strnatcasecmp'); + $php_versions = ['php' => PHP_VERSION]; + foreach ($extensions as $extension) { + $php_versions[$extension] = phpversion($extension); + } + $data['php_versions'] = $php_versions; + } + foreach ($grouped_issues as $file => $issue_types) { + foreach ($issue_types as $issue_type => $existing_issue_type) { + $data['files'][$file][$issue_type] = []; + sort($existing_issue_type['s']); + foreach ($existing_issue_type['s'] as $selection) { + $data['files'][$file][$issue_type][] = trim($selection); + } + } + } + return json_encode($data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); + } + + /** + * @return array}>> + */ + public function read(string $content): array + { + /** @var array{files: array>>} $data */ + $data = json_decode($content, true, 512, JSON_THROW_ON_ERROR); + $grouped_issues = []; + foreach ($data['files'] as $file => $issue_types) { + foreach ($issue_types as $issue_type => $selections) { + $grouped_issues[$file][$issue_type]['o'] = count($selections); + $grouped_issues[$file][$issue_type]['s'] = []; + foreach ($selections as $selection) { + $grouped_issues[$file][$issue_type]['s'][] = $selection; + } + } + } + return $grouped_issues; + } +} diff --git a/src/Psalm/Internal/BaselineFormatter/XmlBaselineFormatter.php b/src/Psalm/Internal/BaselineFormatter/XmlBaselineFormatter.php new file mode 100644 index 00000000000..c68adcef421 --- /dev/null +++ b/src/Psalm/Internal/BaselineFormatter/XmlBaselineFormatter.php @@ -0,0 +1,150 @@ +createElement('files'); + $filesNode->setAttribute('psalm-version', PSALM_VERSION); + + if ($include_php_versions) { + $extensions = [...get_loaded_extensions(), ...get_loaded_extensions(true)]; + + usort($extensions, 'strnatcasecmp'); + + $filesNode->setAttribute('php-version', implode(';' . "\n\t", [...[ + ('php:' . PHP_VERSION), + ], ...array_map( + static fn(string $extension): string => $extension . ':' . phpversion($extension), + $extensions + )])); + } + + foreach ($grouped_issues as $file => $issueTypes) { + $fileNode = $baselineDoc->createElement('file'); + + $fileNode->setAttribute('src', $file); + + foreach ($issueTypes as $issueType => $existingIssueType) { + $issueNode = $baselineDoc->createElement($issueType); + + $issueNode->setAttribute('occurrences', (string)$existingIssueType['o']); + + sort($existingIssueType['s']); + + foreach ($existingIssueType['s'] as $selection) { + $codeNode = $baselineDoc->createElement('code'); + $codeNode->textContent = trim($selection); + $issueNode->appendChild($codeNode); + } + $fileNode->appendChild($issueNode); + } + + $filesNode->appendChild($fileNode); + } + + $baselineDoc->appendChild($filesNode); + $baselineDoc->formatOutput = true; + + $xml = preg_replace_callback( + '/)\n/', + /** + * @param string[] $matches + */ + static fn(array $matches): string => sprintf( + "saveXML() + ); + + if ($xml === null) { + throw new RuntimeException('Failed to reformat opening attributes!'); + } + + return $xml; + } + + public function read(string $content): array + { + if ($content === '') { + throw new ConfigException('Baseline file is empty.'); + } + + $baselineDoc = new DOMDocument(); + $baselineDoc->loadXML($content, LIBXML_NOBLANKS); + + $filesElement = $baselineDoc->getElementsByTagName('files'); + + if ($filesElement->length === 0) { + throw new ConfigException('Baseline file does not contain .'); + } + + $files = []; + + /** @var DOMElement $filesElement */ + $filesElement = $filesElement[0]; + + foreach ($filesElement->getElementsByTagName('file') as $file) { + $fileName = $file->getAttribute('src'); + + $fileName = str_replace('\\', '/', $fileName); + + $files[$fileName] = []; + + foreach ($file->childNodes as $issue) { + if (!$issue instanceof DOMElement) { + continue; + } + + $issueType = $issue->tagName; + + $files[$fileName][$issueType] = [ + 'o' => (int)$issue->getAttribute('occurrences'), + 's' => [], + ]; + $codeSamples = $issue->getElementsByTagName('code'); + + foreach ($codeSamples as $codeSample) { + $files[$fileName][$issueType]['s'][] = trim($codeSample->textContent); + } + } + } + + return $files; + } +} diff --git a/src/Psalm/Internal/Cli/Psalm.php b/src/Psalm/Internal/Cli/Psalm.php index 383ea361914..706de267949 100644 --- a/src/Psalm/Internal/Cli/Psalm.php +++ b/src/Psalm/Internal/Cli/Psalm.php @@ -9,6 +9,8 @@ use Psalm\Exception\ConfigCreationException; use Psalm\Exception\ConfigException; use Psalm\Internal\Analyzer\ProjectAnalyzer; +use Psalm\Internal\BaselineFormatter\BaselineFormatterFactory; +use Psalm\Internal\BaselineFormatter\BaselineFormatterInterface; use Psalm\Internal\CliUtils; use Psalm\Internal\Codebase\ReferenceMapGenerator; use Psalm\Internal\Composer; @@ -89,6 +91,7 @@ require_once __DIR__ . '/../../IssueBuffer.php'; /** + * @psalm-import-type psalmFormattedBaseline from ErrorBaseline * @internal */ final class Psalm @@ -158,6 +161,7 @@ final class Psalm 'dump-taint-graph:', 'find-unused-psalm-suppress', 'error-level:', + 'baseline-formatter:' ]; /** @@ -643,30 +647,23 @@ private static function initProviders(array $options, Config $config, string $cu /** * @param array{"set-baseline": string, ...} $options - * @return array}>> + * @return psalmFormattedBaseline */ private static function generateBaseline( array $options, Config $config, string $current_dir, - ?string $path_to_config + ?string $path_to_config, + BaselineFormatterInterface $baseline_formatter ): array { fwrite(STDERR, 'Writing error baseline to file...' . PHP_EOL); - try { - $issue_baseline = ErrorBaseline::read( - new FileProvider, - $options['set-baseline'] - ); - } catch (ConfigException $e) { - $issue_baseline = []; - } - - ErrorBaseline::create( + $issue_baseline = ErrorBaseline::create( new FileProvider, $options['set-baseline'], IssueBuffer::getIssuesData(), - $config->include_php_versions_in_error_baseline || isset($options['include-php-versions']) + $config->include_php_versions_in_error_baseline || isset($options['include-php-versions']), + $baseline_formatter, ); fwrite(STDERR, "Baseline saved to {$options['set-baseline']}."); @@ -683,10 +680,13 @@ private static function generateBaseline( } /** - * @return array}>> + * @return psalmFormattedBaseline */ - private static function updateBaseline(array $options, Config $config): array - { + private static function updateBaseline( + array $options, + Config $config, + BaselineFormatterInterface $baseline_formatter + ): array { $baselineFile = $config->error_baseline; if (empty($baselineFile)) { @@ -696,7 +696,8 @@ private static function updateBaseline(array $options, Config $config): array try { $issue_current_baseline = ErrorBaseline::read( new FileProvider, - $baselineFile + $baselineFile, + $baseline_formatter, ); $total_issues_current_baseline = ErrorBaseline::countTotalIssues($issue_current_baseline); @@ -704,7 +705,8 @@ private static function updateBaseline(array $options, Config $config): array new FileProvider, $baselineFile, IssueBuffer::getIssuesData(), - $config->include_php_versions_in_error_baseline || isset($options['include-php-versions']) + $config->include_php_versions_in_error_baseline || isset($options['include-php-versions']), + $baseline_formatter, ); $total_issues_updated_baseline = ErrorBaseline::countTotalIssues($issue_baseline); @@ -1019,7 +1021,7 @@ private static function initConfig( } /** - * @return array}>> + * @return psalmFormattedBaseline */ private static function initBaseline( array $options, @@ -1027,10 +1029,18 @@ private static function initBaseline( string $current_dir, ?string $path_to_config ): array { + $baseline_formatter = (new BaselineFormatterFactory())->fromOptionsAndConfig($options, $config); + $issue_baseline = []; if (isset($options['set-baseline']) && is_string($options['set-baseline'])) { - $issue_baseline = self::generateBaseline($options, $config, $current_dir, $path_to_config); + $issue_baseline = self::generateBaseline( + $options, + $config, + $current_dir, + $path_to_config, + $baseline_formatter, + ); } if (isset($options['use-baseline'])) { @@ -1046,14 +1056,15 @@ private static function initBaseline( } if (isset($options['update-baseline'])) { - $issue_baseline = self::updateBaseline($options, $config); + $issue_baseline = self::updateBaseline($options, $config, $baseline_formatter); } if (!$issue_baseline && $baseline_file_path && !isset($options['ignore-baseline'])) { try { $issue_baseline = ErrorBaseline::read( new FileProvider, - $baseline_file_path + $baseline_file_path, + $baseline_formatter, ); } catch (ConfigException $exception) { fwrite(STDERR, 'Error while reading baseline: ' . $exception->getMessage() . PHP_EOL); diff --git a/tests/ErrorBaselineTest.php b/tests/ErrorBaselineTest.php index 9adeceedd87..d1929d28872 100644 --- a/tests/ErrorBaselineTest.php +++ b/tests/ErrorBaselineTest.php @@ -9,6 +9,7 @@ use Psalm\ErrorBaseline; use Psalm\Exception\ConfigException; use Psalm\Internal\Analyzer\IssueData; +use Psalm\Internal\BaselineFormatter\XmlBaselineFormatter; use Psalm\Internal\Provider\FileProvider; use Psalm\Internal\RuntimeCaches; @@ -63,7 +64,7 @@ public function testLoadShouldParseXmlBaselineToPhpArray(): void $this->assertSame( $expectedParsedBaseline, - ErrorBaseline::read($this->fileProvider, $baselineFilePath) + ErrorBaseline::read($this->fileProvider, $baselineFilePath, new XmlBaselineFormatter()) ); } @@ -91,7 +92,7 @@ public function testLoadShouldIgnoreLineEndingsInIssueSnippet(): void $this->assertSame( $expectedParsedBaseline, - ErrorBaseline::read($this->fileProvider, $baselineFilePath) + ErrorBaseline::read($this->fileProvider, $baselineFilePath, new XmlBaselineFormatter()) ); } @@ -109,7 +110,7 @@ public function testLoadShouldThrowExceptionWhenFilesAreNotDefinedInBaselineFile ' ); - ErrorBaseline::read($this->fileProvider, $baselineFile); + ErrorBaseline::read($this->fileProvider, $baselineFile, new XmlBaselineFormatter()); } public function testLoadShouldThrowExceptionWhenBaselineFileDoesNotExist(): void @@ -120,7 +121,7 @@ public function testLoadShouldThrowExceptionWhenBaselineFileDoesNotExist(): void $this->fileProvider->expects()->fileExists($baselineFile)->andReturns(false); - ErrorBaseline::read($this->fileProvider, $baselineFile); + ErrorBaseline::read($this->fileProvider, $baselineFile, new XmlBaselineFormatter()); } public function testCountTotalIssuesShouldReturnCorrectNumber(): void @@ -292,7 +293,8 @@ public function testCreateShouldAggregateIssuesPerFile(): void ), ], ], - false + false, + new XmlBaselineFormatter(), ); $this->fileProvider->shouldHaveReceived() @@ -521,7 +523,7 @@ public function testAddingACommentInBaselineDoesntTriggerNotice(): void $this->assertSame( $expectedParsedBaseline, - ErrorBaseline::read($this->fileProvider, $baselineFilePath) + ErrorBaseline::read($this->fileProvider, $baselineFilePath, new XmlBaselineFormatter()) ); } } diff --git a/tests/Internal/BaselineFormatter/BaselineFormatterFactoryTest.php b/tests/Internal/BaselineFormatter/BaselineFormatterFactoryTest.php new file mode 100644 index 00000000000..a8987844ddc --- /dev/null +++ b/tests/Internal/BaselineFormatter/BaselineFormatterFactoryTest.php @@ -0,0 +1,92 @@ +expectException(Exception::class); + $sut->fromKey($key); + } else { + $this->assertInstanceOf($expectedClass, $sut->fromKey($key)); + } + } + + /** + * @return iterable + */ + public function provideForTestFromKey(): iterable + { + yield ['xml', XmlBaselineFormatter::class]; + yield ['json', JsonBaselineFormatter::class]; + yield ['bogus', null]; + } + + /** + * @dataProvider provideForTestFromOptionsAndConfig + * @param class-string $expectedClass + */ + public function testFromOptionsAndConfig(array $options, Config $config, string $expectedClass): void + { + $sut = new BaselineFormatterFactory(); + $this->assertInstanceOf($expectedClass, $sut->fromOptionsAndConfig($options, $config)); + } + + /** + * @return iterable + */ + public function provideForTestFromOptionsAndConfig(): iterable + { + yield [ + ['baseline-formatter' => 'json'], + Config::loadFromXML(__DIR__, ''), + JsonBaselineFormatter::class, + ]; + yield [ + ['baseline-formatter' => 'xml'], + Config::loadFromXML(__DIR__, ''), + XmlBaselineFormatter::class, + ]; + yield [ + ['set-baseline' => 'psalm-baseline.json'], + Config::loadFromXML(__DIR__, ''), + JsonBaselineFormatter::class, + ]; + yield [ + ['set-baseline' => 'psalm-baseline.xml'], + Config::loadFromXML(__DIR__, ''), + XmlBaselineFormatter::class, + ]; + yield [ + [], + Config::loadFromXML(__DIR__, ''), + JsonBaselineFormatter::class, + ]; + yield [ + [], + Config::loadFromXML(__DIR__, ''), + XmlBaselineFormatter::class, + ]; + yield [ + [], + Config::loadFromXML(__DIR__, ''), + XmlBaselineFormatter::class, + ]; + } +} diff --git a/tests/Internal/BaselineFormatter/JsonBaselineFormatterTest.php b/tests/Internal/BaselineFormatter/JsonBaselineFormatterTest.php new file mode 100644 index 00000000000..3f2295c80cd --- /dev/null +++ b/tests/Internal/BaselineFormatter/JsonBaselineFormatterTest.php @@ -0,0 +1,129 @@ +assertSame($expectedResult, $sut->read($content)); + } + + /** + * @return iterable + */ + public function provideForTestRead(): iterable + { + yield [ + <<<'JSON' + { + "files": { + "sample/sample-file.php": { + "MixedAssignment": [ + "foo", + "bar" + ], + "InvalidReturnStatement": [ + "foo" + ] + }, + "sample/sample-file2.php": { + "PossiblyUnusedMethod": [ + "foo", + "bar" + ] + } + } + } + JSON, + [ + 'sample/sample-file.php' => [ + 'MixedAssignment' => ['o' => 2, 's' => ['foo', 'bar']], + 'InvalidReturnStatement' => ['o' => 1, 's' => ['foo']], + ], + 'sample/sample-file2.php' => [ + 'PossiblyUnusedMethod' => ['o' => 2, 's' => ['foo', 'bar']], + ], + ], + ]; + } + + /** + * @dataProvider provideForTestFormat + * @param psalmFormattedBaseline $grouped_issues + */ + public function testFormat(array $grouped_issues, string $expectedResult): void + { + $sut = new JsonBaselineFormatter(); + $this->assertSame($expectedResult, $sut->format($grouped_issues, false)); + } + + /** + * @return iterable + */ + public function provideForTestFormat(): iterable + { + yield [ + [], + <<<'JSON' + { + "psalm_version": "0.0.0" + } + JSON, + ]; + yield [ + [ + 'sample/sample-file.php' => [ + 'MixedAssignment' => ['o' => 2, 's' => ['foo', 'bar']], + 'InvalidReturnStatement' => ['o' => 1, 's' => []], + ], + 'sample/sample-file2.php' => [ + 'PossiblyUnusedMethod' => ['o' => 2, 's' => ['foo', 'bar']], + ], + ], + <<<'JSON' + { + "psalm_version": "0.0.0", + "files": { + "sample\/sample-file.php": { + "MixedAssignment": [ + "bar", + "foo" + ], + "InvalidReturnStatement": [] + }, + "sample\/sample-file2.php": { + "PossiblyUnusedMethod": [ + "bar", + "foo" + ] + } + } + } + JSON, + ]; + } +} diff --git a/tests/Internal/BaselineFormatter/XmlBaselineFormatterTest.php b/tests/Internal/BaselineFormatter/XmlBaselineFormatterTest.php new file mode 100644 index 00000000000..3e31230af1b --- /dev/null +++ b/tests/Internal/BaselineFormatter/XmlBaselineFormatterTest.php @@ -0,0 +1,142 @@ +assertSame($expectedResult, $sut->read($content)); + } + + /** + * @return iterable + */ + public function provideForTestRead(): iterable + { + yield [ + <<<'XML' + + + + + foo + bar + + + + + + foo + bar + + + + XML, + [ + 'sample/sample-file.php' => [ + 'MixedAssignment' => ['o' => 2, 's' => ['foo', 'bar']], + 'InvalidReturnStatement' => ['o' => 1, 's' => []], + ], + 'sample/sample-file2.php' => [ + 'PossiblyUnusedMethod' => ['o' => 2, 's' => ['foo', 'bar']], + ], + ], + ]; + } + + public function testExceptionOnEmptyContent(): void + { + $sut = new XmlBaselineFormatter(); + $this->expectException(ConfigException::class); + $this->expectExceptionMessage('Baseline file is empty.'); + $sut->read(''); + } + + public function testExceptionOnNoFilesElement(): void + { + $sut = new XmlBaselineFormatter(); + $this->expectException(ConfigException::class); + $this->expectExceptionMessage('Baseline file does not contain .'); + $sut->read(''); + } + + /** + * @dataProvider provideForTestFormat + * @param psalmFormattedBaseline $grouped_issues + */ + public function testFormat(array $grouped_issues, string $expectedResult): void + { + $sut = new XmlBaselineFormatter(); + $this->assertSame($expectedResult, $sut->format($grouped_issues, false)); + } + + /** + * @return iterable + */ + public function provideForTestFormat(): iterable + { + yield [ + [], + <<<'XML' + + + + XML, + ]; + yield [ + [ + 'sample/sample-file.php' => [ + 'MixedAssignment' => ['o' => 2, 's' => ['foo', 'bar']], + 'InvalidReturnStatement' => ['o' => 1, 's' => []], + ], + 'sample/sample-file2.php' => [ + 'PossiblyUnusedMethod' => ['o' => 2, 's' => ['foo', 'bar']], + ], + ], + <<<'XML' + + + + + bar + foo + + + + + + bar + foo + + + + + XML, + ]; + } +}