diff --git a/CHANGELOG b/CHANGELOG index 5b07b5955bd..4bd3a1c8deb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # 3.15.0 (2024-XX-XX) + * Remove `MacroAutoImportNodeVisitor` + * Deprecate `MethodCallExpression` in favor of `MacroReferenceExpression` + * Fix support for the "is defined" test on `_self.xxx` (auto-imported) macros + * Fix support for the "is defined" test on inherited macros * Add named arguments support for the dot operator arguments (`foo.bar(some: arg)`) * Add named arguments support for macros * Add a new `guard` tag that allows to test if some Twig callables are available at compilation time diff --git a/doc/deprecated.rst b/doc/deprecated.rst index 1239c77bcea..7d66534130a 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -170,6 +170,9 @@ Nodes deprecated as of Twig 3.12: ``arguments``, ``callable``, ``is_variadic``, and ``dynamic_name``. +* The ``MethodCallExpression`` class is deprecated as of Twig 3.15, use + ``MacroReferenceExpression`` instead. + Node Visitors ------------- diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index 23ecb43a8b7..6892f7c789c 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -24,7 +24,7 @@ use Twig\Node\Expression\ConditionalExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\GetAttrExpression; -use Twig\Node\Expression\MethodCallExpression; +use Twig\Node\Expression\MacroReferenceExpression; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\TempNameExpression; use Twig\Node\Expression\TestExpression; @@ -33,6 +33,7 @@ use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; use Twig\Node\Expression\Unary\SpreadUnary; +use Twig\Node\Expression\Variable\TemplateVariable; use Twig\Node\Node; use Twig\Node\Nodes; @@ -531,11 +532,7 @@ public function parsePostfixExpression($node) public function getFunctionNode($name, $line) { if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { - $arguments = $this->createArguments($line); - $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line); - $node->setAttribute('safe', true); - - return $node; + return new MacroReferenceExpression(new TemplateVariable($alias['node'], $line), $alias['name'], $this->createArguments($line), $line); } $args = $this->parseOnlyArguments(); @@ -561,84 +558,11 @@ public function getFunctionNode($name, $line) public function parseSubscriptExpression($node) { - $stream = $this->parser->getStream(); - $token = $stream->next(); - $lineno = $token->getLine(); - $arguments = new ArrayExpression([], $lineno); - $type = Template::ANY_CALL; - if ('.' == $token->getValue()) { - if ($stream->nextIf(Token::PUNCTUATION_TYPE, '(')) { - $arg = $this->parseExpression(); - $stream->expect(Token::PUNCTUATION_TYPE, ')'); - if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { - $type = Template::METHOD_CALL; - $arguments = $this->createArguments($lineno); - } - - return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); - } - $token = $stream->next(); - if ( - Token::NAME_TYPE == $token->getType() - || - Token::NUMBER_TYPE == $token->getType() - || - (Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) - ) { - $arg = new ConstantExpression($token->getValue(), $lineno); - - if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { - $type = Template::METHOD_CALL; - $arguments = $this->createArguments($lineno); - } - } else { - throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext()); - } - - if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { - $name = $arg->getAttribute('value'); - - $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno); - $node->setAttribute('safe', true); - - return $node; - } - } else { - $type = Template::ARRAY_CALL; - - // slice? - $slice = false; - if ($stream->test(Token::PUNCTUATION_TYPE, ':')) { - $slice = true; - $arg = new ConstantExpression(0, $token->getLine()); - } else { - $arg = $this->parseExpression(); - } - - if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) { - $slice = true; - } - - if ($slice) { - if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { - $length = new ConstantExpression(null, $token->getLine()); - } else { - $length = $this->parseExpression(); - } - - $filter = $this->getFilter('slice', $token->getLine()); - $arguments = new Nodes([$arg, $length]); - $filter = new ($filter->getNodeClass())($node, $filter, $arguments, $token->getLine()); - - $stream->expect(Token::PUNCTUATION_TYPE, ']'); - - return $filter; - } - - $stream->expect(Token::PUNCTUATION_TYPE, ']'); + if ('.' === $this->parser->getStream()->next()->getValue()) { + return $this->parseSubscriptExpressionDot($node); } - return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); + return $this->parseSubscriptExpressionArray($node); } public function parseFilterExpression($node) @@ -825,8 +749,7 @@ private function parseTestExpression(Node $node): TestExpression } if ('defined' === $test->getName() && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) { - $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine()); - $node->setAttribute('safe', true); + $node = new MacroReferenceExpression(new TemplateVariable($alias['node'], $node->getTemplateLine()), $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine()); } $ready = $test instanceof TwigTest; @@ -997,4 +920,91 @@ public function parseOnlyArguments() return new Nodes($args); } + + private function parseSubscriptExpressionDot(Node $node): AbstractExpression + { + $stream = $this->parser->getStream(); + $token = $stream->getCurrent(); + $lineno = $token->getLine(); + $arguments = new ArrayExpression([], $lineno); + $type = Template::ANY_CALL; + + if ($stream->nextIf(Token::PUNCTUATION_TYPE, '(')) { + $attribute = $this->parseExpression(); + $stream->expect(Token::PUNCTUATION_TYPE, ')'); + } else { + $token = $stream->next(); + if ( + Token::NAME_TYPE == $token->getType() + || + Token::NUMBER_TYPE == $token->getType() + || + (Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) + ) { + $attribute = new ConstantExpression($token->getValue(), $token->getLine()); + } else { + throw new SyntaxError(\sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $token->getLine(), $stream->getSourceContext()); + } + } + + if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + $type = Template::METHOD_CALL; + $arguments = $this->createArguments($token->getLine()); + } + + if ( + $node instanceof NameExpression + && + ( + null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name')) + || + '_self' === $node->getAttribute('name') && $attribute instanceof ConstantExpression + ) + ) { + return new MacroReferenceExpression(new TemplateVariable($node->getAttribute('name'), $node->getTemplateLine()), 'macro_'.$attribute->getAttribute('value'), $arguments, $node->getTemplateLine()); + } + + return new GetAttrExpression($node, $attribute, $arguments, $type, $lineno); + } + + private function parseSubscriptExpressionArray(Node $node): AbstractExpression + { + $stream = $this->parser->getStream(); + $token = $stream->getCurrent(); + $lineno = $token->getLine(); + $arguments = new ArrayExpression([], $lineno); + + // slice? + $slice = false; + if ($stream->test(Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + $attribute = new ConstantExpression(0, $token->getLine()); + } else { + $attribute = $this->parseExpression(); + } + + if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + } + + if ($slice) { + if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { + $length = new ConstantExpression(null, $token->getLine()); + } else { + $length = $this->parseExpression(); + } + + $filter = $this->getFilter('slice', $token->getLine()); + $arguments = new Nodes([$attribute, $length]); + $filter = new ($filter->getNodeClass())($node, $filter, $arguments, $token->getLine()); + + $stream->expect(Token::PUNCTUATION_TYPE, ']'); + + return $filter; + } + + $stream->expect(Token::PUNCTUATION_TYPE, ']'); + + return new GetAttrExpression($node, $attribute, $arguments, Template::ARRAY_CALL, $lineno); + } } diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index 21a67c217a9..f8f80ac2f0c 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -66,7 +66,6 @@ use Twig\Node\Expression\Unary\NotUnary; use Twig\Node\Expression\Unary\PosUnary; use Twig\Node\Node; -use Twig\NodeVisitor\MacroAutoImportNodeVisitor; use Twig\OperatorPrecedenceChange; use Twig\Parser; use Twig\Source; @@ -293,7 +292,7 @@ public function getTests(): array public function getNodeVisitors(): array { - return [new MacroAutoImportNodeVisitor()]; + return []; } public function getOperators(): array @@ -1286,6 +1285,8 @@ public static function capitalize(string $charset, $string): string /** * @internal + * + * to be removed in 4.0 */ public static function callMacro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) { diff --git a/src/Node/Expression/MacroReferenceExpression.php b/src/Node/Expression/MacroReferenceExpression.php new file mode 100644 index 00000000000..abe99aa3564 --- /dev/null +++ b/src/Node/Expression/MacroReferenceExpression.php @@ -0,0 +1,56 @@ + + */ +class MacroReferenceExpression extends AbstractExpression +{ + public function __construct(TemplateVariable $template, string $name, AbstractExpression $arguments, int $lineno) + { + parent::__construct(['template' => $template, 'arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('is_defined_test')) { + $compiler + ->subcompile($this->getNode('template')) + ->raw('->hasMacro(') + ->repr($this->getAttribute('name')) + ->raw(', $context') + ->raw(')') + ; + + return; + } + + $compiler + ->subcompile($this->getNode('template')) + ->raw('->getTemplateForMacro(') + ->repr($this->getAttribute('name')) + ->raw(', $context, ') + ->repr($this->getTemplateLine()) + ->raw(', $this->getSourceContext())') + ->raw(\sprintf('->%s', $this->getAttribute('name'))) + ->raw('(...') + ->subcompile($this->getNode('arguments')) + ->raw(')') + ; + } +} diff --git a/src/Node/Expression/MethodCallExpression.php b/src/Node/Expression/MethodCallExpression.php index 1b337960d87..9aede826ca0 100644 --- a/src/Node/Expression/MethodCallExpression.php +++ b/src/Node/Expression/MethodCallExpression.php @@ -17,6 +17,8 @@ class MethodCallExpression extends AbstractExpression { public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno) { + trigger_deprecation('twig/twig', '3.15', 'The "%s" class is deprecated, use "%s" instead.', __CLASS__, MacroReferenceExpression::class); + parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno); if ($node instanceof NameExpression) { diff --git a/src/Node/Expression/Test/DefinedTest.php b/src/Node/Expression/Test/DefinedTest.php index 005ba39cc4b..b6c3ff6f963 100644 --- a/src/Node/Expression/Test/DefinedTest.php +++ b/src/Node/Expression/Test/DefinedTest.php @@ -20,6 +20,7 @@ use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FunctionExpression; use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\MacroReferenceExpression; use Twig\Node\Expression\MethodCallExpression; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\TestExpression; @@ -55,6 +56,8 @@ public function __construct(Node $node, TwigTest|string $name, ?Node $arguments, $this->changeIgnoreStrictCheck($node); } elseif ($node instanceof BlockReferenceExpression) { $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof MacroReferenceExpression) { + $node->setAttribute('is_defined_test', true); } elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) { $node->setAttribute('is_defined_test', true); } elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) { diff --git a/src/Node/Expression/Variable/TemplateVariable.php b/src/Node/Expression/Variable/TemplateVariable.php new file mode 100644 index 00000000000..cde52d0b456 --- /dev/null +++ b/src/Node/Expression/Variable/TemplateVariable.php @@ -0,0 +1,31 @@ +getAttribute('name')) { + $compiler->raw('$this'); + } else { + $compiler + ->raw('$macros[') + ->string($this->getAttribute('name')) + ->raw(']') + ; + } + } +} diff --git a/src/Node/ImportNode.php b/src/Node/ImportNode.php index 9a6033f215b..7f057ed9733 100644 --- a/src/Node/ImportNode.php +++ b/src/Node/ImportNode.php @@ -14,6 +14,7 @@ use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\AssignNameExpression; use Twig\Node\Expression\NameExpression; /** @@ -27,7 +28,7 @@ class ImportNode extends Node /** * @param bool $global */ - public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, $global = true) + public function __construct(AbstractExpression $expr, AbstractExpression|string $var, int $lineno, $global = true) { if (null === $global || \is_string($global)) { trigger_deprecation('twig/twig', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); @@ -36,7 +37,15 @@ public function __construct(AbstractExpression $expr, AbstractExpression $var, i throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a boolean, "%s" given.', __METHOD__, get_debug_type($global))); } - parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno); + if (!\is_string($var)) { + trigger_deprecation('twig/twig', '3.15', \sprintf('Passing a "%s" instance as the second argument of "%s" is deprecated, pass a "string" instead.', $var::class, __CLASS__)); + } else { + $var = new AssignNameExpression($var, $lineno); + } + + $this->deprecateNode('var', new NameDeprecation('var', '3.15')); + + parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global, 'var' => $var->getAttribute('name')], $lineno); } public function compile(Compiler $compiler): void @@ -44,14 +53,14 @@ public function compile(Compiler $compiler): void $compiler ->addDebugInfo($this) ->write('$macros[') - ->repr($this->getNode('var')->getAttribute('name')) + ->repr($this->getAttribute('var')) ->raw('] = ') ; if ($this->getAttribute('global')) { $compiler ->raw('$this->macros[') - ->repr($this->getNode('var')->getAttribute('name')) + ->repr($this->getAttribute('var')) ->raw('] = ') ; } diff --git a/src/NodeVisitor/EscaperNodeVisitor.php b/src/NodeVisitor/EscaperNodeVisitor.php index c942f825b34..6f5aa10bfe5 100644 --- a/src/NodeVisitor/EscaperNodeVisitor.php +++ b/src/NodeVisitor/EscaperNodeVisitor.php @@ -61,7 +61,7 @@ public function enterNode(Node $node, Environment $env): Node } elseif ($node instanceof BlockNode) { $this->statusStack[] = $this->blocks[$node->getAttribute('name')] ?? $this->needEscaping(); } elseif ($node instanceof ImportNode) { - $this->safeVars[] = $node->getNode('var')->getAttribute('name'); + $this->safeVars[] = $node->getAttribute('var'); } return $node; diff --git a/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/src/NodeVisitor/MacroAutoImportNodeVisitor.php deleted file mode 100644 index 5f6d273f4c2..00000000000 --- a/src/NodeVisitor/MacroAutoImportNodeVisitor.php +++ /dev/null @@ -1,74 +0,0 @@ - - * - * @internal - */ -final class MacroAutoImportNodeVisitor implements NodeVisitorInterface -{ - private $inAModule = false; - private $hasMacroCalls = false; - - public function enterNode(Node $node, Environment $env): Node - { - if ($node instanceof ModuleNode) { - $this->inAModule = true; - $this->hasMacroCalls = false; - } - - return $node; - } - - public function leaveNode(Node $node, Environment $env): Node - { - if ($node instanceof ModuleNode) { - $this->inAModule = false; - if ($this->hasMacroCalls) { - $node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0)); - } - } elseif ($this->inAModule) { - if ( - $node instanceof GetAttrExpression - && $node->getNode('node') instanceof NameExpression - && '_self' === $node->getNode('node')->getAttribute('name') - && $node->getNode('attribute') instanceof ConstantExpression - ) { - $this->hasMacroCalls = true; - - $name = $node->getNode('attribute')->getAttribute('value'); - $node = new MethodCallExpression($node->getNode('node'), 'macro_'.$name, $node->getNode('arguments'), $node->getTemplateLine()); - $node->setAttribute('safe', true); - } - } - - return $node; - } - - public function getPriority(): int - { - // we must be ran before auto-escaping - return -10; - } -} diff --git a/src/NodeVisitor/SafeAnalysisNodeVisitor.php b/src/NodeVisitor/SafeAnalysisNodeVisitor.php index 07672164ece..dbe7150c933 100644 --- a/src/NodeVisitor/SafeAnalysisNodeVisitor.php +++ b/src/NodeVisitor/SafeAnalysisNodeVisitor.php @@ -18,6 +18,7 @@ use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\FunctionExpression; use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\MacroReferenceExpression; use Twig\Node\Expression\MethodCallExpression; use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\ParentExpression; @@ -126,12 +127,9 @@ public function leaveNode(Node $node, Environment $env): ?Node } else { $this->setSafe($node, []); } - } elseif ($node instanceof MethodCallExpression) { - if ($node->getAttribute('safe')) { - $this->setSafe($node, ['all']); - } else { - $this->setSafe($node, []); - } + } elseif ($node instanceof MethodCallExpression || $node instanceof MacroReferenceExpression) { + // all macro calls are safe + $this->setSafe($node, ['all']); } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) { $name = $node->getNode('node')->getAttribute('name'); if (\in_array($name, $this->safeVars)) { diff --git a/src/Parser.php b/src/Parser.php index 05ad88ce878..249c23de1d7 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -292,9 +292,15 @@ public function embedTemplate(ModuleNode $template) $this->embeddedTemplates[] = $template; } - public function addImportedSymbol(string $type, string $alias, ?string $name = null, ?AbstractExpression $node = null): void + public function addImportedSymbol(string $type, string $alias, ?string $name = null, AbstractExpression|string|null $internalRef = null): void { - $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node]; + if ($internalRef instanceof AbstractExpression) { + trigger_deprecation('twig/twig', '3.15', 'Passing a non-string internal reference name to "%s" is deprecated ("%s" given).', __METHOD__, $internalRef::class); + + $internalRef = $internalRef->getAttribute('name'); + } + + $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $internalRef]; } public function getImportedSymbol(string $type, string $alias) diff --git a/src/Template.php b/src/Template.php index 86cb560e977..cf36da1192d 100644 --- a/src/Template.php +++ b/src/Template.php @@ -478,6 +478,35 @@ public function yieldParentBlock($name, array $context, array $blocks = []): ite } } + protected function hasMacro(string $name, array $context): bool + { + if (method_exists($this, $name)) { + return true; + } + + if (!$parent = $this->getParent($context)) { + return false; + } + + return $parent->hasMacro($name, $context); + } + + protected function getTemplateForMacro(string $name, array $context, int $line, Source $source): Template + { + if (method_exists($this, $name)) { + return $this; + } + + $parent = $this; + while ($parent = $parent->getParent($context)) { + if (method_exists($parent, $name)) { + return $parent; + } + } + + throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".', substr($name, \strlen('macro_')), $this->getTemplateName()), $line, $source); + } + /** * Auto-generated method to display the template with the given context. * diff --git a/src/TokenParser/FromTokenParser.php b/src/TokenParser/FromTokenParser.php index 92811aa140e..fbd8f560fde 100644 --- a/src/TokenParser/FromTokenParser.php +++ b/src/TokenParser/FromTokenParser.php @@ -50,11 +50,11 @@ public function parse(Token $token): Node $stream->expect(Token::BLOCK_END_TYPE); - $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); - $node = new ImportNode($macro, $var, $token->getLine(), $this->parser->isMainScope()); + $internalRef = $this->parser->getVarName(); + $node = new ImportNode($macro, $internalRef, $token->getLine(), $this->parser->isMainScope()); foreach ($targets as $name => $alias) { - $this->parser->addImportedSymbol('function', $alias->getAttribute('name'), 'macro_'.$name, $var); + $this->parser->addImportedSymbol('function', $alias->getAttribute('name'), 'macro_'.$name, $internalRef); } return $node; diff --git a/src/TokenParser/ImportTokenParser.php b/src/TokenParser/ImportTokenParser.php index f20f35ab3c3..f4f1acd3d73 100644 --- a/src/TokenParser/ImportTokenParser.php +++ b/src/TokenParser/ImportTokenParser.php @@ -11,7 +11,6 @@ namespace Twig\TokenParser; -use Twig\Node\Expression\AssignNameExpression; use Twig\Node\ImportNode; use Twig\Node\Node; use Twig\Token; @@ -29,10 +28,9 @@ public function parse(Token $token): Node { $macro = $this->parser->getExpressionParser()->parseExpression(); $this->parser->getStream()->expect(Token::NAME_TYPE, 'as'); - $var = new AssignNameExpression($this->parser->getStream()->expect(Token::NAME_TYPE)->getValue(), $token->getLine()); + $var = $this->parser->getStream()->expect(Token::NAME_TYPE)->getValue(); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); - - $this->parser->addImportedSymbol('template', $var->getAttribute('name')); + $this->parser->addImportedSymbol('template', $var); return new ImportNode($macro, $var, $token->getLine(), $this->parser->isMainScope()); } diff --git a/src/TokenParser/MacroTokenParser.php b/src/TokenParser/MacroTokenParser.php index 19260302079..7b34eeef689 100644 --- a/src/TokenParser/MacroTokenParser.php +++ b/src/TokenParser/MacroTokenParser.php @@ -16,13 +16,11 @@ use Twig\Node\EmptyNode; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; -use Twig\Node\Expression\NameExpression; use Twig\Node\Expression\TempNameExpression; use Twig\Node\Expression\Unary\NegUnary; use Twig\Node\Expression\Unary\PosUnary; use Twig\Node\MacroNode; use Twig\Node\Node; -use Twig\Node\Nodes; use Twig\Token; /** diff --git a/tests/Fixtures/tests/defined_for_macros.test b/tests/Fixtures/tests/defined_for_macros.test index 1aa45fc8268..657b43e1788 100644 --- a/tests/Fixtures/tests/defined_for_macros.test +++ b/tests/Fixtures/tests/defined_for_macros.test @@ -1,41 +1,80 @@ --TEST-- "defined" support for macros --TEMPLATE-- +{% extends 'macros.twig' %} + +{% import 'macros.twig' as macros_ext %} +{% from 'macros.twig' import lol, baz %} {% import _self as macros %} {% from _self import hello, bar %} -{% if macros.hello is defined -%} - OK -{% endif %} +{% block content %} + {{~ macros.hello is defined ? 'OK' : 'KO' }} + {{~ macros.hello() is defined ? 'OK' : 'KO' }} + + {{~ macros_ext.lol is defined ? 'OK' : 'KO' }} + {{~ macros_ext.lol() is defined ? 'OK' : 'KO' }} + + {{~ macros.foo is not defined ? 'OK' : 'KO' }} + {{~ macros.foo() is not defined ? 'OK' : 'KO' }} + + {{~ macros_ext.hello is not defined ? 'OK' : 'KO' }} + {{~ macros_ext.hello() is not defined ? 'OK' : 'KO' }} + + {{~ hello is defined ? 'OK' : 'KO' }} + {{~ hello() is defined ? 'OK' : 'KO' }} + + {{~ lol is defined ? 'OK' : 'KO' }} + {{~ lol() is defined ? 'OK' : 'KO' }} + + {{~ baz is not defined ? 'OK' : 'KO' }} + {{~ baz() is not defined ? 'OK' : 'KO' }} -{% if macros.foo is not defined -%} - OK -{% endif %} + {{~ _self.hello is defined ? 'OK' : 'KO' }} + {{~ _self.hello() is defined ? 'OK' : 'KO' }} -{% if hello is defined -%} - OK -{% endif %} + {{~ _self.bar is not defined ? 'OK' : 'KO' }} + {{~ _self.bar() is not defined ? 'OK' : 'KO' }} -{% if bar is not defined -%} - OK -{% endif %} + {{~ _self.lol is defined ? 'OK' : 'KO' }} + {{~ _self.lol() is defined ? 'OK' : 'KO' }} +{% endblock %} -{% if foo is not defined -%} - OK -{% endif %} +{% macro hello(name) %}{% endmacro %} +--TEMPLATE(macros.twig)-- +{% block content %} +{% endblock %} -{% macro hello(name) %} - Hello {{ name }} -{% endmacro %} +{% macro lol(name) -%}{% endmacro %} --DATA-- return [] --EXPECT-- OK +OK +OK OK +OK OK +OK +OK + +OK OK OK +OK + +OK +OK + +OK +OK + +OK +OK + +OK +OK diff --git a/tests/Node/ImportTest.php b/tests/Node/ImportTest.php index eb69e218384..4337f968ac7 100644 --- a/tests/Node/ImportTest.php +++ b/tests/Node/ImportTest.php @@ -11,7 +11,6 @@ * file that was distributed with this source code. */ -use Twig\Node\Expression\AssignNameExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\ImportNode; use Twig\Test\NodeTestCase; @@ -21,11 +20,10 @@ class ImportTest extends NodeTestCase public function testConstructor() { $macro = new ConstantExpression('foo.twig', 1); - $var = new AssignNameExpression('macro', 1); - $node = new ImportNode($macro, $var, 1); + $node = new ImportNode($macro, $var = 'macro', 1); $this->assertEquals($macro, $node->getNode('expr')); - $this->assertEquals($var, $node->getNode('var')); + $this->assertEquals($var, $node->getAttribute('var')); } public static function provideTests(): iterable @@ -33,8 +31,7 @@ public static function provideTests(): iterable $tests = []; $macro = new ConstantExpression('foo.twig', 1); - $var = new AssignNameExpression('macro', 1); - $node = new ImportNode($macro, $var, 1); + $node = new ImportNode($macro, 'macro', 1); $tests[] = [$node, <<