Skip to content

PEAR.WhiteSpace.ObjectOperatorIndent expects wrong indentation when there are multiple chained calls in the same statement #1154

Closed

Description

@Daimona

NOTE: Copied over from squizlabs/PHP_CodeSniffer#3718 with a reduced example and some more information.

Describe the bug

If you have multiple chained calls within the same statement (for example, an array where multiple elements consist of chained method calls), but using different indentation, then the PEAR.WhiteSpace.ObjectOperatorIndent sniff will expect an incorrect amount of indentation.

Code sample

<?php

namespace Example;

function toJson(): array {
    return [
        'brand' => [
            'title' => $item->product()->brand(),
        ],
        'charges' => $item->charges()
            ->toArray(),
    ];
}

Custom ruleset

<?xml version="1.0"?>
<ruleset name="My Custom Standard">
  <rule ref="PEAR.WhiteSpace.ObjectOperatorIndent" />
</ruleset>

To reproduce

Steps to reproduce the behavior:

  1. Create a file called test.php with the code sample above
  2. Run phpcs test.php
  3. See error message displayed
 11 | ERROR | [x] Object operator not indented correctly; expected 16 spaces but found 12
    |       |     (PEAR.WhiteSpace.ObjectOperatorIndent.Incorrect)
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY

Expected behavior

No issue emitted for the above code.

Versions (please complete the following information)

Operating System Ubuntu 24.04
PHP version 8.3
PHP_CodeSniffer version master
Standard custom
Install type composer

Additional context

The sniff does:

$end = $phpcsFile->findEndOfStatement($stackPtr);
$next = $phpcsFile->findNext($this->targets, ($stackPtr + 1), $end);

But the whole array literal in the above example is just one statement. So, when analyzing the chained calls at line 8, $next will find the chained calls at line 10-11 and expect them to be indented like the ones at line 8.

Please confirm

  • I have searched the issue list and am not opening a duplicate issue.
  • I have read the Contribution Guidelines and this is not a support question.
  • I confirm that this bug is a bug in PHP_CodeSniffer and not in one of the external standards.
  • I have verified the issue still exists in the master branch of PHP_CodeSniffer.

Activity

  1. rodrigoprimo commented on Jul 1, 2025

    @rodrigoprimo
    Contributor

    Thanks for copying this issue from the old repository, @Daimona. Also, thanks to @lucassabreu for the original report.

    I was able to reproduce it. Here is a simplified code snippet that produces the same bug:

    $array = [
        [
            $item->first()->second(),
        ],
        $item->first()
            ->second(),
    ];

    But the whole array literal in the above example is just one statement. So, when analyzing the chained calls at line 8, $next will find the chained calls at line 10-11 and expect them to be indented like the ones at line 8.

    I believe the problem is actually in another part of the code. The call to File::findEndOfStatement() correctly identifies the comma as the end of the statement and assigns its position to $end. $end is used to limit the scope of the search performed by File::fileNext(), and thus it does not include the chained calls on lines 10-11.

    Below, the sniff loops over all the object operators in a given chained call using a while loop:

    Here is the code where $next is reassigned to the next object operator:

    $next = $phpcsFile->findNext(
    $this->targets,
    ($next + 1),
    null,
    false,
    null,
    true
    );

    I believe the problem is that the sniff is relying on the $local parameter of findNext() to limit the scope of the search, and $local only considers a semicolon as the end of a statement:

    if ($local === true && $this->tokens[$i]['code'] === T_SEMICOLON) {
    break;
    }

    Different chained calls are not necessarily separated by a semicolon (as in the example that was provided). This means that the sniff currently considers the object operators on the second chained method call as part of the first chained method call and checks their indentation using the indentation of the first element on the first chained call as its base.

    Unless I'm missing something, this is a relatively easy fix to make, and I will prepare a PR for it.

  2. self-assigned this
    on Jul 1, 2025
  3. added this to the 3.13.3 milestone on Jul 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions