1<?php 2/** 3 * Sniffs_Squiz_WhiteSpace_OperatorSpacingSniff. 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 * Sniffs_Squiz_WhiteSpace_OperatorSpacingSniff. 18 * 19 * Verifies that operators have valid spacing surrounding them. 20 * 21 * @category PHP 22 * @package PHP_CodeSniffer 23 * @author Greg Sherwood <gsherwood@squiz.net> 24 * @author Marc McIntyre <mmcintyre@squiz.net> 25 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 26 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 27 * @version Release: @package_version@ 28 * @link http://pear.php.net/package/PHP_CodeSniffer 29 */ 30class Squiz_Sniffs_WhiteSpace_OperatorSpacingSniff implements PHP_CodeSniffer_Sniff 31{ 32 33 /** 34 * A list of tokenizers this sniff supports. 35 * 36 * @var array 37 */ 38 public $supportedTokenizers = array( 39 'PHP', 40 'JS', 41 ); 42 43 /** 44 * Allow newlines instead of spaces. 45 * 46 * @var boolean 47 */ 48 public $ignoreNewlines = false; 49 50 51 /** 52 * Returns an array of tokens this test wants to listen for. 53 * 54 * @return array 55 */ 56 public function register() 57 { 58 $comparison = PHP_CodeSniffer_Tokens::$comparisonTokens; 59 $operators = PHP_CodeSniffer_Tokens::$operators; 60 $assignment = PHP_CodeSniffer_Tokens::$assignmentTokens; 61 $inlineIf = array( 62 T_INLINE_THEN, 63 T_INLINE_ELSE, 64 ); 65 66 return array_unique( 67 array_merge($comparison, $operators, $assignment, $inlineIf) 68 ); 69 70 }//end register() 71 72 73 /** 74 * Processes this sniff, when one of its tokens is encountered. 75 * 76 * @param PHP_CodeSniffer_File $phpcsFile The current file being checked. 77 * @param int $stackPtr The position of the current token in 78 * the stack passed in $tokens. 79 * 80 * @return void 81 */ 82 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 83 { 84 $tokens = $phpcsFile->getTokens(); 85 86 // Skip default values in function declarations. 87 // Skip declare statements. 88 if ($tokens[$stackPtr]['code'] === T_EQUAL 89 || $tokens[$stackPtr]['code'] === T_MINUS 90 ) { 91 if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { 92 $parenthesis = array_keys($tokens[$stackPtr]['nested_parenthesis']); 93 $bracket = array_pop($parenthesis); 94 if (isset($tokens[$bracket]['parenthesis_owner']) === true) { 95 $function = $tokens[$bracket]['parenthesis_owner']; 96 if ($tokens[$function]['code'] === T_FUNCTION 97 || $tokens[$function]['code'] === T_CLOSURE 98 || $tokens[$function]['code'] === T_DECLARE 99 ) { 100 return; 101 } 102 } 103 } 104 } 105 106 if ($tokens[$stackPtr]['code'] === T_EQUAL) { 107 // Skip for '=&' case. 108 if (isset($tokens[($stackPtr + 1)]) === true 109 && $tokens[($stackPtr + 1)]['code'] === T_BITWISE_AND 110 ) { 111 return; 112 } 113 } 114 115 // Skip short ternary such as: "$foo = $bar ?: true;". 116 if (($tokens[$stackPtr]['code'] === T_INLINE_THEN 117 && $tokens[($stackPtr + 1)]['code'] === T_INLINE_ELSE) 118 || ($tokens[($stackPtr - 1)]['code'] === T_INLINE_THEN 119 && $tokens[$stackPtr]['code'] === T_INLINE_ELSE) 120 ) { 121 return; 122 } 123 124 if ($tokens[$stackPtr]['code'] === T_BITWISE_AND) { 125 // If it's not a reference, then we expect one space either side of the 126 // bitwise operator. 127 if ($phpcsFile->isReference($stackPtr) === true) { 128 return; 129 } 130 131 // Check there is one space before the & operator. 132 if ($tokens[($stackPtr - 1)]['code'] !== T_WHITESPACE) { 133 $error = 'Expected 1 space before "&" operator; 0 found'; 134 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBeforeAmp'); 135 if ($fix === true) { 136 $phpcsFile->fixer->addContentBefore($stackPtr, ' '); 137 } 138 139 $phpcsFile->recordMetric($stackPtr, 'Space before operator', 0); 140 } else { 141 if ($tokens[($stackPtr - 2)]['line'] !== $tokens[$stackPtr]['line']) { 142 $found = 'newline'; 143 } else { 144 $found = $tokens[($stackPtr - 1)]['length']; 145 } 146 147 $phpcsFile->recordMetric($stackPtr, 'Space before operator', $found); 148 if ($found !== 1 149 && ($found !== 'newline' || $this->ignoreNewlines === false) 150 ) { 151 $error = 'Expected 1 space before "&" operator; %s found'; 152 $data = array($found); 153 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBeforeAmp', $data); 154 if ($fix === true) { 155 $phpcsFile->fixer->replaceToken(($stackPtr - 1), ' '); 156 } 157 } 158 }//end if 159 160 // Check there is one space after the & operator. 161 if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { 162 $error = 'Expected 1 space after "&" operator; 0 found'; 163 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceAfterAmp'); 164 if ($fix === true) { 165 $phpcsFile->fixer->addContent($stackPtr, ' '); 166 } 167 168 $phpcsFile->recordMetric($stackPtr, 'Space after operator', 0); 169 } else { 170 if ($tokens[($stackPtr + 2)]['line'] !== $tokens[$stackPtr]['line']) { 171 $found = 'newline'; 172 } else { 173 $found = $tokens[($stackPtr + 1)]['length']; 174 } 175 176 $phpcsFile->recordMetric($stackPtr, 'Space after operator', $found); 177 if ($found !== 1 178 && ($found !== 'newline' || $this->ignoreNewlines === false) 179 ) { 180 $error = 'Expected 1 space after "&" operator; %s found'; 181 $data = array($found); 182 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfterAmp', $data); 183 if ($fix === true) { 184 $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); 185 } 186 } 187 }//end if 188 189 return; 190 }//end if 191 192 if ($tokens[$stackPtr]['code'] === T_MINUS || $tokens[$stackPtr]['code'] === T_PLUS) { 193 // Check minus spacing, but make sure we aren't just assigning 194 // a minus value or returning one. 195 $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 196 if ($tokens[$prev]['code'] === T_RETURN) { 197 // Just returning a negative value; eg. (return -1). 198 return; 199 } 200 201 if (isset(PHP_CodeSniffer_Tokens::$operators[$tokens[$prev]['code']]) === true) { 202 // Just trying to operate on a negative value; eg. ($var * -1). 203 return; 204 } 205 206 if (isset(PHP_CodeSniffer_Tokens::$comparisonTokens[$tokens[$prev]['code']]) === true) { 207 // Just trying to compare a negative value; eg. ($var === -1). 208 return; 209 } 210 211 if (isset(PHP_CodeSniffer_Tokens::$booleanOperators[$tokens[$prev]['code']]) === true) { 212 // Just trying to compare a negative value; eg. ($var || -1 === $b). 213 return; 214 } 215 216 if (isset(PHP_CodeSniffer_Tokens::$assignmentTokens[$tokens[$prev]['code']]) === true) { 217 // Just trying to assign a negative value; eg. ($var = -1). 218 return; 219 } 220 221 // A list of tokens that indicate that the token is not 222 // part of an arithmetic operation. 223 $invalidTokens = array( 224 T_COMMA => true, 225 T_OPEN_PARENTHESIS => true, 226 T_OPEN_SQUARE_BRACKET => true, 227 T_OPEN_SHORT_ARRAY => true, 228 T_DOUBLE_ARROW => true, 229 T_COLON => true, 230 T_INLINE_THEN => true, 231 T_INLINE_ELSE => true, 232 T_CASE => true, 233 ); 234 235 if (isset($invalidTokens[$tokens[$prev]['code']]) === true) { 236 // Just trying to use a negative value; eg. myFunction($var, -2). 237 return; 238 } 239 }//end if 240 241 $operator = $tokens[$stackPtr]['content']; 242 243 if ($tokens[($stackPtr - 1)]['code'] !== T_WHITESPACE) { 244 $error = "Expected 1 space before \"$operator\"; 0 found"; 245 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceBefore'); 246 if ($fix === true) { 247 $phpcsFile->fixer->addContentBefore($stackPtr, ' '); 248 } 249 250 $phpcsFile->recordMetric($stackPtr, 'Space before operator', 0); 251 } else if (isset(PHP_CodeSniffer_Tokens::$assignmentTokens[$tokens[$stackPtr]['code']]) === false) { 252 // Don't throw an error for assignments, because other standards allow 253 // multiple spaces there to align multiple assignments. 254 if ($tokens[($stackPtr - 2)]['line'] !== $tokens[$stackPtr]['line']) { 255 $found = 'newline'; 256 } else { 257 $found = $tokens[($stackPtr - 1)]['length']; 258 } 259 260 $phpcsFile->recordMetric($stackPtr, 'Space before operator', $found); 261 if ($found !== 1 262 && ($found !== 'newline' || $this->ignoreNewlines === false) 263 ) { 264 $error = 'Expected 1 space before "%s"; %s found'; 265 $data = array( 266 $operator, 267 $found, 268 ); 269 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingBefore', $data); 270 if ($fix === true) { 271 $phpcsFile->fixer->beginChangeset(); 272 if ($found === 'newline') { 273 $i = ($stackPtr - 2); 274 while ($tokens[$i]['code'] === T_WHITESPACE) { 275 $phpcsFile->fixer->replaceToken($i, ''); 276 $i--; 277 } 278 } 279 280 $phpcsFile->fixer->replaceToken(($stackPtr - 1), ' '); 281 $phpcsFile->fixer->endChangeset(); 282 } 283 }//end if 284 }//end if 285 286 if (isset($tokens[($stackPtr + 1)]) === false) { 287 return; 288 } 289 290 if ($tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) { 291 $error = "Expected 1 space after \"$operator\"; 0 found"; 292 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NoSpaceAfter'); 293 if ($fix === true) { 294 $phpcsFile->fixer->addContent($stackPtr, ' '); 295 } 296 297 $phpcsFile->recordMetric($stackPtr, 'Space after operator', 0); 298 } else { 299 if (isset($tokens[($stackPtr + 2)]) === true 300 && $tokens[($stackPtr + 2)]['line'] !== $tokens[$stackPtr]['line'] 301 ) { 302 $found = 'newline'; 303 } else { 304 $found = $tokens[($stackPtr + 1)]['length']; 305 } 306 307 $phpcsFile->recordMetric($stackPtr, 'Space after operator', $found); 308 if ($found !== 1 309 && ($found !== 'newline' || $this->ignoreNewlines === false) 310 ) { 311 $error = 'Expected 1 space after "%s"; %s found'; 312 $data = array( 313 $operator, 314 $found, 315 ); 316 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'SpacingAfter', $data); 317 if ($fix === true) { 318 $phpcsFile->fixer->replaceToken(($stackPtr + 1), ' '); 319 } 320 } 321 }//end if 322 323 }//end process() 324 325 326}//end class 327