1<?php 2/** 3 * PEAR_Sniffs_Whitespace_ScopeClosingBraceSniff. 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 * PEAR_Sniffs_Whitespace_ScopeClosingBraceSniff. 18 * 19 * Checks that the closing braces of scopes are aligned 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 PEAR_Sniffs_WhiteSpace_ScopeClosingBraceSniff implements PHP_CodeSniffer_Sniff 31{ 32 33 /** 34 * The number of spaces code should be indented. 35 * 36 * @var int 37 */ 38 public $indent = 4; 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 PHP_CodeSniffer_Tokens::$scopeOpeners; 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 All the tokens found in the document. 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 this is an inline condition (ie. there is no scope opener), then 67 // return, as this is not a new scope. 68 if (isset($tokens[$stackPtr]['scope_closer']) === false) { 69 return; 70 } 71 72 $scopeStart = $tokens[$stackPtr]['scope_opener']; 73 $scopeEnd = $tokens[$stackPtr]['scope_closer']; 74 75 // If the scope closer doesn't think it belongs to this scope opener 76 // then the opener is sharing its closer with other tokens. We only 77 // want to process the closer once, so skip this one. 78 if (isset($tokens[$scopeEnd]['scope_condition']) === false 79 || $tokens[$scopeEnd]['scope_condition'] !== $stackPtr 80 ) { 81 return; 82 } 83 84 // We need to actually find the first piece of content on this line, 85 // because if this is a method with tokens before it (public, static etc) 86 // or an if with an else before it, then we need to start the scope 87 // checking from there, rather than the current token. 88 $lineStart = ($stackPtr - 1); 89 for ($lineStart; $lineStart > 0; $lineStart--) { 90 if (strpos($tokens[$lineStart]['content'], $phpcsFile->eolChar) !== false) { 91 break; 92 } 93 } 94 95 $lineStart++; 96 97 $startColumn = 1; 98 if ($tokens[$lineStart]['code'] === T_WHITESPACE) { 99 $startColumn = $tokens[($lineStart + 1)]['column']; 100 } else if ($tokens[$lineStart]['code'] === T_INLINE_HTML) { 101 $trimmed = ltrim($tokens[$lineStart]['content']); 102 if ($trimmed === '') { 103 $startColumn = $tokens[($lineStart + 1)]['column']; 104 } else { 105 $startColumn = (strlen($tokens[$lineStart]['content']) - strlen($trimmed)); 106 } 107 } 108 109 // Check that the closing brace is on it's own line. 110 $lastContent = $phpcsFile->findPrevious( 111 array( 112 T_WHITESPACE, 113 T_INLINE_HTML, 114 T_OPEN_TAG, 115 ), 116 ($scopeEnd - 1), 117 $scopeStart, 118 true 119 ); 120 121 if ($tokens[$lastContent]['line'] === $tokens[$scopeEnd]['line']) { 122 $error = 'Closing brace must be on a line by itself'; 123 $fix = $phpcsFile->addFixableError($error, $scopeEnd, 'Line'); 124 if ($fix === true) { 125 $phpcsFile->fixer->addNewlineBefore($scopeEnd); 126 } 127 128 return; 129 } 130 131 // Check now that the closing brace is lined up correctly. 132 $lineStart = ($scopeEnd - 1); 133 for ($lineStart; $lineStart > 0; $lineStart--) { 134 if (strpos($tokens[$lineStart]['content'], $phpcsFile->eolChar) !== false) { 135 break; 136 } 137 } 138 139 $lineStart++; 140 141 $braceIndent = 0; 142 if ($tokens[$lineStart]['code'] === T_WHITESPACE) { 143 $braceIndent = ($tokens[($lineStart + 1)]['column'] - 1); 144 } else if ($tokens[$lineStart]['code'] === T_INLINE_HTML) { 145 $trimmed = ltrim($tokens[$lineStart]['content']); 146 if ($trimmed === '') { 147 $braceIndent = ($tokens[($lineStart + 1)]['column'] - 1); 148 } else { 149 $braceIndent = (strlen($tokens[$lineStart]['content']) - strlen($trimmed) - 1); 150 } 151 } 152 153 $fix = false; 154 if ($tokens[$stackPtr]['code'] === T_CASE 155 || $tokens[$stackPtr]['code'] === T_DEFAULT 156 ) { 157 // BREAK statements should be indented n spaces from the 158 // CASE or DEFAULT statement. 159 $expectedIndent = ($startColumn + $this->indent - 1); 160 if ($braceIndent !== $expectedIndent) { 161 $error = 'Case breaking statement indented incorrectly; expected %s spaces, found %s'; 162 $data = array( 163 $expectedIndent, 164 $braceIndent, 165 ); 166 $fix = $phpcsFile->addFixableError($error, $scopeEnd, 'BreakIndent', $data); 167 } 168 } else { 169 $expectedIndent = max(0, ($startColumn - 1)); 170 if ($braceIndent !== $expectedIndent) { 171 $error = 'Closing brace indented incorrectly; expected %s spaces, found %s'; 172 $data = array( 173 $expectedIndent, 174 $braceIndent, 175 ); 176 $fix = $phpcsFile->addFixableError($error, $scopeEnd, 'Indent', $data); 177 } 178 }//end if 179 180 if ($fix === true) { 181 $spaces = str_repeat(' ', $expectedIndent); 182 if ($braceIndent === 0) { 183 $phpcsFile->fixer->addContentBefore($lineStart, $spaces); 184 } else { 185 $phpcsFile->fixer->replaceToken($lineStart, ltrim($tokens[$lineStart]['content'])); 186 $phpcsFile->fixer->addContentBefore($lineStart, $spaces); 187 } 188 } 189 190 }//end process() 191 192 193}//end class 194