8000 [TwigBridge] LintCommand supports Github Actions annotations · symfony/symfony@b51062a · GitHub
[go: up one dir, main page]

Skip to content

Commit b51062a

Browse files
committed
[TwigBridge] LintCommand supports Github Actions annotations
1 parent c01b032 commit b51062a

File tree

3 files changed

+75
-5
lines changed

3 files changed

+75
-5
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.3
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.2.0
511
-----
612

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

Lines changed: 26 additions & 5 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;
@@ -37,6 +38,10 @@ class LintCommand extends Command
3738
protected static $defaultName = 'lint:twig';
3839

3940
private $twig;
41+
/**
42+
* @var string|null
43+
*/
44+
private $format;
4045

4146
public function __construct(Environment $twig)
4247
{
@@ -49,7 +54,7 @@ protected function configure()
4954
{
5055
$this
5156
->setDescription('Lints a template and outputs encountered errors')
52-
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
57+
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format')
5358
->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors')
5459
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
5560
->setHelp(<<<'EOF'
@@ -79,6 +84,15 @@ protected function execute(InputInterface $input, OutputInterface $output)
7984
$io = new SymfonyStyle($input, $output);
8085
$filenames = $input->getArgument('filename');
8186
$showDeprecations = $input->getOption('show-deprecations');
87+
$this->format = $input->getOption('format');
88+
89+
if ('github' === $this->format && !class_exists(GithubActionReporter::class)) {
90+
throw new \InvalidArgumentException('The "github" format is only available since "symfony/console" >= 5.3.');
91+
}
92+
93+
if (null === $this->format) {
94+
$this->format = class_exists(GithubActionReporter::class) && GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt';
95+
}
8296

8397
if (['-'] === $filenames) {
8498
return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]);
@@ -168,26 +182,29 @@ private function validate(string $template, string $file): array
168182

169183
private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files)
170184
{
171-
switch ($input->getOption('format')) {
185+
switch ($this->format) {
172186
case 'txt':
173187
return $this->displayTxt($output, $io, $files);
174188
case 'json':
175189
return $this->displayJson($output, $files);
190+
case 'github':
191+
return $this->displayTxt($output, $io, $files, true);
176192
default:
177193
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
178194
}
179195
}
180196

181-
private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo)
197+
private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false)
182198
{
183199
$errors = 0;
200+
$githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null;
184201

185202
foreach ($filesInfo as $info) {
186203
if ($info['valid'] && $output->isVerbose()) {
187204
$io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
188205
} elseif (!$info['valid']) {
189206
++$errors;
190-
$this->renderException($io, $info['template'], $info['exception'], $info['file']);
207+
$this->renderException($io, $info['template'], $info['exception'], $info['file'], $githubReporter);
191208
}
192209
}
193210

@@ -219,10 +236,14 @@ private function displayJson(OutputInterface $output, array $filesInfo)
219236
return min($errors, 1);
220237
}
221238

222-
private function renderException(OutputInterface $output, string $template, Error $exception, string $file = null)
239+
private function renderException(OutputInterface $output, string $template, Error $exception, string $file = null, ?GithubActionReporter $githubReporter = null)
223240
{
224241
$line = $exception->getTemplateLine();
225242

243+
if ($githubReporter) {
244+
$githubReporter->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line);
245+
}
246+
226247
if ($file) {
227248
$output->text(sprintf('<error> ERROR </error> in %s (line %s)', $file, $line));
228249
} 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/'));

0 commit comments

Comments
 (0)
0