Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ImplicitCastSpacingSniff implements Sniff
*/
public function register(): array
{
return [T_BOOLEAN_NOT, T_NONE, T_ASPERAND, T_INC, T_DEC, T_MINUS];
return [T_BOOLEAN_NOT, T_NONE, T_ASPERAND, T_INC, T_DEC, T_MINUS, T_BITWISE_AND];
}

/**
Expand All @@ -38,6 +38,11 @@ public function process(File $phpcsFile, $stackPtr): void
return;
}

// `&` marks a reference here; as bitwise and it wants its spaces.
if ($tokens[$stackPtr]['code'] === T_BITWISE_AND && !$this->isReferenceOperator($phpcsFile, $stackPtr)) {
return;
}

// A minus is only an implicit cast when it negates; as a subtraction it wants its spaces.
if ($tokens[$stackPtr]['code'] === T_MINUS) {
if (!$this->isUnaryOperator($phpcsFile, $stackPtr)) {
Expand Down Expand Up @@ -65,6 +70,48 @@ public function process(File $phpcsFile, $stackPtr): void
}
}

/**
* `&` binds a reference rather than a bitwise and when it sits in front of a variable - or the
* ellipsis of a by-reference variadic - and
* either follows something that cannot end a value, or stands in a parameter list.
*
* The parameter list case needs its own test because a type hint precedes the marker in
* `function f(array & $items)`, and a type hint reads exactly like the left operand of a
* bitwise and. Requiring a variable on the right keeps a default such as
* `function f(int $x = A & B)` out of it.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $stackPtr
*
* @return bool
*/
protected function isReferenceOperator(File $phpcsFile, int $stackPtr): bool
{
$tokens = $phpcsFile->getTokens();

$nextIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true);
if ($nextIndex === false || !in_array($tokens[$nextIndex]['code'], [T_VARIABLE, T_ELLIPSIS], true)) {
return false;
}

if ($this->isUnaryOperator($phpcsFile, $stackPtr)) {
return true;
}

foreach ($tokens[$stackPtr]['nested_parenthesis'] ?? [] as $opener => $closer) {
if (!isset($tokens[$opener]['parenthesis_owner'])) {
continue;
}

$owner = $tokens[$opener]['parenthesis_owner'];
if (in_array($tokens[$owner]['code'], [T_FUNCTION, T_CLOSURE, T_FN], true)) {
return true;
}
}

return false;
}

/**
* A minus is unary only when what precedes it cannot end a value - an operator, an opening
* bracket, a comma, `return` and so on.
Expand Down Expand Up @@ -111,6 +158,7 @@ protected function isUnaryOperator(File $phpcsFile, int $stackPtr): bool
T_ECHO => T_ECHO,
T_PRINT => T_PRINT,
T_CASE => T_CASE,
T_AS => T_AS,
T_BOOLEAN_NOT => T_BOOLEAN_NOT,
T_YIELD => T_YIELD,
T_YIELD_FROM => T_YIELD_FROM,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ class ImplicitCastSpacingSniffTest extends TestCase
*/
public function testImplicitCastSpacingSniffer(): void
{
$this->assertSnifferFindsFixableErrors(new ImplicitCastSpacingSniff(), 4, 4);
$this->assertSnifferFindsFixableErrors(new ImplicitCastSpacingSniff(), 8, 8);
}

/**
* @return void
*/
public function testImplicitCastSpacingFixer(): void
{
$this->assertSnifferCanFixErrors(new ImplicitCastSpacingSniff(), 4);
$this->assertSnifferCanFixErrors(new ImplicitCastSpacingSniff(), 8);
}
}
35 changes: 33 additions & 2 deletions tests/_data/ImplicitCastSpacing/after.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class ImplicitCastSpacingExample
{
public function run(bool $ready, int $count, int $mask, callable $callable): int
public function run(bool $ready, int $count, int $mask, callable $callable, array $items): int
{
$not = !$ready;
$silenced = @$callable();
Expand All @@ -23,9 +23,40 @@ public function run(bool $ready, int $count, int $mask, callable $callable): int
$fqcnMinus = \PHP_INT_MAX - 1;
$doubleNegated = - -$count;
$arrow = fn (): int => -$count;
$reference = &$items;
$validReference = &$items;
$validBitwiseAnd = $count & $mask;

foreach ($items as &$item) {
$item = (int)$item;
}
unset($item);

$this->byRef($items);

return (int)$not + (int)$silenced + $negated + $flipped + (int)$validNot
+ (int)$validSilenced + $validNegated + $validSubtraction
+ $constantMinus + $boolMinus + $nullMinus + $fqcnMinus + $doubleNegated + $arrow();
+ $constantMinus + $boolMinus + $nullMinus + $fqcnMinus + $doubleNegated + $arrow()
+ count($reference) + count($validReference) + $validBitwiseAnd;
}

/**
* @param array<int> $items
*
* @return void
*/
protected function byRef(array &$items): void
{
$items[] = 1;
}

/**
* @param int ...$args
*
* @return void
*/
protected function byRefVariadic(&...$args): void
{
$args[] = 1;
}
}
35 changes: 33 additions & 2 deletions tests/_data/ImplicitCastSpacing/before.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class ImplicitCastSpacingExample
{
public function run(bool $ready, int $count, int $mask, callable $callable): int
public function run(bool $ready, int $count, int $mask, callable $callable, array $items): int
{
$not = ! $ready;
$silenced = @ $callable();
Expand All @@ -23,9 +23,40 @@ public function run(bool $ready, int $count, int $mask, callable $callable): int
$fqcnMinus = \PHP_INT_MAX - 1;
$doubleNegated = - -$count;
$arrow = fn (): int => - $count;
$reference = & $items;
$validReference = &$items;
$validBitwiseAnd = $count & $mask;

foreach ($items as & $item) {
$item = (int)$item;
}
unset($item);
Comment on lines +26 to +33

$this->byRef($items);

return (int)$not + (int)$silenced + $negated + $flipped + (int)$validNot
+ (int)$validSilenced + $validNegated + $validSubtraction
+ $constantMinus + $boolMinus + $nullMinus + $fqcnMinus + $doubleNegated + $arrow();
+ $constantMinus + $boolMinus + $nullMinus + $fqcnMinus + $doubleNegated + $arrow()
+ count($reference) + count($validReference) + $validBitwiseAnd;
}

/**
* @param array<int> $items
*
* @return void
*/
protected function byRef(array & $items): void
{
$items[] = 1;
}

/**
* @param int ...$args
*
* @return void
*/
protected function byRefVariadic(& ...$args): void
{
$args[] = 1;
}
}
Loading