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
2 changes: 1 addition & 1 deletion PhpCollective/Sniffs/Internal/DisallowFunctionsSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ protected function checkForbiddenFunctions(File $phpcsFile, int $stackPtr): void
}

$previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
if (!$previous || in_array($tokens[$previous]['code'], static::$wrongTokens, true)) {
if ($previous === false || in_array($tokens[$previous]['code'], static::$wrongTokens, true)) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions PhpCollective/Sniffs/PHP/DisallowFunctionsSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ protected function checkForbiddenFunctions(File $phpcsFile, int $stackPtr): void

$tokenContent = $tokens[$stackPtr]['content'];
$previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
if (!$previous || in_array($tokens[$previous]['code'], static::$wrongTokens)) {
if ($previous === false || in_array($tokens[$previous]['code'], static::$wrongTokens)) {
return;
}

Expand Down Expand Up @@ -88,7 +88,7 @@ protected function checkImplodeUsage(File $phpcsFile, int $stackPtr): void
}

$previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
if (!$previous || in_array($tokens[$previous]['code'], static::$wrongTokens)) {
if ($previous === false || in_array($tokens[$previous]['code'], static::$wrongTokens)) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion PhpCollective/Sniffs/PHP/NoIsNullSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function process(File $phpcsFile, $stackPtr): void

$tokenContent = $tokens[$stackPtr]['content'];
$previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
if (!$previous || in_array($tokens[$previous]['code'], $wrongTokens)) {
if ($previous === false || in_array($tokens[$previous]['code'], $wrongTokens)) {
return;
Comment on lines 41 to 44
}

Expand Down
2 changes: 1 addition & 1 deletion PhpCollective/Sniffs/PHP/RemoveFunctionAliasSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ protected function checkFixableAliases(File $phpcsFile, int $stackPtr): void

$tokenContent = $tokens[$stackPtr]['content'];
$previous = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
if (!$previous || in_array($tokens[$previous]['code'], $wrongTokens)) {
if ($previous === false || in_array($tokens[$previous]['code'], $wrongTokens)) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion PhpCollective/Sniffs/PHP/ShortCastSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function process(File $phpcsFile, $stackPtr): void

if ($tokens[$stackPtr]['content'] === '!') {
$prevIndex = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
if (!$prevIndex || $tokens[$prevIndex]['content'] !== '!') {
if ($prevIndex === false || $tokens[$prevIndex]['content'] !== '!') {
return;
}

Expand Down
72 changes: 71 additions & 1 deletion PhpCollective/Sniffs/WhiteSpace/ImplicitCastSpacingSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;

/**
* No whitespace should be between implicit cast and variable, the same as with other casts.
Expand All @@ -21,7 +22,7 @@ class ImplicitCastSpacingSniff implements Sniff
*/
public function register(): array
{
return [T_BOOLEAN_NOT, T_NONE, T_ASPERAND, T_INC, T_DEC];
return [T_BOOLEAN_NOT, T_NONE, T_ASPERAND, T_INC, T_DEC, T_MINUS];
}

/**
Expand All @@ -37,6 +38,19 @@ public function process(File $phpcsFile, $stackPtr): void
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)) {
return;
}

// `- -$i` must keep its space: closing it up would produce `--$i`, a decrement.
$followingIndex = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true);
if ($followingIndex !== false && in_array($tokens[$followingIndex]['code'], [T_MINUS, T_DEC], true)) {
return;
}
}

$nextIndex = $phpcsFile->findNext(T_WHITESPACE, $stackPtr + 1, null, true);

if ($nextIndex === false || $nextIndex - $stackPtr === 1) {
Expand All @@ -51,6 +65,62 @@ public function process(File $phpcsFile, $stackPtr): void
}
}

/**
* A minus is unary only when what precedes it cannot end a value - an operator, an opening
* bracket, a comma, `return` and so on.
*
* The test is deliberately this way round. Listing what may PRECEDE a subtraction instead
* would have to enumerate every value-producing token, and anything forgotten (`__LINE__`,
* `true`, `null`, a qualified constant) would be read as a negation and "fixed" into
* `__LINE__ -1`. Defaulting to binary keeps an unknown predecessor harmless.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $stackPtr
*
* @return bool
*/
protected function isUnaryOperator(File $phpcsFile, int $stackPtr): bool
{
$tokens = $phpcsFile->getTokens();

$previousIndex = $phpcsFile->findPrevious(Tokens::$emptyTokens, $stackPtr - 1, null, true);
if ($previousIndex === false) {
return true;
}

$unaryPrefixes = Tokens::$operators
Comment on lines +90 to +91
+ Tokens::$assignmentTokens
+ Tokens::$comparisonTokens
+ Tokens::$booleanOperators
+ Tokens::$castTokens
+ [
T_OPEN_PARENTHESIS => T_OPEN_PARENTHESIS,
T_OPEN_SQUARE_BRACKET => T_OPEN_SQUARE_BRACKET,
T_OPEN_SHORT_ARRAY => T_OPEN_SHORT_ARRAY,
T_OPEN_CURLY_BRACKET => T_OPEN_CURLY_BRACKET,
T_COMMA => T_COMMA,
T_SEMICOLON => T_SEMICOLON,
T_COLON => T_COLON,
T_DOUBLE_ARROW => T_DOUBLE_ARROW,
T_INLINE_THEN => T_INLINE_THEN,
T_INLINE_ELSE => T_INLINE_ELSE,
T_RETURN => T_RETURN,
T_ECHO => T_ECHO,
T_PRINT => T_PRINT,
T_CASE => T_CASE,
T_BOOLEAN_NOT => T_BOOLEAN_NOT,
T_YIELD => T_YIELD,
T_YIELD_FROM => T_YIELD_FROM,
T_THROW => T_THROW,
T_FN_ARROW => T_FN_ARROW,
T_MATCH_ARROW => T_MATCH_ARROW,
T_OPEN_TAG => T_OPEN_TAG,
T_OPEN_TAG_WITH_ECHO => T_OPEN_TAG_WITH_ECHO,
];

return isset($unaryPrefixes[$tokens[$previousIndex]['code']]);
}

/**
* @param \PHP_CodeSniffer\Files\File $phpcsFile
* @param int $stackPtr
Expand Down
33 changes: 29 additions & 4 deletions PhpCollective/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,16 @@

<rule ref="Squiz.PHP.Eval"/>
<!--
Setting the property replaces the sniff's defaults, so `sizeof` and `delete` have to be
repeated here. `create_function` and `each` live here rather than on
Setting the property replaces the sniff's defaults, so `delete` has to be repeated
here. `create_function` and `each` live here rather than on
Generic.PHP.DeprecatedFunctions because that sniff builds its list from the running
PHP version, where both are long gone.
PHP version, where both are long gone. `sizeof` is deliberately NOT listed: it is
already reported by PhpCollective.PHP.RemoveFunctionAlias, which unlike this sniff
can fix it.
-->
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array">
<element key="sizeof" value="count"/>
<element key="delete" value="unset"/>
<element key="create_function" value="null"/>
<element key="each" value="null"/>
Expand All @@ -234,6 +235,30 @@
<exclude name="PSR12.Files.FileHeader"/>
<exclude name="PSR12.Files.DeclareStatement"/>
<exclude name="PSR12.Files.OpenTag"/>
<!-- Long casts are covered by SlevomatCodingStandard.PHP.TypeCast, which catches more. -->
<exclude name="PSR12.Keywords.ShortFormTypeKeywords"/>
</rule>

<!--
Three rules used to report every long cast. SlevomatCodingStandard.PHP.TypeCast above is
the only one that covers all of them - it also rewrites `(double)` to `(float)` and copes
with inner spaces like `( boolean )` - so the two narrower ones are silenced here.

ShortCast matches token content literally and only knows `(boolean)` and `(integer)`; its
DoubleNotInvalid check stays on, since nothing else flags `!!`.
-->
<rule ref="PhpCollective.PHP.ShortCast">
<exclude name="PhpCollective.PHP.ShortCast.LongInvalid"/>
</rule>

<!--
The incrementer half duplicates Generic.WhiteSpace.IncrementDecrementSpacing, which also
handles targets like `$b[0] ++`. The remaining check, spacing after `!`, `@` and friends,
has no equivalent and stays on.
-->
<rule ref="PhpCollective.WhiteSpace.ImplicitCastSpacing">
<exclude name="PhpCollective.WhiteSpace.ImplicitCastSpacing.WhitespaceBeforeVariable"/>
<exclude name="PhpCollective.WhiteSpace.ImplicitCastSpacing.WhitespaceAfterVariable"/>
</rule>

<rule ref="SlevomatCodingStandard.Operators.SpreadOperatorSpacing"/>
Expand Down
5 changes: 2 additions & 3 deletions docs/sniffs.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# PhpCollective Code Sniffer


The PhpCollectiveStrict standard contains 251 sniffs
The PhpCollectiveStrict standard contains 250 sniffs

Generic (27 sniffs)
-------------------
Expand Down Expand Up @@ -163,7 +163,7 @@ PSR2 (12 sniffs)
- PSR2.Namespaces.NamespaceDeclaration
- PSR2.Namespaces.UseDeclaration

PSR12 (14 sniffs)
PSR12 (13 sniffs)
-----------------
- PSR12.Classes.AnonClassDeclaration
- PSR12.Classes.ClassInstantiation
Expand All @@ -174,7 +174,6 @@ PSR12 (14 sniffs)
- PSR12.Files.ImportStatement
- PSR12.Functions.NullableTypeDeclaration
- PSR12.Functions.ReturnTypeDeclaration
- PSR12.Keywords.ShortFormTypeKeywords
- PSR12.Namespaces.CompoundNamespaceDepth
- PSR12.Operators.OperatorSpacing
- PSR12.Properties.ConstantVisibility
Expand Down
19 changes: 13 additions & 6 deletions tests/_data/ImplicitCastSpacing/after.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@ public function run(bool $ready, int $count, int $mask, callable $callable): int
{
$not = !$ready;
$silenced = @$callable();
$flipped = ~ $mask;
++$count;
$count--;
$negated = -$count;

$flipped = ~ $mask;
$validNot = !$ready;
$validSilenced = @$callable();
++$count;
$count++;
$validNegated = -$count;
$validSubtraction = $count - $mask;
$constantMinus = __LINE__ - 1;
$boolMinus = (int)true - 1;
$nullMinus = (int)null - 1;
$fqcnMinus = \PHP_INT_MAX - 1;
$doubleNegated = - -$count;
$arrow = fn (): int => -$count;

return (int)$not + (int)$silenced + $flipped + $count + (int)$validNot + (int)$validSilenced;
return (int)$not + (int)$silenced + $negated + $flipped + (int)$validNot
+ (int)$validSilenced + $validNegated + $validSubtraction
+ $constantMinus + $boolMinus + $nullMinus + $fqcnMinus + $doubleNegated + $arrow();
}
}
19 changes: 13 additions & 6 deletions tests/_data/ImplicitCastSpacing/before.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@ public function run(bool $ready, int $count, int $mask, callable $callable): int
{
$not = ! $ready;
$silenced = @ $callable();
$flipped = ~ $mask;
++ $count;
$count --;
$negated = - $count;

$flipped = ~ $mask;
$validNot = !$ready;
$validSilenced = @$callable();
++$count;
$count++;
$validNegated = -$count;
$validSubtraction = $count - $mask;
$constantMinus = __LINE__ - 1;
$boolMinus = (int)true - 1;
$nullMinus = (int)null - 1;
$fqcnMinus = \PHP_INT_MAX - 1;
$doubleNegated = - -$count;
$arrow = fn (): int => - $count;

return (int)$not + (int)$silenced + $flipped + $count + (int)$validNot + (int)$validSilenced;
return (int)$not + (int)$silenced + $negated + $flipped + (int)$validNot
+ (int)$validSilenced + $validNegated + $validSubtraction
+ $constantMinus + $boolMinus + $nullMinus + $fqcnMinus + $doubleNegated + $arrow();
}
}
62 changes: 62 additions & 0 deletions tests/_data/ImplicitCastSpacing/before.tokens.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
// T_OPEN_TAG T_WHITESPACE

// T_WHITESPACE
declare(strict_types=1);
// T_DECLARE T_OPEN_PARENTHESIS T_STRING T_EQUAL T_LNUMBER T_CLOSE_PARENTHESIS T_SEMICOLON T_WHITESPACE

// T_WHITESPACE
namespace PhpCollective;
// T_NAMESPACE T_WHITESPACE T_STRING T_SEMICOLON T_WHITESPACE

// T_WHITESPACE
class ImplicitCastSpacingExample
// T_CLASS T_WHITESPACE T_STRING T_WHITESPACE
{
// T_OPEN_CURLY_BRACKET T_WHITESPACE
public function run(bool $ready, int $count, int $mask, callable $callable): int
// T_WHITESPACE T_PUBLIC T_WHITESPACE T_FUNCTION T_WHITESPACE T_STRING T_OPEN_PARENTHESIS T_STRING T_WHITESPACE T_VARIABLE T_COMMA T_WHITESPACE T_STRING T_WHITESPACE T_VARIABLE T_COMMA T_WHITESPACE T_STRING T_WHITESPACE T_VARIABLE T_COMMA T_WHITESPACE T_CALLABLE T_WHITESPACE T_VARIABLE T_CLOSE_PARENTHESIS T_COLON T_WHITESPACE T_STRING T_WHITESPACE
{
// T_WHITESPACE T_OPEN_CURLY_BRACKET T_WHITESPACE
$not = ! $ready;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_BOOLEAN_NOT T_WHITESPACE T_VARIABLE T_SEMICOLON T_WHITESPACE
$silenced = @ $callable();
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_ASPERAND T_WHITESPACE T_VARIABLE T_OPEN_PARENTHESIS T_CLOSE_PARENTHESIS T_SEMICOLON T_WHITESPACE
$negated = - $count;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_MINUS T_WHITESPACE T_VARIABLE T_SEMICOLON T_WHITESPACE

// T_WHITESPACE
$flipped = ~ $mask;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_BITWISE_NOT T_WHITESPACE T_VARIABLE T_SEMICOLON T_WHITESPACE
$validNot = !$ready;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_BOOLEAN_NOT T_VARIABLE T_SEMICOLON T_WHITESPACE
$validSilenced = @$callable();
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_ASPERAND T_VARIABLE T_OPEN_PARENTHESIS T_CLOSE_PARENTHESIS T_SEMICOLON T_WHITESPACE
$validNegated = -$count;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_MINUS T_VARIABLE T_SEMICOLON T_WHITESPACE
$validSubtraction = $count - $mask;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_VARIABLE T_WHITESPACE T_MINUS T_WHITESPACE T_VARIABLE T_SEMICOLON T_WHITESPACE
$constantMinus = __LINE__ - 1;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_LINE T_WHITESPACE T_MINUS T_WHITESPACE T_LNUMBER T_SEMICOLON T_WHITESPACE
$boolMinus = (int)true - 1;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_INT_CAST T_TRUE T_WHITESPACE T_MINUS T_WHITESPACE T_LNUMBER T_SEMICOLON T_WHITESPACE
$nullMinus = (int)null - 1;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_INT_CAST T_NULL T_WHITESPACE T_MINUS T_WHITESPACE T_LNUMBER T_SEMICOLON T_WHITESPACE
$fqcnMinus = \PHP_INT_MAX - 1;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_NAME_FULLY_QUALIFIED T_WHITESPACE T_MINUS T_WHITESPACE T_LNUMBER T_SEMICOLON T_WHITESPACE
$doubleNegated = - -$count;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_MINUS T_WHITESPACE T_MINUS T_VARIABLE T_SEMICOLON T_WHITESPACE
$arrow = fn (): int => - $count;
// T_WHITESPACE T_VARIABLE T_WHITESPACE T_EQUAL T_WHITESPACE T_FN T_WHITESPACE T_OPEN_PARENTHESIS T_CLOSE_PARENTHESIS T_COLON T_WHITESPACE T_STRING T_WHITESPACE T_FN_ARROW T_WHITESPACE T_MINUS T_WHITESPACE T_VARIABLE T_SEMICOLON T_WHITESPACE

// T_WHITESPACE
return (int)$not + (int)$silenced + $negated + $flipped + (int)$validNot
// T_WHITESPACE T_RETURN T_WHITESPACE T_INT_CAST T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_INT_CAST T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_INT_CAST T_VARIABLE T_WHITESPACE
+ (int)$validSilenced + $validNegated + $validSubtraction
// T_WHITESPACE T_PLUS T_WHITESPACE T_INT_CAST T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_WHITESPACE
+ $constantMinus + $boolMinus + $nullMinus + $fqcnMinus + $doubleNegated + $arrow();
// T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_WHITESPACE T_PLUS T_WHITESPACE T_VARIABLE T_OPEN_PARENTHESIS T_CLOSE_PARENTHESIS T_SEMICOLON T_WHITESPACE
}
// T_WHITESPACE T_CLOSE_CURLY_BRACKET T_WHITESPACE
}
// T_CLOSE_CURLY_BRACKET T_WHITESPACE
Loading