Skip to content
Permalink
Browse files

minor #24007 [ExpressionLanguage] SyntaxError : make a proposal when …

…no function or variable found (fmata)

This PR was merged into the 3.4 branch.

Discussion
----------

[ExpressionLanguage] SyntaxError : make a proposal when no function or variable found

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | needed ?

Now when an Expression fails to be parsed, a SyntaxError is raised with an explicit message but it's impossible to extract the function, variable or the pair token  type/token value to parse.
When a functional user encounters a SyntaxError, by default there is no mechanism to understand why what he typed is incorrect.

This PR exposes when it's possible the subject of the violation and in case of function or variable undefined in the Expression, a proposal is made.

```php
<?php

require 'vendor/autoload.php';

use Symfony\Component\ExpressionLanguage\Lexer;
use Symfony\Component\ExpressionLanguage\Parser;
use Symfony\Component\ExpressionLanguage\SyntaxError;

$lexer = new Lexer();
$parser = new Parser(array());

try {
    $parser->parse($lexer->tokenize('user.city.departement in [departement, departement2]'), array('user', 'departement1', 'departement2'));
} catch (SyntaxError $e) {
    echo $e->getMessage();
}
```
Outputs :

> Variable "departement" is not valid around position 27 for expression `user.city.departement in [departement, departement2]`.
>
> Did you mean "departement1"?

Commits
-------

f19cf8a [ExpressionLanguage] make a proposal in SyntaxError message
  • Loading branch information...
fabpot committed Sep 10, 2017
2 parents a0bdd5a + f19cf8a commit 565a1c4b480e228c6640688fef0086536dde9132
@@ -195,13 +195,13 @@ public function parsePrimaryExpression()
default:
if ('(' === $this->stream->current->value) {
if (false === isset($this->functions[$token->value])) {
throw new SyntaxError(sprintf('The function "%s" does not exist', $token->value), $token->cursor, $this->stream->getExpression());
throw new SyntaxError(sprintf('The function "%s" does not exist', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, array_keys($this->functions));
}
$node = new Node\FunctionNode($token->value, $this->parseArguments());
} else {
if (!in_array($token->value, $this->names, true)) {
throw new SyntaxError(sprintf('Variable "%s" is not valid', $token->value), $token->cursor, $this->stream->getExpression());
throw new SyntaxError(sprintf('Variable "%s" is not valid', $token->value), $token->cursor, $this->stream->getExpression(), $token->value, $this->names);
}
// is the name used in the compiled code different
@@ -13,14 +13,29 @@
class SyntaxError extends \LogicException
{
public function __construct($message, $cursor = 0, $expression = '')
public function __construct($message, $cursor = 0, $expression = '', $subject = null, array $proposals = null)
{
$message = sprintf('%s around position %d', $message, $cursor);
if ($expression) {
$message = sprintf('%s for expression `%s`', $message, $expression);
}
$message .= '.';
if (null !== $subject && null !== $proposals) {
$minScore = INF;
foreach ($proposals as $proposal) {
$distance = levenshtein($subject, $proposal);
if ($distance < $minScore) {
$guess = $proposal;
$minScore = $distance;
}
}
if (isset($guess) && $minScore < 3) {
$message .= sprintf(' Did you mean "%s"?', $guess);
}
}
parent::__construct($message);
}
}
@@ -195,4 +195,16 @@ public function getInvalidPostfixData()
),
);
}
/**
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
* @expectedExceptionMessage Did you mean "baz"?
*/
public function testNameProposal()
{
$lexer = new Lexer();
$parser = new Parser(array());
$parser->parse($lexer->tokenize('foo > bar'), array('foo', 'baz'));
}
}

0 comments on commit 565a1c4

Please sign in to comment.
You can’t perform that action at this time.