Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 44 additions & 10 deletions src/Printer/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
use PHPStan\PhpDocParser\Parser\TokenIterator;
use function array_keys;
use function array_map;
use function array_shift;
use function assert;
use function count;
use function get_class;
Expand All @@ -77,6 +78,8 @@
use function in_array;
use function is_array;
use function preg_match_all;
use function preg_split;
use function rtrim;
use function sprintf;
use function str_replace;
use function strlen;
Expand All @@ -96,6 +99,12 @@ final class Printer
/** @var Differ<Node> */
private Differ $differ;

/**
* Inserted before every continuation line of multi-line text inside a printed PhpDocTextNode or tag value.
* printFormatPreserving() sets it to the indentation detected in the original PHPDoc.
*/
private string $continuationLinePrefix = "\n * ";

/**
* Map From "{$class}->{$subNode}" to string that should be inserted
* between elements of this list subnode
Expand Down Expand Up @@ -187,14 +196,18 @@ public function printFormatPreserving(PhpDocNode $node, PhpDocNode $originalNode
});

$tokenIndex = 0;
$result = $this->printArrayFormatPreserving(
$node->children,
$originalNode->children,
$originalTokens,
$tokenIndex,
PhpDocNode::class,
'children',
);
try {
$result = $this->printArrayFormatPreserving(
$node->children,
$originalNode->children,
$originalTokens,
$tokenIndex,
PhpDocNode::class,
'children',
);
} finally {
$this->continuationLinePrefix = "\n * ";
}
if ($result !== null) {
return $result . $originalTokens->getContentBetween($tokenIndex, $originalTokens->getTokenCount());
}
Expand All @@ -214,7 +227,7 @@ function (PhpDocChildNode $child): string {
)) . "\n */";
}
if ($node instanceof PhpDocTextNode) {
return $node->text;
return $this->printMultilineText($node->text);
}
if ($node instanceof PhpDocTagNode) {
if ($node->value instanceof DoctrineTagValueNode) {
Expand All @@ -224,7 +237,7 @@ function (PhpDocChildNode $child): string {
return trim(sprintf('%s %s', $node->name, $this->print($node->value)));
}
if ($node instanceof PhpDocTagValueNode) {
return $this->printTagValue($node);
return $this->printMultilineText($this->printTagValue($node));
}
if ($node instanceof TypeNode) {
return $this->printType($node);
Expand Down Expand Up @@ -292,6 +305,25 @@ function (PhpDocChildNode $child): string {
throw new LogicException(sprintf('Unknown node type %s', get_class($node)));
}

private function printMultilineText(string $text): string
{
if (strpos($text, "\n") === false) {
return $text;
}

$lines = preg_split('~\r?\n~', $text);
if ($lines === false) {
return $text;
}

$result = array_shift($lines);
foreach ($lines as $line) {
$result .= $line === '' ? rtrim($this->continuationLinePrefix) : $this->continuationLinePrefix . $line;
}

return $result;
}

private function printTagValue(PhpDocTagValueNode $node): string
{
// only nodes that contain another node are handled here
Expand Down Expand Up @@ -590,6 +622,8 @@ private function printArrayFormatPreserving(array $nodes, array $originalNodes,

if ($insertStr === "\n * ") {
$insertStr = sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
// the lexer strips exactly one space after the asterisk, any further indentation stays in the text
$this->continuationLinePrefix = sprintf('%s%s* ', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent);
}

foreach ($diff as $i => $diffElem) {
Expand Down
105 changes: 105 additions & 0 deletions tests/PHPStan/Printer/PrinterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PureUnlessCallableIsImpureTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PureUnlessParameterIsPassedTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
Expand Down Expand Up @@ -2690,6 +2691,88 @@ public function enterNode(Node $node)
*/'),
$addCommentToObjectShapeItemMiddle,
];
$changeMultilineText = new class extends AbstractNodeVisitor {

public function enterNode(Node $node)
{
if ($node instanceof PhpDocTextNode) {
$node->text = str_replace('Foo', 'Bar', $node->text);
}
if ($node instanceof ParamTagValueNode) {
$node->description = str_replace('Foo', 'Bar', $node->description);
}

return $node;
}

};

yield [
self::nowdoc('
/**
* First line Foo
* second line Foo
*
* Third line Foo
*
* @param int $a Foo description
* continues Foo
* @param int $b
*/'),
self::nowdoc('
/**
* First line Bar
* second line Bar
*
* Third line Bar
*
* @param int $a Bar description
* continues Bar
* @param int $b
*/'),
$changeMultilineText,
];

yield [
self::nowdoc('
/**
* First line Foo
* second line Foo
*/'),
self::nowdoc('
/**
* First line Bar
* second line Bar
*/'),
$changeMultilineText,
];

$addMultilineText = new class extends AbstractNodeVisitor {

public function enterNode(Node $node)
{
if ($node instanceof PhpDocNode) {
array_unshift($node->children, new PhpDocTextNode("Added first\nadded second"));
}

return $node;
}

};

yield [
self::nowdoc('
/**
* @param int $a
*/'),
self::nowdoc('
/**
* Added first
* added second
* @param int $a
*/'),
$addMultilineText,
];
}

/**
Expand Down Expand Up @@ -2951,6 +3034,28 @@ public function dataPrintPhpDocNode(): iterable
]),
'/**
* @param int $a
*/',
];
yield [
new PhpDocNode([
new PhpDocTextNode("First line\nsecond line\n\nthird line"),
new PhpDocTextNode(''),
new PhpDocTagNode('@param', new ParamTagValueNode(
new IdentifierTypeNode('int'),
false,
'$a',
"description\n continues",
false,
)),
]),
'/**
* First line
* second line
*
* third line
*
* @param int $a description
* continues
*/',
];
}
Expand Down
Loading