Skip to content

Commit 4f7f059

Browse files
author
Vincent Langlet
committed
✨ Add Yoda rule
1 parent 294e1df commit 4f7f059

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
/**
4+
* Enforces Yoda conditional statements.
5+
*/
6+
class Symfony3Custom_Sniffs_Formatting_YodaConditionSniff implements PHP_CodeSniffer_Sniff
7+
{
8+
/**
9+
* Returns an array of tokens this test wants to listen for.
10+
*
11+
* @return array
12+
*/
13+
public function register()
14+
{
15+
return array(
16+
T_IS_EQUAL,
17+
T_IS_NOT_EQUAL,
18+
T_IS_IDENTICAL,
19+
T_IS_NOT_IDENTICAL,
20+
);
21+
}
22+
23+
/**
24+
* Processes this test, when one of its tokens is encountered.
25+
*
26+
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
27+
* @param int $stackPtr The position of the current token in the
28+
* stack passed in $tokens.
29+
*
30+
* @return void
31+
*/
32+
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
33+
{
34+
$tokens = $phpcsFile->getTokens();
35+
36+
$beginners = PHP_CodeSniffer_Tokens::$booleanOperators;
37+
$beginners[] = T_IF;
38+
$beginners[] = T_ELSEIF;
39+
40+
$beginning = $phpcsFile->findPrevious($beginners, $stackPtr, null, false, null, true);
41+
42+
$needsYoda = false;
43+
44+
// Note: going backwards!
45+
for ($i = $stackPtr; $i > $beginning; $i--) {
46+
// Ignore whitespace.
47+
if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']])) {
48+
continue;
49+
}
50+
51+
// If this is a variable or array, we've seen all we need to see.
52+
if (T_VARIABLE === $tokens[$i]['code'] || T_CLOSE_SQUARE_BRACKET === $tokens[$i]['code']) {
53+
$needsYoda = true;
54+
break;
55+
}
56+
57+
// If this is a function call or something, we are OK.
58+
if (in_array(
59+
$tokens[$i]['code'],
60+
array(T_CONSTANT_ENCAPSED_STRING, T_CLOSE_PARENTHESIS, T_OPEN_PARENTHESIS, T_RETURN),
61+
true
62+
)) {
63+
return;
64+
}
65+
}
66+
67+
if (!$needsYoda) {
68+
return;
69+
}
70+
71+
// Check if this is a var to var comparison, e.g.: if ( $var1 == $var2 ).
72+
$nextNonEmpty = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
73+
74+
if (isset(PHP_CodeSniffer_Tokens::$castTokens[$tokens[$nextNonEmpty]['code']])) {
75+
$nextNonEmpty = $phpcsFile->findNext(
76+
PHP_CodeSniffer_Tokens::$emptyTokens,
77+
($nextNonEmpty + 1),
78+
null,
79+
true
80+
);
81+
}
82+
83+
if (in_array($tokens[$nextNonEmpty]['code'], array(T_SELF, T_PARENT, T_STATIC), true)) {
84+
$nextNonEmpty = $phpcsFile->findNext(
85+
array_merge(PHP_CodeSniffer_Tokens::$emptyTokens, array(T_DOUBLE_COLON)),
86+
($nextNonEmpty + 1),
87+
null,
88+
true
89+
);
90+
}
91+
92+
if (T_VARIABLE === $tokens[$nextNonEmpty]['code']) {
93+
return;
94+
}
95+
96+
$phpcsFile->addError('Use Yoda Condition checks, you must.', $stackPtr, 'NotYoda');
97+
}
98+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
if ( $true == true || $false == false ) { // Bad x 2 (in an 'if').
3+
echo 'True';
4+
} elseif ( $true == true && $false == true ) { // Bad x 2 (in an 'elseif').
5+
echo 'False';
6+
} elseif ( false == $true && true == $false ) { // Good - this is the correct way to do conditional checks.
7+
echo 'False';
8+
}
9+
10+
// Test for 'equals' conditional.
11+
if ( $true == true ) { // Bad x 1.
12+
echo 'True';
13+
} elseif ( false == $true ) { // Good.
14+
echo 'False';
15+
}
16+
17+
// Test for 'not equals' conditional.
18+
if ( $true != true ) { // Bad x 1.
19+
echo 'True';
20+
} elseif ( false != $true ) { // Good.
21+
echo 'False';
22+
}
23+
24+
// Test for 'exactly equals' conditional.
25+
if ( $true === true ) { // Bad x 1.
26+
echo 'True';
27+
} elseif ( false === $true ) { // Good.
28+
echo 'False';
29+
}
30+
31+
// Test for 'not exactly equals' conditional.
32+
if ( $true !== true ) { // Bad x 1.
33+
echo 'True';
34+
} elseif ( false !== $true ) { // Good.
35+
echo 'False';
36+
}
37+
38+
// Make sure the test excludes functions on the conditional check.
39+
if ( strtolower( $check ) == $true ) { // Good.
40+
echo 'True';
41+
}
42+
// Makes sure the test excludes variable casting in the conditional check.
43+
if ( true == (bool) $true ) { // Good.
44+
echo 'True';
45+
} elseif ( false == $true ) { // Good.
46+
echo 'False';
47+
}
48+
// Testing for string comparison.
49+
if ( $true == 'true' ) { // Bad x 1.
50+
echo 'True';
51+
} elseif ( 'false' == $true ) { // Good x 1.
52+
echo 'False';
53+
}
54+
// Testing for integer comparison.
55+
if ( $true == 0 ) { // Bad x 1.
56+
echo 'True';
57+
} elseif ( 1 == $false ) { // Good x 1.
58+
echo 'False';
59+
}
60+
61+
// Testing constant comparison.
62+
if ( $taxonomy === MyClass::TAXONOMY_SLUG ) { // Bad.
63+
$link = true;
64+
} elseif ( MyClass::TAXONOMY_SLUG === $taxonomy ) { // Ok.
65+
$link = false;
66+
}
67+
68+
if ( $foo === FOO_CONSTANT ) { // Bad.
69+
$link = true;
70+
} elseif ( FOO_CONSTANT === $foo ) { // Ok.
71+
$link = false;
72+
}
73+
74+
if ( $foo == $bar ) {} // Ok.
75+
76+
$accessibility_mode = ( 'on' === sanitize_key( $_GET['accessibility-mode'] ) ) ? 'on' : 'off'; // Ok.
77+
78+
if ( $on !== self::$network_mode ) { // Ok.
79+
self::$network_mode = (bool) $on;
80+
}
81+
82+
return 0 === strpos( $foo, 'a' ); // Ok.
83+
return 0 == $foo; // Ok.
84+
return $foo == 0; // Bad.
85+
86+
if ( (int) $a['interval'] === (int) $b['interval'] ) {} // Ok.
87+
88+
if ( $GLOBALS['wpdb']->num_rows === 0 ) {} // Bad.
89+
90+
if ( $true == strtolower( $check ) ) {} // Bad.
91+
92+
$update = 'yes' === strtolower( $this->from_post( 'update' ) ); // Ok.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/**
4+
* Unit test class for the YodaConditions sniff.
5+
*
6+
* A sniff unit test checks a .inc file for expected violations of a single
7+
* coding standard. Expected errors and warnings are stored in this class.
8+
*/
9+
class Symfony3Custom_Tests_Formatting_YodaConditionUnitTest extends AbstractSniffUnitTest
10+
{
11+
/**
12+
* Returns the lines where errors should occur.
13+
*
14+
* @return array <int line number> => <int number of errors>
15+
*/
16+
public function getErrorList()
17+
{
18+
return array(
19+
2 => 2,
20+
4 => 2,
21+
11 => 1,
22+
18 => 1,
23+
25 => 1,
24+
32 => 1,
25+
49 => 1,
26+
55 => 1,
27+
62 => 1,
28+
68 => 1,
29+
84 => 1,
30+
88 => 1,
31+
90 => 1,
32+
);
33+
}
34+
35+
/**
36+
* Returns the lines where warnings should occur.
37+
*
38+
* @return array <int line number> => <int number of warnings>
39+
*/
40+
public function getWarningList()
41+
{
42+
return array();
43+
}
44+
}

0 commit comments

Comments
 (0)