mirror of
https://github.com/twigphp/Twig.git
synced 2026-09-01 04:57:23 +00:00
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:
@@ -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') }}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)];
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
];
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user