From d02f4389e4c434b3fc75692960d2414e486eeea3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 7 Oct 2025 10:26:59 +0200 Subject: [PATCH] Introduce all-methods tag --- .../PhpDoc/AllMethodsImpureTagValueNode.php | 26 ++++++++ src/Ast/PhpDoc/AllMethodsPureTagValueNode.php | 26 ++++++++ src/Ast/PhpDoc/PhpDocNode.php | 22 +++++++ src/Parser/PhpDocParser.php | 22 +++++++ src/Printer/Printer.php | 8 +++ tests/PHPStan/Parser/PhpDocParserTest.php | 62 +++++++++++++++++++ tests/PHPStan/Printer/PrinterTest.php | 36 +++++++++++ 7 files changed, 202 insertions(+) create mode 100644 src/Ast/PhpDoc/AllMethodsImpureTagValueNode.php create mode 100644 src/Ast/PhpDoc/AllMethodsPureTagValueNode.php diff --git a/src/Ast/PhpDoc/AllMethodsImpureTagValueNode.php b/src/Ast/PhpDoc/AllMethodsImpureTagValueNode.php new file mode 100644 index 0000000..b98a352 --- /dev/null +++ b/src/Ast/PhpDoc/AllMethodsImpureTagValueNode.php @@ -0,0 +1,26 @@ +description = $description; + } + + public function __toString(): string + { + return trim($this->description); + } + +} diff --git a/src/Ast/PhpDoc/AllMethodsPureTagValueNode.php b/src/Ast/PhpDoc/AllMethodsPureTagValueNode.php new file mode 100644 index 0000000..74ebf61 --- /dev/null +++ b/src/Ast/PhpDoc/AllMethodsPureTagValueNode.php @@ -0,0 +1,26 @@ +description = $description; + } + + public function __toString(): string + { + return trim($this->description); + } + +} diff --git a/src/Ast/PhpDoc/PhpDocNode.php b/src/Ast/PhpDoc/PhpDocNode.php index a1fdbd8..3d8c07f 100644 --- a/src/Ast/PhpDoc/PhpDocNode.php +++ b/src/Ast/PhpDoc/PhpDocNode.php @@ -118,6 +118,28 @@ public function getPureUnlessCallableIsImpureTagValues(string $tagName = '@pure- ); } + /** + * @return AllMethodsImpureTagValueNode[] + */ + public function getAllMethodsImpureTagValues(string $tagName = '@phpstan-all-methods-impure'): array + { + return array_filter( + array_column($this->getTagsByName($tagName), 'value'), + static fn (PhpDocTagValueNode $value): bool => $value instanceof AllMethodsImpureTagValueNode, + ); + } + + /** + * @return AllMethodsPureTagValueNode[] + */ + public function getAllMethodsPureTagValues(string $tagName = '@phpstan-all-methods-pure'): array + { + return array_filter( + array_column($this->getTagsByName($tagName), 'value'), + static fn (PhpDocTagValueNode $value): bool => $value instanceof AllMethodsPureTagValueNode, + ); + } + /** * @return TemplateTagValueNode[] */ diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index cbb0e1a..889ccc1 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -362,6 +362,14 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph $tagValue = $this->parsePureUnlessCallableIsImpureTagValue($tokens); break; + case '@phpstan-all-methods-impure': + $tagValue = $this->parseAllMethodsImpureTagValue($tokens); + break; + + case '@phpstan-all-methods-pure': + $tagValue = $this->parseAllMethodsPureTagValue($tokens); + break; + case '@var': case '@phpstan-var': case '@psalm-var': @@ -877,6 +885,20 @@ private function parsePureUnlessCallableIsImpureTagValue(TokenIterator $tokens): return new Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode($parameterName, $description); } + private function parseAllMethodsImpureTagValue(TokenIterator $tokens): Ast\PhpDoc\AllMethodsImpureTagValueNode + { + $description = $this->parseOptionalDescription($tokens, false); + + return new Ast\PhpDoc\AllMethodsImpureTagValueNode($description); + } + + private function parseAllMethodsPureTagValue(TokenIterator $tokens): Ast\PhpDoc\AllMethodsPureTagValueNode + { + $description = $this->parseOptionalDescription($tokens, false); + + return new Ast\PhpDoc\AllMethodsPureTagValueNode($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 36f6ebe..a5ee765 100644 --- a/src/Printer/Printer.php +++ b/src/Printer/Printer.php @@ -8,6 +8,8 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode; use PHPStan\PhpDocParser\Ast\Node; +use PHPStan\PhpDocParser\Ast\PhpDoc\AllMethodsImpureTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\AllMethodsPureTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagMethodValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagPropertyValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagValueNode; @@ -354,6 +356,12 @@ private function printTagValue(PhpDocTagValueNode $node): string if ($node instanceof PureUnlessCallableIsImpureTagValueNode) { return trim("{$node->parameterName} {$node->description}"); } + if ($node instanceof AllMethodsImpureTagValueNode) { + return trim($node->description); + } + if ($node instanceof AllMethodsPureTagValueNode) { + return trim($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 287e69a..e5d823f 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -13,6 +13,8 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\DoctrineConstExprStringNode; use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeTraverser; +use PHPStan\PhpDocParser\Ast\PhpDoc\AllMethodsImpureTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\AllMethodsPureTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagMethodValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagPropertyValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\AssertTagValueNode; @@ -102,6 +104,8 @@ protected function setUp(): void * @dataProvider provideTypelessParamTagsData * @dataProvider provideParamClosureThisTagsData * @dataProvider providePureUnlessCallableIsImpureTagsData + * @dataProvider provideAllMethodsImpureTagsData + * @dataProvider provideAllMethodsPureTagsData * @dataProvider provideVarTagsData * @dataProvider provideReturnTagsData * @dataProvider provideThrowsTagsData @@ -757,6 +761,64 @@ public function providePureUnlessCallableIsImpureTagsData(): Iterator ]; } + public function provideAllMethodsImpureTagsData(): Iterator + { + yield [ + 'OK', + '/** @phpstan-all-methods-impure */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-all-methods-impure', + new AllMethodsImpureTagValueNode( + '', + ), + ), + ]), + ]; + + yield [ + 'OK with description', + '/** @phpstan-all-methods-impure test two three */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-all-methods-impure', + new AllMethodsImpureTagValueNode( + 'test two three', + ), + ), + ]), + ]; + } + + public function provideAllMethodsPureTagsData(): Iterator + { + yield [ + 'OK', + '/** @phpstan-all-methods-pure */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-all-methods-pure', + new AllMethodsPureTagValueNode( + '', + ), + ), + ]), + ]; + + yield [ + 'OK with description', + '/** @phpstan-all-methods-pure test two three */', + new PhpDocNode([ + new PhpDocTagNode( + '@phpstan-all-methods-pure', + new AllMethodsPureTagValueNode( + 'test two three', + ), + ), + ]), + ]; + } + public function provideVarTagsData(): Iterator { yield [ diff --git a/tests/PHPStan/Printer/PrinterTest.php b/tests/PHPStan/Printer/PrinterTest.php index 464b723..765e689 100644 --- a/tests/PHPStan/Printer/PrinterTest.php +++ b/tests/PHPStan/Printer/PrinterTest.php @@ -13,6 +13,8 @@ use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Ast\NodeTraverser; use PHPStan\PhpDocParser\Ast\NodeVisitor; +use PHPStan\PhpDocParser\Ast\PhpDoc\AllMethodsImpureTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\AllMethodsPureTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineAnnotation; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArgument; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArray; @@ -2115,6 +2117,40 @@ public function enterNode(Node $node) }, ]; + yield [ + '/** @phpstan-all-methods-impure test */', + '/** @phpstan-all-methods-impure foo */', + new class extends AbstractNodeVisitor { + + public function enterNode(Node $node) + { + if ($node instanceof AllMethodsImpureTagValueNode) { + $node->description = 'foo'; + } + + return $node; + } + + }, + ]; + + yield [ + '/** @phpstan-all-methods-pure test */', + '/** @phpstan-all-methods-pure foo */', + new class extends AbstractNodeVisitor { + + public function enterNode(Node $node) + { + if ($node instanceof AllMethodsPureTagValueNode) { + $node->description = 'foo'; + } + + return $node; + } + + }, + ]; + yield [ '/** @return Foo[abc] */', '/** @return self::FOO[abc] */',