From 5794d41a9f2e169f1e8e2a55b9e91d80951d01f8 Mon Sep 17 00:00:00 2001 From: Pierre Hennequart Date: Sat, 21 Jun 2025 17:02:54 +0200 Subject: [PATCH] [WebProfilerBundle] Fix missing indent on non php files opended in the profiler --- .../Bridge/Twig/Extension/CodeExtension.php | 100 +++++++++++++----- .../Tests/Extension/CodeExtensionTest.php | 95 +++++++++++++++++ .../Twig/Tests/Fixtures/hello_world.json | 4 + .../Twig/Tests/Fixtures/hello_world.php | 4 + 4 files changed, 176 insertions(+), 27 deletions(-) create mode 100644 src/Symfony/Bridge/Twig/Tests/Fixtures/hello_world.json create mode 100644 src/Symfony/Bridge/Twig/Tests/Fixtures/hello_world.php diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index 63718e32bb2db..a6199e696858b 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -121,39 +121,85 @@ public function formatArgsAsText(array $args): string */ public function fileExcerpt(string $file, int $line, int $srcContext = 3): ?string { - if (is_file($file) && is_readable($file)) { - // highlight_file could throw warnings - // see https://bugs.php.net/25725 - $code = @highlight_file($file, true); - if (\PHP_VERSION_ID >= 80300) { - // remove main pre/code tags - $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); - // split multiline span tags - $code = preg_replace_callback('#]++)>((?:[^<\\n]*+\\n)++[^<]*+)#', function ($m) { - return "".str_replace("\n", "\n", $m[2]).''; - }, $code); - $content = explode("\n", $code); - } else { - // remove main code/span tags - $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); - // split multiline spans - $code = preg_replace_callback('#]++)>((?:[^<]*+
)++[^<]*+)
#', fn ($m) => "".str_replace('
', "

", $m[2]).'', $code); - $content = explode('
', $code); - } + if (!is_file($file) || !is_readable($file)) { + return null; + } + + $contents = file_get_contents($file); + + if (!str_contains($contents, ' $srcContext) { - $srcContext = \count($content); + $srcContext = \count($lines); } - for ($i = max($line - $srcContext, 1), $max = min($line + $srcContext, \count($content)); $i <= $max; ++$i) { - $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; - } + return $this->formatFileExcerpt( + $this->extractExcerptLines($lines, $line, $srcContext), + $line, + $srcContext + ); + } - return '
    '.implode("\n", $lines).'
'; + // highlight_string could throw warnings + // see https://bugs.php.net/25725 + $code = @highlight_string($contents, true); + + if (\PHP_VERSION_ID >= 80300) { + // remove main pre/code tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + // split multiline span tags + $code = preg_replace_callback( + '#]++)>((?:[^<\\n]*+\\n)++[^<]*+)#', + static fn (array $m): string => "".str_replace("\n", "\n", $m[2]).'', + $code + ); + $lines = explode("\n", $code); + } else { + // remove main code/span tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + // split multiline spans + $code = preg_replace_callback( + '#]++)>((?:[^<]*+
)++[^<]*+)
#', + static fn (array $m): string => "".str_replace('
', "

", $m[2]).'', + $code + ); + $lines = explode('
', $code); } - return null; + if (0 > $srcContext) { + $srcContext = \count($lines); + } + + return $this->formatFileExcerpt( + array_map( + self::fixCodeMarkup(...), + $this->extractExcerptLines($lines, $line, $srcContext), + ), + $line, + $srcContext + ); + } + + private function extractExcerptLines(array $lines, int $selectedLine, int $srcContext): array + { + return \array_slice( + $lines, + max($selectedLine - $srcContext, 0), + min($srcContext * 2 + 1, \count($lines) - $selectedLine + $srcContext), + true + ); + } + + private function formatFileExcerpt(array $lines, int $selectedLine, int $srcContext): string + { + $start = max($selectedLine - $srcContext, 1); + + return "
    ".implode("\n", array_map( + static fn (string $line, int $num): string => '{$line}", + $lines, + array_keys($lines), + )).'
'; } /** @@ -243,7 +289,7 @@ protected static function fixCodeMarkup(string $line): string // missing tag at the end of line $opening = strpos($line, ''); - if (false !== $opening && (false === $closing || $closing > $opening)) { + if (false !== $opening && (false === $closing || $closing < $opening)) { $line .= ''; } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php index 62bbcf6300880..53075f96113a4 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php @@ -129,6 +129,101 @@ public function testFormatFileIntegration() $this->assertEquals($expected, $this->render($template)); } + /** + * @dataProvider fileExcerptIntegrationProvider + */ + public function testFileExcerptIntegration(string $expected, array $data) + { + $template = <<<'TWIG' +{{ file_path|file_excerpt(line, src_context) }} +TWIG; + $html = $this->render($template, $data); + + // highlight_file function output changed sing PHP 8.3 + // see https://github.com/php/php-src/blob/e2667f17bc24e3cd200bb3eda457f566f1f77f8f/UPGRADING#L239-L242 + if (\PHP_VERSION_ID < 80300) { + $html = str_replace(' ', ' ', $html); + } + + $html = html_entity_decode($html); + + $this->assertEquals($expected, $html); + } + + public static function fileExcerptIntegrationProvider() + { + $fixturesPath = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; + + yield 'php file' => [ + 'expected' => <<<'HTML' +
  1. +
  2. +
  3. echo 'Hello';
  4. +
  5. echo 'World!';
  6. +
+HTML, + 'data' => [ + 'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.php', + 'line' => 0, + 'src_context' => 3, + ], + ]; + + yield 'php file with selected line and no source context' => [ + 'expected' => <<<'HTML' +
  1. +
  2. +
  3. echo 'Hello';
  4. +
  5. echo 'World!';
  6. +
+HTML, + 'data' => [ + 'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.php', + 'line' => 1, + 'src_context' => -1, + ], + ]; + + yield 'php file excerpt with selected line and custom source context' => [ + 'expected' => <<<'HTML' +
  1. echo 'Hello';
  2. +
  3. echo 'World!';
  4. +
+HTML, + 'data' => [ + 'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.php', + 'line' => 3, + 'src_context' => 1, + ], + ]; + + yield 'php file excerpt with out of bound selected line' => [ + 'expected' => <<<'HTML' +
    +HTML, + 'data' => [ + 'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.php', + 'line' => 100, + 'src_context' => 1, + ], + ]; + + yield 'json file' => [ + 'expected' => <<<'HTML' +
    1. [
    2. +
    3. "Hello",
    4. +
    5. "World!"
    6. +
    7. ]
    8. +
    +HTML, + 'data' => [ + 'file_path' => $fixturesPath.\DIRECTORY_SEPARATOR.'hello_world.json', + 'line' => 0, + 'src_context' => 3, + ], + ]; + } + public function testFormatFileFromTextIntegration() { $template = <<<'TWIG' diff --git a/src/Symfony/Bridge/Twig/Tests/Fixtures/hello_world.json b/src/Symfony/Bridge/Twig/Tests/Fixtures/hello_world.json new file mode 100644 index 0000000000000..56cc557387321 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Fixtures/hello_world.json @@ -0,0 +1,4 @@ +[ + "Hello", + "World!" +] diff --git a/src/Symfony/Bridge/Twig/Tests/Fixtures/hello_world.php b/src/Symfony/Bridge/Twig/Tests/Fixtures/hello_world.php new file mode 100644 index 0000000000000..4d7bf8fdf167e --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Fixtures/hello_world.php @@ -0,0 +1,4 @@ + 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