Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feature #2182 added Twig_Source to hold information about the origina…
…l template (fabpot)

This PR was merged into the 1.x branch.

Discussion
----------

added Twig_Source to hold information about the original template

Alternative to #2170

Commits
-------

6c76583 added Twig_Source to hold information about the original template
  • Loading branch information
fabpot committed Oct 17, 2016
2 parents e292ab0 + 6c76583 commit 1b7b147
Show file tree
Hide file tree
Showing 36 changed files with 436 additions and 148 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
@@ -1,5 +1,6 @@
* 1.27.0 (2016-XX-XX)

* added Twig_Source to hold information about the original template
* deprecated Twig_Error::getTemplateFile() and Twig_Error::setTemplateFile() in favor of Twig_Error::getTemplateName() and Twig_Error::setTemplateName()
* deprecated Parser::getFilename()
* fixed template paths when a template name contains a protocol like vfs://
Expand Down
5 changes: 4 additions & 1 deletion doc/deprecated.rst
Expand Up @@ -21,7 +21,7 @@ Token Parsers
* ``Twig_TokenParserBroker``

* As of Twig 1.27, ``Twig_Parser::getFilename()`` is deprecated. From a token
parser, use ``$this->parser->getStream()->getFilename()`` instead.
parser, use ``$this->parser->getStream()->getSourceContext()->getPath()`` instead.

Extensions
----------
Expand Down Expand Up @@ -119,6 +119,9 @@ Nodes
instances, storing a ``null`` value is deprecated and won't be possible in
Twig 2.x.

* As of Twig 1.27, the ``filename`` attribute on ``Twig_Node_Module`` is
deprecated. Use ``name`` instead.

Interfaces
----------

Expand Down
6 changes: 5 additions & 1 deletion doc/internals.rst
Expand Up @@ -44,7 +44,11 @@ an instance of ``Twig_Token``, and the stream is an instance of
You can manually convert a source code into a token stream by calling the
``tokenize()`` method of an environment::

$stream = $twig->tokenize($source, $identifier);
$stream = $twig->tokenize(new Twig_Source($source, $identifier));

.. versionadded:: 1.27
``Twig_Source`` was introduced in version 1.27, pass the source and the
identifier directly on previous versions.

As the stream has a ``__toString()`` method, you can have a textual
representation of it by echoing the object::
Expand Down
8 changes: 6 additions & 2 deletions doc/recipes.rst
Expand Up @@ -306,7 +306,7 @@ saving it. If the template code is stored in a `$template` variable, here is
how you can do it::

try {
$twig->parse($twig->tokenize($template));
$twig->parse($twig->tokenize(new Twig_Source($template)));

// the $template is valid
} catch (Twig_Error_Syntax $e) {
Expand All @@ -318,14 +318,18 @@ If you iterate over a set of files, you can pass the filename to the

foreach ($files as $file) {
try {
$twig->parse($twig->tokenize($template, $file));
$twig->parse($twig->tokenize(new Twig_Source($template, $file->getFilename(), $file)));

// the $template is valid
} catch (Twig_Error_Syntax $e) {
// $template contains one or more syntax errors
}
}

.. versionadded:: 1.27
``Twig_Source`` was introduced in version 1.27, pass the source and the
identifier directly on previous versions.

.. note::

This method won't catch any sandbox policy violations because the policy
Expand Down
35 changes: 26 additions & 9 deletions lib/Twig/Environment.php
Expand Up @@ -403,7 +403,14 @@ public function loadTemplate($name, $index = null)
}

if (!class_exists($cls, false)) {
$content = $this->compileSource($this->getLoader()->getSource($name), $name);
$loader = $this->getLoader();
if ($loader instanceof Twig_SourceContextLoaderInterface) {
$source = $loader->getSourceContext($name);
} else {
$source = new Twig_Source($loader->getSource($name), $name);
}
$content = $this->compileSource($source);

if ($this->bcWriteCacheFile) {
$this->writeCacheFile($key, $content);
} else {
Expand Down Expand Up @@ -583,20 +590,25 @@ public function setLexer(Twig_LexerInterface $lexer)
/**
* Tokenizes a source code.
*
* @param string $source The template source code
* @param string $name The template name
* @param string|Twig_Source $source The template source code
* @param string $name The template name (deprecated)
*
* @return Twig_TokenStream A Twig_TokenStream instance
*
* @throws Twig_Error_Syntax When the code is syntactically wrong
*/
public function tokenize($source, $name = null)
{
if (!$source instanceof Twig_Source) {
@trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
$source = new Twig_Source($source, $name);
}

if (null === $this->lexer) {
$this->lexer = new Twig_Lexer($this);
}

return $this->lexer->tokenize($source, $name);
return $this->lexer->tokenize($source);
}

/**
Expand Down Expand Up @@ -692,22 +704,27 @@ public function compile(Twig_NodeInterface $node)
/**
* Compiles a template source code.
*
* @param string $source The template source code
* @param string $name The template name
* @param string|Twig_Source $source The template source code
* @param string $name The template name (deprecated)
*
* @return string The compiled PHP source code
*
* @throws Twig_Error_Syntax When there was an error during tokenizing, parsing or compiling
*/
public function compileSource($source, $name = null)
{
if (!$source instanceof Twig_Source) {
@trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
$source = new Twig_Source($source, $name);
}

try {
return $this->compile($this->parse($this->tokenize($source, $name)));
return $this->compile($this->parse($this->tokenize($source)));
} catch (Twig_Error $e) {
$e->setTemplateName($name);
$e->setTemplateName($source->getName());
throw $e;
} catch (Exception $e) {
throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
throw new Twig_Error_Syntax(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source->getName(), $e);
}
}

Expand Down
32 changes: 16 additions & 16 deletions lib/Twig/ExpressionParser.php
Expand Up @@ -171,7 +171,7 @@ public function parsePrimaryExpression()
$negClass = 'Twig_Node_Expression_Unary_Neg';
$posClass = 'Twig_Node_Expression_Unary_Pos';
if (!(in_array($ref->getName(), array($negClass, $posClass)) || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass))) {
throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getFilename());
throw new Twig_Error_Syntax(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()->getName());
}

$this->parser->getStream()->next();
Expand All @@ -187,7 +187,7 @@ public function parsePrimaryExpression()
} elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
$node = $this->parseHashExpression();
} else {
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getFilename());
throw new Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()->getName());
}
}

Expand Down Expand Up @@ -278,7 +278,7 @@ public function parseHashExpression()
} else {
$current = $stream->getCurrent();

throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getFilename());
throw new Twig_Error_Syntax(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Twig_Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()->getName());
}

$stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
Expand Down Expand Up @@ -317,11 +317,11 @@ public function getFunctionNode($name, $line)
case 'parent':
$this->parseArguments();
if (!count($this->parser->getBlockStack())) {
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getFilename());
throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()->getName());
}

if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getFilename());
throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()->getName());
}

return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
Expand All @@ -330,7 +330,7 @@ public function getFunctionNode($name, $line)
case 'attribute':
$args = $this->parseArguments();
if (count($args) < 2) {
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getFilename());
throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()->getName());
}

return new Twig_Node_Expression_GetAttr($args->getNode(0), $args->getNode(1), count($args) > 2 ? $args->getNode(2) : null, Twig_Template::ANY_CALL, $line);
Expand Down Expand Up @@ -379,18 +379,18 @@ public function parseSubscriptExpression($node)
}
}
} else {
throw new Twig_Error_Syntax('Expected name or number.', $lineno, $stream->getFilename());
throw new Twig_Error_Syntax('Expected name or number.', $lineno, $stream->getSourceContext()->getName());
}

if ($node instanceof Twig_Node_Expression_Name && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
if (!$arg instanceof Twig_Node_Expression_Constant) {
throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getFilename());
throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()->getName());
}

$name = $arg->getAttribute('value');

if ($this->parser->isReservedMacroName($name)) {
throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getFilename());
throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()->getName());
}

$node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno);
Expand Down Expand Up @@ -500,15 +500,15 @@ public function parseArguments($namedArguments = false, $definition = false)
$name = null;
if ($namedArguments && $token = $stream->nextIf(Twig_Token::OPERATOR_TYPE, '=')) {
if (!$value instanceof Twig_Node_Expression_Name) {
throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getFilename());
throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given.', get_class($value)), $token->getLine(), $stream->getSourceContext()->getName());
}
$name = $value->getAttribute('name');

if ($definition) {
$value = $this->parsePrimaryExpression();

if (!$this->checkConstantExpression($value)) {
throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getFilename());
throw new Twig_Error_Syntax(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()->getName());
}
} else {
$value = $this->parseExpression();
Expand Down Expand Up @@ -542,7 +542,7 @@ public function parseAssignmentExpression()
$token = $stream->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
$value = $token->getValue();
if (in_array(strtolower($value), array('true', 'false', 'none', 'null'))) {
throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getFilename());
throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()->getName());
}
$targets[] = new Twig_Node_Expression_AssignName($value, $token->getLine());

Expand Down Expand Up @@ -572,7 +572,7 @@ protected function getFunctionNodeClass($name, $line)
$env = $this->parser->getEnvironment();

if (false === $function = $env->getFunction($name)) {
$e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getFilename());
$e = new Twig_Error_Syntax(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()->getName());
$e->addSuggestions($name, array_keys($env->getFunctions()));

throw $e;
Expand All @@ -586,7 +586,7 @@ protected function getFunctionNodeClass($name, $line)
if ($function->getAlternative()) {
$message .= sprintf('. Use "%s" instead', $function->getAlternative());
}
$message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getFilename(), $line);
$message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line);

@trigger_error($message, E_USER_DEPRECATED);
}
Expand All @@ -603,7 +603,7 @@ protected function getFilterNodeClass($name, $line)
$env = $this->parser->getEnvironment();

if (false === $filter = $env->getFilter($name)) {
$e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getFilename());
$e = new Twig_Error_Syntax(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()->getName());
$e->addSuggestions($name, array_keys($env->getFilters()));

throw $e;
Expand All @@ -617,7 +617,7 @@ protected function getFilterNodeClass($name, $line)
if ($filter->getAlternative()) {
$message .= sprintf('. Use "%s" instead', $filter->getAlternative());
}
$message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getFilename(), $line);
$message .= sprintf(' in %s at line %d.', $this->parser->getStream()->getSourceContext()->getName(), $line);

@trigger_error($message, E_USER_DEPRECATED);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/Twig/Extension/Core.php
Expand Up @@ -284,7 +284,7 @@ public function parseTestExpression(Twig_Parser $parser, Twig_NodeInterface $nod
if ($test->getAlternative()) {
$message .= sprintf('. Use "%s" instead', $test->getAlternative());
}
$message .= sprintf(' in %s at line %d.', $stream->getFilename(), $stream->getCurrent()->getLine());
$message .= sprintf(' in %s at line %d.', $stream->getSourceContext()->getName(), $stream->getCurrent()->getLine());

@trigger_error($message, E_USER_DEPRECATED);
}
Expand Down Expand Up @@ -319,7 +319,7 @@ protected function getTest(Twig_Parser $parser, $line)
}
}

$e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getFilename());
$e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()->getName());
$e->addSuggestions($name, array_keys($env->getTests()));

throw $e;
Expand Down
16 changes: 12 additions & 4 deletions lib/Twig/Lexer.php
Expand Up @@ -26,6 +26,7 @@ class Twig_Lexer implements Twig_LexerInterface
protected $states;
protected $brackets;
protected $env;
// to be renamed to $name in 2.0 (where it is private)
protected $filename;
protected $options;
protected $regexes;
Expand Down Expand Up @@ -75,17 +76,24 @@ public function __construct(Twig_Environment $env, array $options = array())
/**
* {@inheritdoc}
*/
public function tokenize($code, $filename = null)
public function tokenize($code, $name = null)
{
if (!$code instanceof Twig_Source) {
@trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a Twig_Source instance instead.', __METHOD__), E_USER_DEPRECATED);
$source = new Twig_Source($code, $name);
} else {
$source = $code;
}

if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
} else {
$mbEncoding = null;
}

$this->code = str_replace(array("\r\n", "\r"), "\n", $code);
$this->filename = $filename;
$this->code = str_replace(array("\r\n", "\r"), "\n", $source->getCode());
$this->filename = $source->getName();
$this->cursor = 0;
$this->lineno = 1;
$this->end = strlen($this->code);
Expand Down Expand Up @@ -136,7 +144,7 @@ public function tokenize($code, $filename = null)
mb_internal_encoding($mbEncoding);
}

return new Twig_TokenStream($this->tokens, $this->filename, $this->env->isDebug() ? $code : '');
return new Twig_TokenStream($this->tokens, $source);
}

protected function lexData()
Expand Down
6 changes: 3 additions & 3 deletions lib/Twig/LexerInterface.php
Expand Up @@ -21,12 +21,12 @@ interface Twig_LexerInterface
/**
* Tokenizes a source code.
*
* @param string $code The source code
* @param string $filename A unique identifier for the source code
* @param string|Twig_Source $code The source code
* @param string $name A unique identifier for the source code
*
* @return Twig_TokenStream A token stream instance
*
* @throws Twig_Error_Syntax When the code is syntactically wrong
*/
public function tokenize($code, $filename = null);
public function tokenize($code, $name = null);
}

0 comments on commit 1b7b147

Please sign in to comment.