diff --git a/PhpCollective/Sniffs/WhiteSpace/ConsistentIndentSniff.php b/PhpCollective/Sniffs/WhiteSpace/ConsistentIndentSniff.php index 2f6446d..ae230e7 100644 --- a/PhpCollective/Sniffs/WhiteSpace/ConsistentIndentSniff.php +++ b/PhpCollective/Sniffs/WhiteSpace/ConsistentIndentSniff.php @@ -77,6 +77,9 @@ public function process(File $phpcsFile, $stackPtr): void if ($this->isInsideSwitchCase($phpcsFile, $nextToken, $tokens)) { return; } + if ($this->isInsideAnonClass($tokens, $nextToken)) { + return; + } if ($tokens[$nextToken]['code'] === T_COMMENT || $tokens[$nextToken]['code'] === T_DOC_COMMENT_OPEN_TAG) { return; } @@ -482,6 +485,38 @@ protected function isInsideSwitchCase(File $phpcsFile, int $stackPtr, array $tok return false; } + /** + * Check if the current position is inside an anonymous class body. + * + * Anonymous classes are frequently passed as arguments to a multi-line + * function call (e.g. `new Service(new class () extends Base { ... })`). + * In that case the class scope is opened inside an unclosed parenthesis, + * so the body carries one continuation indent level that `getExpectedIndent()` + * (which only counts scope conditions) cannot see. `Generic.WhiteSpace.ScopeIndent` + * does account for it, so flagging here produces a fixer conflict where the two + * sniffs dedent/indent the same lines forever ("FAILED TO FIX"). Defer to + * ScopeIndent for anonymous class bodies, the same way closures are skipped. + * + * @param array> $tokens + * @param int $stackPtr + * + * @return bool + */ + protected function isInsideAnonClass(array $tokens, int $stackPtr): bool + { + if (empty($tokens[$stackPtr]['conditions'])) { + return false; + } + + foreach ($tokens[$stackPtr]['conditions'] as $code) { + if ($code === T_ANON_CLASS) { + return true; + } + } + + return false; + } + /** * Check if the previous line is "complete" (ends with statement terminator or closing brace). * If not, the next line might be a continuation. diff --git a/tests/_data/ConsistentIndent/after.php b/tests/_data/ConsistentIndent/after.php index dd03f2f..6f54790 100644 --- a/tests/_data/ConsistentIndent/after.php +++ b/tests/_data/ConsistentIndent/after.php @@ -108,4 +108,21 @@ public function nullCoalesceShouldNotBeFlagged($params): ?int ?? $params['home_id'] ?? null; } + + public function anonClassAsArgumentShouldNotBeFlagged(array $payload): object + { + return new Service( + new class ($payload) extends Base { + public function __construct(private array $payload) + { + parent::__construct(); + } + + public function defaultProvider(): string + { + return 'codex'; + } + }, + ); + } } diff --git a/tests/_data/ConsistentIndent/before.php b/tests/_data/ConsistentIndent/before.php index c193d8b..49b8c09 100644 --- a/tests/_data/ConsistentIndent/before.php +++ b/tests/_data/ConsistentIndent/before.php @@ -108,4 +108,21 @@ public function nullCoalesceShouldNotBeFlagged($params): ?int ?? $params['home_id'] ?? null; } + + public function anonClassAsArgumentShouldNotBeFlagged(array $payload): object + { + return new Service( + new class ($payload) extends Base { + public function __construct(private array $payload) + { + parent::__construct(); + } + + public function defaultProvider(): string + { + return 'codex'; + } + }, + ); + } }