1<?php 2/** 3 * Squiz_Sniffs_Functions_FunctionDeclarationArgumentSpacingSniff. 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_Functions_FunctionDeclarationArgumentSpacingSniff. 18 * 19 * Checks that arguments in function declarations are spaced correctly. 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_Functions_FunctionDeclarationArgumentSpacingSniff implements PHP_CodeSniffer_Sniff 31{ 32 33 /** 34 * How many spaces should surround the equals signs. 35 * 36 * @var int 37 */ 38 public $equalsSpacing = 0; 39 40 /** 41 * How many spaces should follow the opening bracket. 42 * 43 * @var int 44 */ 45 public $requiredSpacesAfterOpen = 0; 46 47 /** 48 * How many spaces should precede the closing bracket. 49 * 50 * @var int 51 */ 52 public $requiredSpacesBeforeClose = 0; 53 54 55 /** 56 * Returns an array of tokens this test wants to listen for. 57 * 58 * @return array 59 */ 60 public function register() 61 { 62 return array( 63 T_FUNCTION, 64 T_CLOSURE, 65 ); 66 67 }//end register() 68 69 70 /** 71 * Processes this test, when one of its tokens is encountered. 72 * 73 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 74 * @param int $stackPtr The position of the current token in the 75 * stack passed in $tokens. 76 * 77 * @return void 78 */ 79 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 80 { 81 $tokens = $phpcsFile->getTokens(); 82 83 if (isset($tokens[$stackPtr]['parenthesis_opener']) === false 84 || isset($tokens[$stackPtr]['parenthesis_closer']) === false 85 || $tokens[$stackPtr]['parenthesis_opener'] === null 86 || $tokens[$stackPtr]['parenthesis_closer'] === null 87 ) { 88 return; 89 } 90 91 $this->equalsSpacing = (int) $this->equalsSpacing; 92 $this->requiredSpacesAfterOpen = (int) $this->requiredSpacesAfterOpen; 93 $this->requiredSpacesBeforeClose = (int) $this->requiredSpacesBeforeClose; 94 95 $openBracket = $tokens[$stackPtr]['parenthesis_opener']; 96 $this->processBracket($phpcsFile, $openBracket); 97 98 if ($tokens[$stackPtr]['code'] === T_CLOSURE) { 99 $use = $phpcsFile->findNext(T_USE, ($tokens[$openBracket]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']); 100 if ($use !== false) { 101 $openBracket = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1), null); 102 $this->processBracket($phpcsFile, $openBracket); 103 } 104 } 105 106 }//end process() 107 108 109 /** 110 * Processes the contents of a single set of brackets. 111 * 112 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 113 * @param int $openBracket The position of the open bracket 114 * in the stack passed in $tokens. 115 * 116 * @return void 117 */ 118 public function processBracket(PHP_CodeSniffer_File $phpcsFile, $openBracket) 119 { 120 $tokens = $phpcsFile->getTokens(); 121 $closeBracket = $tokens[$openBracket]['parenthesis_closer']; 122 $multiLine = ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']); 123 124 $nextParam = $openBracket; 125 $params = array(); 126 while (($nextParam = $phpcsFile->findNext(T_VARIABLE, ($nextParam + 1), $closeBracket)) !== false) { 127 $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($nextParam + 1), ($closeBracket + 1), true); 128 if ($nextToken === false) { 129 break; 130 } 131 132 $nextCode = $tokens[$nextToken]['code']; 133 134 if ($nextCode === T_EQUAL) { 135 // Check parameter default spacing. 136 $spacesBefore = 0; 137 if (($nextToken - $nextParam) > 1) { 138 $spacesBefore = strlen($tokens[($nextParam + 1)]['content']); 139 } 140 141 if ($spacesBefore !== $this->equalsSpacing) { 142 $error = 'Incorrect spacing between argument "%s" and equals sign; expected '.$this->equalsSpacing.' but found %s'; 143 $data = array( 144 $tokens[$nextParam]['content'], 145 $spacesBefore, 146 ); 147 148 $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpaceBeforeEquals', $data); 149 if ($fix === true) { 150 $padding = str_repeat(' ', $this->equalsSpacing); 151 if ($spacesBefore === 0) { 152 $phpcsFile->fixer->addContentBefore($nextToken, $padding); 153 } else { 154 $phpcsFile->fixer->replaceToken(($nextToken - 1), $padding); 155 } 156 } 157 }//end if 158 159 $spacesAfter = 0; 160 if ($tokens[($nextToken + 1)]['code'] === T_WHITESPACE) { 161 $spacesAfter = strlen($tokens[($nextToken + 1)]['content']); 162 } 163 164 if ($spacesAfter !== $this->equalsSpacing) { 165 $error = 'Incorrect spacing between default value and equals sign for argument "%s"; expected '.$this->equalsSpacing.' but found %s'; 166 $data = array( 167 $tokens[$nextParam]['content'], 168 $spacesAfter, 169 ); 170 171 $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpaceAfterDefault', $data); 172 if ($fix === true) { 173 $padding = str_repeat(' ', $this->equalsSpacing); 174 if ($spacesAfter === 0) { 175 $phpcsFile->fixer->addContent($nextToken, $padding); 176 } else { 177 $phpcsFile->fixer->replaceToken(($nextToken + 1), $padding); 178 } 179 } 180 }//end if 181 }//end if 182 183 // Find and check the comma (if there is one). 184 $nextComma = $phpcsFile->findNext(T_COMMA, ($nextParam + 1), $closeBracket); 185 if ($nextComma !== false) { 186 // Comma found. 187 if ($tokens[($nextComma - 1)]['code'] === T_WHITESPACE) { 188 $error = 'Expected 0 spaces between argument "%s" and comma; %s found'; 189 $data = array( 190 $tokens[$nextParam]['content'], 191 strlen($tokens[($nextComma - 1)]['content']), 192 ); 193 194 $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpaceBeforeComma', $data); 195 if ($fix === true) { 196 $phpcsFile->fixer->replaceToken(($nextComma - 1), ''); 197 } 198 } 199 } 200 201 $checkToken = ($nextParam - 1); 202 $prev = $phpcsFile->findPrevious(T_WHITESPACE, $checkToken, null, true); 203 if ($tokens[$prev]['code'] === T_ELLIPSIS) { 204 $checkToken = ($prev - 1); 205 } 206 207 // Take references into account when expecting the 208 // location of whitespace. 209 if ($phpcsFile->isReference($checkToken) === true) { 210 $whitespace = ($checkToken - 1); 211 } else { 212 $whitespace = $checkToken; 213 } 214 215 if (empty($params) === false) { 216 // This is not the first argument in the function declaration. 217 $arg = $tokens[$nextParam]['content']; 218 219 // Before we throw an error, make sure there is no type hint. 220 $comma = $phpcsFile->findPrevious(T_COMMA, ($nextParam - 1)); 221 $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($comma + 1), null, true); 222 if ($phpcsFile->isReference($nextToken) === true) { 223 $nextToken++; 224 } 225 226 $gap = 0; 227 if ($tokens[$whitespace]['code'] === T_WHITESPACE) { 228 $gap = strlen($tokens[$whitespace]['content']); 229 } 230 231 if ($nextToken !== $nextParam) { 232 // There was a type hint, so check the spacing between 233 // the hint and the variable as well. 234 $hint = $tokens[$nextToken]['content']; 235 236 if ($gap !== 1) { 237 $error = 'Expected 1 space between type hint and argument "%s"; %s found'; 238 $data = array( 239 $arg, 240 $gap, 241 ); 242 $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpacingAfterHint', $data); 243 if ($fix === true) { 244 if ($gap === 0) { 245 $phpcsFile->fixer->addContent($whitespace, ' '); 246 } else { 247 $phpcsFile->fixer->replaceToken($whitespace, ' '); 248 } 249 } 250 } 251 252 if ($multiLine === false) { 253 if ($tokens[($comma + 1)]['code'] !== T_WHITESPACE) { 254 $error = 'Expected 1 space between comma and type hint "%s"; 0 found'; 255 $data = array($hint); 256 $fix = $phpcsFile->addFixableError($error, $nextToken, 'NoSpaceBeforeHint', $data); 257 if ($fix === true) { 258 $phpcsFile->fixer->addContent($comma, ' '); 259 } 260 } else { 261 $gap = strlen($tokens[($comma + 1)]['content']); 262 if ($gap !== 1) { 263 $error = 'Expected 1 space between comma and type hint "%s"; %s found'; 264 $data = array( 265 $hint, 266 $gap, 267 ); 268 $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpacingBeforeHint', $data); 269 if ($fix === true) { 270 $phpcsFile->fixer->replaceToken(($comma + 1), ' '); 271 } 272 } 273 }//end if 274 }//end if 275 } else { 276 // No type hint. 277 if ($gap === 0) { 278 $error = 'Expected 1 space between comma and argument "%s"; 0 found'; 279 $data = array($arg); 280 $fix = $phpcsFile->addFixableError($error, $nextToken, 'NoSpaceBeforeArg', $data); 281 if ($fix === true) { 282 $phpcsFile->fixer->addContent($whitespace, ' '); 283 } 284 } else if ($gap !== 1) { 285 // Just make sure this is not actually an indent. 286 if ($tokens[$whitespace]['line'] === $tokens[($whitespace - 1)]['line']) { 287 $error = 'Expected 1 space between comma and argument "%s"; %s found'; 288 $data = array( 289 $arg, 290 $gap, 291 ); 292 293 $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpacingBeforeArg', $data); 294 if ($fix === true) { 295 $phpcsFile->fixer->replaceToken($whitespace, ' '); 296 } 297 } 298 }//end if 299 }//end if 300 } else { 301 $gap = 0; 302 if ($tokens[$whitespace]['code'] === T_WHITESPACE) { 303 $gap = strlen($tokens[$whitespace]['content']); 304 } 305 306 $arg = $tokens[$nextParam]['content']; 307 308 // Before we throw an error, make sure there is no type hint. 309 $bracket = $phpcsFile->findPrevious(T_OPEN_PARENTHESIS, ($nextParam - 1)); 310 $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($bracket + 1), null, true); 311 if ($phpcsFile->isReference($nextToken) === true) { 312 $nextToken++; 313 } 314 315 if ($tokens[$nextToken]['code'] !== T_ELLIPSIS && $nextToken !== $nextParam) { 316 // There was a type hint, so check the spacing between 317 // the hint and the variable as well. 318 $hint = $tokens[$nextToken]['content']; 319 320 if ($gap !== 1) { 321 $error = 'Expected 1 space between type hint and argument "%s"; %s found'; 322 $data = array( 323 $arg, 324 $gap, 325 ); 326 $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpacingAfterHint', $data); 327 if ($fix === true) { 328 if ($gap === 0) { 329 $phpcsFile->fixer->addContent($nextToken, ' '); 330 } else { 331 $phpcsFile->fixer->replaceToken(($nextToken + 1), ' '); 332 } 333 } 334 } 335 336 $spaceAfterOpen = 0; 337 if ($tokens[($bracket + 1)]['code'] === T_WHITESPACE) { 338 $spaceAfterOpen = strlen($tokens[($bracket + 1)]['content']); 339 } 340 341 if ($multiLine === false && $spaceAfterOpen !== $this->requiredSpacesAfterOpen) { 342 $error = 'Expected %s spaces between opening bracket and type hint "%s"; %s found'; 343 $data = array( 344 $this->requiredSpacesAfterOpen, 345 $hint, 346 $spaceAfterOpen, 347 ); 348 $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpacingAfterOpenHint', $data); 349 if ($fix === true) { 350 $padding = str_repeat(' ', $this->requiredSpacesAfterOpen); 351 if ($spaceAfterOpen === 0) { 352 $phpcsFile->fixer->addContent($openBracket, $padding); 353 } else { 354 $phpcsFile->fixer->replaceToken(($openBracket + 1), $padding); 355 } 356 } 357 } 358 } else if ($multiLine === false && $gap !== $this->requiredSpacesAfterOpen) { 359 $error = 'Expected %s spaces between opening bracket and argument "%s"; %s found'; 360 $data = array( 361 $this->requiredSpacesAfterOpen, 362 $arg, 363 $gap, 364 ); 365 $fix = $phpcsFile->addFixableError($error, $nextToken, 'SpacingAfterOpen', $data); 366 if ($fix === true) { 367 $padding = str_repeat(' ', $this->requiredSpacesAfterOpen); 368 if ($gap === 0) { 369 $phpcsFile->fixer->addContent($openBracket, $padding); 370 } else { 371 $phpcsFile->fixer->replaceToken(($openBracket + 1), $padding); 372 } 373 } 374 }//end if 375 }//end if 376 377 $params[] = $nextParam; 378 }//end while 379 380 $gap = 0; 381 if ($tokens[($closeBracket - 1)]['code'] === T_WHITESPACE) { 382 $gap = strlen($tokens[($closeBracket - 1)]['content']); 383 } 384 385 if (empty($params) === true) { 386 // There are no parameters for this function. 387 if (($closeBracket - $openBracket) !== 1) { 388 $error = 'Expected 0 spaces between brackets of function declaration; %s found'; 389 $data = array($gap); 390 $fix = $phpcsFile->addFixableError($error, $openBracket, 'SpacingBetween', $data); 391 if ($fix === true) { 392 $phpcsFile->fixer->replaceToken(($openBracket + 1), ''); 393 } 394 } 395 } else if ($multiLine === false && $gap !== $this->requiredSpacesBeforeClose) { 396 $lastParam = array_pop($params); 397 $error = 'Expected %s spaces between argument "%s" and closing bracket; %s found'; 398 $data = array( 399 $this->requiredSpacesBeforeClose, 400 $tokens[$lastParam]['content'], 401 $gap, 402 ); 403 $fix = $phpcsFile->addFixableError($error, $closeBracket, 'SpacingBeforeClose', $data); 404 if ($fix === true) { 405 $padding = str_repeat(' ', $this->requiredSpacesBeforeClose); 406 if ($gap === 0) { 407 $phpcsFile->fixer->addContentBefore($closeBracket, $padding); 408 } else { 409 $phpcsFile->fixer->replaceToken(($closeBracket - 1), $padding); 410 } 411 } 412 }//end if 413 414 }//end processBracket() 415 416 417}//end class 418