From 5c476407271ecb701eee36d0bb78bc228310a3f5 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 18 Feb 2022 16:58:31 +0800 Subject: [PATCH] Retain spaceless tag in Twig --- modules/system/twig/Extension.php | 4 ++- modules/system/twig/Loader.php | 8 ++--- modules/system/twig/SecurityPolicy.php | 6 ++-- modules/system/twig/SpacelessNode.php | 38 ++++++++++++++++++++ modules/system/twig/SpacelessTokenParser.php | 29 +++++++++++++++ tests/unit/system/twig/SpacelessTest.php | 25 +++++++++++++ 6 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 modules/system/twig/SpacelessNode.php create mode 100644 modules/system/twig/SpacelessTokenParser.php create mode 100644 tests/unit/system/twig/SpacelessTest.php diff --git a/modules/system/twig/Extension.php b/modules/system/twig/Extension.php index c564854d14..372ed7903d 100644 --- a/modules/system/twig/Extension.php +++ b/modules/system/twig/Extension.php @@ -84,7 +84,9 @@ public function getFilters() */ public function getTokenParsers() { - $parsers = []; + $parsers = [ + new SpacelessTokenParser + ]; /* * Include extensions provided by plugins diff --git a/modules/system/twig/Loader.php b/modules/system/twig/Loader.php index cf0a5accf2..e4fd2641d2 100644 --- a/modules/system/twig/Loader.php +++ b/modules/system/twig/Loader.php @@ -45,17 +45,17 @@ protected function findTemplate($name) return $this->cache[$name] = $path; } - public function getSourceContext($name) + public function getSourceContext(string $name): \Twig\Source { return new TwigSource(File::get($this->findTemplate($name)), $name); } - public function getCacheKey($name) + public function getCacheKey(string $name): string { return $this->findTemplate($name); } - public function isFresh($name, $time) + public function isFresh(string $name, int $time): bool { return File::lastModified($this->findTemplate($name)) <= $time; } @@ -65,7 +65,7 @@ public function getFilename($name) return $this->findTemplate($name); } - public function exists($name) + public function exists(string $name) { try { $this->findTemplate($name); diff --git a/modules/system/twig/SecurityPolicy.php b/modules/system/twig/SecurityPolicy.php index b1920c586d..19d2c348e2 100644 --- a/modules/system/twig/SecurityPolicy.php +++ b/modules/system/twig/SecurityPolicy.php @@ -56,7 +56,7 @@ public function __construct() * @throws SecurityNotAllowedFilterError if a given filter is not allowed * @throws SecurityNotAllowedFunctionError if a given function is not allowed */ - public function checkSecurity($tags, $filters, $functions) + public function checkSecurity($tags, $filters, $functions): void { } @@ -67,7 +67,7 @@ public function checkSecurity($tags, $filters, $functions) * @param string $property * @throws SecurityNotAllowedPropertyError */ - public function checkPropertyAllowed($obj, $property) + public function checkPropertyAllowed($obj, $property): void { } @@ -78,7 +78,7 @@ public function checkPropertyAllowed($obj, $property) * @param string $method * @throws SecurityNotAllowedMethodError */ - public function checkMethodAllowed($obj, $method) + public function checkMethodAllowed($obj, $method): void { // No need to check Twig internal objects if ($obj instanceof Template || $obj instanceof Markup) { diff --git a/modules/system/twig/SpacelessNode.php b/modules/system/twig/SpacelessNode.php new file mode 100644 index 0000000000..939326596c --- /dev/null +++ b/modules/system/twig/SpacelessNode.php @@ -0,0 +1,38 @@ + + */ +class SpacelessNode extends Node implements NodeOutputInterface +{ + public function __construct(Node $body, int $lineno, string $tag = 'spaceless') + { + parent::__construct(['body' => $body], [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ; + if ($compiler->getEnvironment()->isDebug()) { + $compiler->write("ob_start();\n"); + } else { + $compiler->write("ob_start(function () { return ''; });\n"); + } + $compiler + ->subcompile($this->getNode('body')) + ->write("echo trim(preg_replace('/>\s+<', ob_get_clean()));\n") + ; + } +} diff --git a/modules/system/twig/SpacelessTokenParser.php b/modules/system/twig/SpacelessTokenParser.php new file mode 100644 index 0000000000..d17eab1500 --- /dev/null +++ b/modules/system/twig/SpacelessTokenParser.php @@ -0,0 +1,29 @@ +parser->getStream(); + $lineno = $token->getLine(); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideSpacelessEnd'], true); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new SpacelessNode($body, $lineno, $this->getTag()); + } + + public function decideSpacelessEnd(Token $token) + { + return $token->test('endspaceless'); + } + + public function getTag() + { + return 'spaceless'; + } +} diff --git a/tests/unit/system/twig/SpacelessTest.php b/tests/unit/system/twig/SpacelessTest.php new file mode 100644 index 0000000000..7f58468415 --- /dev/null +++ b/tests/unit/system/twig/SpacelessTest.php @@ -0,0 +1,25 @@ +app->make('twig.environment'); + $template = $twig->createTemplate( + '

Test

+ + {% spaceless %} +

This is a sentence.

+ +

This is another sentence.

+ {% endspaceless %}' + ); + + $this->assertEquals( + '

Test

+ +

This is a sentence.

This is another sentence.

', + $template->render() + ); + } +}