Skip to content

Preserve blank lines between statements in format-preserving pretty printer - #1169

Open
Eljees wants to merge 1 commit into
nikic:masterfrom
Eljees:fix/preserve-blank-lines-format-preserving-pstmts
Open

Eljees wants to merge 1 commit into
nikic:masterfrom
Eljees:fix/preserve-blank-lines-format-preserving-pstmts

Conversation

@Eljees

@Eljees Eljees commented Sep 25, 2026

Copy link
Copy Markdown

Fixes #803.

pStmts() is used as the plain (non-format-preserving) fallback printer for a
statement list whenever the containing node doesn't have a usable origNode
attribute — for example when an unrelated ancestor node was replaced, as in
the reproduction from #803 (wrapping an existing class into
return new class extends X { ... };). It always inserted a single newline
between statements, even when $this->origTokens was available and the
individual statements themselves still carried valid origNode attributes
(so their own bodies were still being format-preserved). This silently
dropped any blank line that used to separate two such statements.

Reproduction (before this patch)

$code = <<<'EOC'
<?php

class Foo extends Migration
{
    public function up()
    {
        //
    }

    public function down()
    {
        //
    }
}

EOC;
// ... parse $code with token tracking, clone via CloningVisitor ...
$newStmts[0] = new Return_(new New_($classNode)); // wrap existing class

echo (new Standard)->printFormatPreserving($newStmts, $oldStmts, $oldTokens);

Output before the fix (blank line between up() and down() lost):

<?php

return new class extends Migration
{
    public function up()
    {
        //
    }
    public function down()
    {
        //
    }
};

Fix

pStmts() now checks, for each pair of consecutive statements that both
still carry a valid origNode attribute, whether the original source text
between them (via $this->origTokens) is pure whitespace containing a blank
line — and if so, re-inserts it. If anything else used to sit between them
(another statement that was removed, inline HTML, a comment), the existing
single-newline behavior is kept, since a raw line-number gap alone doesn't
tell us a blank line was actually there (this distinction is covered by the
existing inlineHtml.test fixtures, which continue to pass unchanged).

Added test/code/formatPreservation/blankLineBetweenMethods.test,
reproducing the exact scenario from #803.

Testing

vendor/bin/phpunit — 1923 tests, 2797 assertions, all passing (previously
1922/1922 on master; the added test failed for the documented reason
before the fix and passes after it). No other test's expected output
changed.

Signed-off-by: Eljees 3.14hell@gmail.com

…rinter

pStmts() always inserted a single newline between statements, even when
$this->origTokens was available. This method is used as a plain,
non-preserving fallback whenever a containing node lacks an 'origNode'
attribute (e.g. because an unrelated ancestor node was replaced), so any
blank line that used to separate two still-unmodified statements was
silently dropped, even though each statement's own content was still
being format-preserved individually.

This is exactly the case reported in nikic#803: wrapping an existing class
declaration into `return new class extends X { ... };` causes the
blank line between the class's own methods to disappear, because the
class body ends up printed via pStmts() instead of going through the
list format-preservation logic in pArray().

Fix pStmts() to check, for each pair of consecutive statements that
still carry a valid 'origNode' attribute, whether the original source
between them was pure whitespace containing a blank line -- and if so,
re-insert it. If anything else (another statement, inline HTML, a
comment) used to sit between them, the old single-newline behavior is
kept, since a raw line-number gap alone doesn't tell us a blank line
was actually there.

Adds a regression test reproducing the exact scenario from nikic#803.

Fixes nikic#803

Signed-off-by: Eljees <3.14hell@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Printing anonymous classes removes newlines between methods

1 participant