8000 [TwigBridge] Collect all deprecations with `lint:twig` command by Fan2Shrek · Pull Request #60039 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[TwigBridge] Collect all deprecations with lint:twig command #60039

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub&rdqu 8000 o;, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 29, 2025
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
0