diff --git a/build/target-repository/README.md b/build/target-repository/README.md index ee25b39e2c..6880cbb902 100644 --- a/build/target-repository/README.md +++ b/build/target-repository/README.md @@ -215,7 +215,6 @@ We currently provide formatters for: - `console`: Human-oriented printing à la PHP CS Fixer. - `json`: A custom JSON blob for arbitrary tooling. - `checkstyle`: Useful for Github Action Reports. -- `gitlab`: For Gitlab code quality reports or Code Climate tooling. You can use the output format option as below diff --git a/src/Console/Output/GitlabOutputFormatter.php b/src/Console/Output/GitlabOutputFormatter.php deleted file mode 100644 index 877bb49b11..0000000000 --- a/src/Console/Output/GitlabOutputFormatter.php +++ /dev/null @@ -1,249 +0,0 @@ -generateReport($errorAndDiffResult, $configuration); - $this->easyCodingStandardStyle->writeln($output); - return $this->exitCodeResolver->resolve($errorAndDiffResult, $configuration); - } - - public function generateReport(ErrorAndDiffResult $errorAndDiffResult, Configuration $configuration): string - { - $reportedQualityIssues = (! $configuration->isFixer() && $configuration->shouldShowDiffs()) - ? merge( - $this->generateIssuesForErrors( - $errorAndDiffResult->getErrors(), - false - ), - $this->generateIssuesForFixes( - $errorAndDiffResult->getFileDiffs(), - false - ), - ) - : $this->generateIssuesForErrors( - $errorAndDiffResult->getErrors(), - false - ); - - return $this->encode($reportedQualityIssues); - } - - /** - * @param CodingStandardError[] $errors - * @return GitlabIssue[] - */ - private function generateIssuesForErrors(array $errors, bool $absoluteFilePath = false): array - { - return map( - fn (CodingStandardError $codingStandardError): array => [ - 'type' => 'issue', - 'description' => $codingStandardError->getMessage(), - 'check_name' => $codingStandardError->getCheckerClass(), - 'fingerprint' => $this->generateFingerprint( - $codingStandardError->getCheckerClass(), - $codingStandardError->getMessage(), - $codingStandardError->getRelativeFilePath(), - ), - 'severity' => 'minor', - 'categories' => ['Style'], - 'location' => [ - 'path' => $absoluteFilePath ? $codingStandardError->getAbsoluteFilePath() ?? '' : $codingStandardError->getRelativeFilePath(), - 'lines' => [ - 'begin' => $codingStandardError->getLine(), - 'end' => $codingStandardError->getLine(), - ], - ], - ], - $errors, - ); - } - - /** - * Reports each chunk of changes as a separate issue. - * - * @param FileDiff[] $diffs - * @return GitlabIssue[] - */ - private function generateIssuesForFixes(array $diffs, bool $absoluteFilePath = false): array - { - return merge( - ...map( - fn (FileDiff $fileDiff): array => map( - fn (Chunk $chunk): array => $this->generateIssueForChunk($fileDiff, $chunk, $absoluteFilePath), - $this->diffParser->parse($fileDiff->getDiff())[0] - ->chunks(), - ), - $diffs, - ), - ); - } - - /** - * @return GitlabIssue - */ - private function generateIssueForChunk(FileDiff $fileDiff, Chunk $chunk, bool $absoluteFilePath): array - { - $checkersAsFqcns = implode(',', $fileDiff->getAppliedCheckers()); - $checkersAsClasses = implode(', ', map( - static fn (string $checker): string => preg_replace('/.*\\\/', '', $checker) ?? $checker, - $fileDiff->getAppliedCheckers(), - )); - - $message = 'Chunk has fixable errors: ' . $checkersAsClasses; - $lineStart = $chunk->start(); - $lineEnd = $lineStart + $chunk->startRange() - 1; - - return [ - 'type' => 'issue', - 'description' => $message, - 'check_name' => $checkersAsFqcns, - 'fingerprint' => $this->generateFingerprint( - $checkersAsFqcns, - $message, - $fileDiff->getRelativeFilePath(), - implode( - '\n', - map(static fn (Line $line): string => sprintf( - '%d:%s', - $line->type(), - $line->content() - ), $chunk->lines()) - ), - ), - 'severity' => 'minor', - 'categories' => ['Style'], - 'remediation_points' => 50_000, - 'location' => [ - 'path' => $absoluteFilePath ? $fileDiff->getAbsoluteFilePath() ?? '' : $fileDiff->getRelativeFilePath(), - 'lines' => [ - 'begin' => $lineStart, - 'end' => $lineEnd, - ], - ], - ]; - } - - /** - * Generate a fingerprint for a given quality issue. This is used to - * track the presence of an issue between runs, so it should be unique - * and consistent for a given issue. - * - * Subsequently, changing the fingerprint or the data it uses is - * _technically_ a breaking change. Users would see existing issues being - * marked as "new" on the commit proceeding updating ECS. - * - * DO NOT include position information as salting, or every time - * lines are added/removed the lines below it will be reported as - * new errors. - */ - private function generateFingerprint( - string $checker, - string $message, - string $relativeFilePath, - string $salt = '', - ): string { - // We implode to add a separator that cannot show up in PHP - // class names or Linux file names and SHOULD never show up in - // messages. This guarantees the same fingerprint won't be generated - // by accident, by the associative property of concatenation. As in: - // - // (ABC + ABC = ABCABC) == (ABCA + BC = ABCABC) - // (ABC + \0 + ABC = ABC\0ABC) != (ABCA + \0 + BC = ABCA\0BC) - return md5(implode("\0", [$checker, $message, $relativeFilePath, $salt])); - } - - /** - * @param GitlabIssue[] $lineItems - */ - private function encode(array $lineItems): string - { - return json_encode( - $lineItems, - JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE - ); - } -} diff --git a/src/Console/Output/OutputFormatterCollector.php b/src/Console/Output/OutputFormatterCollector.php index f58c627ee2..36d42c3d2d 100644 --- a/src/Console/Output/OutputFormatterCollector.php +++ b/src/Console/Output/OutputFormatterCollector.php @@ -17,6 +17,7 @@ final class OutputFormatterCollector */ private const array REMOVED_FORMATS = [ 'junit' => ConsoleOutputFormatter::NAME, + 'gitlab' => ConsoleOutputFormatter::NAME, ]; /** diff --git a/tests/Console/Output/Fixture/gitlab/errors_and_fixes.json b/tests/Console/Output/Fixture/gitlab/errors_and_fixes.json deleted file mode 100644 index 7589c0f93b..0000000000 --- a/tests/Console/Output/Fixture/gitlab/errors_and_fixes.json +++ /dev/null @@ -1,54 +0,0 @@ -[ - { - "type": "issue", - "description": "This is a test", - "check_name": "PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\Files\\LineLengthSniff", - "fingerprint": "3a2b7d4273d6f644730e17c73cef9295", - "severity": "minor", - "categories": [ - "Style" - ], - "location": { - "path": "tests/Console/Output/Source/RandomFile.php", - "lines": { - "begin": 3, - "end": 3 - } - } - }, - { - "type": "issue", - "description": "This is another test", - "check_name": "PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\Files\\LineLengthSniff", - "fingerprint": "84089637032031fa43547c8d854d4ab5", - "severity": "minor", - "categories": [ - "Style" - ], - "location": { - "path": "tests/Console/Output/Source/RandomFile.php", - "lines": { - "begin": 5, - "end": 5 - } - } - }, - { - "type": "issue", - "description": "Chunk has fixable errors: LineLengthFixer", - "check_name": "Symplify\\CodingStandard\\Fixer\\LineLength\\LineLengthFixer", - "fingerprint": "6c9be79241a22740764324c389c90f98", - "severity": "minor", - "categories": [ - "Style" - ], - "remediation_points": 50000, - "location": { - "path": "tests/Console/Output/Source/RandomFile.php", - "lines": { - "begin": 4, - "end": 9 - } - } - } -] diff --git a/tests/Console/Output/Fixture/gitlab/no_issues.json b/tests/Console/Output/Fixture/gitlab/no_issues.json deleted file mode 100644 index fe51488c70..0000000000 --- a/tests/Console/Output/Fixture/gitlab/no_issues.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/tests/Console/Output/Fixture/gitlab/only_errors.json b/tests/Console/Output/Fixture/gitlab/only_errors.json deleted file mode 100644 index c49e61c26a..0000000000 --- a/tests/Console/Output/Fixture/gitlab/only_errors.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "type": "issue", - "description": "This is a test", - "check_name": "PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\Files\\LineLengthSniff", - "fingerprint": "3a2b7d4273d6f644730e17c73cef9295", - "severity": "minor", - "categories": [ - "Style" - ], - "location": { - "path": "tests/Console/Output/Source/RandomFile.php", - "lines": { - "begin": 3, - "end": 3 - } - } - }, - { - "type": "issue", - "description": "This is another test", - "check_name": "PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\Files\\LineLengthSniff", - "fingerprint": "84089637032031fa43547c8d854d4ab5", - "severity": "minor", - "categories": [ - "Style" - ], - "location": { - "path": "tests/Console/Output/Source/RandomFile.php", - "lines": { - "begin": 5, - "end": 5 - } - } - } -] diff --git a/tests/Console/Output/Fixture/gitlab/only_fixes.json b/tests/Console/Output/Fixture/gitlab/only_fixes.json deleted file mode 100644 index d96ef53335..0000000000 --- a/tests/Console/Output/Fixture/gitlab/only_fixes.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "type": "issue", - "description": "Chunk has fixable errors: LineLengthFixer", - "check_name": "Symplify\\CodingStandard\\Fixer\\LineLength\\LineLengthFixer", - "fingerprint": "6c9be79241a22740764324c389c90f98", - "severity": "minor", - "categories": [ - "Style" - ], - "remediation_points": 50000, - "location": { - "path": "tests/Console/Output/Source/RandomFile.php", - "lines": { - "begin": 4, - "end": 9 - } - } - } -] diff --git a/tests/Console/Output/Fixture/gitlab/only_fixes_with_offset.json b/tests/Console/Output/Fixture/gitlab/only_fixes_with_offset.json deleted file mode 100644 index 2892d75711..0000000000 --- a/tests/Console/Output/Fixture/gitlab/only_fixes_with_offset.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "type": "issue", - "description": "Chunk has fixable errors: LineLengthFixer", - "check_name": "Symplify\\CodingStandard\\Fixer\\LineLength\\LineLengthFixer", - "fingerprint": "6c9be79241a22740764324c389c90f98", - "severity": "minor", - "categories": [ - "Style" - ], - "remediation_points": 50000, - "location": { - "path": "tests/Console/Output/Source/RandomFile.php", - "lines": { - "begin": 6, - "end": 11 - } - } - } -] diff --git a/tests/Console/Output/GitlabOutputFormatterTest.php b/tests/Console/Output/GitlabOutputFormatterTest.php deleted file mode 100644 index 93a091f7a5..0000000000 --- a/tests/Console/Output/GitlabOutputFormatterTest.php +++ /dev/null @@ -1,188 +0,0 @@ -gitlabOutputFormatter = $this->make(gitlabOutputFormatter::class); - $this->colorConsoleDiffFormatter = $this->make(ColorConsoleDiffFormatter::class); - $this->differ = $this->make(DifferInterface::class); - } - - public function testGracefullyHandlesNoIssues(): void - { - $configuration = new Configuration(); - $filePathForExpectedOutput = $this->path('/Fixture/gitlab/no_issues.json'); - - $errorAndDiffResult = new ErrorAndDiffResult([], [], []); - - $this->assertJsonStringEqualsJsonFile( - $filePathForExpectedOutput, - $this->gitlabOutputFormatter->generateReport($errorAndDiffResult, $configuration), - ); - } - - public function testReportsErrorsInTheRightFormat(): void - { - $configuration = new Configuration(); - $filePathForExpectedOutput = $this->path('/Fixture/gitlab/only_errors.json'); - - [$simulatedErrors] = $this->getMockedIssues(); - - $errorAndDiffResult = new ErrorAndDiffResult($simulatedErrors, [], []); - $output = $this->gitlabOutputFormatter->generateReport($errorAndDiffResult, $configuration); - - $this->assertJsonStringEqualsJsonFile($filePathForExpectedOutput, $output); - } - - public function testReportsFixesInTheRightFormat(): void - { - $configuration = new Configuration(); - $filePathForChanges = $this->path('/Source/RandomFileWithEdits.php'); - $filePathForExpectedOutput = $this->path('/Fixture/gitlab/only_fixes.json'); - - [$_, $simulatedFixes] = $this->getMockedIssues($filePathForChanges); - - $errorAndDiffResult = new ErrorAndDiffResult([], $simulatedFixes, []); - $output = $this->gitlabOutputFormatter->generateReport($errorAndDiffResult, $configuration); - - $this->assertJsonStringEqualsJsonFile($filePathForExpectedOutput, $output); - } - - public function testReportsErrorsAndFixesByDefault(): void - { - $configuration = new Configuration(); - $filePathForChanges = $this->path('/Source/RandomFileWithEdits.php'); - $filePathForExpectedOutput = $this->path('/Fixture/gitlab/errors_and_fixes.json'); - - [$simulatedErrors, $simulatedFixes] = $this->getMockedIssues($filePathForChanges); - - $errorAndDiffResult = new ErrorAndDiffResult($simulatedErrors, $simulatedFixes, []); - $output = $this->gitlabOutputFormatter->generateReport($errorAndDiffResult, $configuration); - - $this->assertJsonStringEqualsJsonFile($filePathForExpectedOutput, $output); - } - - public function testReportsOnlyErrorsWithNoDiffsFlag(): void - { - $configuration = new Configuration(showDiffs: false); - $filePathForChanges = $this->path('/Source/RandomFileWithEdits.php'); - $filePathForExpectedOutput = $this->path('/Fixture/gitlab/only_errors.json'); - - [$simulatedErrors, $simulatedFixes] = $this->getMockedIssues($filePathForChanges); - - $errorAndDiffResult = new ErrorAndDiffResult($simulatedErrors, $simulatedFixes, []); - $output = $this->gitlabOutputFormatter->generateReport($errorAndDiffResult, $configuration); - - $this->assertJsonStringEqualsJsonFile($filePathForExpectedOutput, $output); - } - - public function testReportsOnlyErrorsWithFixFlag(): void - { - $configuration = new Configuration(isFixer: true); - $filePathForChanges = $this->path('/Source/RandomFileWithEdits.php'); - $filePathForExpectedOutput = $this->path('/Fixture/gitlab/only_errors.json'); - - [$simulatedErrors, $simulatedFixes] = $this->getMockedIssues($filePathForChanges); - - $errorAndDiffResult = new ErrorAndDiffResult($simulatedErrors, $simulatedFixes, []); - $output = $this->gitlabOutputFormatter->generateReport($errorAndDiffResult, $configuration); - - $this->assertJsonStringEqualsJsonFile($filePathForExpectedOutput, $output); - } - - #[Depends('testReportsFixesInTheRightFormat')] - public function testIssueFingerpintsDoNotChangeFromSimpleLineOffsets(): void - { - $configuration = new Configuration(); - $filePathForOriginal = $this->path('/Source/RandomFileWithSimpleOffset.php'); - $mockedFilePathForOriginal = $this->path('/Source/RandomFile.php'); - $filePathForChanges = $this->path('/Source/RandomFileWithEditsAndSimpleOffset.php'); - $filePathForExpectedOutput = $this->path('/Fixture/gitlab/only_fixes_with_offset.json'); - - $diff = $this->differ->diff( - file_get_contents($filePathForOriginal) ?: 'ERROR 1', - file_get_contents($filePathForChanges) ?: 'ERROR 2', - ); - - // We need to mock the filepath because it's used as fingerprint material. - $simulatedFixes = [ - new FileDiff( - $mockedFilePathForOriginal, - $diff, - $this->colorConsoleDiffFormatter->format($diff), - [LineLengthFixer::class], - ), - ]; - - $errorAndDiffResult = new ErrorAndDiffResult([], $simulatedFixes, []); - $output = $this->gitlabOutputFormatter->generateReport($errorAndDiffResult, $configuration); - - $this->assertJsonStringEqualsJsonFile($filePathForExpectedOutput, $output); - } - - private function path(string $path): string - { - return StaticRelativeFilePathHelper::resolveFromCwd(__DIR__ . $path); - } - - /** - * @return array{CodingStandardError[], FileDiff[]} - */ - private function getMockedIssues(?string $filePathForChanges = null): array - { - $filePathForOriginal = $this->path('/Source/RandomFile.php'); - - $simulatedErrors = [ - new CodingStandardError(3, 'This is a test', LineLengthSniff::class, $filePathForOriginal), - new CodingStandardError(5, 'This is another test', LineLengthSniff::class, $filePathForOriginal), - ]; - - if ($filePathForChanges === null) { - return [$simulatedErrors, []]; - } - - $diff = $this->differ->diff( - file_get_contents($filePathForOriginal) ?: 'ERROR 1', - file_get_contents($filePathForChanges) ?: 'ERROR 2', - ); - - $simulatedFixes = [ - new FileDiff( - $filePathForOriginal, - $diff, - $this->colorConsoleDiffFormatter->format($diff), - [LineLengthFixer::class], - ), - ]; - - return [$simulatedErrors, $simulatedFixes]; - } -} diff --git a/tests/Console/Output/OutputFormatterCollectorTest.php b/tests/Console/Output/OutputFormatterCollectorTest.php index f89f946a49..20cde93a48 100644 --- a/tests/Console/Output/OutputFormatterCollectorTest.php +++ b/tests/Console/Output/OutputFormatterCollectorTest.php @@ -6,7 +6,6 @@ use Symplify\EasyCodingStandard\Console\Output\CheckstyleOutputFormatter; use Symplify\EasyCodingStandard\Console\Output\ConsoleOutputFormatter; -use Symplify\EasyCodingStandard\Console\Output\GitlabOutputFormatter; use Symplify\EasyCodingStandard\Console\Output\JsonOutputFormatter; use Symplify\EasyCodingStandard\Console\Output\OutputFormatterCollector; use Symplify\EasyCodingStandard\Testing\PHPUnit\AbstractTestCase; @@ -32,10 +31,6 @@ public function test(): void JsonOutputFormatter::class, $this->outputFormatterCollector->getByName(JsonOutputFormatter::getName()) ); - $this->assertInstanceOf( - GitlabOutputFormatter::class, - $this->outputFormatterCollector->getByName(GitlabOutputFormatter::getName()) - ); $this->assertInstanceOf( CheckstyleOutputFormatter::class, $this->outputFormatterCollector->getByName(CheckstyleOutputFormatter::getName()) @@ -49,4 +44,12 @@ public function testRemovedJUnitFormatFallsBackToConsole(): void $this->outputFormatterCollector->getByName('junit') ); } + + public function testRemovedGitlabFormatFallsBackToConsole(): void + { + $this->assertInstanceOf( + ConsoleOutputFormatter::class, + $this->outputFormatterCollector->getByName('gitlab') + ); + } }