1<?php 2/** 3 * Squiz_Sniffs_Functions_MultiLineFunctionDeclarationSniff. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 11 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 12 * @link http://pear.php.net/package/PHP_CodeSniffer 13 */ 14 15if (class_exists('PEAR_Sniffs_Functions_FunctionDeclarationSniff', true) === false) { 16 $error = 'Class PEAR_Sniffs_Functions_FunctionDeclarationSniff not found'; 17 throw new PHP_CodeSniffer_Exception($error); 18} 19 20/** 21 * Squiz_Sniffs_Functions_MultiLineFunctionDeclarationSniff. 22 * 23 * Ensure single and multi-line function declarations are defined correctly. 24 * 25 * @category PHP 26 * @package PHP_CodeSniffer 27 * @author Greg Sherwood <gsherwood@squiz.net> 28 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 29 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 30 * @version Release: @package_version@ 31 * @link http://pear.php.net/package/PHP_CodeSniffer 32 */ 33class Squiz_Sniffs_Functions_MultiLineFunctionDeclarationSniff extends PEAR_Sniffs_Functions_FunctionDeclarationSniff 34{ 35 36 /** 37 * A list of tokenizers this sniff supports. 38 * 39 * @var array 40 */ 41 public $supportedTokenizers = array( 42 'PHP', 43 'JS', 44 ); 45 46 47 /** 48 * Determine if this is a multi-line function declaration. 49 * 50 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 51 * @param int $stackPtr The position of the current token 52 * in the stack passed in $tokens. 53 * @param int $openBracket The position of the opening bracket 54 * in the stack passed in $tokens. 55 * @param array $tokens The stack of tokens that make up 56 * the file. 57 * 58 * @return void 59 */ 60 public function isMultiLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $openBracket, $tokens) 61 { 62 $bracketsToCheck = array($stackPtr => $openBracket); 63 64 // Closures may use the USE keyword and so be multi-line in this way. 65 if ($tokens[$stackPtr]['code'] === T_CLOSURE) { 66 $use = $phpcsFile->findNext(T_USE, ($tokens[$openBracket]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']); 67 if ($use !== false) { 68 $open = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1)); 69 if ($open !== false) { 70 $bracketsToCheck[$use] = $open; 71 } 72 } 73 } 74 75 foreach ($bracketsToCheck as $stackPtr => $openBracket) { 76 // If the first argument is on a new line, this is a multi-line 77 // function declaration, even if there is only one argument. 78 $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($openBracket + 1), null, true); 79 if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { 80 return true; 81 } 82 83 $closeBracket = $tokens[$openBracket]['parenthesis_closer']; 84 85 $end = $phpcsFile->findEndOfStatement($openBracket + 1); 86 while ($tokens[$end]['code'] === T_COMMA) { 87 // If the next bit of code is not on the same line, this is a 88 // multi-line function declaration. 89 $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($end + 1), $closeBracket, true); 90 if ($next === false) { 91 continue(2); 92 } 93 94 if ($tokens[$next]['line'] !== $tokens[$end]['line']) { 95 return true; 96 } 97 98 $end = $phpcsFile->findEndOfStatement($next); 99 } 100 101 // We've reached the last argument, so see if the next content 102 // (should be the close bracket) is also on the same line. 103 $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($end + 1), $closeBracket, true); 104 if ($next !== false && $tokens[$next]['line'] !== $tokens[$end]['line']) { 105 return true; 106 } 107 }//end foreach 108 109 return false; 110 111 }//end isMultiLineDeclaration() 112 113 114 /** 115 * Processes multi-line declarations. 116 * 117 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 118 * @param int $stackPtr The position of the current token 119 * in the stack passed in $tokens. 120 * @param array $tokens The stack of tokens that make up 121 * the file. 122 * 123 * @return void 124 */ 125 public function processMultiLineDeclaration(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens) 126 { 127 // We do everything the parent sniff does, and a bit more. 128 parent::processMultiLineDeclaration($phpcsFile, $stackPtr, $tokens); 129 130 $openBracket = $tokens[$stackPtr]['parenthesis_opener']; 131 $this->processBracket($phpcsFile, $openBracket, $tokens, 'function'); 132 133 if ($tokens[$stackPtr]['code'] !== T_CLOSURE) { 134 return; 135 } 136 137 $use = $phpcsFile->findNext(T_USE, ($tokens[$stackPtr]['parenthesis_closer'] + 1), $tokens[$stackPtr]['scope_opener']); 138 if ($use === false) { 139 return; 140 } 141 142 $openBracket = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($use + 1), null); 143 $this->processBracket($phpcsFile, $openBracket, $tokens, 'use'); 144 145 // Also check spacing. 146 if ($tokens[($use - 1)]['code'] === T_WHITESPACE) { 147 $gap = strlen($tokens[($use - 1)]['content']); 148 } else { 149 $gap = 0; 150 } 151 152 }//end processMultiLineDeclaration() 153 154 155 /** 156 * Processes the contents of a single set of brackets. 157 * 158 * @param PHP_CodeSniffer_File $phpcsFile The file being scanned. 159 * @param int $openBracket The position of the open bracket 160 * in the stack passed in $tokens. 161 * @param array $tokens The stack of tokens that make up 162 * the file. 163 * @param string $type The type of the token the brackets 164 * belong to (function or use). 165 * 166 * @return void 167 */ 168 public function processBracket(PHP_CodeSniffer_File $phpcsFile, $openBracket, $tokens, $type='function') 169 { 170 $errorPrefix = ''; 171 if ($type === 'use') { 172 $errorPrefix = 'Use'; 173 } 174 175 $closeBracket = $tokens[$openBracket]['parenthesis_closer']; 176 177 // The open bracket should be the last thing on the line. 178 if ($tokens[$openBracket]['line'] !== $tokens[$closeBracket]['line']) { 179 $next = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($openBracket + 1), null, true); 180 if ($tokens[$next]['line'] !== ($tokens[$openBracket]['line'] + 1)) { 181 $error = 'The first parameter of a multi-line '.$type.' declaration must be on the line after the opening bracket'; 182 $fix = $phpcsFile->addFixableError($error, $next, $errorPrefix.'FirstParamSpacing'); 183 if ($fix === true) { 184 $phpcsFile->fixer->addNewline($openBracket); 185 } 186 } 187 } 188 189 // Each line between the brackets should contain a single parameter. 190 $lastComma = null; 191 for ($i = ($openBracket + 1); $i < $closeBracket; $i++) { 192 // Skip brackets, like arrays, as they can contain commas. 193 if (isset($tokens[$i]['bracket_opener']) === true) { 194 $i = $tokens[$i]['bracket_closer']; 195 continue; 196 } 197 198 if (isset($tokens[$i]['parenthesis_opener']) === true) { 199 $i = $tokens[$i]['parenthesis_closer']; 200 continue; 201 } 202 203 if ($tokens[$i]['code'] !== T_COMMA) { 204 continue; 205 } 206 207 $next = $phpcsFile->findNext(T_WHITESPACE, ($i + 1), null, true); 208 if ($tokens[$next]['line'] === $tokens[$i]['line']) { 209 $error = 'Multi-line '.$type.' declarations must define one parameter per line'; 210 $fix = $phpcsFile->addFixableError($error, $next, $errorPrefix.'OneParamPerLine'); 211 if ($fix === true) { 212 $phpcsFile->fixer->addNewline($i); 213 } 214 } 215 }//end for 216 217 }//end processBracket() 218 219 220}//end class 221