From 465dd4a8f9e10342ea22b6f440e485cc3999742d Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Thu, 6 Aug 2026 16:14:51 +0200 Subject: [PATCH] Cover return by reference in ImplicitCastSpacing The reference marker added in #82 only matched in front of a variable or a variadic ellipsis, so `function & name()` slipped through - the name is not a variable. That form is checked first now, keyed on the preceding function, closure or arrow-function keyword. Found while retiring psr2r-sniffer's UnaryOperatorSpacing against this rule: return by reference was the one construct it still reported that this sniff did not. --- .../Sniffs/WhiteSpace/ImplicitCastSpacingSniff.php | 6 ++++++ .../Sniffs/WhiteSpace/ImplicitCastSpacingSniffTest.php | 4 ++-- tests/_data/ImplicitCastSpacing/after.php | 10 ++++++++++ tests/_data/ImplicitCastSpacing/before.php | 10 ++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniff.php b/PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniff.php index 3d6c5eb..cb1d5e8 100644 --- a/PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniff.php +++ b/PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniff.php @@ -89,6 +89,12 @@ protected function isReferenceOperator(File $phpcsFile, int $stackPtr): bool { $tokens = $phpcsFile->getTokens(); + // Return by reference: `function & name()`. The name is not a variable, so this comes first. + $previousIndex = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true); + if ($previousIndex !== false && in_array($tokens[$previousIndex]['code'], [T_FUNCTION, T_CLOSURE, T_FN], true)) { + return true; + } + $nextIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true); if ($nextIndex === false || !in_array($tokens[$nextIndex]['code'], [T_VARIABLE, T_ELLIPSIS], true)) { return false; diff --git a/tests/PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniffTest.php b/tests/PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniffTest.php index 758ca52..8667a44 100644 --- a/tests/PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniffTest.php +++ b/tests/PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniffTest.php @@ -17,7 +17,7 @@ class ImplicitCastSpacingSniffTest extends TestCase */ public function testImplicitCastSpacingSniffer(): void { - $this->assertSnifferFindsFixableErrors(new ImplicitCastSpacingSniff(), 8, 8); + $this->assertSnifferFindsFixableErrors(new ImplicitCastSpacingSniff(), 9, 9); } /** @@ -25,6 +25,6 @@ public function testImplicitCastSpacingSniffer(): void */ public function testImplicitCastSpacingFixer(): void { - $this->assertSnifferCanFixErrors(new ImplicitCastSpacingSniff(), 8); + $this->assertSnifferCanFixErrors(new ImplicitCastSpacingSniff(), 9); } } diff --git a/tests/_data/ImplicitCastSpacing/after.php b/tests/_data/ImplicitCastSpacing/after.php index 55a07ff..4813277 100644 --- a/tests/_data/ImplicitCastSpacing/after.php +++ b/tests/_data/ImplicitCastSpacing/after.php @@ -59,4 +59,14 @@ protected function byRefVariadic(&...$args): void { $args[] = 1; } + + /** + * @return array + */ + protected function &byRefReturn(): array + { + static $items = []; + + return $items; + } } diff --git a/tests/_data/ImplicitCastSpacing/before.php b/tests/_data/ImplicitCastSpacing/before.php index d9ffb05..52dae35 100644 --- a/tests/_data/ImplicitCastSpacing/before.php +++ b/tests/_data/ImplicitCastSpacing/before.php @@ -59,4 +59,14 @@ protected function byRefVariadic(& ...$args): void { $args[] = 1; } + + /** + * @return array + */ + protected function & byRefReturn(): array + { + static $items = []; + + return $items; + } }