1<?php 2/** 3 * Squiz_Sniffs_Operators_IncrementDecrementUsageSniff. 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 * Squiz_Sniffs_Operators_IncrementDecrementUsageSniff. 18 * 19 * Tests that the ++ operators are used when possible and not 20 * used when it makes the code confusing. 21 * 22 * @category PHP 23 * @package PHP_CodeSniffer 24 * @author Greg Sherwood <gsherwood@squiz.net> 25 * @author Marc McIntyre <mmcintyre@squiz.net> 26 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 27 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 28 * @version Release: @package_version@ 29 * @link http://pear.php.net/package/PHP_CodeSniffer 30 */ 31class Squiz_Sniffs_Operators_IncrementDecrementUsageSniff implements PHP_CodeSniffer_Sniff 32{ 33 34 35 /** 36 * Returns an array of tokens this test wants to listen for. 37 * 38 * @return array 39 */ 40 public function register() 41 { 42 return array( 43 T_EQUAL, 44 T_PLUS_EQUAL, 45 T_MINUS_EQUAL, 46 T_INC, 47 T_DEC, 48 ); 49 50 }//end register() 51 52 53 /** 54 * Processes this test, when one of its tokens is encountered. 55 * 56 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 57 * @param int $stackPtr The position of the current token 58 * in the stack passed in $tokens. 59 * 60 * @return void 61 */ 62 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 63 { 64 $tokens = $phpcsFile->getTokens(); 65 66 if ($tokens[$stackPtr]['code'] === T_INC || $tokens[$stackPtr]['code'] === T_DEC) { 67 $this->processIncDec($phpcsFile, $stackPtr); 68 } else { 69 $this->processAssignment($phpcsFile, $stackPtr); 70 } 71 72 }//end process() 73 74 75 /** 76 * Checks to ensure increment and decrement operators are not confusing. 77 * 78 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 79 * @param int $stackPtr The position of the current token 80 * in the stack passed in $tokens. 81 * 82 * @return void 83 */ 84 protected function processIncDec(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 85 { 86 $tokens = $phpcsFile->getTokens(); 87 88 // Work out where the variable is so we know where to 89 // start looking for other operators. 90 if ($tokens[($stackPtr - 1)]['code'] === T_VARIABLE 91 || ($tokens[($stackPtr - 1)]['code'] === T_STRING 92 && $tokens[($stackPtr - 2)]['code'] === T_OBJECT_OPERATOR) 93 ) { 94 $start = ($stackPtr + 1); 95 } else { 96 $start = ($stackPtr + 2); 97 } 98 99 $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $start, null, true); 100 if ($next === false) { 101 return; 102 } 103 104 if (isset(PHP_CodeSniffer_Tokens::$arithmeticTokens[$tokens[$next]['code']]) === true) { 105 $error = 'Increment and decrement operators cannot be used in an arithmetic operation'; 106 $phpcsFile->addError($error, $stackPtr, 'NotAllowed'); 107 return; 108 } 109 110 $prev = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($start - 3), null, true); 111 if ($prev === false) { 112 return; 113 } 114 115 // Check if this is in a string concat. 116 if ($tokens[$next]['code'] === T_STRING_CONCAT || $tokens[$prev]['code'] === T_STRING_CONCAT) { 117 $error = 'Increment and decrement operators must be bracketed when used in string concatenation'; 118 $phpcsFile->addError($error, $stackPtr, 'NoBrackets'); 119 } 120 121 }//end processIncDec() 122 123 124 /** 125 * Checks to ensure increment and decrement operators are used. 126 * 127 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 128 * @param int $stackPtr The position of the current token 129 * in the stack passed in $tokens. 130 * 131 * @return void 132 */ 133 protected function processAssignment(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 134 { 135 $tokens = $phpcsFile->getTokens(); 136 137 $assignedVar = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true); 138 // Not an assignment, return. 139 if ($tokens[$assignedVar]['code'] !== T_VARIABLE) { 140 return; 141 } 142 143 $statementEnd = $phpcsFile->findNext(array(T_SEMICOLON, T_CLOSE_PARENTHESIS, T_CLOSE_SQUARE_BRACKET, T_CLOSE_CURLY_BRACKET), $stackPtr); 144 145 // If there is anything other than variables, numbers, spaces or operators we need to return. 146 $noiseTokens = $phpcsFile->findNext(array(T_LNUMBER, T_VARIABLE, T_WHITESPACE, T_PLUS, T_MINUS, T_OPEN_PARENTHESIS), ($stackPtr + 1), $statementEnd, true); 147 148 if ($noiseTokens !== false) { 149 return; 150 } 151 152 // If we are already using += or -=, we need to ignore 153 // the statement if a variable is being used. 154 if ($tokens[$stackPtr]['code'] !== T_EQUAL) { 155 $nextVar = $phpcsFile->findNext(T_VARIABLE, ($stackPtr + 1), $statementEnd); 156 if ($nextVar !== false) { 157 return; 158 } 159 } 160 161 if ($tokens[$stackPtr]['code'] === T_EQUAL) { 162 $nextVar = ($stackPtr + 1); 163 $previousVariable = ($stackPtr + 1); 164 $variableCount = 0; 165 while (($nextVar = $phpcsFile->findNext(T_VARIABLE, ($nextVar + 1), $statementEnd)) !== false) { 166 $previousVariable = $nextVar; 167 $variableCount++; 168 } 169 170 if ($variableCount !== 1) { 171 return; 172 } 173 174 $nextVar = $previousVariable; 175 if ($tokens[$nextVar]['content'] !== $tokens[$assignedVar]['content']) { 176 return; 177 } 178 } 179 180 // We have only one variable, and it's the same as what is being assigned, 181 // so we need to check what is being added or subtracted. 182 $nextNumber = ($stackPtr + 1); 183 $previousNumber = ($stackPtr + 1); 184 $numberCount = 0; 185 while (($nextNumber = $phpcsFile->findNext(array(T_LNUMBER), ($nextNumber + 1), $statementEnd, false)) !== false) { 186 $previousNumber = $nextNumber; 187 $numberCount++; 188 } 189 190 if ($numberCount !== 1) { 191 return; 192 } 193 194 $nextNumber = $previousNumber; 195 if ($tokens[$nextNumber]['content'] === '1') { 196 if ($tokens[$stackPtr]['code'] === T_EQUAL) { 197 $opToken = $phpcsFile->findNext(array(T_PLUS, T_MINUS), ($nextVar + 1), $statementEnd); 198 if ($opToken === false) { 199 // Operator was before the variable, like: 200 // $var = 1 + $var; 201 // So we ignore it. 202 return; 203 } 204 205 $operator = $tokens[$opToken]['content']; 206 } else { 207 $operator = substr($tokens[$stackPtr]['content'], 0, 1); 208 } 209 210 // If we are adding or subtracting negative value, the operator 211 // needs to be reversed. 212 if ($tokens[$stackPtr]['code'] !== T_EQUAL) { 213 $negative = $phpcsFile->findPrevious(T_MINUS, ($nextNumber - 1), $stackPtr); 214 if ($negative !== false) { 215 if ($operator === '+') { 216 $operator = '-'; 217 } else { 218 $operator = '+'; 219 } 220 } 221 } 222 223 $expected = $tokens[$assignedVar]['content'].$operator.$operator; 224 $found = $phpcsFile->getTokensAsString($assignedVar, ($statementEnd - $assignedVar + 1)); 225 226 if ($operator === '+') { 227 $error = 'Increment'; 228 } else { 229 $error = 'Decrement'; 230 } 231 232 $error .= " operators should be used where possible; found \"$found\" but expected \"$expected\""; 233 $phpcsFile->addError($error, $stackPtr, 'Found'); 234 }//end if 235 236 }//end processAssignment() 237 238 239}//end class 240