Skip to content

Commit

Permalink
PhpDocParser - option to preserve type alias with parse error
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Apr 4, 2023
1 parent afb728a commit bfec872
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 2 deletions.
37 changes: 37 additions & 0 deletions src/Ast/Type/InvalidTypeNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDocParser\Ast\Type;

use PHPStan\PhpDocParser\Ast\NodeAttributes;
use PHPStan\PhpDocParser\Parser\ParserException;

class InvalidTypeNode implements TypeNode
{

use NodeAttributes;

/** @var mixed[] */
private $exceptionArgs;

public function __construct(ParserException $exception)
{
$this->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*';
}

}
16 changes: 15 additions & 1 deletion src/Parser/PhpDocParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}


Expand Down Expand Up @@ -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);
Expand Down
120 changes: 119 additions & 1 deletion tests/PHPStan/Parser/PhpDocParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -64,6 +65,9 @@ class PhpDocParserTest extends TestCase
/** @var PhpDocParser */
private $phpDocParserWithRequiredWhitespaceBeforeDescription;

/** @var PhpDocParser */
private $phpDocParserWithPreserveTypeAliasesWithInvalidTypes;

protected function setUp(): void
{
parent::setUp();
Expand All @@ -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);
}


Expand Down Expand Up @@ -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(
Expand All @@ -120,6 +126,13 @@ public function testParse(
$input,
$withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode ?? $expectedPhpDocNode
);

$this->executeTestParse(
$this->phpDocParserWithPreserveTypeAliasesWithInvalidTypes,
$label,
$input,
$withPreserveTypeAliasesWithInvalidTypesExpectedPhpDocNode ?? $withRequiredWhitespaceBeforeDescriptionExpectedPhpDocNode ?? $expectedPhpDocNode
);
}


Expand Down Expand Up @@ -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 [
Expand Down

0 comments on commit bfec872

Please sign in to comment.