Skip to content

fix: preserve multiline style of the modified returned array - #84

Open
artengin wants to merge 3 commits into
masterfrom
fix-returned-array-item-multiline-formatting
Open

artengin wants to merge 3 commits into
masterfrom
fix-returned-array-item-multiline-formatting

Conversation

@artengin

@artengin artengin commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@coveralls

coveralls commented Sep 7, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 34157484442

Coverage increased (+0.5%) to 98.929%

Details

  • Coverage increased (+0.5%) from the base build.
  • Patch coverage: 25 of 25 lines across 2 files are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 467
Covered Lines: 462
Line Coverage: 98.93%
Coverage Strength: 11.39 hits per line

💛 - Coveralls

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

foreach ($returnNode->expr->items as $item) {
if ($item instanceof ArrayItem && $this->isSameKey($item->key)) {
$item->value = $this->value;
return;

P2 Badge Mark replaced array items as modified

When the requested key already exists, this branch replaces its value without setting the new Modified attribute. Therefore an inline returned array containing more than two items never enters the new pArray() formatting path and remains inline, contrary to the documented rule for modified arrays; mark the containing item or array before returning.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/Printer.php
Comment on lines +72 to +77
if ($this->hasParentOfType($node, PropertyItem::class) || $isMultiline === true) {
return '[' . $this->pCommaSeparatedMultiline($node->items, true) . $this->nl . ']';
}

return parent::pExpr_Array($node);
return ($isMultiline === false)
? '[' . $this->pCommaSeparated($node->items) . ']'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve long array syntax when reprinting

When an item is appended to a returned array written with array(...), pArray() now forces the entire expression through this override, which always emits square brackets. Consequently, a formatting-only update unexpectedly converts long array syntax to short syntax; select delimiters based on the array's kind attribute or delegate that part to the parent printer.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short array syntax is the standard in Laravel projects, so the linter rewrites array(...) to [...] on any file it touches anyway. Preserving long syntax here would only postpone an edit the linter makes regardless, so not supporting it is a deliberate choice.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ffa6c9921

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/Printer.php
Comment thread src/Printer.php
Comment on lines +93 to +96
$firstItem = $origNodes[0] ?? null;
$endPos = $firstItem?->getStartTokenPos() ?? $this->origTokens->findRight($pos, ']');

return str_contains($this->origTokens->getTokenCode($pos, $endPos, 0), "\n");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Inspect the whole original array for multiline formatting

When a one-item array places its first item on the opening line but its closing bracket on a later line, such as return ['first',\n];, this stops scanning at the first item's start and misses the newline after it. Appending a second item does not satisfy count($nodes) > 2, so the array is marked non-multiline and collapsed onto one line despite the documented rule that an already multiline array stays multiline.

Useful? React with 👍 / 👎.

@vitgrams vitgrams Sep 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@artengin I noticed this case as well. I don't think it's critical, though. Looks good for me

    protected function getSortableFields(): array
    {
        return ['id',
        ];
    }

VS

    protected function getSortableFields(): array
    {
        return ['id', 'name'];
    }

@artengin artengin assigned vitgrams and unassigned DenTray Sep 9, 2026
return ['first' => 1, 'second' => 2];
}

protected function getDefaultFilters(): array

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@artengin It might be worth adding some of the test cases below to explicitly confirm/document the existing behavior. Please take a look.

I added these locally, and everything worked as expected for me.

  1. Comments associated with array elements are preserved

Before:

protected function getValidationRules(): array
{
    return [
        // profile
        'name' => 'required|string',
        'email' => 'required|email',

        // address
        'city' => 'nullable|string'
    ];
}

After:

protected function getValidationRules(): array
{
    return [
        // profile
        'name' => 'required|string',
        'email' => 'required|email',
        // address
        'city' => 'nullable|string',
        'zip' => 'nullable|string',
    ];
}
  1. Nested arrays preserve their original formatting

Before:

protected function getUserData(): array
{
    return [
        'name' => 'John',
        'settings' => [
            'locale' => 'en',
            'timezone' => 'UTC',
        ],
    ];
}

After:

protected function getUserData(): array
{
    return [
        'name' => 'John',
        'settings' => [
            'locale' => 'en',
            'timezone' => 'UTC',
        ],
        'roles' => ['admin', 'editor'],
    ];
}
  1. Empty multiline array → append

This is the only case I found where the wasMultiline fallback branch is triggered (findRight(']')), preserving the original multiline array syntax even when adding the first element.

Before:

protected function getExtraFilters(): array
{
    return [
    ];
}

After:

protected function getExtraFilters(): array
{
    return [
        'is_hidden',
    ];
}

@vitgrams

vitgrams commented Sep 15, 2026

Copy link
Copy Markdown

@artengin The solution in its current form works well for me. I have just one suggestion:

Perhaps we could replace Multiline with OriginNode, which is supported by the parent printer out of the box. Please take a look at this alternative approach and let me know what you think. The implementation could then look something like this:

    protected function pArray(
        array $nodes,
        array $origNodes,
        int &$pos,
        int $indentAdjustment,
        string $parentNodeClass,
        string $subNodeName,
        ?int $fixup,
    ): ?string {
        $hasModifiedItem = ($parentNodeClass === Array_::class && $subNodeName === 'items')
            && $this->hasModifiedItem($nodes);

        return ($hasModifiedItem && !$this->isMultiline($origNodes))
            ? null
            : parent::pArray($nodes, $origNodes, $pos, $indentAdjustment, $parentNodeClass, $subNodeName, $fixup);
    }


    protected function pExpr_Array(Array_ $node): string
    {
        if ($this->hasParentOfType($node, PropertyItem::class) || $this->shouldBeMultiline($node)) {
            return '[' . $this->pCommaSeparatedMultiline($node->items, true) . $this->nl . ']';
        }

        return parent::pExpr_Array($node);
    }

    protected function shouldBeMultiline(Array_ $node): bool
    {
        return !empty($this->origTokens)
            && $this->hasModifiedItem($node->items)
            && (count($node->items) > 2 || $this->wasMultiline($node->getAttribute(StatementAttributeEnum::OrigNode->value)));
    }
    
    protected function wasMultiline(?Array_ $origNode): bool
    {
        if (empty($origNode)) {
            return false;
        }

        $startPos = $origNode->getStartTokenPos();
        $endPos = Arr::get($origNode->items, 0)?->getStartTokenPos() ?? $origNode->getEndTokenPos();

        return str_contains($this->origTokens->getTokenCode($startPos, $endPos, 0), "\n");
    }

The main differences are:

  • pArray no longer writes to the AST or decides the style — it only selects the path. If the original elements are already separated by line breaks (isMultiline($origNodes) from php-parser), it delegates to parent::pArray, which handles writing each item on a new line while preserving the interior formatting (blank lines, standalone comments). The interception (return null → fallback) remains only for edge cases: 0–1 items and single-line arrays.
  • pExpr_Array now determines the style itself via shouldBeMultiline: the Modified marker on items + (count > 2 or wasMultiline(origNode)). The === false branch with pCommaSeparated was removed — single-line arrays are now printed by parent::pExpr_Array, which preserves item comments and respects kind (array() vs []).
  • wasMultiline now works from origNode with the exact boundary from getEndTokenPos(). This fixes the findRight(']') issue that could go beyond the array boundary for legacy array().
  • findModifiedArray, which previously hopped through Parent, was replaced with the simpler boolean hasModifiedItem (array_any, as in AbstractNodeVisitor).

@vitgrams vitgrams assigned artengin and unassigned vitgrams Sep 15, 2026
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.

4 participants