Skip to content

Commit dd29757

Browse files
committed
feature symfony#54978 [ExpressionLanguage] Add comment support to expression language (valtzu)
This PR was squashed before being merged into the 7.2 branch. Discussion ---------- [ExpressionLanguage] Add comment support to expression language | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | License | MIT When expression language expressions are provided by the user, it would be really helpful if we could include comments in the expression. Many times the user will write something like ``` customer.group == 'good_customer' or customer.id == 123 ``` which in my opinion, would be much better with a comment ``` customer.group == 'good_customer' or customer.id == 123 /* 123 is the test customer */ ``` so that the next person reading the expression knows what the magical 123 is. --- The implementation is quite simple, given it's ok to strip the comments from AST & the compiled version. ### Why `/*` & `*/` This seems to be the most common way among different programming languages, including JS, C#, Go, C, PHP, Java, Rust – also in business scenarios you often times want multi-line comments, for which this syntax usually stands for. Also, this makes it possible to use comments in the middle of single-line expressions. --- At the moment, unlike multi-line comments in PHP, we always require closing `*/`. Commits ------- 0f8dee7 [ExpressionLanguage] Add comment support to expression language
2 parents 2be6b0c + 0f8dee7 commit dd29757

File tree

4 files changed

+76
-0
lines changed

4 files changed

+76
-0
lines changed

src/Symfony/Component/ExpressionLanguage/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add support for null-coalescing unknown variables
8+
* Add support for comments using `/*` & `*/`
89

910
7.1
1011
---

src/Symfony/Component/ExpressionLanguage/Lexer.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ public function tokenize(string $expression): TokenStream
6969
// strings
7070
$tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1);
7171
$cursor += \strlen($match[0]);
72+
} elseif (preg_match('{/\*.*?\*/}A', $expression, $match, 0, $cursor)) {
73+
// comments
74+
$cursor += \strlen($match[0]);
7275
} elseif (preg_match('/(?<=^|[\s(])starts with(?=[\s(])|(?<=^|[\s(])ends with(?=[\s(])|(?<=^|[\s(])contains(?=[\s(])|(?<=^|[\s(])matches(?=[\s(])|(?<=^|[\s(])not in(?=[\s(])|(?<=^|[\s(])not(?=[\s(])|(?<=^|[\s(])and(?=[\s(])|\=\=\=|\!\=\=|(?<=^|[\s(])or(?=[\s(])|\|\||&&|\=\=|\!\=|\>\=|\<\=|(?<=^|[\s(])in(?=[\s(])|\.\.|\*\*|\!|\||\^|&|\<|\>|\+|\-|~|\*|\/|%/A', $expression, $match, 0, $cursor)) {
7376
// operators
7477
$tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1);

src/Symfony/Component/ExpressionLanguage/Tests/ExpressionLanguageTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,43 @@ public function testRegisterAfterCompile($registerCallback)
459459
$registerCallback($el);
460460
}
461461

462+
public static function validCommentProvider()
463+
{
464+
yield ['1 /* comment */ + 1'];
465+
yield ['1 /* /* comment with spaces */'];
466+
yield ['1 /** extra stars **/ + 1'];
467+
yield ["/* multi\nline */ 'foo'"];
468+
}
469+
470+
/**
471+
* @dataProvider validCommentProvider
472+
*/
473+
public function testLintAllowsComments($expression)
474+
{
475+
$el = new ExpressionLanguage();
476+
$el->lint($expression, []);
477+
478+
$this->expectNotToPerformAssertions();
479+
}
480+
481+
public static function invalidCommentProvider()
482+
{
483+
yield ['1 + no start */'];
484+
yield ['1 /* no closing'];
485+
yield ['1 /* double closing */ */'];
486+
}
487+
488+
/**
489+
* @dataProvider invalidCommentProvider
490+
*/
491+
public function testLintThrowsOnInvalidComments($expression)
492+
{
493+
$el = new ExpressionLanguage();
494+
495+
$this->expectException(SyntaxError::class);
496+
$el->lint($expression, []);
497+
}
498+
462499
public function testLintDoesntThrowOnValidExpression()
463500
{
464501
$el = new ExpressionLanguage();
@@ -477,6 +514,13 @@ public function testLintThrowsOnInvalidExpression()
477514
$el->lint('node.', ['node']);
478515
}
479516

517+
public function testCommentsIgnored()
518+
{
519+
$expressionLanguage = new ExpressionLanguage();
520+
$this->assertSame(3, $expressionLanguage->evaluate('1 /* foo */ + 2'));
521+
$this->assertSame('(1 + 2)', $expressionLanguage->compile('1 /* foo */ + 2'));
522+
}
523+
480524
public static function getRegisterCallbacks()
481525
{
482526
return [

src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,34 @@ public static function getTokenizeData()
151151
],
152152
'-.7_189e+10',
153153
],
154+
[
155+
[
156+
new Token('number', 65536, 1),
157+
],
158+
'65536 /* this is 2^16 */',
159+
],
160+
[
161+
[
162+
new Token('number', 2, 1),
163+
new Token('operator', '*', 21),
164+
new Token('number', 4, 23),
165+
],
166+
'2 /* /* comment1 */ * 4',
167+
],
168+
[
169+
[
170+
new Token('string', '/* this is', 1),
171+
new Token('operator', '~', 14),
172+
new Token('string', 'not a comment */', 16),
173+
],
174+
'"/* this is" ~ "not a comment */"',
175+
],
176+
[
177+
[
178+
new Token('string', '/* this is not a comment */', 1),
179+
],
180+
'"/* this is not a comment */"',
181+
],
154182
];
155183
}
156184
}

0 commit comments

Comments
 (0)