Skip to content

Commit 13c01f7

Browse files
committed
feat(formatter): add Github Actions formatter
Signed-off-by: Emilien Escalle <[email protected]>
1 parent 1cc8739 commit 13c01f7

File tree

6 files changed

+190
-4
lines changed

6 files changed

+190
-4
lines changed

src/CssLint/Formatter/FormatterFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace CssLint\Formatter;
66

77
use RuntimeException;
8+
use CssLint\Formatter\GithubActionsFormatter;
89

910
/**
1011
* Factory to create FormatterManager based on requested names.
@@ -16,7 +17,7 @@ class FormatterFactory
1617

1718
public function __construct()
1819
{
19-
$availableFormatters = [new PlainFormatter()];
20+
$availableFormatters = [new PlainFormatter(), new GithubActionsFormatter()];
2021
foreach ($availableFormatters as $formatter) {
2122
$this->available[$formatter->getName()] = $formatter;
2223
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CssLint\Formatter;
6+
7+
use CssLint\LintError;
8+
use Throwable;
9+
10+
/**
11+
* Formatter for GitHub Actions annotations.
12+
*/
13+
class GithubActionsFormatter implements FormatterInterface
14+
{
15+
public function getName(): string
16+
{
17+
return 'github-actions';
18+
}
19+
20+
public function startLinting(string $source): void
21+
{
22+
echo "::group::Lint {$source}" . PHP_EOL;
23+
}
24+
25+
public function printFatalError(?string $source, mixed $error): void
26+
{
27+
$message = $error instanceof Throwable ? $error->getMessage() : (string) $error;
28+
$location = '';
29+
if ($source) {
30+
$location = "file={$source}";
31+
}
32+
echo "::error {$location}::{$message}" . PHP_EOL;
33+
}
34+
35+
public function printLintError(string $source, LintError $lintError): void
36+
{
37+
$key = $lintError->getKey();
38+
$message = $lintError->getMessage();
39+
$startPosition = $lintError->getStart();
40+
$line = $startPosition->getLine();
41+
$col = $startPosition->getColumn();
42+
echo "::error file={$source},line={$line},col={$col}::{$key->value} - {$message}" . PHP_EOL;
43+
}
44+
45+
public function endLinting(string $source, bool $isValid): void
46+
{
47+
if ($isValid) {
48+
echo "::notice ::Success: {$source} is valid." . PHP_EOL;
49+
} else {
50+
echo "::error file={$source}::{$source} is invalid CSS." . PHP_EOL;
51+
}
52+
echo "::endgroup::" . PHP_EOL;
53+
}
54+
}

src/CssLint/Formatter/PlainFormatter.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace CssLint\Formatter;
66

7+
use CssLint\LintError;
78
use Generator;
89
use Throwable;
910

@@ -30,9 +31,9 @@ public function printFatalError(?string $source, mixed $error): void
3031
echo "\033[31m/!\ Error: " . $error . "\033[0m" . PHP_EOL;
3132
}
3233

33-
public function printLintError(string $source, mixed $error): void
34+
public function printLintError(string $source, LintError $lintError): void
3435
{
35-
echo "\033[31m - " . $error . "\033[0m" . PHP_EOL;
36+
echo "\033[31m - " . $lintError . "\033[0m" . PHP_EOL;
3637
}
3738

3839
public function endLinting(string $source, bool $isValid): void

src/CssLint/LintError.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,44 @@ public function jsonSerialize(): array
7474
'end' => $this->end->jsonSerialize(),
7575
];
7676
}
77+
78+
/**
79+
* Get the key of the lint error.
80+
*
81+
* @return LintErrorKey
82+
*/
83+
public function getKey(): LintErrorKey
84+
{
85+
return $this->key;
86+
}
87+
88+
/**
89+
* Get the message of the lint error.
90+
*
91+
* @return string
92+
*/
93+
public function getMessage(): string
94+
{
95+
return $this->message;
96+
}
97+
98+
/**
99+
* Get the start position of the lint error.
100+
*
101+
* @return Position
102+
*/
103+
public function getStart(): Position
104+
{
105+
return $this->start;
106+
}
107+
108+
/**
109+
* Get the end position of the lint error.
110+
*
111+
* @return Position
112+
*/
113+
public function getEnd(): Position
114+
{
115+
return $this->end;
116+
}
77117
}

tests/TestSuite/CliTest.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ public function unvalidOptionsProvider()
129129
'non array options' => ['true', 'Unable to parse option argument: must be a json object'],
130130
'not allowed option' => ['{ "unknownOption": true }', 'Invalid option key: "unknownOption"'],
131131
'invalid option "allowedIndentationChars" value' => ['{ "allowedIndentationChars": "invalid" }', 'Option "allowedIndentationChars" must be an array'],
132-
133132
];
134133
}
135134

@@ -149,6 +148,17 @@ public function testRunWithInvalidOptionsFormatShouldReturnAnError(string $optio
149148
]));
150149
}
151150

151+
public function testRunWithFormatterArgumentShouldReturnSuccessCode()
152+
{
153+
$fileToLint = $this->testFixturesDir . '/valid.css';
154+
$this->expectOutputString(
155+
"::group::Lint CSS file \"$fileToLint\"" . PHP_EOL .
156+
"::notice ::Success: CSS file \"$fileToLint\" is valid." . PHP_EOL .
157+
"::endgroup::" . PHP_EOL
158+
);
159+
$this->assertEquals(0, $this->cli->run(['php-css-lint', '--formatter=github-actions', $fileToLint]), $this->getActualOutput());
160+
}
161+
152162
public function validCssFilesProvider(): array
153163
{
154164
return [
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\TestSuite\Formatter;
6+
7+
use CssLint\Formatter\GithubActionsFormatter;
8+
use CssLint\Formatter\FormatterFactory;
9+
use CssLint\Formatter\FormatterManager;
10+
use CssLint\LintError;
11+
use CssLint\LintErrorKey;
12+
use Exception;
13+
use PHPUnit\Framework\TestCase;
14+
use CssLint\Position;
15+
16+
class GithubActionsFormatterTest extends TestCase
17+
{
18+
public function testGetNameReturnsGithubActions(): void
19+
{
20+
$formatter = new GithubActionsFormatter();
21+
$this->assertSame('github-actions', $formatter->getName());
22+
}
23+
24+
public function testStartLintingOutputsGroup(): void
25+
{
26+
$formatter = new GithubActionsFormatter();
27+
$this->expectOutputString("::group::Lint file.css" . PHP_EOL);
28+
$formatter->startLinting('file.css');
29+
}
30+
31+
public function testPrintFatalErrorWithThrowable(): void
32+
{
33+
$formatter = new GithubActionsFormatter();
34+
$error = new Exception('fatal error');
35+
$this->expectOutputString("::error file=file.css::fatal error" . PHP_EOL);
36+
$formatter->printFatalError('file.css', $error);
37+
}
38+
39+
public function testPrintFatalErrorWithoutSource(): void
40+
{
41+
$formatter = new GithubActionsFormatter();
42+
$this->expectOutputString("::error ::some error" . PHP_EOL);
43+
$formatter->printFatalError(null, 'some error');
44+
}
45+
46+
public function testPrintLintError(): void
47+
{
48+
$positionArr = ['line' => 10, 'column' => 5];
49+
$lintError = new LintError(
50+
key: LintErrorKey::INVALID_AT_RULE_DECLARATION,
51+
message: 'issue found',
52+
start: new Position($positionArr['line'], $positionArr['column']),
53+
end: new Position($positionArr['line'], $positionArr['column'])
54+
);
55+
56+
$formatter = new GithubActionsFormatter();
57+
$this->expectOutputString("::error file=file.css,line=10,col=5::issue found" . PHP_EOL);
58+
$formatter->printLintError('file.css', $lintError);
59+
}
60+
61+
public function testEndLintingOutputsEndGroup(): void
62+
{
63+
$formatter = new GithubActionsFormatter();
64+
$this->expectOutputString(
65+
"::notice ::Success: file.css is valid." . PHP_EOL .
66+
"::endgroup::" . PHP_EOL
67+
);
68+
$formatter->endLinting('file.css', true);
69+
}
70+
71+
public function testFactoryIntegration(): void
72+
{
73+
$factory = new FormatterFactory();
74+
$available = $factory->getAvailableFormatters();
75+
$this->assertContains('github-actions', $available);
76+
77+
$manager = $factory->create('github-actions');
78+
$this->assertInstanceOf(FormatterManager::class, $manager);
79+
}
80+
}

0 commit comments

Comments
 (0)