squizlabs / PHP_CodeSniffer

PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.
BSD 3-Clause "New" or "Revised" License
10.66k stars 1.48k forks source link

Generic.WhiteSpace.ScopeIndent false positive with nested `match`-es #3875

Closed andrey-yantsen closed 3 months ago

andrey-yantsen commented 1 year ago

Describe the bug

The ScopeIndent sniff suggests a quite strange indentation when processing multi-nested match-statements

Code sample

<?php

echo match (1) {
    0 => match (2) {
        2 => match (3) {
            3 => 3,
            default => -1,
        },
    },
    1 => match (2) {
        1 => match (3) {
            3 => 3,
            default => -1,
        },
        2 => match (3) {
            3 => 3,
            default => -1,
        },
    },
};

How that code will look if formatted as suggested

<?php

echo match (1) {
    0 => match (2) {
        2 => match (3) {
            3 => 3,
            default => -1,
        },
    },
    1 => match (2) {
        1 => match (3) {
            3 => 3,
            default => -1,
        },
            2 => match (3) {
                3 => 3,
                default => -1,
            },
    },
};

Custom ruleset

N/A — reproducible with PSR12

To reproduce

Steps to reproduce the behavior:

  1. Create a file called test.php with the code sample above
  2. Run phpcs -s --standard=psr12 test.php
  3. See errors displayed
    ------------------------------------------------------------------------------------------------------------------------------
    FOUND 4 ERRORS AFFECTING 4 LINES
    ------------------------------------------------------------------------------------------------------------------------------
    15 | ERROR | [x] Line indented incorrectly; expected at least 12 spaces, found 8
    |       |     (Generic.WhiteSpace.ScopeIndent.Incorrect)
    16 | ERROR | [x] Line indented incorrectly; expected at least 16 spaces, found 12
    |       |     (Generic.WhiteSpace.ScopeIndent.Incorrect)
    17 | ERROR | [x] Line indented incorrectly; expected at least 16 spaces, found 12
    |       |     (Generic.WhiteSpace.ScopeIndent.Incorrect)
    18 | ERROR | [x] Line indented incorrectly; expected 12 spaces, found 8 (Generic.WhiteSpace.ScopeIndent.IncorrectExact)
    ------------------------------------------------------------------------------------------------------------------------------
    PHPCBF CAN FIX THE 4 MARKED SNIFF VIOLATIONS AUTOMATICALLY
    ------------------------------------------------------------------------------------------------------------------------------

Expected behavior

No indentation errors.

Versions (please complete the following information)

Operating System macOS 12.5
PHP version 8.0
PHP_CodeSniffer version 3.7.2
Standard PSR2
Install type Local composer

Additional context

none

Please confirm:

jrfnl commented 1 year ago

Tested & confirmed.

DannyvdSluijs commented 11 months ago

In trying to provide more information to this bug, taking the file and the suggested command I can reproduce as well (as @jrfnl had already pointed out.) I also ran the command with the $debug enabled which returns the full reasoning about how the indenting level was computed. The full output is the code block in the bottom but I wanted to highlight the following from the output which seems to give some hints:

Close scope (T_MATCH) on line 14
        => removed open scope 81 (T_MATCH) on line 11
        * first token is 77 (T_LNUMBER) on line 11 *
        => indent set to 8 by token 107 (T_CLOSE_CURLY_BRACKET)
Close match on line 14
        * token is inside condition 68 (T_MATCH) on line 10 *
        * using condition *
        * previous token is T_MATCH on line 10 *
        * first token on line 10 is 64 (T_LNUMBER) *
        * amended previous is T_CLOSE_CURLY_BRACKET on line 8 *
        * amended first token is 56 (T_CLOSE_CURLY_BRACKET) on line 8 *
        * first token is a scope closer *
        * ignoring scope closer *
        => indent set to 12 by token 56 (T_CLOSE_CURLY_BRACKET)

Full debug output:

Start with token 0 on line 1 with indent 0
Opening parenthesis found on line 3
        => disabling exact indent checking until 8 (T_CLOSE_PARENTHESIS)
Closing parenthesis found on line 3
         * ignoring single-line definition *
Open scope (T_MATCH) on line 3
        => added open scope 148 (T_CLOSE_CURLY_BRACKET) on line 20, pointing to condition 4 (T_MATCH) on line 3
        => indent set to 4 by token 10 (T_OPEN_CURLY_BRACKET)
Opening parenthesis found on line 4
        => disabling exact indent checking until 21 (T_CLOSE_PARENTHESIS)
Closing parenthesis found on line 4
         * ignoring single-line definition *
Open scope (T_MATCH) on line 4
        => added open scope 60 (T_CLOSE_CURLY_BRACKET) on line 9, pointing to condition 17 (T_MATCH) on line 4
        => indent set to 8 by token 23 (T_OPEN_CURLY_BRACKET)
Opening parenthesis found on line 5
        => disabling exact indent checking until 34 (T_CLOSE_PARENTHESIS)
Closing parenthesis found on line 5
         * ignoring single-line definition *
Open scope (T_MATCH) on line 5
        => added open scope 56 (T_CLOSE_CURLY_BRACKET) on line 8, pointing to condition 30 (T_MATCH) on line 5
        => indent set to 12 by token 36 (T_OPEN_CURLY_BRACKET)
Close scope (T_MATCH) on line 8
        => removed open scope 30 (T_MATCH) on line 5
        * first token is 26 (T_LNUMBER) on line 5 *
        => indent set to 8 by token 56 (T_CLOSE_CURLY_BRACKET)
Close match on line 8
        * token is inside condition 17 (T_MATCH) on line 4 *
        * using condition *
        * previous token is T_MATCH on line 4 *
        * first token on line 4 is 13 (T_LNUMBER) *
        => indent set to 8 by token 13 (T_LNUMBER)
Close scope (T_MATCH) on line 9
        => removed open scope 17 (T_MATCH) on line 4
        * first token is 13 (T_LNUMBER) on line 4 *
        => indent set to 4 by token 60 (T_CLOSE_CURLY_BRACKET)
Close match on line 9
        * token is inside condition 4 (T_MATCH) on line 3 *
        * using condition *
        * previous token is T_MATCH on line 3 *
        * first token on line 3 is 2 (T_ECHO) *
        => indent set to 4 by token 2 (T_ECHO)
Opening parenthesis found on line 10
        => disabling exact indent checking until 72 (T_CLOSE_PARENTHESIS)
Closing parenthesis found on line 10
         * ignoring single-line definition *
Open scope (T_MATCH) on line 10
        => added open scope 145 (T_CLOSE_CURLY_BRACKET) on line 19, pointing to condition 68 (T_MATCH) on line 10
        => indent set to 8 by token 74 (T_OPEN_CURLY_BRACKET)
Opening parenthesis found on line 11
        => disabling exact indent checking until 85 (T_CLOSE_PARENTHESIS)
Closing parenthesis found on line 11
         * ignoring single-line definition *
Open scope (T_MATCH) on line 11
        => added open scope 107 (T_CLOSE_CURLY_BRACKET) on line 14, pointing to condition 81 (T_MATCH) on line 11
        => indent set to 12 by token 87 (T_OPEN_CURLY_BRACKET)
Close scope (T_MATCH) on line 14
        => removed open scope 81 (T_MATCH) on line 11
        * first token is 77 (T_LNUMBER) on line 11 *
        => indent set to 8 by token 107 (T_CLOSE_CURLY_BRACKET)
Close match on line 14
        * token is inside condition 68 (T_MATCH) on line 10 *
        * using condition *
        * previous token is T_MATCH on line 10 *
        * first token on line 10 is 64 (T_LNUMBER) *
        * amended previous is T_CLOSE_CURLY_BRACKET on line 8 *
        * amended first token is 56 (T_CLOSE_CURLY_BRACKET) on line 8 *
        * first token is a scope closer *
        * ignoring scope closer *
        => indent set to 12 by token 56 (T_CLOSE_CURLY_BRACKET)
[Line 15] Line indented incorrectly; expected at least 12 spaces, found 8
        => add adjustment of 4 for token 111 (T_LNUMBER) on line 15
Opening parenthesis found on line 15
        => disabling exact indent checking until 119 (T_CLOSE_PARENTHESIS)
Closing parenthesis found on line 15
         * ignoring single-line definition *
Open scope (T_MATCH) on line 15
        => added open scope 141 (T_CLOSE_CURLY_BRACKET) on line 18, pointing to condition 115 (T_MATCH) on line 15
        => indent set to 16 by token 121 (T_OPEN_CURLY_BRACKET)
Indent adjusted to 16 for T_LNUMBER on line 16
        => add adjustment of 4 for token 124 (T_LNUMBER) on line 16
[Line 16] Line indented incorrectly; expected at least 16 spaces, found 12
        => add adjustment of 4 for token 124 (T_LNUMBER) on line 16
Indent adjusted to 16 for T_MATCH_DEFAULT on line 17
        => add adjustment of 4 for token 132 (T_MATCH_DEFAULT) on line 17
[Line 17] Line indented incorrectly; expected at least 16 spaces, found 12
        => add adjustment of 4 for token 132 (T_MATCH_DEFAULT) on line 17
Close scope (T_MATCH) on line 18
        => removed open scope 115 (T_MATCH) on line 15
        * first token is 111 (T_LNUMBER) on line 15 *
        => indent set to 12 by token 141 (T_CLOSE_CURLY_BRACKET)
[Line 18] Line indented incorrectly; expected 12 spaces, found 8
        => add adjustment of 4 for token 141 (T_CLOSE_CURLY_BRACKET) on line 18
Close match on line 18
        * token is inside condition 68 (T_MATCH) on line 10 *
        * using condition *
        * previous token is T_MATCH on line 10 *
        * first token on line 10 is 64 (T_LNUMBER) *
        * amended previous is T_CLOSE_CURLY_BRACKET on line 8 *
        * amended first token is 56 (T_CLOSE_CURLY_BRACKET) on line 8 *
        * first token is a scope closer *
        * ignoring scope closer *
        => indent set to 12 by token 56 (T_CLOSE_CURLY_BRACKET)
Close scope (T_MATCH) on line 19
        => removed open scope 68 (T_MATCH) on line 10
        * first token is 64 (T_LNUMBER) on line 10 *
        => indent set to 4 by token 145 (T_CLOSE_CURLY_BRACKET)
Close match on line 19
        * token is inside condition 4 (T_MATCH) on line 3 *
        * using condition *
        * previous token is T_MATCH on line 3 *
        * first token on line 3 is 2 (T_ECHO) *
        => indent set to 4 by token 2 (T_ECHO)
Close scope (T_MATCH) on line 20
        => removed open scope 4 (T_MATCH) on line 3
        * first token is 2 (T_ECHO) on line 3 *
        => indent set to 0 by token 148 (T_CLOSE_CURLY_BRACKET)
Close match on line 20
        * could not find a previous T_EQUAL or T_RETURN token; will use current token *
        * previous token is T_CLOSE_CURLY_BRACKET on line 20 *
        * first token on line 20 is 148 (T_CLOSE_CURLY_BRACKET) *
        * amended previous is T_ECHO on line 3 *
        * amended first token is 2 (T_ECHO) on line 3 *
        => indent set to 0 by token 2 (T_ECHO)