Merge branch '3.x' into 4.x

* 3.x:
  Bump actions/cache for CI
  Remove an obsolete comment
  Throws proper Twig exception when using cycle on an empty array
  Add tests when a template doesn't have output nodes
  Optimize TextNodes
  Remove list() usage in code
  Remove usage of list() in favor of []
This commit is contained in:
Fabien Potencier
2024-01-22 18:50:54 +01:00
9 changed files with 77 additions and 8 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
-1
View File
@@ -261,7 +261,6 @@ class Environment
*
* * The cache key for the given template;
* * The currently enabled extensions;
* * Whether the Twig C extension is available or not;
* * PHP version;
* * Twig version;
* * Options with what environment was created.
+4
View File
@@ -329,6 +329,10 @@ final class CoreExtension extends AbstractExtension
return $values;
}
if (!\count($values)) {
throw new RuntimeError('The "cycle" function does not work on empty arrays');
}
return $values[$position % \count($values)];
}
+2 -2
View File
@@ -48,7 +48,7 @@ class SetNode extends Node implements NodeCaptureInterface
$compiler->addDebugInfo($this);
if (\count($this->getNode('names')) > 1) {
$compiler->write('list(');
$compiler->write('[');
foreach ($this->getNode('names') as $idx => $node) {
if ($idx) {
$compiler->raw(', ');
@@ -56,7 +56,7 @@ class SetNode extends Node implements NodeCaptureInterface
$compiler->subcompile($node);
}
$compiler->raw(')');
$compiler->raw(']');
} else {
$compiler->subcompile($this->getNode('names'), false);
}
+43
View File
@@ -24,6 +24,7 @@ use Twig\Node\ForNode;
use Twig\Node\IncludeNode;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\Node\TextNode;
/**
* Tries to optimize the AST.
@@ -43,6 +44,7 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
public const OPTIMIZE_NONE = 0;
public const OPTIMIZE_FOR = 2;
public const OPTIMIZE_RAW_FILTER = 4;
public const OPTIMIZE_TEXT_NODES = 8;
/**
* @var ForNode[]
@@ -87,6 +89,42 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
$node = $this->optimizePrintNode($node);
if (self::OPTIMIZE_TEXT_NODES === (self::OPTIMIZE_TEXT_NODES & $this->optimizers)) {
$node = $this->mergeTextNodeCalls($node);
}
return $node;
}
private function mergeTextNodeCalls(Node $node): Node
{
$text = '';
$names = [];
foreach ($node as $k => $n) {
if (!$n instanceof TextNode) {
return $node;
}
$text .= $n->getAttribute('data');
$names[] = $k;
}
if (!$text) {
return $node;
}
if (Node::class === get_class($node)) {
return new TextNode($text, $node->getTemplateLine());
}
foreach ($names as $i => $name) {
if (0 === $i) {
$node->setNode($name, new TextNode($text, $node->getTemplateLine()));
} else {
$node->removeNode($name);
}
}
return $node;
}
@@ -104,6 +142,11 @@ final class OptimizerNodeVisitor implements NodeVisitorInterface
}
$exprNode = $node->getNode('expr');
if ($exprNode instanceof ConstantExpression && is_string($exprNode->getAttribute('value'))) {
return new TextNode($exprNode->getAttribute('value'), $exprNode->getTemplateLine());
}
if (
$exprNode instanceof BlockReferenceExpression
|| $exprNode instanceof ParentExpression
-3
View File
@@ -223,9 +223,6 @@ class ExpressionParserTest extends TestCase
public function getTestsForString()
{
return [
[
'{{ "foo" }}', new ConstantExpression('foo', 1),
],
[
'{{ "foo #{bar}" }}', new ConcatBinary(
new ConstantExpression('foo ', 1),
@@ -0,0 +1,8 @@
--TEST--
"cycle" function returns an error on empty arrays
--TEMPLATE--
{{ cycle([], 0) }}
--DATA--
return []
--EXCEPTION--
Twig\Error\RuntimeError: The "cycle" function does not work on empty arrays in "index.twig" at line 2
+1 -1
View File
@@ -78,7 +78,7 @@ EOF
$node = new SetNode(false, $names, $values, 1);
$tests[] = [$node, <<<EOF
// line 1
list(\$context["foo"], \$context["bar"]) = ["foo", {$this->getVariableGetter('bar')}];
[\$context["foo"], \$context["bar"]] = ["foo", {$this->getVariableGetter('bar')}];
EOF
];
+18
View File
@@ -123,6 +123,24 @@ class TemplateTest extends TestCase
];
}
/**
* @dataProvider getRenderTemplateWithoutOutputData
*/
public function testRenderTemplateWithoutOutput(string $template)
{
$twig = new Environment(new ArrayLoader(['index' => $template]));
$this->assertSame('', $twig->render('index'));
}
public function getRenderTemplateWithoutOutputData()
{
return [
[''],
['{% for var in [] %}{% endfor %}'],
['{% if false %}{% endif %}'],
];
}
public function testRenderBlockWithUndefinedBlock()
{
$this->expectException(RuntimeError::class);