Skip to content

Commit

Permalink
minor #24007 [ExpressionLanguage] SyntaxError : make a proposal when …
Browse files Browse the repository at this point in the history
…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 565a1c4
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 3 deletions.
4 changes: 2 additions & 2 deletions src/Symfony/Component/ExpressionLanguage/Parser.php
Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion src/Symfony/Component/ExpressionLanguage/SyntaxError.php
Expand Up @@ -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);
}
}
12 changes: 12 additions & 0 deletions src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php
Expand Up @@ -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.