Skip to content

feat: hug a single heredoc argument and attach the comma to its closing marker - #2555

Open
mreiden wants to merge 1 commit into
prettier:mainfrom
mreiden:feat/heredoc-arguments
Open

mreiden wants to merge 1 commit into
prettier:mainfrom
mreiden:feat/heredoc-arguments

Conversation

@mreiden

@mreiden mreiden commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Problem

A call with one heredoc or nowdoc argument breaks the argument list and puts the trailing comma on a line of its own:

$this->addSql(
    <<<'SQL'
    CREATE TABLE messenger_messages (...)
    SQL
    ,
);

Both habits date from PHP < 7.3, where the closing marker had to be alone on its line, so SQL, and SQL); were parse errors. Flexible heredocs (7.3) lifted that, and PER-CS describes a different layout:

  • 4.7: "A single argument being split across multiple lines (as might be the case with a closure or array) does not constitute splitting the argument list itself" — the carve-out that lets a lone closure or array hug the call.

  • 10 shows the marker followed directly by the comma:

    var_dump(
        'foo',
        <<<'COMPLIANT'
            This
            is
            a
            compliant
            parameter
            COMPLIANT,
        'bar',
    );

Doctrine migrations and anything else that passes SQL as a heredoc get reformatted into the three-line SQL / , / ); stutter on every save.

Change (PHP ≥ 7.3 only; below 7.3 the output is unchanged)

  • A lone heredoc/nowdoc argument hugs the call, as a multi-line quoted string already does (isStringOnItsOwnLine), with the body and marker indented once past the statement per section 10:

    $this->addSql(<<<'SQL'
        CREATE TABLE messenger_messages (...)
        SQL);
  • In a split argument list or an array the comma follows the marker: shouldPrintHardlineBeforeTrailingComma() returns false for flexible heredocs, and the line break docShouldHaveTrailingNewline() used to add after a non-last heredoc argument, for part, static variable or array key is dropped for the same reason.

  • Kept in every version: a heredoc used as a . operand still puts the operator on its own line (the bin printer lays it out; without the break it printed EOF. $var), and the : of case <<<'X' … X stays on the next line because php-parser does not accept MARKER:, although PHP does (check_heredoc_indentation_level raises on the next heredoc).

The heredoc body is untouched: php-parser strips the closing marker's indentation as PHP does, and the printer only decides where the marker lands, so the string value is identical before and after.

Tests

New tests/heredoc-arguments/ (run at 8.4 and 7.2) covers a lone heredoc and nowdoc argument, new, split lists with the heredoc first and in the middle, member chains, array entries and a concatenation operand. Nine existing snapshots change across call, new, echo, comments, encapsed, nowdoc and trailing_commas, all of them the two layout changes above. Full suite passes with AST_COMPARE=1 (1186 tests); outputs pass php -l and are idempotent.

🤖 Generated with Claude Code

A call or `new` with one heredoc or nowdoc argument broke the argument
list and put the trailing comma on a line of its own:

    $this->addSql(
        <<<'SQL'
        CREATE TABLE ...
        SQL
        ,
    );

Both come from PHP < 7.3, where the closing marker had to be alone on
its line. Flexible heredocs lifted that, and PER-CS says otherwise:
4.7 lets a single multi-line argument stay on the call's line (as a
closure or array does), and the 10 example ends the marker with the
comma (`COMPLIANT,`).

From PHP 7.3 on:

- a lone heredoc/nowdoc argument hugs the call, like a multi-line
  quoted string already did, with the body and marker indented once
  past the statement: `$this->addSql(<<<'SQL' ... SQL);`
- in a split list or an array the comma follows the marker: `SQL,`
- the line break that followed a non-last heredoc argument, a `for`
  part, a static variable or an array key is gone for the same reason.

Kept in every version: the `.` of a concatenation stays on its own
line (the `bin` printer lays it out), and the `:` of a `case` does too,
because php-parser does not accept `MARKER:` although PHP does.
Below 7.3 nothing changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@benjaminkincses

Copy link
Copy Markdown
Contributor

logic looks right. checked the 7.3 gates and every call site passes options, fixture covers hug, multi-arg, chains, arrays, concat. one thing missing: no case covers the case carve-out. that : exception only exists because php-parser chokes on it, worth a fixture pinning it before this lands.

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.

2 participants