From 6cafed9212aa56aaa3aaf3b67c0fd1e5de53f50c Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Mon, 2 May 2022 14:10:08 +0200 Subject: [PATCH] Add negated assertions --- src/Ast/PhpDoc/AssertTagValueNode.php | 9 +++++++-- src/Lexer/Lexer.php | 3 +++ src/Parser/PhpDocParser.php | 3 ++- tests/PHPStan/Parser/PhpDocParserTest.php | 23 +++++++++++++++++++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/Ast/PhpDoc/AssertTagValueNode.php b/src/Ast/PhpDoc/AssertTagValueNode.php index 1fec861..8f45f7b 100644 --- a/src/Ast/PhpDoc/AssertTagValueNode.php +++ b/src/Ast/PhpDoc/AssertTagValueNode.php @@ -17,20 +17,25 @@ class AssertTagValueNode implements PhpDocTagValueNode /** @var string */ public $parameter; + /** @var bool */ + public $isNegated; + /** @var string (may be empty) */ public $description; - public function __construct(TypeNode $type, string $parameter, string $description) + public function __construct(TypeNode $type, string $parameter, bool $isNegated, string $description) { $this->type = $type; $this->parameter = $parameter; + $this->isNegated = $isNegated; $this->description = $description; } public function __toString(): string { - return trim("{$this->type} {$this->parameter} {$this->description}"); + $isNegated = $this->isNegated ? '!' : ''; + return trim("{$this->type} {$isNegated}{$this->parameter} {$this->description}"); } } diff --git a/src/Lexer/Lexer.php b/src/Lexer/Lexer.php index e759cd5..6888964 100644 --- a/src/Lexer/Lexer.php +++ b/src/Lexer/Lexer.php @@ -48,12 +48,14 @@ class Lexer public const TOKEN_WILDCARD = 30; public const TOKEN_OPEN_CURLY_BRACKET = 31; public const TOKEN_CLOSE_CURLY_BRACKET = 32; + public const TOKEN_NEGATED = 33; public const TOKEN_LABELS = [ self::TOKEN_REFERENCE => '\'&\'', self::TOKEN_UNION => '\'|\'', self::TOKEN_INTERSECTION => '\'&\'', self::TOKEN_NULLABLE => '\'?\'', + self::TOKEN_NEGATED => '\'!\'', self::TOKEN_OPEN_PARENTHESES => '\'(\'', self::TOKEN_CLOSE_PARENTHESES => '\')\'', self::TOKEN_OPEN_ANGLE_BRACKET => '\'<\'', @@ -129,6 +131,7 @@ class Lexer self::TOKEN_UNION => '\\|', self::TOKEN_INTERSECTION => '&', self::TOKEN_NULLABLE => '\\?', + self::TOKEN_NEGATED => '!', self::TOKEN_OPEN_PARENTHESES => '\\(', self::TOKEN_CLOSE_PARENTHESES => '\\)', diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index ab1c879..fd034d7 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -431,10 +431,11 @@ class PhpDocParser private function parseAssertTagValue(TokenIterator $tokens): Ast\PhpDoc\AssertTagValueNode { + $isNegated = $tokens->tryConsumeTokenType(Lexer::TOKEN_NEGATED); $type = $this->typeParser->parse($tokens); $parameter = $this->parseRequiredVariableName($tokens); $description = $this->parseOptionalDescription($tokens); - return new Ast\PhpDoc\AssertTagValueNode($type, $parameter, $description); + return new Ast\PhpDoc\AssertTagValueNode($type, $parameter, $isNegated, $description); } private function parseOptionalVariableName(TokenIterator $tokens): string diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index d5d49ed..cdaa65d 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -3493,6 +3493,7 @@ some text in the middle' new AssertTagValueNode( new IdentifierTypeNode('Type'), '$var', + false, '' ) ), @@ -3508,6 +3509,7 @@ some text in the middle' new AssertTagValueNode( new IdentifierTypeNode('Type'), '$var', + false, '' ) ), @@ -3523,6 +3525,7 @@ some text in the middle' new AssertTagValueNode( new IdentifierTypeNode('Type'), '$var', + false, 'assert Type to $var' ) ), @@ -3541,6 +3544,7 @@ some text in the middle' new IdentifierTypeNode('Other'), ]), '$var', + false, '' ) ), @@ -3575,11 +3579,13 @@ some text in the middle' new AssertTagValueNode( new IdentifierTypeNode('Type'), '$var', + false, '' ) ), ]), ]; + yield [ 'OK assert-if-false', '/** @phpstan-assert-if-false Type $var */', @@ -3589,6 +3595,23 @@ some text in the middle' new AssertTagValueNode( new IdentifierTypeNode('Type'), '$var', + false, + '' + ) + ), + ]), + ]; + + yield [ + 'OK negated', + '/** @phpstan-assert !Type $var */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-assert', + new AssertTagValueNode( + new IdentifierTypeNode('Type'), + '$var', + true, '' ) ),