Skip to content

Commit

Permalink
Merge pull request #1483 from WordPress-Coding-Standards/feature/1481…
Browse files Browse the repository at this point in the history
…-new-typecast-sniff

Core: New sniff to examine type casts
  • Loading branch information
JDGrimes authored Sep 11, 2018
2 parents 69ee4c9 + 63a6816 commit c5515a7
Show file tree
Hide file tree
Showing 5 changed files with 331 additions and 0 deletions.
1 change: 1 addition & 0 deletions WordPress-Core/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@
<rule ref="Generic.Formatting.SpaceAfterCast"/>
<rule ref="Squiz.WhiteSpace.CastSpacing"/>
<rule ref="WordPress.WhiteSpace.CastStructureSpacing"/>
<rule ref="WordPress.PHP.TypeCasts"/>

<!-- Covers rule: ... array items, only include a space around the index if it is a variable. -->
<rule ref="WordPress.Arrays.ArrayKeySpacingRestrictions"/>
Expand Down
154 changes: 154 additions & 0 deletions WordPress/Sniffs/PHP/TypeCastsSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php
/**
* WordPress Coding Standard.
*
* @package WPCS\WordPressCodingStandards
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
* @license https://opensource.org/licenses/MIT MIT
*/

namespace WordPress\Sniffs\PHP;

use WordPress\Sniff;
use PHP_CodeSniffer_Tokens as Tokens;

/**
* Verifies the correct usage of type cast keywords.
*
* Type casts should be:
* - lowercase;
* - short form, i.e. (bool) not (boolean);
* - normalized, i.e. (float) not (real).
*
* Additionally, the use of the (unset) and (binary) casts is discouraged.
*
* @link https://make.wordpress.org/core/handbook/best-practices/....
*
* @package WPCS\WordPressCodingStandards
*
* @since 1.2.0
*/
class TypeCastsSniff extends Sniff {

/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register() {
return Tokens::$castTokens;
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @param int $stackPtr The position of the current token in the stack.
*
* @return void
*/
public function process_token( $stackPtr ) {

$token_code = $this->tokens[ $stackPtr ]['code'];
$typecast = $this->tokens[ $stackPtr ]['content'];
$typecast_lc = strtolower( $typecast );

$this->phpcsFile->recordMetric( $stackPtr, 'Typecast encountered', $typecast );

switch ( $token_code ) {
case \T_BOOL_CAST:
if ( '(bool)' !== $typecast_lc ) {
$fix = $this->phpcsFile->addFixableError(
'Short form type keywords must be used; expected "(bool)" but found "%s"',
$stackPtr,
'LongBoolFound',
array( $typecast )
);

if ( true === $fix ) {
$this->phpcsFile->fixer->replaceToken( $stackPtr, '(bool)' );
}
}
break;

case \T_INT_CAST:
if ( '(int)' !== $typecast_lc ) {
$fix = $this->phpcsFile->addFixableError(
'Short form type keywords must be used; expected "(int)" but found "%s"',
$stackPtr,
'LongIntFound',
array( $typecast )
);

if ( true === $fix ) {
$this->phpcsFile->fixer->replaceToken( $stackPtr, '(int)' );
}
}
break;

case \T_DOUBLE_CAST:
if ( '(float)' !== $typecast_lc ) {
$fix = $this->phpcsFile->addFixableError(
'Normalized type keywords must be used; expected "(float)" but found "%s"',
$stackPtr,
'DoubleRealFound',
array( $typecast )
);

if ( true === $fix ) {
$this->phpcsFile->fixer->replaceToken( $stackPtr, '(float)' );
}
}
break;

case \T_UNSET_CAST:
$this->phpcsFile->addWarning(
'Using the "(unset)" cast is strongly discouraged. Use the "unset()" language construct or assign "null" as the value to the variable instead.',
$stackPtr,
'UnsetFound'
);
break;

case \T_STRING_CAST:
case \T_BINARY_CAST:
if ( \T_STRING_CAST === $token_code && '(binary)' !== $typecast_lc ) {
break;
}

$this->phpcsFile->addWarning(
'Using binary casting is strongly discouraged. Found: "%s"',
$stackPtr,
'BinaryFound',
array( $typecast )
);
break;
}

/*
* {@internal Once the minimum PHPCS version has gone up to PHPCS 3.3.0+, the lowercase
* check below can be removed in favour of adding the `Generic.PHP.LowerCaseType` sniff
* to the ruleset.
* Note: the `register()` function also needs adjusting in that case to only register the
* targetted type casts above and the metrics recording should probably be adjusted as well.
* The above mentioned Generic sniff records metrics about the case of typecasts, so we
* don't need to worry about those no longer being recorded. They will be, just slightly
* differently.}}
*/
if ( $typecast_lc !== $typecast ) {
$data = array(
$typecast_lc,
$typecast,
);

$fix = $this->phpcsFile->addFixableError(
'PHP type casts must be lowercase; expected "%s" but found "%s"',
$stackPtr,
'NonLowercaseFound',
$data
);
if ( true === $fix ) {
$this->phpcsFile->fixer->replaceToken( $stackPtr, $typecast_lc );
}
}
}

}
49 changes: 49 additions & 0 deletions WordPress/Tests/PHP/TypeCastsUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

// OK.
$a = (bool) $b;
$a = (int) $b;
$a = (float) $b;
$a = (string) $b;
$a = (array) $b;
$a = (object) $b;

// Error: Wrong form.
$a = (boolean) $b;
$a = (integer) $b;
$a = (double) $b;
$a = (real) $b;

// Warning: Discouraged casts.
$a = (unset) $b; // Warning.
$a = (binary) $b; // Warning.
$a = b"binary string"; // Warning. Currently false negative. Related to PHPCS bug report #1574.
$a = b"binary $string"; // Warning.

// Error: Mixed case.
$a = (Bool) $b;
$a = (Boolean) $b; // + wrong form.
$a = (Int) $b;
$a = (Integer) $b; // + wrong form.
$a = (Float) $b;
$a = (Double) $b; // + wrong form.
$a = (Real) $b; // + wrong form.
$a = (String) $b;
$a = (Array) $b;
$a = (Object) $b;
$a = (Unset) $b; // + discouraged.
$a = (Binary) $b; // + discouraged.

// Error: Uppercase.
$a = (BOOL) $b;
$a = (BOOLEAN) $b; // + wrong form.
$a = (INT) $b;
$a = (INTEGER) $b; // + wrong form.
$a = (FLOAT) $b;
$a = (DOUBLE) $b; // + wrong form.
$a = (REAL) $b; // + wrong form.
$a = (STRING) $b;
$a = (ARRAY) $b;
$a = (OBJECT) $b;
$a = (UNSET) $b; // + discouraged.
$a = (BINARY) $b; // + discouraged.
49 changes: 49 additions & 0 deletions WordPress/Tests/PHP/TypeCastsUnitTest.inc.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

// OK.
$a = (bool) $b;
$a = (int) $b;
$a = (float) $b;
$a = (string) $b;
$a = (array) $b;
$a = (object) $b;

// Error: Wrong form.
$a = (bool) $b;
$a = (int) $b;
$a = (float) $b;
$a = (float) $b;

// Warning: Discouraged casts.
$a = (unset) $b; // Warning.
$a = (binary) $b; // Warning.
$a = b"binary string"; // Warning. Currently false negative. Related to PHPCS bug report #1574.
$a = b"binary $string"; // Warning.

// Error: Mixed case.
$a = (bool) $b;
$a = (bool) $b; // + wrong form.
$a = (int) $b;
$a = (int) $b; // + wrong form.
$a = (float) $b;
$a = (float) $b; // + wrong form.
$a = (float) $b; // + wrong form.
$a = (string) $b;
$a = (array) $b;
$a = (object) $b;
$a = (unset) $b; // + discouraged.
$a = (binary) $b; // + discouraged.

// Error: Uppercase.
$a = (bool) $b;
$a = (bool) $b; // + wrong form.
$a = (int) $b;
$a = (int) $b; // + wrong form.
$a = (float) $b;
$a = (float) $b; // + wrong form.
$a = (float) $b; // + wrong form.
$a = (string) $b;
$a = (array) $b;
$a = (object) $b;
$a = (unset) $b; // + discouraged.
$a = (binary) $b; // + discouraged.
78 changes: 78 additions & 0 deletions WordPress/Tests/PHP/TypeCastsUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php
/**
* Unit test class for WordPress Coding Standard.
*
* @package WPCS\WordPressCodingStandards
* @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards
* @license https://opensource.org/licenses/MIT MIT
*/

namespace WordPress\Tests\PHP;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

/**
* Unit test class for the TypeCasts sniff.
*
* @package WPCS\WordPressCodingStandards
*
* @since 1.2.0
*/
class TypeCastsUnitTest extends AbstractSniffUnitTest {

/**
* Returns the lines where errors should occur.
*
* @return array <int line number> => <int number of errors>
*/
public function getErrorList() {
return array(
12 => 1,
13 => 1,
14 => 1,
15 => 1,
24 => 1,
25 => 2,
26 => 1,
27 => 2,
28 => 1,
29 => 2,
30 => 2,
31 => 1,
32 => 1,
33 => 1,
34 => 1,
35 => 1,
38 => 1,
39 => 2,
40 => 1,
41 => 2,
42 => 1,
43 => 2,
44 => 2,
45 => 1,
46 => 1,
47 => 1,
48 => 1,
49 => 1,
);
}

/**
* Returns the lines where warnings should occur.
*
* @return array <int line number> => <int number of warnings>
*/
public function getWarningList() {
return array(
18 => 1,
19 => 1,
// 20 => 1,
21 => 1,
34 => 1,
35 => 1,
48 => 1,
49 => 1,
);
}
}

0 comments on commit c5515a7

Please sign in to comment.