1<?php 2/** 3 * Squiz_Sniffs_PHP_DisallowMultipleAssignmentsSniff. 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_PHP_DisallowMultipleAssignmentsSniff. 18 * 19 * Ensures that there is only one value assignment on a line, and that it is 20 * the first thing on the line. 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_PHP_DisallowMultipleAssignmentsSniff 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(T_EQUAL); 43 44 }//end register() 45 46 47 /** 48 * Processes this test, when one of its tokens is encountered. 49 * 50 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 51 * @param int $stackPtr The position of the current token in the 52 * stack passed in $tokens. 53 * 54 * @return void 55 */ 56 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 57 { 58 $tokens = $phpcsFile->getTokens(); 59 60 // Ignore default value assignments in function definitions. 61 $function = $phpcsFile->findPrevious(array(T_FUNCTION, T_CLOSURE), ($stackPtr - 1), null, false, null, true); 62 if ($function !== false) { 63 $opener = $tokens[$function]['parenthesis_opener']; 64 $closer = $tokens[$function]['parenthesis_closer']; 65 if ($opener < $stackPtr && $closer > $stackPtr) { 66 return; 67 } 68 } 69 70 /* 71 The general rule is: 72 Find an equal sign and go backwards along the line. If you hit an 73 end bracket, skip to the opening bracket. When you find a variable, 74 stop. That variable must be the first non-empty token on the line 75 or in the statement. If not, throw an error. 76 */ 77 78 for ($varToken = ($stackPtr - 1); $varToken >= 0; $varToken--) { 79 // Skip brackets. 80 if (isset($tokens[$varToken]['parenthesis_opener']) === true && $tokens[$varToken]['parenthesis_opener'] < $varToken) { 81 $varToken = $tokens[$varToken]['parenthesis_opener']; 82 continue; 83 } 84 85 if (isset($tokens[$varToken]['bracket_opener']) === true) { 86 $varToken = $tokens[$varToken]['bracket_opener']; 87 continue; 88 } 89 90 if ($tokens[$varToken]['code'] === T_SEMICOLON) { 91 // We've reached the next statement, so we 92 // didn't find a variable. 93 return; 94 } 95 96 if ($tokens[$varToken]['code'] === T_VARIABLE) { 97 // We found our variable. 98 break; 99 } 100 }//end for 101 102 if ($varToken <= 0) { 103 // Didn't find a variable. 104 return; 105 } 106 107 // Deal with this type of variable: self::$var by setting the var 108 // token to be "self" rather than "$var". 109 if ($tokens[($varToken - 1)]['code'] === T_DOUBLE_COLON) { 110 $varToken = ($varToken - 2); 111 } 112 113 // Deal with this type of variable: $obj->$var by setting the var 114 // token to be "$obj" rather than "$var". 115 if ($tokens[($varToken - 1)]['code'] === T_OBJECT_OPERATOR) { 116 $varToken = ($varToken - 2); 117 } 118 119 // Deal with this type of variable: $$var by setting the var 120 // token to be "$" rather than "$var". 121 if ($tokens[($varToken - 1)]['content'] === '$') { 122 $varToken--; 123 } 124 125 // Ignore member var definitions. 126 $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($varToken - 1), null, true); 127 if (isset(PHP_CodeSniffer_Tokens::$scopeModifiers[$tokens[$prev]['code']]) === true 128 || $tokens[$prev]['code'] === T_VAR 129 ) { 130 return; 131 } 132 133 if ($tokens[$prev]['code'] === T_STATIC) { 134 return; 135 } 136 137 // Make sure this variable is the first thing in the statement. 138 $varLine = $tokens[$varToken]['line']; 139 $prevLine = 0; 140 for ($i = ($varToken - 1); $i >= 0; $i--) { 141 if ($tokens[$i]['code'] === T_SEMICOLON) { 142 // We reached the end of the statement. 143 return; 144 } 145 146 if ($tokens[$i]['code'] === T_INLINE_THEN) { 147 // We reached the end of the inline THEN statement. 148 return; 149 } 150 151 if ($tokens[$i]['code'] === T_INLINE_ELSE) { 152 // We reached the end of the inline ELSE statement. 153 return; 154 } 155 156 if ($tokens[$i]['code'] === T_OPEN_TAG) { 157 // We reached the end of the code block. 158 return; 159 } 160 161 if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i]['code']]) === false) { 162 $prevLine = $tokens[$i]['line']; 163 break; 164 } 165 }//end for 166 167 // Ignore the first part of FOR loops as we are allowed to 168 // assign variables there even though the variable is not the 169 // first thing on the line. Also ignore WHILE loops. 170 if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS && isset($tokens[$i]['parenthesis_owner']) === true) { 171 $owner = $tokens[$i]['parenthesis_owner']; 172 if ($tokens[$owner]['code'] === T_FOR || $tokens[$owner]['code'] === T_WHILE) { 173 return; 174 } 175 } 176 177 if ($prevLine === $varLine) { 178 $error = 'Assignments must be the first block of code on a line'; 179 $phpcsFile->addError($error, $stackPtr, 'Found'); 180 } 181 182 }//end process() 183 184 185}//end class 186