Skip to content

Commit 58d222a

Browse files
adds GitLab output format support to CLI. (#474)
* Add GitLab output format support to CLI. Introduced the "gitlab" format for CLI output, alongside existing "text" and "json" formats. This includes a new `GitlabPrinter` implementation, updates to the `PrinterFactory` and output option handling, as well as corresponding unit and E2E tests. The change enhances integration options, particularly for GitLab CI.
1 parent 71009b5 commit 58d222a

File tree

9 files changed

+149
-7
lines changed

9 files changed

+149
-7
lines changed

src/CLI/Command/Check.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ protected function configure(): void
9494
self::FORMAT_PARAM,
9595
'f',
9696
InputOption::VALUE_OPTIONAL,
97-
'Output format: json or text (default)',
97+
'Output format: text (default), json, gitlab',
9898
'text'
9999
);
100100
}
@@ -112,7 +112,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
112112
$skipBaseline = (bool) $input->getOption(self::SKIP_BASELINE_PARAM);
113113
$ignoreBaselineLinenumbers = (bool) $input->getOption(self::IGNORE_BASELINE_LINENUMBERS_PARAM);
114114
$format = $input->getOption(self::FORMAT_PARAM);
115-
$onlyErrors = Printer::FORMAT_JSON === $format;
115+
$onlyErrors = Printer::FORMAT_JSON === $format || Printer::FORMAT_GITLAB === $format;
116116

117117
if (true !== $skipBaseline && !$useBaseline && file_exists(self::DEFAULT_BASELINE_FILENAME)) {
118118
$useBaseline = self::DEFAULT_BASELINE_FILENAME;
@@ -285,7 +285,7 @@ private function printNoViolationsDetectedMessage(OutputInterface $output, bool
285285
{
286286
if (!$onlyErrors) {
287287
$output->writeln('<info>NO VIOLATIONS DETECTED!</info>');
288-
} elseif (Printer::FORMAT_JSON === $format) {
288+
} elseif (Printer::FORMAT_JSON === $format || Printer::FORMAT_GITLAB === $format) {
289289
$output->writeln('<info>[]</info>');
290290
}
291291
}

src/CLI/Printer/GitlabPrinter.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Arkitect\CLI\Printer;
5+
6+
use Arkitect\Rules\Violation;
7+
8+
class GitlabPrinter implements Printer
9+
{
10+
public function print(array $violationsCollection): string
11+
{
12+
$allErrors = [];
13+
14+
/**
15+
* @var string $key
16+
* @var Violation[] $violationsByFqcn
17+
*/
18+
foreach ($violationsCollection as $class => $violationsByFqcn) {
19+
foreach ($violationsByFqcn as $violation) {
20+
$checkName = $class.'.'.$this->toKebabCase($violation->getError());
21+
22+
$error = [
23+
'description' => $violation->getError(),
24+
'check_name' => $checkName,
25+
'fingerprint' => hash('sha256', $checkName),
26+
'severity' => 'major',
27+
'location' => [
28+
'path' => $violation->getFilePath(),
29+
'lines' => [
30+
'begin' => $violation->getLine() ?? 1,
31+
],
32+
],
33+
];
34+
35+
$allErrors[] = $error;
36+
}
37+
}
38+
39+
return json_encode($allErrors);
40+
}
41+
42+
private function toKebabCase(string $string): string
43+
{
44+
$string = preg_replace('/[^a-zA-Z0-9]+/', ' ', $string);
45+
$string = preg_replace('/\s+/', ' ', $string);
46+
$string = strtolower(trim($string));
47+
$string = str_replace(' ', '-', $string);
48+
49+
return $string;
50+
}
51+
}

src/CLI/Printer/Printer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ interface Printer
99

1010
public const FORMAT_JSON = 'json';
1111

12+
public const FORMAT_GITLAB = 'gitlab';
13+
1214
public function print(array $violationsCollection): string;
1315
}

src/CLI/Printer/PrinterFactory.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ final class PrinterFactory
88
public function create(string $format): Printer
99
{
1010
switch ($format) {
11-
case 'json':
11+
case Printer::FORMAT_GITLAB:
12+
return new GitlabPrinter();
13+
case Printer::FORMAT_JSON:
1214
return new JsonPrinter();
1315
default:
1416
return new TextPrinter();

tests/E2E/Cli/CheckCommandTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,40 @@ public function test_json_format_output_no_errors(): void
195195
$this->assertCount(0, $json);
196196
}
197197

198+
public function test_gitlab_format_output(): void
199+
{
200+
$configFilePath = __DIR__.'/../_fixtures/configMvcForYieldBug.php';
201+
202+
$cmdTester = $this->runCheck($configFilePath, null, null, false, false, false, 'gitlab');
203+
204+
$this->assertCheckHasErrors($cmdTester);
205+
206+
$display = $cmdTester->getDisplay();
207+
208+
$this->assertJson($display);
209+
210+
self::assertSame(<<<JSON
211+
[{"description":"should have a name that matches *Controller because all controllers should be end name with Controller","check_name":"App\\\\Controller\\\\Foo.should-have-a-name-that-matches-controller-because-all-controllers-should-be-end-name-with-controller","fingerprint":"1e960c3f49b5ec63ece40321072ef2bd0bc33ad11b7be326f304255d277dc860","severity":"major","location":{"path":"Controller\/Foo.php","lines":{"begin":1}}}]
212+
213+
JSON, $display);
214+
}
215+
216+
public function test_gitlab_format_output_no_errors(): void
217+
{
218+
$configFilePath = __DIR__.'/../_fixtures/configMvcWithoutErrors.php';
219+
220+
$cmdTester = $this->runCheck($configFilePath, null, null, false, false, false, 'gitlab');
221+
222+
$this->assertCheckHasSuccess($cmdTester);
223+
224+
$display = $cmdTester->getDisplay();
225+
226+
$this->assertJson($display);
227+
228+
$json = json_decode($display, true);
229+
$this->assertCount(0, $json);
230+
}
231+
198232
protected function runCheck(
199233
$configFilePath = null,
200234
?bool $stopOnFailure = null,
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Arkitect\Tests\Unit\CLI\Printer;
5+
6+
use Arkitect\CLI\Printer\GitlabPrinter;
7+
use Arkitect\Rules\Violation;
8+
use PHPUnit\Framework\TestCase;
9+
10+
class GitlabPrinterTest extends TestCase
11+
{
12+
/**
13+
* Test the `print` method returns a valid JSON string
14+
* with violations details when violations exist.
15+
*/
16+
public function test_print_with_violations(): void
17+
{
18+
$violation1 = new Violation('App\\ExampleClass', 'Some error message', 42, 'tests/Unit/CLI/Printer/GitlabPrinterTest.php');
19+
$violation2 = new Violation('App\\AnotherExampleClass', 'Another error message', null, 'tests/Unit/CLI/Printer/GitlabPrinterTest.php');
20+
21+
$violationsCollection = [
22+
'RuleA' => [$violation1],
23+
'RuleB' => [$violation2],
24+
];
25+
26+
$printer = new GitlabPrinter();
27+
28+
$result = $printer->print($violationsCollection);
29+
30+
self::assertSame(<<<JSON
31+
[{"description":"Some error message","check_name":"RuleA.some-error-message","fingerprint":"7ddcfd42f5f2af3d00864ef959a0327f508cb5227aedca96d919d681a5dcde4a","severity":"major","location":{"path":"tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php","lines":{"begin":42}}},{"description":"Another error message","check_name":"RuleB.another-error-message","fingerprint":"800c2ceafbf4023e401200186ecabdfe59891c5d6670e86571e3c50339df07dc","severity":"major","location":{"path":"tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php","lines":{"begin":1}}}]
32+
JSON, $result);
33+
}
34+
35+
/**
36+
* Test the `print` method returns an empty JSON array
37+
* when no violations are provided.
38+
*/
39+
public function test_print_with_no_violations(): void
40+
{
41+
$violationsCollection = [];
42+
43+
$printer = new GitlabPrinter();
44+
45+
$result = $printer->print($violationsCollection);
46+
47+
$this->assertIsString($result, 'Result should be a string');
48+
$this->assertJson($result, 'Result should be a valid JSON string');
49+
50+
$decodedResult = json_decode($result, true);
51+
$this->assertEmpty($decodedResult, 'Result should be an empty array');
52+
}
53+
}

tests/Unit/Printer/JsonPrinterTest.php renamed to tests/Unit/CLI/Printer/JsonPrinterTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
declare(strict_types=1);
33

4-
namespace Arkitect\Tests\Unit\Printer;
4+
namespace Arkitect\Tests\Unit\CLI\Printer;
55

66
use Arkitect\CLI\Printer\JsonPrinter;
77
use Arkitect\Rules\Violation;

tests/Unit/Printer/PrinterFactoryTest.php renamed to tests/Unit/CLI/Printer/PrinterFactoryTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
declare(strict_types=1);
33

4-
namespace Arkitect\Tests\Unit\Printer;
4+
namespace Arkitect\Tests\Unit\CLI\Printer;
55

66
use Arkitect\CLI\Printer\JsonPrinter;
77
use Arkitect\CLI\Printer\PrinterFactory;

tests/Unit/Printer/TextPrinterTest.php renamed to tests/Unit/CLI/Printer/TextPrinterTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
declare(strict_types=1);
33

4-
namespace Arkitect\Tests\Unit\Printer;
4+
namespace Arkitect\Tests\Unit\CLI\Printer;
55

66
use Arkitect\CLI\Printer\TextPrinter;
77
use Arkitect\Rules\Violation;

0 commit comments

Comments
 (0)