Merge branch '3.x' into 4.x

* 3.x:
  Use trigger_deprecation() instead of trigger_error() for deprecations
  Tweak docs
  clarify that macros shadow other macros/functions
This commit is contained in:
Fabien Potencier
2024-08-08 14:47:26 +02:00
13 changed files with 132 additions and 33 deletions
+12
View File
@@ -285,6 +285,18 @@ deprecated one when that makes sense::
// ...
}, ['deprecated' => true, 'alternative' => 'new_one']);
.. versionadded:: 3.11
The ``deprecating_package`` option was added in Twig 3.11.
You can also set the ``deprecating_package`` option to specify the package that
is deprecating the filter, and ``deprecated`` can be set to the package version
when the filter was deprecated::
$filter = new \Twig\TwigFilter('obsolete', function () {
// ...
}, ['deprecated' => '1.1', 'deprecating_package' => 'foo/bar']);
When a filter is deprecated, Twig emits a deprecation notice when compiling a
template using it. See :ref:`deprecation-notices` for more information.
+11
View File
@@ -23,6 +23,17 @@ You can also deprecate a macro in the following way:
Note that by default, the deprecation notices are silenced and never displayed nor logged.
See :ref:`deprecation-notices` to learn how to handle them.
.. versionadded:: 3.11
The ``package`` and ``version`` options were added in Twig 3.11.
You can optionally add the package and the version that introduced the deprecation:
.. code-block:: twig
{% deprecated 'The "base.twig" template is deprecated, use "layout.twig" instead.' package='twig/twig' %}
{% deprecated 'The "base.twig" template is deprecated, use "layout.twig" instead.' package='twig/twig' version='3.11' %}
.. note::
Don't use the ``deprecated`` tag to deprecate a ``block`` as the
+3 -3
View File
@@ -74,8 +74,8 @@ via the ``from`` tag:
.. caution::
As macros imported via ``from`` are called like functions, be careful to
not override existing functions:
As macros imported via ``from`` are called like functions, be careful that
they shadow existing functions:
.. code-block:: twig
@@ -113,7 +113,7 @@ Imported macros are not available in the body of ``embed`` tags, you need
to explicitly re-import macros inside the tag.
When calling ``import`` or ``from`` from a ``block`` tag, the imported macros
are only defined in the current block and they override macros defined at the
are only defined in the current block and they shadow macros defined at the
template level with the same names.
Checking if a Macro is defined
+3 -12
View File
@@ -767,16 +767,13 @@ class ExpressionParser
$stream = $this->parser->getStream();
$message = \sprintf('Twig Test "%s" is deprecated', $test->getName());
if ($test->getDeprecatedVersion()) {
$message .= \sprintf(' since version %s', $test->getDeprecatedVersion());
}
if ($test->getAlternative()) {
$message .= \sprintf('. Use "%s" instead', $test->getAlternative());
}
$src = $stream->getSourceContext();
$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
@trigger_error($message, \E_USER_DEPRECATED);
trigger_deprecation($test->getDeprecatingPackage(), $test->getDeprecatedVersion(), $message);
}
return $test->getNodeClass();
@@ -793,16 +790,13 @@ class ExpressionParser
if ($function->isDeprecated()) {
$message = \sprintf('Twig Function "%s" is deprecated', $function->getName());
if ($function->getDeprecatedVersion()) {
$message .= \sprintf(' since version %s', $function->getDeprecatedVersion());
}
if ($function->getAlternative()) {
$message .= \sprintf('. Use "%s" instead', $function->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, \E_USER_DEPRECATED);
trigger_deprecation($function->getDeprecatingPackage(), $function->getDeprecatedVersion(), $message);
}
return $function->getNodeClass();
@@ -819,16 +813,13 @@ class ExpressionParser
if ($filter->isDeprecated()) {
$message = \sprintf('Twig Filter "%s" is deprecated', $filter->getName());
if ($filter->getDeprecatedVersion()) {
$message .= \sprintf(' since version %s', $filter->getDeprecatedVersion());
}
if ($filter->getAlternative()) {
$message .= \sprintf('. Use "%s" instead', $filter->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= \sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, \E_USER_DEPRECATED);
trigger_deprecation($filter->getDeprecatingPackage(), $filter->getDeprecatedVersion(), $message);
}
return $filter->getNodeClass();
+27 -9
View File
@@ -35,21 +35,39 @@ class DeprecatedNode extends Node
$expr = $this->getNode('expr');
if ($expr instanceof ConstantExpression) {
$compiler->write('@trigger_error(')
->subcompile($expr);
} else {
if (!$expr instanceof ConstantExpression) {
$varName = $compiler->getVarName();
$compiler->write(\sprintf('$%s = ', $varName))
$compiler
->write(\sprintf('$%s = ', $varName))
->subcompile($expr)
->raw(";\n")
->write(\sprintf('@trigger_error($%s', $varName));
;
}
$compiler->write('trigger_deprecation(');
if ($this->hasNode('package')) {
$compiler->subcompile($this->getNode('package'));
} else {
$compiler->raw("''");
}
$compiler->raw(', ');
if ($this->hasNode('version')) {
$compiler->subcompile($this->getNode('version'));
} else {
$compiler->raw("''");
}
$compiler->raw(', ');
if ($expr instanceof ConstantExpression) {
$compiler->subcompile($expr);
} else {
$compiler->write(\sprintf('$%s', $varName));
}
$compiler
->raw('.')
->string(\sprintf(' ("%s" at line %d).', $this->getTemplateName(), $this->getTemplateLine()))
->raw(", E_USER_DEPRECATED);\n")
->raw(".")
->string(\sprintf(' in "%s" at line %d.', $this->getTemplateName(), $this->getTemplateLine()))
->raw(");\n")
;
}
}
+26 -3
View File
@@ -11,6 +11,7 @@
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\DeprecatedNode;
use Twig\Node\Node;
use Twig\Token;
@@ -21,6 +22,8 @@ use Twig\Token;
* {% deprecated 'The "base.twig" template is deprecated, use "layout.twig" instead.' %}
* {% extends 'layout.html.twig' %}
*
* {% deprecated 'The "base.twig" template is deprecated, use "layout.twig" instead.' package="foo/bar" version="1.1" %}
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @internal
@@ -29,11 +32,31 @@ final class DeprecatedTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$expr = $this->parser->getExpressionParser()->parseExpression();
$stream = $this->parser->getStream();
$expressionParser = $this->parser->getExpressionParser();
$expr = $expressionParser->parseExpression();
$node = new DeprecatedNode($expr, $token->getLine(), $this->getTag());
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
while ($stream->test(Token::NAME_TYPE)) {
$k = $stream->getCurrent()->getValue();
$stream->next();
$stream->expect(Token::OPERATOR_TYPE, '=');
return new DeprecatedNode($expr, $token->getLine(), $this->getTag());
switch ($k) {
case 'package':
$node->setNode('package', $expressionParser->parseExpression());
break;
case 'version':
$node->setNode('version', $expressionParser->parseExpression());
break;
default:
throw new SyntaxError(\sprintf('Unknown "%s" option.', $k), $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
}
$stream->expect(Token::BLOCK_END_TYPE);
return $node;
}
public function getTag(): string
+6
View File
@@ -52,6 +52,7 @@ final class TwigFilter
'preserves_safety' => null,
'node_class' => FilterExpression::class,
'deprecated' => false,
'deprecating_package' => '',
'alternative' => null,
], $options);
}
@@ -134,6 +135,11 @@ final class TwigFilter
return (bool) $this->options['deprecated'];
}
public function getDeprecatingPackage(): string
{
return $this->options['deprecating_package'];
}
public function getDeprecatedVersion(): string
{
return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];
+6
View File
@@ -50,6 +50,7 @@ final class TwigFunction
'is_safe_callback' => null,
'node_class' => FunctionExpression::class,
'deprecated' => false,
'deprecating_package' => '',
'alternative' => null,
], $options);
}
@@ -122,6 +123,11 @@ final class TwigFunction
return (bool) $this->options['deprecated'];
}
public function getDeprecatingPackage(): string
{
return $this->options['deprecating_package'];
}
public function getDeprecatedVersion(): string
{
return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];
+6
View File
@@ -44,6 +44,7 @@ final class TwigTest
'is_variadic' => false,
'node_class' => TestExpression::class,
'deprecated' => false,
'deprecating_package' => '',
'alternative' => null,
'one_mandatory_argument' => false,
], $options);
@@ -89,6 +90,11 @@ final class TwigTest
return (bool) $this->options['deprecated'];
}
public function getDeprecatingPackage(): string
{
return $this->options['deprecating_package'];
}
public function getDeprecatedVersion(): string
{
return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];
@@ -0,0 +1,10 @@
--TEST--
Deprecating a template with "deprecated" tag
--TEMPLATE--
{% deprecated 'The "index.twig" template is deprecated, use "greeting.twig" instead.' package="foo/bar" %}
Hello Fabien
--DATA--
return []
--EXPECT--
Hello Fabien
@@ -0,0 +1,10 @@
--TEST--
Deprecating a template with "deprecated" tag
--TEMPLATE--
{% deprecated 'The "index.twig" template is deprecated, use "greeting.twig" instead.' package="foo/bar" version=1.1 %}
Hello Fabien
--DATA--
return []
--EXPECT--
Hello Fabien
+10 -4
View File
@@ -39,25 +39,29 @@ class DeprecatedTest extends NodeTestCase
$expr = new ConstantExpression('This section is deprecated', 1);
$node = new DeprecatedNode($expr, 1, 'deprecated');
$node->setSourceContext(new Source('', 'foo.twig'));
$node->setNode('package', new ConstantExpression('twig/twig', 1));
$node->setNode('version', new ConstantExpression('1.1', 1));
$tests[] = [$node, <<<EOF
// line 1
@trigger_error("This section is deprecated"." (\"foo.twig\" at line 1).", E_USER_DEPRECATED);
trigger_deprecation("twig/twig", "1.1", "This section is deprecated"." in \"foo.twig\" at line 1.");
EOF
];
$t = new Node([
new ConstantExpression(true, 1),
new DeprecatedNode($expr, 2, 'deprecated'),
$dep = new DeprecatedNode($expr, 2, 'deprecated'),
], [], 1);
$node = new IfNode($t, null, 1);
$node->setSourceContext(new Source('', 'foo.twig'));
$dep->setNode('package', new ConstantExpression('twig/twig', 1));
$dep->setNode('version', new ConstantExpression('1.1', 1));
$tests[] = [$node, <<<EOF
// line 1
if (true) {
// line 2
@trigger_error("This section is deprecated"." (\"foo.twig\" at line 2).", E_USER_DEPRECATED);
trigger_deprecation("twig/twig", "1.1", "This section is deprecated"." in \"foo.twig\" at line 2.");
}
EOF
];
@@ -68,6 +72,8 @@ EOF
$expr = new FunctionExpression('foo', new Node(), 1);
$node = new DeprecatedNode($expr, 1, 'deprecated');
$node->setSourceContext(new Source('', 'foo.twig'));
$node->setNode('package', new ConstantExpression('twig/twig', 1));
$node->setNode('version', new ConstantExpression('1.1', 1));
$compiler = $this->getCompiler($environment);
$varName = $compiler->getVarName();
@@ -75,7 +81,7 @@ EOF
$tests[] = [$node, <<<EOF
// line 1
\$$varName = foo();
@trigger_error(\$$varName." (\"foo.twig\" at line 1).", E_USER_DEPRECATED);
trigger_deprecation("twig/twig", "1.1", \$$varName." in \"foo.twig\" at line 1.");
EOF
, $environment];
+2 -2
View File
@@ -25,12 +25,12 @@ class DeprecationCollectorTest extends TestCase
public function testCollect()
{
$twig = new Environment(new ArrayLoader());
$twig->addFunction(new TwigFunction('deprec', [$this, 'deprec'], ['deprecated' => '1.1']));
$twig->addFunction(new TwigFunction('deprec', [$this, 'deprec'], ['deprecated' => '1.1', 'deprecating_package' => 'foo/bar']));
$collector = new DeprecationCollector($twig);
$deprecations = $collector->collect(new Iterator());
$this->assertEquals(['Twig Function "deprec" is deprecated since version 1.1 in deprec.twig at line 1.'], $deprecations);
$this->assertEquals(['Since foo/bar 1.1: Twig Function "deprec" is deprecated in deprec.twig at line 1.'], $deprecations);
}
public function deprec()