From 08bda72355689714ca0a6294ec61b58f611c345f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 7 Dec 2011 08:51:22 +0100 Subject: [PATCH] enhanced exceptions for unknown filters, functions, tests, and tags --- CHANGELOG | 2 +- lib/Twig/Environment.php | 14 +++++ lib/Twig/Node/Expression/Filter.php | 17 +---- lib/Twig/Node/Expression/Function.php | 21 ++----- lib/Twig/Node/Expression/Test.php | 10 +++- lib/Twig/Parser.php | 7 ++- test/Twig/Tests/Node/Expression/FilterTest.php | 12 ++++ test/Twig/Tests/Node/Expression/FunctionTest.php | 17 +++--- test/Twig/Tests/Node/Expression/TestTest.php | 67 ++++++++++++++++++++++ test/Twig/Tests/ParserTest.php | 16 +++++ 10 files changed, 141 insertions(+), 42 deletions(-) create mode 100644 test/Twig/Tests/Node/Expression/TestTest.php diff --git a/CHANGELOG b/CHANGELOG index f0d2816..9d58718 100644 --- 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) diff --git a/lib/Twig/Environment.php b/lib/Twig/Environment.php index f225c32..7d22787 100644 --- a/lib/Twig/Environment.php +++ b/lib/Twig/Environment.php @@ -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(); diff --git a/lib/Twig/Node/Expression/Filter.php b/lib/Twig/Node/Expression/Filter.php index 0056240..8559e7b 100644 --- a/lib/Twig/Node/Expression/Filter.php +++ b/lib/Twig/Node/Expression/Filter.php @@ -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); diff --git a/lib/Twig/Node/Expression/Function.php b/lib/Twig/Node/Expression/Function.php index e3f973f..d7bafc0 100644 --- a/lib/Twig/Node/Expression/Function.php +++ b/lib/Twig/Node/Expression/Function.php @@ -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 diff --git a/lib/Twig/Node/Expression/Test.php b/lib/Twig/Node/Expression/Test.php index 088f60e..4e0b25e 100644 --- a/lib/Twig/Node/Expression/Test.php +++ b/lib/Twig/Node/Expression/Test.php @@ -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'); diff --git a/lib/Twig/Parser.php b/lib/Twig/Parser.php index 8fcf9c4..b626d77 100644 --- a/lib/Twig/Parser.php +++ b/lib/Twig/Parser.php @@ -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(); diff --git a/test/Twig/Tests/Node/Expression/FilterTest.php b/test/Twig/Tests/Node/Expression/FilterTest.php index c5ddd4b..2c52482 100644 --- a/test/Twig/Tests/Node/Expression/FilterTest.php +++ b/test/Twig/Tests/Node/Expression/FilterTest.php @@ -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); } } diff --git a/test/Twig/Tests/Node/Expression/FunctionTest.php b/test/Twig/Tests/Node/Expression/FunctionTest.php index 5f1e5a6..68c5c61 100644 --- a/test/Twig/Tests/Node/Expression/FunctionTest.php +++ b/test/Twig/Tests/Node/Expression/FunctionTest.php @@ -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 index 0000000..08668c0 --- /dev/null +++ b/test/Twig/Tests/Node/Expression/TestTest.php @@ -0,0 +1,67 @@ +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); + } +} diff --git a/test/Twig/Tests/ParserTest.php b/test/Twig/Tests/ParserTest.php index d0db089..147a278 100644 --- a/test/Twig/Tests/ParserTest.php +++ b/test/Twig/Tests/ParserTest.php @@ -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) -- 1.7.2.5