From bfec8729f7e23c40670f98e27e694cfdb13fc12a Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 4 Apr 2023 10:05:53 +0200 Subject: [PATCH] PhpDocParser - option to preserve type alias with parse error --- src/Ast/Type/InvalidTypeNode.php | 37 +++++++ src/Parser/PhpDocParser.php | 16 ++- tests/PHPStan/Parser/PhpDocParserTest.php | 120 +++++++++++++++++++++- 3 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 src/Ast/Type/InvalidTypeNode.php diff --git a/src/Ast/Type/InvalidTypeNode.php b/src/Ast/Type/InvalidTypeNode.php new file mode 100644 index 00000000..af3d3d76 --- /dev/null +++ b/src/Ast/Type/InvalidTypeNode.php @@ -0,0 +1,37 @@ +exceptionArgs = [ + $exception->getCurrentTokenValue(), + $exception->getCurrentTokenType(), + $exception->getCurrentOffset(), + $exception->getExpectedTokenType(), + $exception->getExpectedTokenValue(), + ]; + } + + public function getException(): ParserException + { + return new ParserException(...$this->exceptionArgs); + } + + public function __toString(): string + { + return '*Invalid type*'; + } + +} diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index d9942b3d..9d4564ea 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -28,11 +28,15 @@ class PhpDocParser /** @var bool */ private $requireWhitespaceBeforeDescription; - public function __construct(TypeParser $typeParser, ConstExprParser $constantExprParser, bool $requireWhitespaceBeforeDescription = false) + /** @var bool */ + private $preserveTypeAliasesWithInvalidTypes; + + public function __construct(TypeParser $typeParser, ConstExprParser $constantExprParser, bool $requireWhitespaceBeforeDescription = false, bool $preserveTypeAliasesWithInvalidTypes = false) { $this->typeParser = $typeParser; $this->constantExprParser = $constantExprParser; $this->requireWhitespaceBeforeDescription = $requireWhitespaceBeforeDescription; + $this->preserveTypeAliasesWithInvalidTypes = $preserveTypeAliasesWithInvalidTypes; } @@ -453,6 +457,16 @@ private function parseTypeAliasTagValue(TokenIterator $tokens): Ast\PhpDoc\TypeA // support psalm-type syntax $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); + if ($this->preserveTypeAliasesWithInvalidTypes) { + try { + $type = $this->typeParser->parse($tokens); + + return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type); + } catch (ParserException $e) { + return new Ast\PhpDoc\TypeAliasTagValueNode($alias, new Ast\Type\InvalidTypeNode($e)); + } + } + $type = $this->typeParser->parse($tokens); return new Ast\PhpDoc\TypeAliasTagValueNode($alias, $type); diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index aa06b5e7..d70bbd70 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -45,6 +45,7 @@ use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode; use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Ast\Type\InvalidTypeNode; use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; @@ -64,6 +65,9 @@ class PhpDocParserTest extends TestCase /** @var PhpDocParser */ private $phpDocParserWithRequiredWhitespaceBeforeDescription; + /** @var PhpDocParser */ + private $phpDocParserWithPreserveTypeAliasesWithInvalidTypes; + protected function setUp(): void { parent::setUp(); @@ -72,6 +76,7 @@ protected function setUp(): void $typeParser = new TypeParser($constExprParser); $this->phpDocParser = new PhpDocParser($typeParser, $constExprParser); $this->phpDocParserWithRequiredWhitespaceBeforeDescription = new PhpDocParser($typeParser, $constExprParser, true); + $this->phpDocParserWithPreserveTypeAliasesWithInvalidTypes = new PhpDocParser($typeParser, $constExprParser, true, true); } @@ -104,7 +109,8 @@ public function testParse( string $label, string $input, PhpDocNode $expectedPhpDocNode, - ?PhpDocNode $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode = null + ?PhpDocNode $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode = null, + ?PhpDocNode $withPreserveTypeAliasesWithInvalidTypesExpectedPhpDocNode = null ): void { $this->executeTestParse( @@ -120,6 +126,13 @@ public function testParse( $input, $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode ?? $expectedPhpDocNode ); + + $this->executeTestParse( + $this->phpDocParserWithPreserveTypeAliasesWithInvalidTypes, + $label, + $input, + $withPreserveTypeAliasesWithInvalidTypesExpectedPhpDocNode ?? $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode ?? $expectedPhpDocNode + ); } @@ -3834,6 +3847,111 @@ public function provideTypeAliasTagsData(): Iterator ) ), ]), + null, + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new TypeAliasTagValueNode( + 'TypeAlias', + new InvalidTypeNode(new ParserException( + '*/', + Lexer::TOKEN_CLOSE_PHPDOC, + 28, + Lexer::TOKEN_IDENTIFIER, + null + )) + ) + ), + ]), + ]; + + yield [ + 'invalid without type with newline', + '/** + * @phpstan-type TypeAlias + */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new InvalidTagValueNode( + 'TypeAlias', + new ParserException( + "\n\t\t\t ", + Lexer::TOKEN_PHPDOC_EOL, + 34, + Lexer::TOKEN_IDENTIFIER + ) + ) + ), + ]), + null, + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new TypeAliasTagValueNode( + 'TypeAlias', + new InvalidTypeNode(new ParserException( + "\n\t\t\t ", + Lexer::TOKEN_PHPDOC_EOL, + 34, + Lexer::TOKEN_IDENTIFIER, + null + )) + ) + ), + ]), + ]; + + yield [ + 'invalid without type but valid tag below', + '/** + * @phpstan-type TypeAlias + * @mixin T + */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new InvalidTagValueNode( + 'TypeAlias', + new ParserException( + "\n\t\t\t * ", + Lexer::TOKEN_PHPDOC_EOL, + 34, + Lexer::TOKEN_IDENTIFIER + ) + ) + ), + new PhpDocTagNode( + '@mixin', + new MixinTagValueNode( + new IdentifierTypeNode('T'), + '' + ) + ), + ]), + null, + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-type', + new TypeAliasTagValueNode( + 'TypeAlias', + new InvalidTypeNode(new ParserException( + "\n\t\t\t * ", + Lexer::TOKEN_PHPDOC_EOL, + 34, + Lexer::TOKEN_IDENTIFIER, + null + )) + ) + ), + new PhpDocTagNode( + '@mixin', + new MixinTagValueNode( + new IdentifierTypeNode('T'), + '' + ) + ), + ]), ]; yield [