diff --git a/CHANGELOG b/CHANGELOG index 6522bb120ff..8fcd964030f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ # 3.15.0 (2024-XX-XX) + * Add support for inline comments * Add support for accessing class constants with the dot operator * Add `Profile::getStartTime()` and `Profile::getEndTime()` * Fix "ignore missing" when used on an "embed" tag diff --git a/doc/templates.rst b/doc/templates.rst index 1da89bcf214..cbbf5d51ad8 100644 --- a/doc/templates.rst +++ b/doc/templates.rst @@ -259,9 +259,9 @@ Go to the :doc:`tags` page to learn more about the built-in tags. Comments -------- -To comment-out part of a line in a template, use the comment syntax ``{# ... -#}``. This is useful for debugging or to add information for other template -designers or yourself: +To comment-out part of a template, use the comment syntax ``{# ... #}``. This +is useful for debugging or to add information for other template designers or +yourself: .. code-block:: twig @@ -271,6 +271,40 @@ designers or yourself: {% endfor %} #} +If you want to add comments inside a block, variable, or comment, use an inline +comment. They start with ``#`` and continue to the end of the line: + +.. code-block:: twig + + {{ + # this is an inline comment + "Hello World"|upper + # this is an inline comment + }} + + {{ + { + # this is an inline comment + fruit: 'apple', # this is an inline comment + color: 'red', # this is an inline comment + }|join(', ') + }} + +Inline comments can also be on the same line as the expression: + +.. code-block:: twig + + {{ + "Hello World"|upper # this is an inline comment + }} + +As inline comments continue until the end of the current line, the following +code does not work as ``}}``would be part of the comment: + +.. code-block:: twig + + {{ "Hello World"|upper # this is an inline comment }} + Including other Templates ------------------------- diff --git a/src/Lexer.php b/src/Lexer.php index 982c87e3f7f..1754791265e 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -48,6 +48,7 @@ class Lexer public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; public const REGEX_DQ_STRING_DELIM = '/"/A'; public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + public const REGEX_INLINE_COMMENT = '/#[^\n]*/A'; public const PUNCTUATION = '()[]{}?:.,|'; private const SPECIAL_CHARS = [ @@ -384,6 +385,10 @@ private function lexExpression(): void $this->pushState(self::STATE_STRING); $this->moveCursor($match[0]); } + // inline comment + elseif (preg_match(self::REGEX_INLINE_COMMENT, $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + } // unlexable else { throw new SyntaxError(\sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); diff --git a/tests/LexerTest.php b/tests/LexerTest.php index 04e9930b912..53a6cb155dd 100644 --- a/tests/LexerTest.php +++ b/tests/LexerTest.php @@ -534,4 +534,102 @@ public static function getTemplateForStrings() yield ['日本では、春になると桜の花が咲きます。多くの人々は、公園や川の近くに集まり、お花見を楽しみます。桜の花びらが風に舞い、まるで雪のように見える瞬間は、とても美しいです。']; yield ['في العالم العربي، يُعتبر الخط العربي أحد أجمل أشكال الفن. يُستخدم الخط في تزيين المساجد والكتب والمخطوطات القديمة. يتميز الخط العربي بجماله وتناسقه، ويُعتبر رمزًا للثقافة الإسلامية.']; } + + public function testInlineCommentWithHashInString() + { + $lexer = new Lexer(new Environment(new ArrayLoader())); + $stream = $lexer->tokenize(new Source('{{ "me # this is NOT an inline comment" }}', 'index')); + $stream->expect(Token::VAR_START_TYPE); + $stream->expect(Token::STRING_TYPE, 'me # this is NOT an inline comment'); + $stream->expect(Token::VAR_END_TYPE); + $this->assertTrue($stream->isEOF()); + } + + /** + * @dataProvider getTemplateForInlineCommentsForVariable + */ + public function testInlineCommentForVariable(string $template) + { + $lexer = new Lexer(new Environment(new ArrayLoader())); + $stream = $lexer->tokenize(new Source($template, 'index')); + $stream->expect(Token::VAR_START_TYPE); + $stream->expect(Token::STRING_TYPE, 'me'); + $stream->expect(Token::VAR_END_TYPE); + $this->assertTrue($stream->isEOF()); + } + + public static function getTemplateForInlineCommentsForVariable() + { + yield ['{{ + "me" + # this is an inline comment + }}']; + yield ['{{ + # this is an inline comment + "me" + }}']; + yield ['{{ + "me" # this is an inline comment + }}']; + yield ['{{ + # this is an inline comment + "me" # this is an inline comment + # this is an inline comment + }}']; + } + + /** + * @dataProvider getTemplateForInlineCommentsForBlock + */ + public function testInlineCommentForBlock(string $template) + { + $lexer = new Lexer(new Environment(new ArrayLoader())); + $stream = $lexer->tokenize(new Source($template, 'index')); + $stream->expect(Token::BLOCK_START_TYPE); + $stream->expect(Token::NAME_TYPE, 'if'); + $stream->expect(Token::NAME_TYPE, 'true'); + $stream->expect(Token::BLOCK_END_TYPE); + $stream->expect(Token::TEXT_TYPE, 'me'); + $stream->expect(Token::BLOCK_START_TYPE); + $stream->expect(Token::NAME_TYPE, 'endif'); + $stream->expect(Token::BLOCK_END_TYPE); + $this->assertTrue($stream->isEOF()); + } + + public static function getTemplateForInlineCommentsForBlock() + { + yield ['{% + if true + # this is an inline comment + %}me{% endif %}']; + yield ['{% + # this is an inline comment + if true + %}me{% endif %}']; + yield ['{% + if true # this is an inline comment + %}me{% endif %}']; + yield ['{% + # this is an inline comment + if true # this is an inline comment + # this is an inline comment + %}me{% endif %}']; + } + + /** + * @dataProvider getTemplateForInlineCommentsForComment + */ + public function testInlineCommentForComment(string $template) + { + $lexer = new Lexer(new Environment(new ArrayLoader())); + $stream = $lexer->tokenize(new Source($template, 'index')); + $this->assertTrue($stream->isEOF()); + } + + public static function getTemplateForInlineCommentsForComment() + { + yield ['{# + Some regular comment # this is an inline comment + #}']; + } }