diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index e817652..bf57a26 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -24,10 +24,14 @@ class PhpDocParser /** @var ConstExprParser */ private $constantExprParser; - public function __construct(TypeParser $typeParser, ConstExprParser $constantExprParser) + /** @var bool */ + private $requireWhitespaceBeforeDescription; + + public function __construct(TypeParser $typeParser, ConstExprParser $constantExprParser, bool $requireWhitespaceBeforeDescription = false) { $this->typeParser = $typeParser; $this->constantExprParser = $constantExprParser; + $this->requireWhitespaceBeforeDescription = $requireWhitespaceBeforeDescription; } @@ -492,7 +496,8 @@ class PhpDocParser } if ( - !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END) + $this->requireWhitespaceBeforeDescription + && !$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL, Lexer::TOKEN_CLOSE_PHPDOC, Lexer::TOKEN_END) && !$tokens->isPrecededByHorizontalWhitespace() ) { $tokens->consumeTokenType(Lexer::TOKEN_HORIZONTAL_WS); // will throw exception diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index aaa992c..df52c03 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -53,12 +53,17 @@ class PhpDocParserTest extends TestCase /** @var PhpDocParser */ private $phpDocParser; + /** @var PhpDocParser */ + private $phpDocParserWithRequiredWhitespaceBeforeDescription; + protected function setUp(): void { parent::setUp(); $this->lexer = new Lexer(); $constExprParser = new ConstExprParser(); - $this->phpDocParser = new PhpDocParser(new TypeParser($constExprParser), $constExprParser); + $typeParser = new TypeParser($constExprParser); + $this->phpDocParser = new PhpDocParser($typeParser, $constExprParser); + $this->phpDocParserWithRequiredWhitespaceBeforeDescription = new PhpDocParser($typeParser, $constExprParser, true); } @@ -83,14 +88,37 @@ class PhpDocParserTest extends TestCase * @dataProvider provideRealWorldExampleData * @dataProvider provideDescriptionWithOrWithoutHtml */ - public function testParse(string $label, string $input, PhpDocNode $expectedPhpDocNode, int $nextTokenType = Lexer::TOKEN_END): void + public function testParse( + string $label, + string $input, + PhpDocNode $expectedPhpDocNode, + ?PhpDocNode $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode = null + ): void + { + $this->executeTestParse( + $this->phpDocParser, + $label, + $input, + $expectedPhpDocNode + ); + + $this->executeTestParse( + $this->phpDocParserWithRequiredWhitespaceBeforeDescription, + $label, + $input, + $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode ?? $expectedPhpDocNode + ); + } + + + private function executeTestParse(PhpDocParser $phpDocParser, string $label, string $input, PhpDocNode $expectedPhpDocNode): void { $tokens = new TokenIterator($this->lexer->tokenize($input)); - $actualPhpDocNode = $this->phpDocParser->parse($tokens); + $actualPhpDocNode = $phpDocParser->parse($tokens); $this->assertEquals($expectedPhpDocNode, $actualPhpDocNode, $label); $this->assertSame((string) $expectedPhpDocNode, (string) $actualPhpDocNode); - $this->assertSame($nextTokenType, $tokens->currentTokenType()); + $this->assertSame(Lexer::TOKEN_END, $tokens->currentTokenType()); } @@ -675,6 +703,16 @@ class PhpDocParserTest extends TestCase ) ), ]), + new PhpDocNode([ + new PhpDocTagNode( + '@var', + new VarTagValueNode( + new IdentifierTypeNode('Foo'), + '$foo', + '#desc' + ) + ), + ]), ]; yield [ @@ -1459,6 +1497,15 @@ class PhpDocParserTest extends TestCase yield [ 'invalid variadic callable', '/** @return \Closure(...int, string): string */', + new PhpDocNode([ + new PhpDocTagNode( + '@return', + new ReturnTagValueNode( + new IdentifierTypeNode('\Closure'), + '(...int, string): string' + ) + ), + ]), new PhpDocNode([ new PhpDocTagNode( '@return', @@ -2265,6 +2312,16 @@ class PhpDocParserTest extends TestCase yield [ 'callable with incomplete signature without return type', '/** @var callable(int) */', + new PhpDocNode([ + new PhpDocTagNode( + '@var', + new VarTagValueNode( + new IdentifierTypeNode('callable'), + '', + '(int)' + ) + ), + ]), new PhpDocNode([ new PhpDocTagNode( '@var', @@ -4241,6 +4298,20 @@ Finder::findFiles('*.php') '/**' . PHP_EOL . ' * @return Foo Important description' . PHP_EOL . ' */', + new PhpDocNode([ + new PhpDocTagNode( + '@return', + new ReturnTagValueNode( + new GenericTypeNode( + new IdentifierTypeNode('Foo'), + [ + new IdentifierTypeNode('strong'), + ] + ), + 'Important description' + ) + ), + ]), new PhpDocNode([ new PhpDocTagNode( '@return', @@ -4305,14 +4376,20 @@ Finder::findFiles('*.php') * @dataProvider dataParseTagValue * @param PhpDocNode $expectedPhpDocNode */ - public function testParseTagValue(string $tag, string $phpDoc, Node $expectedPhpDocNode, int $nextTokenType = Lexer::TOKEN_END): void + public function testParseTagValue(string $tag, string $phpDoc, Node $expectedPhpDocNode): void + { + $this->executeTestParseTagValue($this->phpDocParser, $tag, $phpDoc, $expectedPhpDocNode); + $this->executeTestParseTagValue($this->phpDocParserWithRequiredWhitespaceBeforeDescription, $tag, $phpDoc, $expectedPhpDocNode); + } + + private function executeTestParseTagValue(PhpDocParser $phpDocParser, string $tag, string $phpDoc, Node $expectedPhpDocNode): void { $tokens = new TokenIterator($this->lexer->tokenize($phpDoc)); - $actualPhpDocNode = $this->phpDocParser->parseTagValue($tokens, $tag); + $actualPhpDocNode = $phpDocParser->parseTagValue($tokens, $tag); $this->assertEquals($expectedPhpDocNode, $actualPhpDocNode); $this->assertSame((string) $expectedPhpDocNode, (string) $actualPhpDocNode); - $this->assertSame($nextTokenType, $tokens->currentTokenType()); + $this->assertSame(Lexer::TOKEN_END, $tokens->currentTokenType()); } }