diff --git a/src/Ast/PhpDoc/ParamClosureThisTagValueNode.php b/src/Ast/PhpDoc/ParamClosureThisTagValueNode.php new file mode 100644 index 0000000..0ac2131 --- /dev/null +++ b/src/Ast/PhpDoc/ParamClosureThisTagValueNode.php @@ -0,0 +1,35 @@ +type = $type; + $this->parameterName = $parameterName; + $this->description = $description; + } + + public function __toString(): string + { + return trim("{$this->type} {$this->parameterName} {$this->description}"); + } + +} diff --git a/src/Ast/PhpDoc/PhpDocNode.php b/src/Ast/PhpDoc/PhpDocNode.php index b3b6881..ade55b7 100644 --- a/src/Ast/PhpDoc/PhpDocNode.php +++ b/src/Ast/PhpDoc/PhpDocNode.php @@ -118,6 +118,20 @@ static function (PhpDocTagValueNode $value): bool { } + /** + * @return ParamClosureThisTagValueNode[] + */ + public function getParamClosureThisTagValues(string $tagName = '@param-closure-this'): array + { + return array_filter( + array_column($this->getTagsByName($tagName), 'value'), + static function (PhpDocTagValueNode $value): bool { + return $value instanceof ParamClosureThisTagValueNode; + } + ); + } + + /** * @return TemplateTagValueNode[] */ diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index 9431fb6..d7678a3 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -397,6 +397,11 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph $tagValue = $this->parseParamLaterInvokedCallableTagValue($tokens); break; + case '@param-closure-this': + case '@phpstan-param-closure-this': + $tagValue = $this->parseParamClosureThisTagValue($tokens); + break; + case '@var': case '@phpstan-var': case '@psalm-var': @@ -889,6 +894,16 @@ private function parseParamLaterInvokedCallableTagValue(TokenIterator $tokens): } + private function parseParamClosureThisTagValue(TokenIterator $tokens): Ast\PhpDoc\ParamClosureThisTagValueNode + { + $type = $this->typeParser->parse($tokens); + $parameterName = $this->parseRequiredVariableName($tokens); + $description = $this->parseOptionalDescription($tokens); + + return new Ast\PhpDoc\ParamClosureThisTagValueNode($type, $parameterName, $description); + } + + private function parseVarTagValue(TokenIterator $tokens): Ast\PhpDoc\VarTagValueNode { $type = $this->typeParser->parse($tokens); diff --git a/src/Printer/Printer.php b/src/Printer/Printer.php index 837c9e5..b176993 100644 --- a/src/Printer/Printer.php +++ b/src/Printer/Printer.php @@ -20,6 +20,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamClosureThisTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamOutTagValueNode; @@ -312,6 +313,9 @@ private function printTagValue(PhpDocTagValueNode $node): string if ($node instanceof ParamLaterInvokedCallableTagValueNode) { return trim("{$node->parameterName} {$node->description}"); } + if ($node instanceof ParamClosureThisTagValueNode) { + return trim("{$node->type} {$node->parameterName} {$node->description}"); + } if ($node instanceof PropertyTagValueNode) { $type = $this->printType($node->type); return trim("{$type} {$node->propertyName} {$node->description}"); diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 8d0ce73..8085ec6 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -29,6 +29,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamClosureThisTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamOutTagValueNode; @@ -102,6 +103,7 @@ protected function setUp(): void * @dataProvider provideParamImmediatelyInvokedCallableTagsData * @dataProvider provideParamLaterInvokedCallableTagsData * @dataProvider provideTypelessParamTagsData + * @dataProvider provideParamClosureThisTagsData * @dataProvider provideVarTagsData * @dataProvider provideReturnTagsData * @dataProvider provideThrowsTagsData @@ -686,6 +688,54 @@ public function provideParamLaterInvokedCallableTagsData(): Iterator ]; } + public function provideParamClosureThisTagsData(): Iterator + { + yield [ + 'OK', + '/** @param-closure-this Foo $a */', + new PhpDocNode([ + new PhpDocTagNode( + '@param-closure-this', + new ParamClosureThisTagValueNode( + new IdentifierTypeNode('Foo'), + '$a', + '' + ) + ), + ]), + ]; + + yield [ + 'OK with prefix', + '/** @phpstan-param-closure-this Foo $a */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-param-closure-this', + new ParamClosureThisTagValueNode( + new IdentifierTypeNode('Foo'), + '$a', + '' + ) + ), + ]), + ]; + + yield [ + 'OK with description', + '/** @param-closure-this Foo $a test */', + new PhpDocNode([ + new PhpDocTagNode( + '@param-closure-this', + new ParamClosureThisTagValueNode( + new IdentifierTypeNode('Foo'), + '$a', + 'test' + ) + ), + ]), + ]; + } + public function provideVarTagsData(): Iterator { yield [ @@ -7185,6 +7235,7 @@ public function testReturnTypeLinesAndIndexes(string $phpDoc, array $lines): voi * @dataProvider provideTypelessParamTagsData * @dataProvider provideParamImmediatelyInvokedCallableTagsData * @dataProvider provideParamLaterInvokedCallableTagsData + * @dataProvider provideParamClosureThisTagsData * @dataProvider provideVarTagsData * @dataProvider provideReturnTagsData * @dataProvider provideThrowsTagsData diff --git a/tests/PHPStan/Printer/PrinterTest.php b/tests/PHPStan/Printer/PrinterTest.php index ddbe431..25a268e 100644 --- a/tests/PHPStan/Printer/PrinterTest.php +++ b/tests/PHPStan/Printer/PrinterTest.php @@ -17,6 +17,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArray; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArrayItem; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamClosureThisTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamLaterInvokedCallableTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; @@ -1703,6 +1704,25 @@ public function enterNode(Node $node) }, ]; + + yield [ + '/** @param-closure-this Foo $test haha */', + '/** @param-closure-this Bar $taste hehe */', + new class extends AbstractNodeVisitor { + + public function enterNode(Node $node) + { + if ($node instanceof ParamClosureThisTagValueNode) { + $node->type = new IdentifierTypeNode('Bar'); + $node->parameterName = '$taste'; + $node->description = 'hehe'; + } + + return $node; + } + + }, + ]; } /**