diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index 9d4564ea..cee8042d 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -460,9 +460,20 @@ private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeA if ($this->preserveTypeAliasesWithInvalidTypes) { try { $type = $this->typeParser->parse($tokens); + if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PHPDOC)) { + if (!$tokens->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) { + throw new ParserException( + $tokens->currentTokenValue(), + $tokens->currentTokenType(), + $tokens->currentTokenOffset(), + Lexer::TOKEN_PHPDOC_EOL + ); + } + } return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type); } catch (ParserException $e) { + $this->parseOptionalDescription($tokens); return new Ast\PhpDoc\TypeAliasTagValueNode($alias, new Ast\Type\InvalidTypeNode($e)); } } diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index d70bbd70..507832a5 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -916,6 +916,24 @@ public function provideVarTagsData(): Iterator ) ), ]), + null, + new PhpDocNode([ + new PhpDocTagNode( + '@psalm-type', + new TypeAliasTagValueNode( + 'PARTSTRUCTURE_PARAM', + new InvalidTypeNode( + new ParserException( + '{', + Lexer::TOKEN_OPEN_CURLY_BRACKET, + 44, + Lexer::TOKEN_PHPDOC_EOL, + null + ) + ) + ) + ), + ]), ]; } @@ -3954,6 +3972,106 @@ public function provideTypeAliasTagsData(): Iterator ]), ]; + yield [ + 'invalid type that should be an error', + '/** + * @phpstan-type Foo array{} + * @phpstan-type InvalidFoo what{} + */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new InvalidTagValueNode( + "Unexpected token \"{\", expected '*/' at offset 65", + new ParserException( + '{', + Lexer::TOKEN_OPEN_CURLY_BRACKET, + 65, + Lexer::TOKEN_CLOSE_PHPDOC, + null + ) + ) + ), + ]), + null, + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new TypeAliasTagValueNode( + 'Foo', + new ArrayShapeNode([]) + ) + ), + new PhpDocTagNode( + '@phpstan-type', + new TypeAliasTagValueNode( + 'InvalidFoo', + new InvalidTypeNode(new ParserException( + '{', + Lexer::TOKEN_OPEN_CURLY_BRACKET, + 65, + Lexer::TOKEN_PHPDOC_EOL, + null + )) + ) + ), + ]), + ]; + + yield [ + 'invalid type that should be an error followed by valid again', + '/** + * @phpstan-type Foo array{} + * @phpstan-type InvalidFoo what{} + * @phpstan-type Bar array{} + */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new InvalidTagValueNode( + "Unexpected token \"{\", expected '*/' at offset 65", + new ParserException( + '{', + Lexer::TOKEN_OPEN_CURLY_BRACKET, + 65, + Lexer::TOKEN_CLOSE_PHPDOC, + null + ) + ) + ), + ]), + null, + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new TypeAliasTagValueNode( + 'Foo', + new ArrayShapeNode([]) + ) + ), + new PhpDocTagNode( + '@phpstan-type', + new TypeAliasTagValueNode( + 'InvalidFoo', + new InvalidTypeNode(new ParserException( + '{', + Lexer::TOKEN_OPEN_CURLY_BRACKET, + 65, + Lexer::TOKEN_PHPDOC_EOL, + null + )) + ) + ), + new PhpDocTagNode( + '@phpstan-type', + new TypeAliasTagValueNode( + 'Bar', + new ArrayShapeNode([]) + ) + ), + ]), + ]; + yield [ 'invalid empty', '/** @phpstan-type */',