diff --git a/src/Printer/Printer.php b/src/Printer/Printer.php index db7d3db7..0e218b50 100644 --- a/src/Printer/Printer.php +++ b/src/Printer/Printer.php @@ -855,7 +855,7 @@ private function printNodeFormatPreserving(Node $node, TokenIterator $originalTo /** @var Node|null $originalNode */ $originalNode = $node->getAttribute(Attribute::ORIGINAL_NODE); if ($originalNode === null) { - return $this->print($node); + return $this->printReplacement($node); } $class = get_class($node); @@ -897,14 +897,14 @@ private function printNodeFormatPreserving(Node $node, TokenIterator $originalTo ); if ($listResult === null) { - return $this->print($node); + return $this->printReplacement($node); } $result .= $listResult; continue; } - return $this->print($node); + return $this->printReplacement($node); } if ($origSubNode === null) { @@ -913,7 +913,7 @@ private function printNodeFormatPreserving(Node $node, TokenIterator $originalTo continue; } - return $this->print($node); + return $this->printReplacement($node); } $subStartPos = $origSubNode->getAttribute(Attribute::START_INDEX); @@ -923,11 +923,16 @@ private function printNodeFormatPreserving(Node $node, TokenIterator $originalTo } if ($subEndPos < $subStartPos) { - return $this->print($node); + // the original sub-node has no tokens, e.g. a Doctrine annotation without arguments + if ($subNode !== null && $this->print($subNode) === $this->print($origSubNode)) { + continue; + } + + return $this->printReplacement($node); } if ($subNode === null) { - return $this->print($node); + return $this->printReplacement($node); } $result .= $originalTokens->getContentBetween($pos, $subStartPos); @@ -956,4 +961,30 @@ private function printNodeFormatPreserving(Node $node, TokenIterator $originalTo return $result . $originalTokens->getContentBetween($pos, $endPos + 1); } + /** + * Prints a node that replaces the tokens of the original node. + * + * The tokens of a Doctrine tag value do not contain the annotation name. + * The parent tag prints the name from its own tokens. + */ + private function printReplacement(Node $node): string + { + if (!$node instanceof DoctrineTagValueNode) { + return $this->print($node); + } + + $arguments = '(' . implode(', ', $node->annotation->arguments) . ')'; + if ($node->annotation->arguments === []) { + $originalNode = $node->getAttribute(Attribute::ORIGINAL_NODE); + if ( + $originalNode instanceof DoctrineTagValueNode + && $originalNode->annotation->getAttribute(Attribute::END_INDEX) < $originalNode->annotation->getAttribute(Attribute::START_INDEX) + ) { + $arguments = ''; + } + } + + return trim($arguments . ' ' . $node->description); + } + } diff --git a/tests/PHPStan/Printer/PrinterTest.php b/tests/PHPStan/Printer/PrinterTest.php index 3f791927..a78358ba 100644 --- a/tests/PHPStan/Printer/PrinterTest.php +++ b/tests/PHPStan/Printer/PrinterTest.php @@ -17,6 +17,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArgument; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArray; use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineArrayItem; +use PHPStan\PhpDocParser\Ast\PhpDoc\Doctrine\DoctrineTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamClosureThisTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamImmediatelyInvokedCallableTagValueNode; @@ -1987,6 +1988,57 @@ public function enterNode(Node $node) }, ]; + yield [ + '/** @\Foo\Id */', + '/** @\Foo\Id */', + $noopVisitor, + ]; + + yield [ + self::nowdoc(' + /** + * @ORM\Column(type="string") + * @\Foo\Id + */'), + self::nowdoc(' + /** + * @ORM\Column(type="string") + * @\Foo\Id + */'), + $noopVisitor, + ]; + + $changeDoctrineDescriptionVisitor = new class extends AbstractNodeVisitor { + + public function enterNode(Node $node) + { + if ($node instanceof DoctrineTagValueNode) { + $node->description = 'other description'; + } + + return $node; + } + + }; + + yield [ + '/** @Route("/path", name="x") some description */', + '/** @Route("/path", name="x") other description */', + $changeDoctrineDescriptionVisitor, + ]; + + yield [ + '/** @\Foo\Route("/path") some description */', + '/** @\Foo\Route("/path") other description */', + $changeDoctrineDescriptionVisitor, + ]; + + yield [ + '/** @\Foo\Id some description */', + '/** @\Foo\Id other description */', + $changeDoctrineDescriptionVisitor, + ]; + yield [ '/** @Foo() */', '/** @Foo(1, 2, 3) */',