Skip to content

Commit 6b23b73

Browse files
committed
Printer: indent a new tag like the other tags
When the printer added a child to a PHPDoc, it used the largest indentation after the asterisk in the PHPDoc. The next lines of a multi-line description often have a larger indentation, so the new tag got it too. Now the printer uses the indentation of the line where the last existing child starts. Amp-Thread-ID: https://ampcode.com/threads/T-01a17930-0285-73c5-a957-4325c77aa1b2
1 parent 5e32d48 commit 6b23b73

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

‎src/Printer/Printer.php‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
use function implode;
7777
use function in_array;
7878
use function is_array;
79+
use function preg_match;
7980
use function preg_match_all;
8081
use function preg_replace_callback;
8182
use function sprintf;
@@ -608,6 +609,11 @@ private function printArrayFormatPreserving(array $nodes, array $originalNodes,
608609
[$isMultiline, $beforeAsteriskIndent, $afterAsteriskIndent] = $this->isMultiline($tokenIndex, $originalNodes, $originalTokens);
609610

610611
if ($insertStr === "\n * ") {
612+
$lastChildIndent = $this->detectLastChildIndent($originalNodes, $originalTokens);
613+
if ($lastChildIndent !== null) {
614+
[$beforeAsteriskIndent, $afterAsteriskIndent] = $lastChildIndent;
615+
}
616+
611617
$insertStr = sprintf('%s%s*%s', $originalTokens->getDetectedNewline() ?? "\n", $beforeAsteriskIndent, $afterAsteriskIndent);
612618
}
613619

@@ -869,6 +875,39 @@ private function isMultiline(int $initialIndex, array $nodes, TokenIterator $ori
869875
return [$isMultiline, $before, $after];
870876
}
871877

878+
/**
879+
* Indentation before and after the asterisk on the line where the last child of a PHPDoc starts.
880+
* Other lines, e.g. the next lines of a multi-line description, can have a different indentation.
881+
*
882+
* @param Node[] $originalNodes
883+
* @return array{string, string}|null
884+
*/
885+
private function detectLastChildIndent(array $originalNodes, TokenIterator $originalTokens): ?array
886+
{
887+
$tokens = $originalTokens->getTokens();
888+
for ($i = count($originalNodes) - 1; $i >= 0; $i--) {
889+
$index = $originalNodes[$i]->getAttribute(Attribute::START_INDEX) - 1;
890+
$lineStart = '';
891+
while (isset($tokens[$index]) && $tokens[$index][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) {
892+
$lineStart = $tokens[$index][Lexer::VALUE_OFFSET] . $lineStart;
893+
$index--;
894+
}
895+
896+
if (!isset($tokens[$index]) || $tokens[$index][Lexer::TYPE_OFFSET] !== Lexer::TOKEN_PHPDOC_EOL) {
897+
continue;
898+
}
899+
900+
$lineStart = $tokens[$index][Lexer::VALUE_OFFSET] . $lineStart;
901+
if (preg_match('~\n(?<before>[\x09\x20]*)\*(?<after>[\x09\x20]*)$~', $lineStart, $match) !== 1) {
902+
continue;
903+
}
904+
905+
return [$match['before'], $match['after']];
906+
}
907+
908+
return null;
909+
}
910+
872911
private function printNodeFormatPreserving(Node $node, TokenIterator $originalTokens): string
873912
{
874913
/** @var Node|null $originalNode */

‎tests/PHPStan/Printer/PrinterTest.php‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use PHPStan\PhpDocParser\Ast\PhpDoc\PureUnlessParameterIsPassedTagValueNode;
3131
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
3232
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
33+
use PHPStan\PhpDocParser\Ast\PhpDoc\ThrowsTagValueNode;
3334
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode;
3435
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
3536
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
@@ -195,6 +196,51 @@ public function enterNode(Node $node)
195196
$removeFirst,
196197
];
197198

199+
$addThrows = new class extends AbstractNodeVisitor {
200+
201+
public function enterNode(Node $node)
202+
{
203+
if ($node instanceof PhpDocNode) {
204+
$node->children[] = new PhpDocTagNode('@throws', new ThrowsTagValueNode(new IdentifierTypeNode('Exception'), ''));
205+
}
206+
207+
return $node;
208+
}
209+
210+
};
211+
212+
yield [
213+
self::nowdoc('
214+
/**
215+
* @param int $foo multi
216+
* line description
217+
*/'),
218+
self::nowdoc('
219+
/**
220+
* @param int $foo multi
221+
* line description
222+
* @throws Exception
223+
*/'),
224+
$addThrows,
225+
];
226+
227+
yield [
228+
self::nowdoc('
229+
/**
230+
* @param int $foo multi
231+
* line description
232+
* @return void
233+
*/'),
234+
self::nowdoc('
235+
/**
236+
* @param int $foo multi
237+
* line description
238+
* @return void
239+
* @throws Exception
240+
*/'),
241+
$addThrows,
242+
];
243+
198244
$removeLast = new class extends AbstractNodeVisitor {
199245

200246
public function enterNode(Node $node)

0 commit comments

Comments
 (0)