1<?php 2/** 3 * A Sniff to enforce the use of IDENTICAL type operators rather than EQUAL operators. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @author Marc McIntyre <mmcintyre@squiz.net> 11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 12 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 13 * @link http://pear.php.net/package/PHP_CodeSniffer 14 */ 15 16/** 17 * A Sniff to enforce the use of IDENTICAL type operators rather than EQUAL operators. 18 * 19 * The use of === true is enforced over implicit true statements, 20 * for example: 21 * 22 * <code> 23 * if ($a) 24 * { 25 * ... 26 * } 27 * </code> 28 * 29 * should be: 30 * 31 * <code> 32 * if ($a === true) 33 * { 34 * ... 35 * } 36 * </code> 37 * 38 * It also enforces the use of === false over ! operators. 39 * 40 * @category PHP 41 * @package PHP_CodeSniffer 42 * @author Greg Sherwood <gsherwood@squiz.net> 43 * @author Marc McIntyre <mmcintyre@squiz.net> 44 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 45 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 46 * @version Release: @package_version@ 47 * @link http://pear.php.net/package/PHP_CodeSniffer 48 */ 49class Squiz_Sniffs_Operators_ComparisonOperatorUsageSniff implements PHP_CodeSniffer_Sniff 50{ 51 52 /** 53 * A list of tokenizers this sniff supports. 54 * 55 * @var array 56 */ 57 public $supportedTokenizers = array( 58 'PHP', 59 'JS', 60 ); 61 62 /** 63 * A list of valid comparison operators. 64 * 65 * @var array 66 */ 67 private static $_validOps = array( 68 T_IS_IDENTICAL, 69 T_IS_NOT_IDENTICAL, 70 T_LESS_THAN, 71 T_GREATER_THAN, 72 T_IS_GREATER_OR_EQUAL, 73 T_IS_SMALLER_OR_EQUAL, 74 T_INSTANCEOF, 75 ); 76 77 /** 78 * A list of invalid operators with their alternatives. 79 * 80 * @var array(int => string) 81 */ 82 private static $_invalidOps = array( 83 'PHP' => array( 84 T_IS_EQUAL => '===', 85 T_IS_NOT_EQUAL => '!==', 86 T_BOOLEAN_NOT => '=== FALSE', 87 ), 88 'JS' => array( 89 T_IS_EQUAL => '===', 90 T_IS_NOT_EQUAL => '!==', 91 ), 92 ); 93 94 95 /** 96 * Registers the token types that this sniff wishes to listen to. 97 * 98 * @return array 99 */ 100 public function register() 101 { 102 return array( 103 T_IF, 104 T_ELSEIF, 105 T_INLINE_THEN, 106 T_WHILE, 107 T_FOR, 108 ); 109 110 }//end register() 111 112 113 /** 114 * Process the tokens that this sniff is listening for. 115 * 116 * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. 117 * @param int $stackPtr The position in the stack where the token 118 * was found. 119 * 120 * @return void 121 */ 122 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 123 { 124 $tokens = $phpcsFile->getTokens(); 125 $tokenizer = $phpcsFile->tokenizerType; 126 127 if ($tokens[$stackPtr]['code'] === T_INLINE_THEN) { 128 $end = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true); 129 if ($tokens[$end]['code'] !== T_CLOSE_PARENTHESIS) { 130 // This inline IF statement does not have its condition 131 // bracketed, so we need to guess where it starts. 132 for ($i = ($end - 1); $i >= 0; $i--) { 133 if ($tokens[$i]['code'] === T_SEMICOLON) { 134 // Stop here as we assume it is the end 135 // of the previous statement. 136 break; 137 } else if ($tokens[$i]['code'] === T_OPEN_TAG) { 138 // Stop here as this is the start of the file. 139 break; 140 } else if ($tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET) { 141 // Stop if this is the closing brace of 142 // a code block. 143 if (isset($tokens[$i]['scope_opener']) === true) { 144 break; 145 } 146 } else if ($tokens[$i]['code'] === T_OPEN_CURLY_BRACKET) { 147 // Stop if this is the opening brace of 148 // a code block. 149 if (isset($tokens[$i]['scope_closer']) === true) { 150 break; 151 } 152 } 153 }//end for 154 155 $start = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($i + 1), null, true); 156 } else { 157 if (isset($tokens[$end]['parenthesis_opener']) === false) { 158 return; 159 } 160 161 $start = $tokens[$end]['parenthesis_opener']; 162 }//end if 163 } else if ($tokens[$stackPtr]['code'] === T_FOR) { 164 if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) { 165 return; 166 } 167 168 $openingBracket = $tokens[$stackPtr]['parenthesis_opener']; 169 $closingBracket = $tokens[$stackPtr]['parenthesis_closer']; 170 171 $start = $phpcsFile->findNext(T_SEMICOLON, $openingBracket, $closingBracket); 172 $end = $phpcsFile->findNext(T_SEMICOLON, ($start + 1), $closingBracket); 173 if ($start === false || $end === false) { 174 return; 175 } 176 } else { 177 if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) { 178 return; 179 } 180 181 $start = $tokens[$stackPtr]['parenthesis_opener']; 182 $end = $tokens[$stackPtr]['parenthesis_closer']; 183 }//end if 184 185 $requiredOps = 0; 186 $foundOps = 0; 187 $foundBools = 0; 188 189 for ($i = $start; $i <= $end; $i++) { 190 $type = $tokens[$i]['code']; 191 if (in_array($type, array_keys(self::$_invalidOps[$tokenizer])) === true) { 192 $error = 'Operator %s prohibited; use %s instead'; 193 $data = array( 194 $tokens[$i]['content'], 195 self::$_invalidOps[$tokenizer][$type], 196 ); 197 $phpcsFile->addError($error, $i, 'NotAllowed', $data); 198 $foundOps++; 199 } else if (in_array($type, self::$_validOps) === true) { 200 $foundOps++; 201 } 202 203 if ($tokens[$i]['code'] === T_TRUE || $tokens[$i]['code'] === T_FALSE) { 204 $foundBools++; 205 } 206 207 if ($phpcsFile->tokenizerType !== 'JS' 208 && ($tokens[$i]['code'] === T_BOOLEAN_AND 209 || $tokens[$i]['code'] === T_BOOLEAN_OR) 210 ) { 211 $requiredOps++; 212 213 // When the instanceof operator is used with another operator 214 // like ===, you can get more ops than are required. 215 if ($foundOps > $requiredOps) { 216 $foundOps = $requiredOps; 217 } 218 219 // If we get to here and we have not found the right number of 220 // comparison operators, then we must have had an implicit 221 // true operation i.e., if ($a) instead of the required 222 // if ($a === true), so let's add an error. 223 if ($requiredOps !== $foundOps) { 224 $error = 'Implicit true comparisons prohibited; use === TRUE instead'; 225 $phpcsFile->addError($error, $stackPtr, 'ImplicitTrue'); 226 $foundOps++; 227 } 228 } 229 }//end for 230 231 $requiredOps++; 232 233 if ($phpcsFile->tokenizerType !== 'JS' 234 && $foundOps < $requiredOps 235 && ($requiredOps !== $foundBools) 236 ) { 237 $error = 'Implicit true comparisons prohibited; use === TRUE instead'; 238 $phpcsFile->addError($error, $stackPtr, 'ImplicitTrue'); 239 } 240 241 }//end process() 242 243 244}//end class 245