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 */',