enhanced exceptions for unknown filters, functions, tests, and tags
authorFabien Potencier <fabien.potencier@gmail.com>
Wed, 7 Dec 2011 07:51:22 +0000 (08:51 +0100)
committerFabien Potencier <fabien.potencier@gmail.com>
Wed, 7 Dec 2011 08:54:44 +0000 (09:54 +0100)
CHANGELOG
lib/Twig/Environment.php
lib/Twig/Node/Expression/Filter.php
lib/Twig/Node/Expression/Function.php
lib/Twig/Node/Expression/Test.php
lib/Twig/Parser.php
test/Twig/Tests/Node/Expression/FilterTest.php
test/Twig/Tests/Node/Expression/FunctionTest.php
test/Twig/Tests/Node/Expression/TestTest.php [new file with mode: 0644]
test/Twig/Tests/ParserTest.php

index f0d2816..9d58718 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,6 @@
 * 1.5.0
 
- * ...
+ * enhanced exceptions for unknown filters, functions, tests, and tags
 
 * 1.4.0 (2011-12-07)
 
index f225c32..7d22787 100644 (file)
@@ -956,6 +956,20 @@ class Twig_Environment
         return $this->binaryOperators;
     }
 
+    public function computeAlternatives($name, $items)
+    {
+        $alternatives = array();
+        foreach ($items as $item) {
+            $lev = levenshtein($name, $item);
+            if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
+                $alternatives[$item] = $lev;
+            }
+        }
+        asort($alternatives);
+
+        return array_keys($alternatives);
+    }
+
     protected function initOperators()
     {
         $this->unaryOperators = array();
index 0056240..8559e7b 100644 (file)
@@ -21,21 +21,12 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression
         $name = $this->getNode('filter')->getAttribute('value');
 
         if (false === $filter = $compiler->getEnvironment()->getFilter($name)) {
-            $alternativeFilters = array();
-
-            foreach ($compiler->getEnvironment()->getFilters() as $filterName => $filter) {
-                if (false !== strpos($filterName, $name)) {
-                    $alternativeFilters[] = $filterName;
-                }
-            }
-
-            $exceptionMessage = sprintf('The filter "%s" does not exist', $name);
-
-            if (count($alternativeFilters)) {
-                $exceptionMessage = sprintf('%s. Did you mean "%s"?', $exceptionMessage, implode('", "', $alternativeFilters));
+            $message = sprintf('The filter "%s" does not exist', $name);
+            if ($alternatives = $compiler->getEnvironment()->computeAlternatives($name, array_keys($compiler->getEnvironment()->getFilters()))) {
+                $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
             }
 
-            throw new Twig_Error_Syntax($exceptionMessage, $this->getLine());
+            throw new Twig_Error_Syntax($message, $this->getLine());
         }
 
         $this->compileFilter($compiler, $filter);
index e3f973f..d7bafc0 100644 (file)
@@ -19,24 +19,13 @@ class Twig_Node_Expression_Function extends Twig_Node_Expression
     {
         $name = $this->getAttribute('name');
 
-        $function = $compiler->getEnvironment()->getFunction($name);
-
-        if (false === $function) {
-            $alternativeFunctions = array();
-
-            foreach ($compiler->getEnvironment()->getFunctions() as $functionName => $function) {
-                if (false !== strpos($functionName, $name)) {
-                    $alternativeFunctions[] = $functionName;
-                }
-            }
-
-            $exceptionMessage = sprintf('The function "%s" does not exist', $name);
-
-            if (count($alternativeFunctions)) {
-                $exceptionMessage = sprintf('%s. Did you mean "%s"?', $exceptionMessage, implode('", "', $alternativeFunctions));
+        if (false === $function = $compiler->getEnvironment()->getFunction($name)) {
+            $message = sprintf('The function "%s" does not exist', $name);
+            if ($alternatives = $compiler->getEnvironment()->computeAlternatives($name, array_keys($compiler->getEnvironment()->getFunctions()))) {
+                $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
             }
 
-            throw new Twig_Error_Syntax($exceptionMessage, $this->getLine());
+            throw new Twig_Error_Syntax($message, $this->getLine());
         }
 
         $compiler
index 088f60e..4e0b25e 100644 (file)
@@ -17,9 +17,15 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression
 
     public function compile(Twig_Compiler $compiler)
     {
+        $name = $this->getAttribute('name');
         $testMap = $compiler->getEnvironment()->getTests();
-        if (!isset($testMap[$this->getAttribute('name')])) {
-            throw new Twig_Error_Syntax(sprintf('The test "%s" does not exist', $this->getAttribute('name')), $this->getLine());
+        if (!isset($testMap[$name])) {
+            $message = sprintf('The test "%s" does not exist', $name);
+            if ($alternatives = $compiler->getEnvironment()->computeAlternatives($name, array_keys($compiler->getEnvironment()->getTests()))) {
+                $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
+            }
+
+            throw new Twig_Error_Syntax($message, $this->getLine());
         }
 
         $name = $this->getAttribute('name');
index 8fcf9c4..b626d77 100644 (file)
@@ -149,7 +149,12 @@ class Twig_Parser implements Twig_ParserInterface
                             throw new Twig_Error_Syntax(sprintf('Unexpected tag name "%s" (expecting closing tag for the "%s" tag defined near line %s)', $token->getValue(), $test[0]->getTag(), $lineno), $token->getLine(), $this->stream->getFilename());
                         }
 
-                        throw new Twig_Error_Syntax(sprintf('Unknown tag name "%s"', $token->getValue()), $token->getLine(), $this->stream->getFilename());
+                        $message = sprintf('Unknown tag name "%s"', $token->getValue());
+                        if ($alternatives = $this->env->computeAlternatives($token->getValue(), array_keys($this->env->getTags()))) {
+                            $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
+                        }
+
+                        throw new Twig_Error_Syntax($message, $token->getLine(), $this->stream->getFilename());
                     }
 
                     $this->stream->next();
index c5ddd4b..2c52482 100644 (file)
@@ -64,10 +64,22 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Tests_Node_TestCase
         return $tests;
     }
 
+    /**
+     * @covers Twig_Node_Expression_Filter::compile
+     * @expectedException        Twig_Error_Syntax
+     * @expectedExceptionMessage The filter "uppe" does not exist. Did you mean "upper" at line 0
+     */
+    public function testUnknownFilter()
+    {
+        $node = $this->createFilter(new Twig_Node_Expression_Constant('foo', 0), 'uppe');
+        $node->compile($this->getCompiler());
+    }
+
     protected function createFilter($node, $name, array $arguments = array())
     {
         $name = new Twig_Node_Expression_Constant($name, 0);
         $arguments = new Twig_Node($arguments);
+
         return new Twig_Node_Expression_Filter($node, $name, $arguments, 0);
     }
 }
index 5f1e5a6..68c5c61 100644 (file)
@@ -35,15 +35,15 @@ class Twig_Tests_Node_Expression_FunctionTest extends Twig_Tests_Node_TestCase
         parent::testCompile($node, $source, $environment);
     }
 
+    /**
+     * @covers Twig_Node_Expression_Filter::compile
+     * @expectedException        Twig_Error_Syntax
+     * @expectedExceptionMessage The function "cycl" does not exist. Did you mean "cycle" at line 0
+     */
     public function testUnknownFunction()
     {
-        $node = $this->createFunction('unknown', array());
-        try {
-            $node->compile($this->getCompiler());
-            $this->fail();
-        } catch (Exception $e) {
-            $this->assertEquals('Twig_Error_Syntax', get_class($e));
-        }
+        $node = $this->createFunction('cycl', array());
+        $node->compile($this->getCompiler());
     }
 
     public function getTests()
@@ -85,7 +85,6 @@ class Twig_Tests_Node_Expression_FunctionTest extends Twig_Tests_Node_TestCase
 
     protected function createFunction($name, array $arguments = array())
     {
-        $arguments = new Twig_Node($arguments);
-        return new Twig_Node_Expression_Function($name, $arguments, 0);
+        return new Twig_Node_Expression_Function($name, new Twig_Node($arguments), 0);
     }
 }
diff --git a/test/Twig/Tests/Node/Expression/TestTest.php b/test/Twig/Tests/Node/Expression/TestTest.php
new file mode 100644 (file)
index 0000000..08668c0
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+
+/*
+ * 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.
+ */
+
+require_once dirname(__FILE__).'/../TestCase.php';
+
+class Twig_Tests_Node_Expression_TestTest extends Twig_Tests_Node_TestCase
+{
+    /**
+     * @covers Twig_Node_Expression_Test::__construct
+     */
+    public function testConstructor()
+    {
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $name = new Twig_Node_Expression_Constant('null', 0);
+        $args = new Twig_Node();
+        $node = new Twig_Node_Expression_Test($expr, $name, $args, 0);
+
+        $this->assertEquals($expr, $node->getNode('node'));
+        $this->assertEquals($args, $node->getNode('arguments'));
+        $this->assertEquals($name, $node->getAttribute('name'));
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Test::compile
+     * @dataProvider getTests
+     */
+    public function testCompile($node, $source, $environment = null)
+    {
+        parent::testCompile($node, $source, $environment);
+    }
+
+    public function getTests()
+    {
+        $tests = array();
+
+        $expr = new Twig_Node_Expression_Constant('foo', 0);
+        $node = new Twig_Node_Expression_Test_Null($expr, 'null', new Twig_Node(array()), 0);
+
+        $tests[] = array($node, '(null === "foo")');
+
+        return $tests;
+    }
+
+    /**
+     * @covers Twig_Node_Expression_Filter::compile
+     * @expectedException        Twig_Error_Syntax
+     * @expectedExceptionMessage The test "nul" does not exist. Did you mean "null" at line 0
+     */
+    public function testUnknownTest()
+    {
+        $node = $this->createTest(new Twig_Node_Expression_Constant('foo', 0), 'nul');
+        $node->compile($this->getCompiler());
+    }
+
+    protected function createTest($node, $name, array $arguments = array())
+    {
+        return new Twig_Node_Expression_Test($node, $name, new Twig_Node($arguments), 0);
+    }
+}
index d0db089..147a278 100644 (file)
@@ -20,6 +20,22 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
     }
 
     /**
+     * @expectedException        Twig_Error_Syntax
+     * @expectedExceptionMessage Unknown tag name "foo". Did you mean "for" at line 0
+     */
+    public function testUnkownTag()
+    {
+        $stream = new Twig_TokenStream(array(
+            new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', 0),
+            new Twig_Token(Twig_Token::NAME_TYPE, 'foo', 0),
+            new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', 0),
+            new Twig_Token(Twig_Token::EOF_TYPE, '', 0),
+        ));
+        $parser = new Twig_Parser(new Twig_Environment());
+        $parser->parse($stream);
+    }
+
+    /**
      * @dataProvider getFilterBodyNodesData
      */
     public function testFilterBodyNodes($input, $expected)