1<?php 2/** 3 * Squiz_Sniffs_Formatting_FunctionSpacingSniff. 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_WhiteSpace_FunctionSpacingSniff. 18 * 19 * Checks the separation between methods in a class or interface. 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_FunctionSpacingSniff implements PHP_CodeSniffer_Sniff 31{ 32 33 /** 34 * The number of blank lines between functions. 35 * 36 * @var int 37 */ 38 public $spacing = 2; 39 40 41 /** 42 * Returns an array of tokens this test wants to listen for. 43 * 44 * @return array 45 */ 46 public function register() 47 { 48 return array(T_FUNCTION); 49 50 }//end register() 51 52 53 /** 54 * Processes this sniff 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 $this->spacing = (int) $this->spacing; 66 67 /* 68 Check the number of blank lines 69 after the function. 70 */ 71 72 if (isset($tokens[$stackPtr]['scope_closer']) === false) { 73 // Must be an interface method, so the closer is the semicolon. 74 $closer = $phpcsFile->findNext(T_SEMICOLON, $stackPtr); 75 } else { 76 $closer = $tokens[$stackPtr]['scope_closer']; 77 } 78 79 // Allow for comments on the same line as the closer. 80 for ($nextLineToken = ($closer + 1); $nextLineToken < $phpcsFile->numTokens; $nextLineToken++) { 81 if ($tokens[$nextLineToken]['line'] !== $tokens[$closer]['line']) { 82 break; 83 } 84 } 85 86 $foundLines = 0; 87 if ($nextLineToken === ($phpcsFile->numTokens - 1)) { 88 // We are at the end of the file. 89 // Don't check spacing after the function because this 90 // should be done by an EOF sniff. 91 $foundLines = $this->spacing; 92 } else { 93 $nextContent = $phpcsFile->findNext(T_WHITESPACE, $nextLineToken, null, true); 94 if ($nextContent === false) { 95 // We are at the end of the file. 96 // Don't check spacing after the function because this 97 // should be done by an EOF sniff. 98 $foundLines = $this->spacing; 99 } else { 100 $foundLines += ($tokens[$nextContent]['line'] - $tokens[$nextLineToken]['line']); 101 } 102 } 103 104 if ($foundLines !== $this->spacing) { 105 $error = 'Expected %s blank line'; 106 if ($this->spacing !== 1) { 107 $error .= 's'; 108 } 109 110 $error .= ' after function; %s found'; 111 $data = array( 112 $this->spacing, 113 $foundLines, 114 ); 115 116 $fix = $phpcsFile->addFixableError($error, $closer, 'After', $data); 117 if ($fix === true) { 118 $phpcsFile->fixer->beginChangeset(); 119 for ($i = $nextLineToken; $i <= $nextContent; $i++) { 120 if ($tokens[$i]['line'] === $tokens[$nextContent]['line']) { 121 $phpcsFile->fixer->addContentBefore($i, str_repeat($phpcsFile->eolChar, $this->spacing)); 122 break; 123 } 124 125 $phpcsFile->fixer->replaceToken($i, ''); 126 } 127 128 $phpcsFile->fixer->endChangeset(); 129 }//end if 130 }//end if 131 132 /* 133 Check the number of blank lines 134 before the function. 135 */ 136 137 $prevLineToken = null; 138 for ($i = $stackPtr; $i > 0; $i--) { 139 if (strpos($tokens[$i]['content'], $phpcsFile->eolChar) === false) { 140 continue; 141 } else { 142 $prevLineToken = $i; 143 break; 144 } 145 } 146 147 if (is_null($prevLineToken) === true) { 148 // Never found the previous line, which means 149 // there are 0 blank lines before the function. 150 $foundLines = 0; 151 $prevContent = 0; 152 } else { 153 $currentLine = $tokens[$stackPtr]['line']; 154 155 $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, $prevLineToken, null, true); 156 if ($tokens[$prevContent]['code'] === T_COMMENT) { 157 // Ignore comments as they can have different spacing rules, and this 158 // isn't a proper function comment anyway. 159 return; 160 } 161 162 if ($tokens[$prevContent]['code'] === T_DOC_COMMENT_CLOSE_TAG 163 && $tokens[$prevContent]['line'] === ($currentLine - 1) 164 ) { 165 // Account for function comments. 166 $prevContent = $phpcsFile->findPrevious(T_WHITESPACE, ($tokens[$prevContent]['comment_opener'] - 1), null, true); 167 } 168 169 // Before we throw an error, check that we are not throwing an error 170 // for another function. We don't want to error for no blank lines after 171 // the previous function and no blank lines before this one as well. 172 $prevLine = ($tokens[$prevContent]['line'] - 1); 173 $i = ($stackPtr - 1); 174 $foundLines = 0; 175 while ($currentLine !== $prevLine && $currentLine > 1 && $i > 0) { 176 if (isset($tokens[$i]['scope_condition']) === true) { 177 $scopeCondition = $tokens[$i]['scope_condition']; 178 if ($tokens[$scopeCondition]['code'] === T_FUNCTION) { 179 // Found a previous function. 180 return; 181 } 182 } else if ($tokens[$i]['code'] === T_FUNCTION) { 183 // Found another interface function. 184 return; 185 } 186 187 $currentLine = $tokens[$i]['line']; 188 if ($currentLine === $prevLine) { 189 break; 190 } 191 192 if ($tokens[($i - 1)]['line'] < $currentLine && $tokens[($i + 1)]['line'] > $currentLine) { 193 // This token is on a line by itself. If it is whitespace, the line is empty. 194 if ($tokens[$i]['code'] === T_WHITESPACE) { 195 $foundLines++; 196 } 197 } 198 199 $i--; 200 }//end while 201 }//end if 202 203 if ($foundLines !== $this->spacing) { 204 $error = 'Expected %s blank line'; 205 if ($this->spacing !== 1) { 206 $error .= 's'; 207 } 208 209 $error .= ' before function; %s found'; 210 $data = array( 211 $this->spacing, 212 $foundLines, 213 ); 214 215 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'Before', $data); 216 if ($fix === true) { 217 if ($prevContent === 0) { 218 $nextSpace = 0; 219 } else { 220 $nextSpace = $phpcsFile->findNext(T_WHITESPACE, ($prevContent + 1), $stackPtr); 221 if ($nextSpace === false) { 222 $nextSpace = ($stackPtr - 1); 223 } 224 } 225 226 if ($foundLines < $this->spacing) { 227 $padding = str_repeat($phpcsFile->eolChar, ($this->spacing - $foundLines)); 228 $phpcsFile->fixer->addContent($nextSpace, $padding); 229 } else { 230 $nextContent = $phpcsFile->findNext(T_WHITESPACE, ($nextSpace + 1), null, true); 231 $phpcsFile->fixer->beginChangeset(); 232 for ($i = $nextSpace; $i < ($nextContent - 1); $i++) { 233 $phpcsFile->fixer->replaceToken($i, ''); 234 } 235 236 $phpcsFile->fixer->replaceToken($i, str_repeat($phpcsFile->eolChar, $this->spacing)); 237 $phpcsFile->fixer->endChangeset(); 238 } 239 }//end if 240 }//end if 241 242 }//end process() 243 244 245}//end class 246