Skip to content

Commit 823141c

Browse files
author
Vincent Langlet
committed
✨ Add check for comparisonOperator
1 parent 971b8d9 commit 823141c

File tree

4 files changed

+199
-0
lines changed

4 files changed

+199
-0
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
/**
4+
* A Sniff to enforce the use of IDENTICAL type operators rather than EQUAL operators.
5+
*/
6+
class Symfony3Custom_Sniffs_Operators_ComparisonOperatorUsageSniff implements PHP_CodeSniffer_Sniff
7+
8+
{
9+
/**
10+
* A list of tokenizers this sniff supports.
11+
*
12+
* @var array
13+
*/
14+
public $supportedTokenizers = array(
15+
'PHP',
16+
'JS',
17+
);
18+
19+
/**
20+
* A list of invalid operators with their alternatives.
21+
*
22+
* @var array(int => string)
23+
*/
24+
private static $_invalidOps = array(
25+
'PHP' => array(
26+
T_IS_EQUAL => '===',
27+
T_IS_NOT_EQUAL => '!==',
28+
),
29+
'JS' => array(
30+
T_IS_EQUAL => '===',
31+
T_IS_NOT_EQUAL => '!==',
32+
),
33+
);
34+
35+
36+
/**
37+
* Registers the token types that this sniff wishes to listen to.
38+
*
39+
* @return array
40+
*/
41+
public function register()
42+
{
43+
return array(
44+
T_IF,
45+
T_ELSEIF,
46+
T_INLINE_THEN,
47+
T_WHILE,
48+
T_FOR,
49+
);
50+
}
51+
52+
/**
53+
* Process the tokens that this sniff is listening for.
54+
*
55+
* @param PHP_CodeSniffer_File $phpcsFile The file where the token was found.
56+
* @param int $stackPtr The position in the stack where the token
57+
* was found.
58+
*
59+
* @return void
60+
*/
61+
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
62+
{
63+
$tokens = $phpcsFile->getTokens();
64+
$tokenizer = $phpcsFile->tokenizerType;
65+
66+
if ($tokens[$stackPtr]['code'] === T_INLINE_THEN) {
67+
$end = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
68+
if ($tokens[$end]['code'] !== T_CLOSE_PARENTHESIS) {
69+
// This inline IF statement does not have its condition
70+
// bracketed, so we need to guess where it starts.
71+
for ($i = ($end - 1); $i >= 0; $i--) {
72+
if ($tokens[$i]['code'] === T_SEMICOLON) {
73+
// Stop here as we assume it is the end
74+
// of the previous statement.
75+
break;
76+
} else if ($tokens[$i]['code'] === T_OPEN_TAG) {
77+
// Stop here as this is the start of the file.
78+
break;
79+
} else if ($tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET) {
80+
// Stop if this is the closing brace of
81+
// a code block.
82+
if (isset($tokens[$i]['scope_opener']) === true) {
83+
break;
84+
}
85+
} else if ($tokens[$i]['code'] === T_OPEN_CURLY_BRACKET) {
86+
// Stop if this is the opening brace of
87+
// a code block.
88+
if (isset($tokens[$i]['scope_closer']) === true) {
89+
break;
90+
}
91+
}
92+
}
93+
94+
$start = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($i + 1), null, true);
95+
} else {
96+
if (isset($tokens[$end]['parenthesis_opener']) === false) {
97+
return;
98+
}
99+
100+
$start = $tokens[$end]['parenthesis_opener'];
101+
}
102+
} elseif ($tokens[$stackPtr]['code'] === T_FOR) {
103+
if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) {
104+
return;
105+
}
106+
107+
$openingBracket = $tokens[$stackPtr]['parenthesis_opener'];
108+
$closingBracket = $tokens[$stackPtr]['parenthesis_closer'];
109+
110+
$start = $phpcsFile->findNext(T_SEMICOLON, $openingBracket, $closingBracket);
111+
$end = $phpcsFile->findNext(T_SEMICOLON, ($start + 1), $closingBracket);
112+
if ($start === false || $end === false) {
113+
return;
114+
}
115+
} else {
116+
if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) {
117+
return;
118+
}
119+
120+
$start = $tokens[$stackPtr]['parenthesis_opener'];
121+
$end = $tokens[$stackPtr]['parenthesis_closer'];
122+
}
123+
124+
for ($i = $start; $i <= $end; $i++) {
125+
$type = $tokens[$i]['code'];
126+
if (in_array($type, array_keys(self::$_invalidOps[$tokenizer])) === true) {
127+
$error = 'Operator %s prohibited; use %s instead';
128+
$data = array(
129+
$tokens[$i]['content'],
130+
self::$_invalidOps[$tokenizer][$type],
131+
);
132+
$fix = $phpcsFile->addFixableError($error, $i, 'NotAllowed', $data);
133+
134+
if ($fix === true) {
135+
$phpcsFile->fixer->beginChangeset();
136+
$phpcsFile->fixer->replaceToken($i, self::$_invalidOps[$tokenizer][$type]);
137+
$phpcsFile->fixer->endChangeset();
138+
}
139+
}
140+
}
141+
}
142+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
if ($value != TRUE) {
3+
} elseif ($value != FALSE) {
4+
}
5+
6+
if ($value == TRUE) {
7+
} elseif ($value == FALSE) {
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
if ($value !== TRUE) {
3+
} elseif ($value !== FALSE) {
4+
}
5+
6+
if ($value === TRUE) {
7+
} elseif ($value === FALSE) {
8+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/**
4+
* Unit test class for the ComparisonOperatorUsage 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_Operators_ComparisonOperatorUsageUnitTest extends AbstractSniffUnitTest
10+
{
11+
/**
12+
* Returns the lines where errors should occur.
13+
*
14+
* The key of the array should represent the line number and the value
15+
* should represent the number of errors that should occur on that line.
16+
*
17+
* @return array<int, int>
18+
*/
19+
public function getErrorList()
20+
{
21+
return array(
22+
2 => 1,
23+
3 => 1,
24+
6 => 1,
25+
7 => 1,
26+
);
27+
}
28+
29+
/**
30+
* Returns the lines where warnings should occur.
31+
*
32+
* The key of the array should represent the line number and the value
33+
* should represent the number of warnings that should occur on that line.
34+
*
35+
* @return array<int, int>
36+
*/
37+
public function getWarningList()
38+
{
39+
return array();
40+
}
41+
}

0 commit comments

Comments
 (0)