Printer drops the `*` prefix on continuation lines of a changed multi-line PhpDocTextNode

Open
#317 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
68/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
php
Domain
tooling

Research direction

Start with PhpDocParser::parseText(), Printer::print(), printNodeFormatPreserving(), and printArrayFormatPreserving(), using the reproduction in the issue to trace how continuation-line prefixes are lost. Ensure both printer paths preserve the asterisk indentation for changed multi-line text and tag values, matching the expected output shown for each path.

Written by the indexing model from the issue text.

Description

Printer::printFormatPreserving() and Printer::print() both emit a multi-line PhpDocTextNode verbatim. PhpDocParser::parseText() joins continuation lines with a bare \n and strips the * prefix, so any text node with more than one line prints as invalid docblock formatting as soon as it is not reused from the original tokens.

Reproduction

phpdoc-parser 2.3.5, PHP 8.5.

<?php declare(strict_types = 1);

use PHPStan\PhpDocParser\Ast\AbstractNodeVisitor;
use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeTraverser;
use PHPStan\PhpDocParser\Ast\NodeVisitor\CloningVisitor;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
use PHPStan\PhpDocParser\Lexer\Lexer;
use PHPStan\PhpDocParser\Parser\ConstExprParser;
use PHPStan\PhpDocParser\Parser\PhpDocParser;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use PHPStan\PhpDocParser\Parser\TypeParser;
use PHPStan\PhpDocParser\ParserConfig;
use PHPStan\PhpDocParser\Printer\Printer;

require __DIR__ . '/vendor/autoload.php';

$config = new ParserConfig(['lines' => true, 'indexes' => true]);
$lexer = new Lexer($config);
$constExprParser = new ConstExprParser($config);
$parser = new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser);

$doc = <<<'DOC'
/**
     * First line Foo
     * second line Foo
     *
     * @param int $a
     */
DOC;

$tokens = $lexer->tokenize($doc);
$ast = $parser->parse(new TokenIterator($tokens));

$visitor = new class extends AbstractNodeVisitor {
    public function enterNode(Node $node): ?Node
    {
        if ($node instanceof PhpDocTextNode) {
            $node->text = str_replace('Foo', 'Bar', $node->text);
        }
        return null;
    }
};

[$newAst] = (new NodeTraverser([new CloningVisitor(), $visitor]))->traverse([$ast]);

$printer = new Printer();
echo $printer->printFormatPreserving($newAst, $ast, new TokenIterator($tokens)), "\n\n";
echo $printer->print($newAst), "\n";
Actual output
/**
     * First line Bar
second line Bar
     *
     * @param int $a
     */

/**
 * First line Bar
second line Bar
 *
 * @param int $a
 */
Expected output
/**
     * First line Bar
     * second line Bar
     *
     * @param int $a
     */

/**
 * First line Bar
 * second line Bar
 *
 * @param int $a
 */
Cause
  • PhpDocParser::parseText() appends $tokens->getDetectedNewline() ?? "\n" between continuation lines and drops the * prefix, so PhpDocTextNode::$text is "First line Foo\nsecond line Foo".
  • Printer::print() returns $node->text for a PhpDocTextNode as is. printNodeFormatPreserving() falls back to print() when a non-node sub-node such as $text changed, so the format-preserving path has the same result.
  • printArrayFormatPreserving() already computes $beforeAsteriskIndent and $afterAsteriskIndent via isMultiline() for inserted nodes, but does not apply them to newlines inside a printed child.

The same applies to multi-line description strings in GenericTagValueNode, ParamTagValueNode, DeprecatedTagValueNode and the other tag value nodes, which also hold bare \n.

Suggested fix

Re-insert the line prefix at every newline inside a printed child. In print() the prefix is "\n * ". In the format-preserving path it is newline . $beforeAsteriskIndent . '*' . $afterAsteriskIndent, which printArrayFormatPreserving() already has in scope. PR follows.

Claude

Dominant language
PHP
Stars
1.5k
Forks
78
Avg merge
9h 5m
Merged PRs (30d)
5

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from phpstan/phpdoc-parser

All issues in phpstan/phpdoc-parser

Similar issues

More PHP issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.