From 5068a3ffed8b2b3c55fe3e1d1103c5d3c8105f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mirtes?= Date: Mon, 26 Oct 2026 18:09:23 +0000 Subject: [PATCH] Lexer: read underscore in a tag name The lexer read @Foo_Bar as the tag @Foo and the text _Bar. A Doctrine annotation class name can contain an underscore, e.g. @Foo_Bar(1). Amp-Thread-ID: https://ampcode.com/threads/T-01a17930-0285-73c5-a957-4325c77aa1b2 --- doc/grammars/lexemes.pp3 | 4 ++-- src/Lexer/Lexer.php | 2 +- tests/PHPStan/Parser/PhpDocParserTest.php | 24 +++++++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/doc/grammars/lexemes.pp3 b/doc/grammars/lexemes.pp3 index 7859a714..d69176ff 100644 --- a/doc/grammars/lexemes.pp3 +++ b/doc/grammars/lexemes.pp3 @@ -173,8 +173,8 @@ * so that the "@baz" of "@author Foo " belongs to the description * it is written in. */ -%token T_PHPDOC_TAG_WS (?<=[\x09\x20])@(?:[a-z][a-z0-9-\\]+:)?[a-z][a-z0-9-\\]*+ -%token T_PHPDOC_TAG @(?:[a-z][a-z0-9-\\]+:)?[a-z][a-z0-9-\\]*+ +%token T_PHPDOC_TAG_WS (?<=[\x09\x20])@(?:[a-z][a-z0-9-\\]+:)?[a-z][a-z0-9_\\-]*+ +%token T_PHPDOC_TAG @(?:[a-z][a-z0-9-\\]+:)?[a-z][a-z0-9_\\-]*+ %token T_DOCTRINE_TAG_WS (?<=[\x09\x20])@[a-z_\\][a-z0-9_:\\]*[a-z_][a-z0-9_]* %token T_DOCTRINE_TAG @[a-z_\\][a-z0-9_:\\]*[a-z_][a-z0-9_]* %token T_PHPDOC_EOL \r?+\n[\x09\x20]*+(?:\*(?!/)\x20?+)? diff --git a/src/Lexer/Lexer.php b/src/Lexer/Lexer.php index c470100c..01de5857 100644 --- a/src/Lexer/Lexer.php +++ b/src/Lexer/Lexer.php @@ -182,7 +182,7 @@ private function generateRegexp(): string self::TOKEN_OPEN_PHPDOC => '/\\*\\*(?=\\s)\\x20?+', self::TOKEN_CLOSE_PHPDOC => '\\*/', - self::TOKEN_PHPDOC_TAG => '@(?:[a-z][a-z0-9-\\\\]+:)?[a-z][a-z0-9-\\\\]*+', + self::TOKEN_PHPDOC_TAG => '@(?:[a-z][a-z0-9-\\\\]+:)?[a-z][a-z0-9_\\\\-]*+', self::TOKEN_DOCTRINE_TAG => '@[a-z_\\\\][a-z0-9_\:\\\\]*[a-z_][a-z0-9_]*', self::TOKEN_PHPDOC_EOL => '\\r?+\\n[\\x09\\x20]*+(?:\\*(?!/)\\x20?+)?', diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index eacece63..238d4a38 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -6338,6 +6338,30 @@ public function provideTagsWithNumbers(): Iterator public function provideTagsWithBackslash(): Iterator { + yield [ + 'OK tag with underscore in it', + '/** @Foo_Bar User */', + new PhpDocNode([ + new PhpDocTagNode( + '@Foo_Bar', + new GenericTagValueNode('User'), + ), + ]), + ]; + + yield [ + 'OK tag with underscore in it and parenthesis', + '/** @Foo\Bar_Baz(name="column_id") */', + new PhpDocNode([ + new PhpDocTagNode( + '@Foo\Bar_Baz', + new DoctrineTagValueNode(new DoctrineAnnotation('@Foo\Bar_Baz', [ + new DoctrineArgument(new IdentifierTypeNode('name'), new DoctrineConstExprStringNode('column_id')), + ]), ''), + ), + ]), + ]; + yield [ 'OK without description and tag with backslashes in it', '/** @ORM\Mapping\Entity User */',