4505200d |
<?php
|
2119e60c |
namespace Twig\Tests;
|
4505200d |
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
|
34bdab4d |
use PHPUnit\Framework\TestCase;
|
90d579e4 |
use Twig\Environment;
|
5ebcecf2 |
use Twig\Error\SyntaxError;
|
90d579e4 |
use Twig\Loader\LoaderInterface;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\Binary\ConcatBinary;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Parser;
use Twig\Source;
|
34bdab4d |
class ExpressionParserTest extends TestCase
|
4505200d |
{
/**
|
14675864 |
* @dataProvider getFailingTestsForAssignment
*/
public function testCanOnlyAssignToNames($template)
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
$parser->parse($env->tokenize(new Source($template, 'index')));
|
14675864 |
}
public function getFailingTestsForAssignment()
{
|
5c55243d |
return [
['{% set false = "foo" %}'],
['{% set FALSE = "foo" %}'],
['{% set true = "foo" %}'],
['{% set TRUE = "foo" %}'],
['{% set none = "foo" %}'],
['{% set NONE = "foo" %}'],
['{% set null = "foo" %}'],
['{% set NULL = "foo" %}'],
['{% set 3 = "foo" %}'],
['{% set 1 + 2 = "foo" %}'],
['{% set "bar" = "foo" %}'],
['{% set %}{% endset %}'],
];
|
14675864 |
}
/**
|
4505200d |
* @dataProvider getTestsForArray
*/
public function testArrayExpression($template, $expected)
{
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
d4db9153 |
$stream = $env->tokenize($source = new Source($template, ''));
|
90d579e4 |
$parser = new Parser($env);
|
d4db9153 |
$expected->setSourceContext($source);
|
4505200d |
|
a6842511 |
$this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr'));
|
4505200d |
}
/**
* @dataProvider getFailingTestsForArray
*/
public function testArraySyntaxError($template)
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
$parser->parse($env->tokenize(new Source($template, 'index')));
|
4505200d |
}
public function getFailingTestsForArray()
{
|
5c55243d |
return [
['{{ [1, "a": "b"] }}'],
['{{ {"a": "b", 2} }}'],
];
|
4505200d |
}
public function getTestsForArray()
{
|
5c55243d |
return [
|
4505200d |
// simple array
|
90d579e4 |
['{{ [1, 2] }}', new ArrayExpression([
new ConstantExpression(0, 1),
new ConstantExpression(1, 1),
|
591f982b |
|
90d579e4 |
new ConstantExpression(1, 1),
new ConstantExpression(2, 1),
|
5c55243d |
], 1),
],
|
4505200d |
// array with trailing ,
|
90d579e4 |
['{{ [1, 2, ] }}', new ArrayExpression([
new ConstantExpression(0, 1),
new ConstantExpression(1, 1),
|
591f982b |
|
90d579e4 |
new ConstantExpression(1, 1),
new ConstantExpression(2, 1),
|
5c55243d |
], 1),
],
|
4505200d |
// simple hash
|
90d579e4 |
['{{ {"a": "b", "b": "c"} }}', new ArrayExpression([
new ConstantExpression('a', 1),
new ConstantExpression('b', 1),
|
591f982b |
|
90d579e4 |
new ConstantExpression('b', 1),
new ConstantExpression('c', 1),
|
5c55243d |
], 1),
],
|
4505200d |
// hash with trailing ,
|
90d579e4 |
['{{ {"a": "b", "b": "c", } }}', new ArrayExpression([
new ConstantExpression('a', 1),
new ConstantExpression('b', 1),
|
591f982b |
|
90d579e4 |
new ConstantExpression('b', 1),
new ConstantExpression('c', 1),
|
5c55243d |
], 1),
],
|
4505200d |
// hash in an array
|
90d579e4 |
['{{ [1, {"a": "b", "b": "c"}] }}', new ArrayExpression([
new ConstantExpression(0, 1),
new ConstantExpression(1, 1),
|
591f982b |
|
90d579e4 |
new ConstantExpression(1, 1),
new ArrayExpression([
new ConstantExpression('a', 1),
new ConstantExpression('b', 1),
|
591f982b |
|
90d579e4 |
new ConstantExpression('b', 1),
new ConstantExpression('c', 1),
|
5c55243d |
], 1),
], 1),
],
|
4505200d |
// array in a hash
|
90d579e4 |
['{{ {"a": [1, 2], "b": "c"} }}', new ArrayExpression([
new ConstantExpression('a', 1),
new ArrayExpression([
new ConstantExpression(0, 1),
new ConstantExpression(1, 1),
new ConstantExpression(1, 1),
new ConstantExpression(2, 1),
|
5c55243d |
], 1),
|
90d579e4 |
new ConstantExpression('b', 1),
new ConstantExpression('c', 1),
|
5c55243d |
], 1),
],
];
|
4505200d |
}
|
773fff59 |
|
ee39215c |
public function testStringExpressionDoesNotConcatenateTwoConsecutiveStrings()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
|
90d579e4 |
$stream = $env->tokenize(new Source('{{ "a" "b" }}', 'index'));
$parser = new Parser($env);
|
ee39215c |
$parser->parse($stream);
}
/**
|
773fff59 |
* @dataProvider getTestsForString
*/
public function testStringExpression($template, $expected)
{
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false, 'optimizations' => 0]);
|
d4db9153 |
$stream = $env->tokenize($source = new Source($template, ''));
|
90d579e4 |
$parser = new Parser($env);
|
d4db9153 |
$expected->setSourceContext($source);
|
773fff59 |
$this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr'));
}
public function getTestsForString()
{
|
5c55243d |
return [
[
|
90d579e4 |
'{{ "foo" }}', new ConstantExpression('foo', 1),
|
5c55243d |
],
[
|
90d579e4 |
'{{ "foo #{bar}" }}', new ConcatBinary(
new ConstantExpression('foo ', 1),
new NameExpression('bar', 1),
|
773fff59 |
1
),
|
5c55243d |
],
[
|
90d579e4 |
'{{ "foo #{bar} baz" }}', new ConcatBinary(
new ConcatBinary(
new ConstantExpression('foo ', 1),
new NameExpression('bar', 1),
|
773fff59 |
1
),
|
90d579e4 |
new ConstantExpression(' baz', 1),
|
773fff59 |
1
|
89df3c20 |
),
|
5c55243d |
],
|
d41d10ca |
|
5c55243d |
[
|
90d579e4 |
'{{ "foo #{"foo #{bar} baz"} baz" }}', new ConcatBinary(
new ConcatBinary(
new ConstantExpression('foo ', 1),
new ConcatBinary(
new ConcatBinary(
new ConstantExpression('foo ', 1),
new NameExpression('bar', 1),
|
773fff59 |
1
),
|
90d579e4 |
new ConstantExpression(' baz', 1),
|
773fff59 |
1
),
1
),
|
90d579e4 |
new ConstantExpression(' baz', 1),
|
773fff59 |
1
),
|
5c55243d |
],
];
|
773fff59 |
}
|
e1ad2bde |
|
a59dcde3 |
public function testAttributeCallDoesNotSupportNamedArguments()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
a59dcde3 |
|
90d579e4 |
$parser->parse($env->tokenize(new Source('{{ foo.bar(name="Foo") }}', 'index')));
|
a59dcde3 |
}
|
74586e9f |
public function testMacroCallDoesNotSupportNamedArguments()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
74586e9f |
|
90d579e4 |
$parser->parse($env->tokenize(new Source('{% from _self import foo %}{% macro foo() %}{% endmacro %}{{ foo(name="Foo") }}', 'index')));
|
74586e9f |
}
|
4647913e |
public function testMacroDefinitionDoesNotSupportNonNameVariableName()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
$this->expectExceptionMessage('An argument must be a name. Unexpected token "string" of value "a" ("name" expected) in "index" at line 1.');
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
4647913e |
|
90d579e4 |
$parser->parse($env->tokenize(new Source('{% macro foo("a") %}{% endmacro %}', 'index')));
|
4647913e |
}
/**
* @dataProvider getMacroDefinitionDoesNotSupportNonConstantDefaultValues
*/
public function testMacroDefinitionDoesNotSupportNonConstantDefaultValues($template)
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
$this->expectExceptionMessage('A default value for an argument must be a constant (a boolean, a string, a number, or an array) in "index" at line 1');
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
4647913e |
|
90d579e4 |
$parser->parse($env->tokenize(new Source($template, 'index')));
|
4647913e |
}
public function getMacroDefinitionDoesNotSupportNonConstantDefaultValues()
{
|
5c55243d |
return [
['{% macro foo(name = "a #{foo} a") %}{% endmacro %}'],
['{% macro foo(name = [["b", "a #{foo} a"]]) %}{% endmacro %}'],
];
|
4647913e |
}
/**
* @dataProvider getMacroDefinitionSupportsConstantDefaultValues
*/
public function testMacroDefinitionSupportsConstantDefaultValues($template)
{
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
4647913e |
|
90d579e4 |
$parser->parse($env->tokenize(new Source($template, 'index')));
|
7259e52f |
// add a dummy assertion here to satisfy PHPUnit, the only thing we want to test is that the code above
// can be executed without throwing any exceptions
$this->addToAssertionCount(1);
|
4647913e |
}
public function getMacroDefinitionSupportsConstantDefaultValues()
{
|
5c55243d |
return [
['{% macro foo(name = "aa") %}{% endmacro %}'],
['{% macro foo(name = 12) %}{% endmacro %}'],
['{% macro foo(name = true) %}{% endmacro %}'],
['{% macro foo(name = ["a"]) %}{% endmacro %}'],
['{% macro foo(name = [["a"]]) %}{% endmacro %}'],
['{% macro foo(name = {a: "a"}) %}{% endmacro %}'],
['{% macro foo(name = {a: {b: "a"}}) %}{% endmacro %}'],
];
|
4647913e |
}
|
e1ad2bde |
public function testUnknownFunction()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
$this->expectExceptionMessage('Unknown "cycl" function. Did you mean "cycle" in "index" at line 1?');
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
e1ad2bde |
|
90d579e4 |
$parser->parse($env->tokenize(new Source('{{ cycl() }}', 'index')));
|
e1ad2bde |
}
|
d0a5ef8c |
public function testUnknownFunctionWithoutSuggestions()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
$this->expectExceptionMessage('Unknown "foobar" function in "index" at line 1.');
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
d0a5ef8c |
|
90d579e4 |
$parser->parse($env->tokenize(new Source('{{ foobar() }}', 'index')));
|
d0a5ef8c |
}
|
e1ad2bde |
public function testUnknownFilter()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
$this->expectExceptionMessage('Unknown "lowe" filter. Did you mean "lower" in "index" at line 1?');
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
e1ad2bde |
|
90d579e4 |
$parser->parse($env->tokenize(new Source('{{ 1|lowe }}', 'index')));
|
e1ad2bde |
}
|
d0a5ef8c |
public function testUnknownFilterWithoutSuggestions()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
$this->expectExceptionMessage('Unknown "foobar" filter in "index" at line 1.');
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
d0a5ef8c |
|
90d579e4 |
$parser->parse($env->tokenize(new Source('{{ 1|foobar }}', 'index')));
|
d0a5ef8c |
}
|
e1ad2bde |
public function testUnknownTest()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
$this->expectExceptionMessage('Unknown "nul" test. Did you mean "null" in "index" at line 1');
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
$stream = $env->tokenize(new Source('{{ 1 is nul }}', 'index'));
|
6c76583e |
$parser->parse($stream);
|
e1ad2bde |
}
|
d0a5ef8c |
public function testUnknownTestWithoutSuggestions()
{
|
5ebcecf2 |
$this->expectException(SyntaxError::class);
|
b776e41f |
$this->expectExceptionMessage('Unknown "foobar" test in "index" at line 1.');
|
656c295e |
$env = new Environment($this->createMock(LoaderInterface::class), ['cache' => false, 'autoescape' => false]);
|
90d579e4 |
$parser = new Parser($env);
|
d0a5ef8c |
|
90d579e4 |
$parser->parse($env->tokenize(new Source('{{ 1 is foobar }}', 'index')));
|
d0a5ef8c |
}
|
4505200d |
}
|