Skip to content

Commit d084f57

Browse files
tmdkclaude
andcommitted
Fix duplicate star on continuation lines starting with * in tag descriptions
When a tag description contains continuation lines beginning with *, the * was duplicated in the parsed output. This affected block-style @param descriptions (e.g. WordPress-style docblocks with @type lines and star- prefixed list items). Root cause: tokenizeLine() split TOKEN_PHPDOC_EOL tokens with trailing whitespace into a bare EOL + TOKEN_HORIZONTAL_WS pair, placing * in both token values. When PHPStan rolled back past such a token on encountering a tag-like token (@type), joinUntil() concatenated both raw values, doubling the star. Fix: prefix every continuation line with "* " before tokenizing. The lexer always consumes the inserted "* " as part of TOKEN_PHPDOC_EOL, leaving original indentation as a separate subsequent token. An unconditional trim(..., "* \t") on every EOL token value strips the inserted "* " back out. The manual split into TOKEN_HORIZONTAL_WS is no longer needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9ce38cc commit d084f57

2 files changed

Lines changed: 46 additions & 14 deletions

File tree

src/DocBlock/Tags/Factory/AbstractPHPStanFactory.php

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function __construct(PHPStanFactory ...$factories)
6161
public function create(string $tagLine, ?TypeContext $context = null): Tag
6262
{
6363
try {
64-
$tokens = $this->tokenizeLine($tagLine . "\n");
64+
$tokens = $this->tokenizeLine($tagLine);
6565
$ast = $this->parser->parseTag($tokens);
6666
if (property_exists($ast->value, 'description') === true) {
6767
$ast->value->setAttribute(
@@ -104,21 +104,15 @@ public function create(string $tagLine, ?TypeContext $context = null): Tag
104104
*/
105105
private function tokenizeLine(string $tagLine): TokenIterator
106106
{
107-
$tokens = $this->lexer->tokenize($tagLine);
107+
// Prefix continuation lines with "* ", which is consumed by the phpstan parser as TOKEN_PHPDOC_EOL.
108+
$tagLine = str_replace("\n", "\n* ", $tagLine);
109+
$tokens = $this->lexer->tokenize($tagLine . "\n");
108110
$fixed = [];
109111
foreach ($tokens as $token) {
110-
if (($token[1] === Lexer::TOKEN_PHPDOC_EOL) && rtrim($token[0], " \t") !== $token[0]) {
111-
$fixed[] = [
112-
rtrim($token[Lexer::VALUE_OFFSET], " \t"),
113-
Lexer::TOKEN_PHPDOC_EOL,
114-
$token[2] ?? 0,
115-
];
116-
$fixed[] = [
117-
ltrim($token[Lexer::VALUE_OFFSET], "\n\r"),
118-
Lexer::TOKEN_HORIZONTAL_WS,
119-
($token[2] ?? 0) + 1,
120-
];
121-
continue;
112+
if ($token[Lexer::TYPE_OFFSET] === Lexer::TOKEN_PHPDOC_EOL) {
113+
// Strip "* " prefix (and other horizontal whitespace) again so it doesn't and up in the
114+
// description when we joinUntil() in create().
115+
$token[Lexer::VALUE_OFFSET] = trim($token[Lexer::VALUE_OFFSET], "* \t");
122116
}
123117

124118
$fixed[] = $token;

tests/integration/InterpretingDocBlocksTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,4 +489,42 @@ public function testParamTagDescriptionIsCorrectly(): void
489489
self::assertSame('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas varius, tellus in cursus
490490
dictum, justo odio sagittis velit, id iaculis mi dui id nisi.', (string) $paramTags->getDescription());
491491
}
492+
493+
public function testParamBlockDescriptionPreservesStarContinuationLines(): void
494+
{
495+
$docComment = <<<DOC
496+
/**
497+
* @param array \$foo {
498+
* Description of foo.
499+
*
500+
* @type string \$bar Description of bar with
501+
* * a list
502+
* * spanning *multiple* lines
503+
* }
504+
*/
505+
DOC;
506+
507+
$factory = DocBlockFactory::createInstance();
508+
$docblock = $factory->create($docComment);
509+
510+
self::assertEquals(
511+
[
512+
new Param(
513+
'foo',
514+
new Array_(),
515+
false,
516+
new Description(
517+
'{' . "\n" .
518+
' Description of foo.' . "\n" .
519+
"\n" .
520+
' @type string $bar Description of bar with' . "\n" .
521+
' * a list' . "\n" .
522+
' * spanning *multiple* lines' . "\n" .
523+
'}'
524+
),
525+
),
526+
],
527+
$docblock->getTags()
528+
);
529+
}
492530
}

0 commit comments

Comments
 (0)