Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Bridge/Twig/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CHANGELOG
* Add `is_granted_for_user()` Twig function
* Add `field_id()` Twig form helper function
* Add a `Twig` constraint that validates Twig templates
* Make `lint:twig` collect all deprecations instead of stopping at the first one

7.2
---
Expand Down
77 changes: 47 additions & 30 deletions src/Symfony/Bridge/Twig/Command/LintCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt');

if (['-'] === $filenames) {
return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), 'Standard Input')]);
return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), 'Standard Input', $showDeprecations)]);
}

if (!$filenames) {
Expand All @@ -107,38 +107,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}

if ($showDeprecations) {
$prevErrorHandler = set_error_handler(static function ($level, $message, $file, $line) use (&$prevErrorHandler) {
if (\E_USER_DEPRECATED === $level) {
$templateLine = 0;
if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) {
$templateLine = $matches[1];
}

throw new Error($message, $templateLine);
}

return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
});
}

try {
$filesInfo = $this->getFilesInfo($filenames);
} finally {
if ($showDeprecations) {
restore_error_handler();
}
}

return $this->display($input, $output, $io, $filesInfo);
return $this->display($input, $output, $io, $this->getFilesInfo($filenames, $showDeprecations));
}

private function getFilesInfo(array $filenames): array
private function getFilesInfo(array $filenames, bool $showDeprecations): array
{
$filesInfo = [];
foreach ($filenames as $filename) {
foreach ($this->findFiles($filename) as $file) {
$filesInfo[] = $this->validate(file_get_contents($file), $file);
$filesInfo[] = $this->validate(file_get_contents($file), $file, $showDeprecations);
}
}

Expand All @@ -156,8 +133,26 @@ protected function findFiles(string $filename): iterable
throw new RuntimeException(\sprintf('File or directory "%s" is not readable.', $filename));
}

private function validate(string $template, string $file): array
private function validate(string $template, string $file, bool $collectDeprecation): array
{
$deprecations = [];
if ($collectDeprecation) {
$prevErrorHandler = set_error_handler(static function ($level, $message, $fileName, $line) use (&$prevErrorHandler, &$deprecations, $file) {
if (\E_USER_DEPRECATED === $level) {
$templateLine = 0;
if (preg_match('/ at line (\d+)[ .]/', $message, $matches)) {
$templateLine = $matches[1];
}

$deprecations[] = ['message' => $message, 'file' => $file, 'line' => $templateLine];

return true;
}

return $prevErrorHandler ? $prevErrorHandler($level, $message, $fileName, $line) : false;
});
}

$realLoader = $this->twig->getLoader();
try {
$temporaryLoader = new ArrayLoader([$file => $template]);
Expand All @@ -169,9 +164,13 @@ private function validate(string $template, string $file): array
$this->twig->setLoader($realLoader);

return ['template' => $template, 'file' => $file, 'line' => $e->getTemplateLine(), 'valid' => false, 'exception' => $e];
} finally {
if ($collectDeprecation) {
restore_error_handler();
}
}

return ['template' => $template, 'file' => $file, 'valid' => true];
return ['template' => $template, 'file' => $file, 'deprecations' => $deprecations, 'valid' => true];
}

private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, array $files): int
Expand All @@ -188,6 +187,11 @@ private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $fi
{
$errors = 0;
$githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($output) : null;
$deprecations = array_merge(...array_column($filesInfo, 'deprecations'));

foreach ($deprecations as $deprecation) {
$this->renderDeprecation($io, $deprecation['line'], $deprecation['message'], $deprecation['file'], $githubReporter);
}

foreach ($filesInfo as $info) {
if ($info['valid'] && $output->isVerbose()) {
Expand All @@ -204,7 +208,7 @@ private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $fi
$io->warning(\sprintf('%d Twig files have valid syntax and %d contain errors.', \count($filesInfo) - $errors, $errors));
}

return min($errors, 1);
return !$deprecations && !$errors ? 0 : 1;
}

private function displayJson(OutputInterface $output, array $filesInfo): int
Expand All @@ -226,6 +230,19 @@ private function displayJson(OutputInterface $output, array $filesInfo): int
return min($errors, 1);
}

private function renderDeprecation(SymfonyStyle $output, int $line, string $message, string $file, ?GithubActionReporter $githubReporter): void
{
$githubReporter?->error($message, $file, $line <= 0 ? null : $line);

if ($file) {
$output->text(\sprintf('<info> DEPRECATION </info> in %s (line %s)', $file, $line));
} else {
$output->text(\sprintf('<info> DEPRECATION </info> (line %s)', $line));
}

$output->text(\sprintf('<info> >> %s</info> ', $message));
}

private function renderException(SymfonyStyle $output, string $template, Error $exception, ?string $file = null, ?GithubActionReporter $githubReporter = null): void
{
$line = $exception->getTemplateLine();
Expand Down
15 changes: 14 additions & 1 deletion src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,20 @@ public function testLintFileWithReportedDeprecation()
$ret = $tester->execute(['filename' => [$filename], '--show-deprecations' => true], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false]);

$this->assertEquals(1, $ret, 'Returns 1 in case of error');
$this->assertMatchesRegularExpression('/ERROR in \S+ \(line 1\)/', trim($tester->getDisplay()));
$this->assertMatchesRegularExpression('/DEPRECATION in \S+ \(line 1\)/', trim($tester->getDisplay()));
$this->assertStringContainsString('Filter "deprecated_filter" is deprecated', trim($tester->getDisplay()));
}

public function testLintFileWithMultipleReportedDeprecation()
{
$tester = $this->createCommandTester();
$filename = $this->createFile("{{ foo|deprecated_filter }}\n{{ bar|deprecated_filter }}");

$ret = $tester->execute(['filename' => [$filename], '--show-deprecations' => true], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false]);

$this->assertEquals(1, $ret, 'Returns 1 in case of error');
$this->assertMatchesRegularExpression('/DEPRECATION in \S+ \(line 1\)/', trim($tester->getDisplay()));
$this->assertMatchesRegularExpression('/DEPRECATION in \S+ \(line 2\)/', trim($tester->getDisplay()));
$this->assertStringContainsString('Filter "deprecated_filter" is deprecated', trim($tester->getDisplay()));
}

Expand Down
Loading