1<?php 2/** 3 * Generic_Sniffs_WhiteSpace_DisallowSpaceIndentSniff. 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 15/** 16 * Generic_Sniffs_WhiteSpace_DisallowSpaceIndentSniff. 17 * 18 * Throws errors if spaces are used for indentation other than precision indentation. 19 * 20 * @category PHP 21 * @package PHP_CodeSniffer 22 * @author Greg Sherwood <gsherwood@squiz.net> 23 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 24 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 25 * @version Release: @package_version@ 26 * @link http://pear.php.net/package/PHP_CodeSniffer 27 */ 28class Generic_Sniffs_WhiteSpace_DisallowSpaceIndentSniff implements PHP_CodeSniffer_Sniff 29{ 30 31 /** 32 * A list of tokenizers this sniff supports. 33 * 34 * @var array 35 */ 36 public $supportedTokenizers = array( 37 'PHP', 38 'JS', 39 'CSS', 40 ); 41 42 /** 43 * The --tab-width CLI value that is being used. 44 * 45 * @var int 46 */ 47 private $_tabWidth = null; 48 49 50 /** 51 * Returns an array of tokens this test wants to listen for. 52 * 53 * @return array 54 */ 55 public function register() 56 { 57 return array(T_OPEN_TAG); 58 59 }//end register() 60 61 62 /** 63 * Processes this test, when one of its tokens is encountered. 64 * 65 * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document. 66 * @param int $stackPtr The position of the current token in 67 * the stack passed in $tokens. 68 * 69 * @return void 70 */ 71 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 72 { 73 if ($this->_tabWidth === null) { 74 $cliValues = $phpcsFile->phpcs->cli->getCommandLineValues(); 75 if (isset($cliValues['tabWidth']) === false || $cliValues['tabWidth'] === 0) { 76 // We have no idea how wide tabs are, so assume 4 spaces for fixing. 77 // It shouldn't really matter because indent checks elsewhere in the 78 // standard should fix things up. 79 $this->_tabWidth = 4; 80 } else { 81 $this->_tabWidth = $cliValues['tabWidth']; 82 } 83 } 84 85 $checkTokens = array( 86 T_WHITESPACE => true, 87 T_INLINE_HTML => true, 88 T_DOC_COMMENT_WHITESPACE => true, 89 ); 90 91 $tokens = $phpcsFile->getTokens(); 92 for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) { 93 if ($tokens[$i]['column'] !== 1 || isset($checkTokens[$tokens[$i]['code']]) === false) { 94 continue; 95 } 96 97 // If tabs are being converted to spaces by the tokeniser, the 98 // original content should be checked instead of the converted content. 99 if (isset($tokens[$i]['orig_content']) === true) { 100 $content = $tokens[$i]['orig_content']; 101 } else { 102 $content = $tokens[$i]['content']; 103 } 104 105 // If this is an inline HTML token, split the content into 106 // indentation whitespace and the actual HTML/text. 107 $nonWhitespace = ''; 108 if ($tokens[$i]['code'] === T_INLINE_HTML && preg_match('`^(\s*)(\S.*)`s', $content, $matches) > 0) { 109 if (isset($matches[1]) === true) { 110 $content = $matches[1]; 111 } 112 113 if (isset($matches[2]) === true) { 114 $nonWhitespace = $matches[2]; 115 } 116 } 117 118 $hasSpaces = strpos($content, ' '); 119 $hasTabs = strpos($content, "\t"); 120 121 if ($hasSpaces === false && $hasTabs === false) { 122 // Empty line. 123 continue; 124 } 125 126 if ($hasSpaces === false && $hasTabs !== false) { 127 // All ok, nothing to do. 128 $phpcsFile->recordMetric($i, 'Line indent', 'tabs'); 129 continue; 130 } 131 132 if ($tokens[$i]['code'] === T_DOC_COMMENT_WHITESPACE && $content === ' ') { 133 // Ignore file/class-level DocBlock, especially for recording metrics. 134 continue; 135 } 136 137 // OK, by now we know there will be spaces. 138 // We just don't know yet whether they need to be replaced or 139 // are precision indentation, nor whether they are correctly 140 // placed at the end of the whitespace. 141 $trimmed = str_replace(' ', '', $content); 142 $numSpaces = (strlen($content) - strlen($trimmed)); 143 $numTabs = (int) floor($numSpaces / $this->_tabWidth); 144 $tabAfterSpaces = strpos($content, "\t", $hasSpaces); 145 146 if ($hasTabs === false) { 147 $phpcsFile->recordMetric($i, 'Line indent', 'spaces'); 148 149 if ($numTabs === 0) { 150 // Ignore: precision indentation. 151 continue; 152 } 153 } else { 154 if ($numTabs === 0) { 155 // Precision indentation. 156 $phpcsFile->recordMetric($i, 'Line indent', 'tabs'); 157 158 if ($tabAfterSpaces === false) { 159 // Ignore: precision indentation is already at the 160 // end of the whitespace. 161 continue; 162 } 163 } else { 164 $phpcsFile->recordMetric($i, 'Line indent', 'mixed'); 165 } 166 }//end if 167 168 $error = 'Tabs must be used to indent lines; spaces are not allowed'; 169 $fix = $phpcsFile->addFixableError($error, $i, 'SpacesUsed'); 170 if ($fix === true) { 171 $remaining = ($numSpaces % $this->_tabWidth); 172 $padding = str_repeat("\t", $numTabs); 173 $padding .= str_repeat(' ', $remaining); 174 $phpcsFile->fixer->replaceToken($i, $trimmed.$padding.$nonWhitespace); 175 } 176 }//end for 177 178 // Ignore the rest of the file. 179 return ($phpcsFile->numTokens + 1); 180 181 }//end process() 182 183 184}//end class 185