Skip to content

Commit 78f4dff

Browse files
committed
[TwigBridge] LintCommand supports Github Actions annotations
1 parent e574a24 commit 78f4dff

File tree

4 files changed

+77
-6
lines changed

4 files changed

+77
-6
lines changed

src/Symfony/Bridge/Twig/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
5.4
5+
---
6+
7+
* Add `github` format & autodetection to render errors as annotations when
8+
running the Twig linter command in a Github Actions environment.
9+
410
5.3
511
---
612

src/Symfony/Bridge/Twig/Command/LintCommand.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bridge\Twig\Command;
1313

14+
use Symfony\Component\Console\CI\GithubActionReporter;
1415
use Symfony\Component\Console\Command\Command;
1516
use Symfony\Component\Console\Exception\InvalidArgumentException;
1617
use Symfony\Component\Console\Exception\RuntimeException;
@@ -39,6 +40,11 @@ class LintCommand extends Command
3940

4041
private $twig;
4142

43+
/**
44+
* @var string|null
45+
*/
46+
private $format;
47+
4248
public function __construct(Environment $twig)
4349
{
4450
parent::__construct();
@@ -80,6 +86,15 @@ protected function execute(InputInterface $input, OutputInterface $output)
8086
$io = new SymfonyStyle($input, $output);
8187
$filenames = $input->getArgument('filename');
8288
$showDeprecations = $input->getOption('show-deprecations');
89+
$this->format = $input->getOption('format');
90+
91+
if ('github' === $this->format && !class_exists(GithubActionReporter::class)) {
92+
throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.');
93+
}
94+
95+
if (null === $this->format) {
96+
$this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt';
97+
}
8398

8499
if (['-'] === $filenames) {
85100
return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]);
@@ -169,26 +184,29 @@ private function validate(string $template, string $file): array
169184

170185
private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files)
171186
{
172-
switch ($input->getOption('format')) {
187+
switch ($this->format) {
173188
case 'txt':
174189
return $this->displayTxt($output, $io, $files);
175190
case 'json':
176191
return $this->displayJson($output, $files);
192+
case 'github':
193+
return $this->displayTxt($output, $io, $files, true);
177194
default:
178195
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
179196
}
180197
}
181198

182-
private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo)
199+
private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false)
183200
{
184201
$errors = 0;
202+
$githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null;
185203

186204
foreach ($filesInfo as $info) {
187205
if ($info['valid'] && $output->isVerbose()) {
188206
$io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
189207
} elseif (!$info['valid']) {
190208
++$errors;
191-
$this->renderException($io, $info['template'], $info['exception'], $info['file']);
209+
$this->renderException($io, $info['template'], $info['exception'], $info['file'], $githubReporter);
192210
}
193211
}
194212

@@ -220,10 +238,14 @@ private function displayJson(OutputInterface $output, array $filesInfo)
220238
return min($errors, 1);
221239
}
222240

223-
private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null)
241+
private function renderException(SymfonyStyle $output, string $template, Error $exception, string $file = null, ?GithubActionReporter $githubReporter = null)
224242
{
225243
$line = $exception->getTemplateLine();
226244

245+
if ($githubReporter) {
246+
$githubReporter->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line);
247+
}
248+
227249
if ($file) {
228250
$output->text(sprintf('<error> ERROR </error> in %s (line %s)', $file, $line));
229251
} else {

src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Bridge\Twig\Command\LintCommand;
1616
use Symfony\Component\Console\Application;
17+
use Symfony\Component\Console\CI\GithubActionReporter;
1718
use Symfony\Component\Console\Output\OutputInterface;
1819
use Symfony\Component\Console\Tester\CommandTester;
1920
use Twig\Environment;
@@ -107,6 +108,48 @@ public function testLintDefaultPaths()
107108
self::assertStringContainsString('OK in', trim($tester->getDisplay()));
108109
}
109110

111+
public function testLintIncorrectFileWithGithubFormat()
112+
{
113+
if (!class_exists(GithubActionReporter::class)) {
114+
$this->expectException(\InvalidArgumentException::class);
115+
$this->expectExceptionMessage('The "github" format is only available since "symfony/console" >= 5.3');
116+
}
117+
118+
$filename = $this->createFile('{{ foo');
119+
$tester = $this->createCommandTester();
120+
121+
$tester->execute(['filename' => [$filename], '--format' => 'github'], ['decorated' => false]);
122+
123+
if (!class_exists(GithubActionReporter::class)) {
124+
return;
125+
}
126+
127+
self::assertEquals(1, $tester->getStatusCode(), 'Returns 1 in case of error');
128+
self::assertStringMatchesFormat('%A::error file=%s, line=1, col=0::Unexpected token "end of template" ("end of print statement" expected).%A', trim($tester->getDisplay()));
129+
}
130+
131+
public function testLintAutodetectsGithubActionEnvironment()
132+
{
133+
// if (!class_exists(GithubActionReporter::class)) {
134+
// $this->markTestSkipped('The "github" format is only available since "symfony/console" >= 5.3.');
135+
// }
136+
137+
$prev = getenv('GITHUB_ACTIONS');
138+
putenv('GITHUB_ACTIONS');
139+
140+
try {
141+
putenv('GITHUB_ACTIONS=1');
142+
143+
$filename = $this->createFile('{{ foo');
144+
$tester = $this->createCommandTester();
145+
146+
$tester->execute(['filename' => [$filename]], ['decorated' => false]);
147+
self::assertStringMatchesFormat('%A::error file=%s, line=1, col=0::Unexpected token "end of template" ("end of print statement" expected).%A', trim($tester->getDisplay()));
148+
} finally {
149+
putenv('GITHUB_ACTIONS'.($prev ? "=$prev" : ''));
150+
}
151+
}
152+
110153
private function createCommandTester(): CommandTester
111154
{
112155
$environment = new Environment(new FilesystemLoader(\dirname(__DIR__).'/Fixtures/templates/'));

src/Symfony/Bridge/Twig/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"symfony/security-http": "^4.4|^5.0|^6.0",
4545
"symfony/serializer": "^5.2|^6.0",
4646
"symfony/stopwatch": "^4.4|^5.0|^6.0",
47-
"symfony/console": "^4.4|^5.0|^6.0",
47+
"symfony/console": "^5.3|^6.0",
4848
"symfony/expression-language": "^4.4|^5.0|^6.0",
4949
"symfony/web-link": "^4.4|^5.0|^6.0",
5050
"symfony/workflow": "^5.2|^6.0",
@@ -55,7 +55,7 @@
5555
"conflict": {
5656
"phpdocumentor/reflection-docblock": "<3.2.2",
5757
"phpdocumentor/type-resolver": "<1.4.0",
58-
"symfony/console": "<4.4",
58+
"symfony/console": "<5.3",
5959
"symfony/form": "<5.3",
6060
"symfony/http-foundation": "<5.3",
6161
"symfony/http-kernel": "<4.4",

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy