Mercurial > hg > isophonics-drupal-site
view vendor/drupal/coder/coder_sniffer/Drupal/Sniffs/WhiteSpace/ScopeIndentSniff.php @ 17:129ea1e6d783
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:21:36 +0000 |
parents | 4c8ae668cc8c |
children |
line wrap: on
line source
<?php /** * \Drupal\Sniffs\WhiteSpace\ScopeIndentSniff. * * @category PHP * @package PHP_CodeSniffer * @link http://pear.php.net/package/PHP_CodeSniffer */ namespace Drupal\Sniffs\WhiteSpace; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Config; use PHP_CodeSniffer\Util\Tokens; /** * Largely copied from * \PHP_CodeSniffer\Standards\Generic\Sniffs\WhiteSpace\ScopeIndentSniff, * modified to make the exact mode working with comments and multi line * statements. * * Checks that control structures are structured correctly, and their content * is indented correctly. This sniff will throw errors if tabs are used * for indentation rather than spaces. * * @category PHP * @package PHP_CodeSniffer * @link http://pear.php.net/package/PHP_CodeSniffer */ class ScopeIndentSniff implements Sniff { /** * A list of tokenizers this sniff supports. * * @var array */ public $supportedTokenizers = array('PHP'); /** * The number of spaces code should be indented. * * @var int */ public $indent = 2; /** * Does the indent need to be exactly right? * * If TRUE, indent needs to be exactly $indent spaces. If FALSE, * indent needs to be at least $indent spaces (but can be more). * * @var bool */ public $exact = true; /** * Should tabs be used for indenting? * * If TRUE, fixes will be made using tabs instead of spaces. * The size of each tab is important, so it should be specified * using the --tab-width CLI argument. * * @var bool */ public $tabIndent = false; /** * The --tab-width CLI value that is being used. * * @var int */ private $_tabWidth = null; /** * List of tokens not needing to be checked for indentation. * * Useful to allow Sniffs based on this to easily ignore/skip some * tokens from verification. For example, inline HTML sections * or PHP open/close tags can escape from here and have their own * rules elsewhere. * * @var int[] */ public $ignoreIndentationTokens = array(); /** * List of tokens not needing to be checked for indentation. * * This is a cached copy of the public version of this var, which * can be set in a ruleset file, and some core ignored tokens. * * @var int[] */ private $_ignoreIndentationTokens = array(); /** * Any scope openers that should not cause an indent. * * @var int[] */ protected $nonIndentingScopes = array(); /** * Show debug output for this sniff. * * @var bool */ private $_debug = false; /** * Returns an array of tokens this test wants to listen for. * * @return array */ public function register() { if (defined('PHP_CODESNIFFER_IN_TESTS') === true) { $this->_debug = false; } return array(T_OPEN_TAG); }//end register() /** * Processes this test, when one of its tokens is encountered. * * @param \PHP_CodeSniffer\Files\File $phpcsFile All the tokens found in the document. * @param int $stackPtr The position of the current token * in the stack passed in $tokens. * * @return void */ public function process(File $phpcsFile, $stackPtr) { $debug = Config::getConfigData('scope_indent_debug'); if ($debug !== null) { $this->_debug = (bool) $debug; } if ($this->_tabWidth === null) { $config = $phpcsFile->config; if (isset($config->tabWidth) === false || $config->tabWidth === 0) { // We have no idea how wide tabs are, so assume 4 spaces for fixing. // It shouldn't really matter because indent checks elsewhere in the // standard should fix things up. $this->_tabWidth = 4; } else { $this->_tabWidth = $config->tabWidth; } } $currentIndent = 0; $lastOpenTag = $stackPtr; $lastCloseTag = null; $openScopes = array(); $adjustments = array(); $setIndents = array(); $tokens = $phpcsFile->getTokens(); $first = $phpcsFile->findFirstOnLine(T_INLINE_HTML, $stackPtr); $trimmed = ltrim($tokens[$first]['content']); if ($trimmed === '') { $currentIndent = ($tokens[$stackPtr]['column'] - 1); } else { $currentIndent = (strlen($tokens[$first]['content']) - strlen($trimmed)); } if ($this->_debug === true) { $line = $tokens[$stackPtr]['line']; echo "Start with token $stackPtr on line $line with indent $currentIndent".PHP_EOL; } if (empty($this->_ignoreIndentationTokens) === true) { $this->_ignoreIndentationTokens = array(T_INLINE_HTML => true); foreach ($this->ignoreIndentationTokens as $token) { if (is_int($token) === false) { if (defined($token) === false) { continue; } $token = constant($token); } $this->_ignoreIndentationTokens[$token] = true; } }//end if $this->exact = (bool) $this->exact; $this->tabIndent = (bool) $this->tabIndent; for ($i = ($stackPtr + 1); $i < $phpcsFile->numTokens; $i++) { if ($i === false) { // Something has gone very wrong; maybe a parse error. break; } $checkToken = null; $checkIndent = null; $exact = (bool) $this->exact; if ($exact === true && isset($tokens[$i]['nested_parenthesis']) === true) { // Don't check indents exactly between parenthesis as they // tend to have custom rules, such as with multi-line function calls // and control structure conditions. $exact = false; } // Detect line changes and figure out where the indent is. if ($tokens[$i]['column'] === 1) { $trimmed = ltrim($tokens[$i]['content']); if ($trimmed === '') { if (isset($tokens[($i + 1)]) === true && $tokens[$i]['line'] === $tokens[($i + 1)]['line'] ) { $checkToken = ($i + 1); $tokenIndent = ($tokens[($i + 1)]['column'] - 1); } } else { $checkToken = $i; $tokenIndent = (strlen($tokens[$i]['content']) - strlen($trimmed)); } } // Closing parenthesis should just be indented to at least // the same level as where they were opened (but can be more). if (($checkToken !== null && $tokens[$checkToken]['code'] === T_CLOSE_PARENTHESIS && isset($tokens[$checkToken]['parenthesis_opener']) === true) || ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS && isset($tokens[$i]['parenthesis_opener']) === true) ) { if ($checkToken !== null) { $parenCloser = $checkToken; } else { $parenCloser = $i; } if ($this->_debug === true) { $line = $tokens[$i]['line']; echo "Closing parenthesis found on line $line".PHP_EOL; } $parenOpener = $tokens[$parenCloser]['parenthesis_opener']; if ($tokens[$parenCloser]['line'] !== $tokens[$parenOpener]['line']) { $parens = 0; if (isset($tokens[$parenCloser]['nested_parenthesis']) === true && empty($tokens[$parenCloser]['nested_parenthesis']) === false ) { end($tokens[$parenCloser]['nested_parenthesis']); $parens = key($tokens[$parenCloser]['nested_parenthesis']); if ($this->_debug === true) { $line = $tokens[$parens]['line']; echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL; } } $condition = 0; if (isset($tokens[$parenCloser]['conditions']) === true && empty($tokens[$parenCloser]['conditions']) === false ) { end($tokens[$parenCloser]['conditions']); $condition = key($tokens[$parenCloser]['conditions']); if ($this->_debug === true) { $line = $tokens[$condition]['line']; $type = $tokens[$condition]['type']; echo "\t* token is inside condition $condition ($type) on line $line *".PHP_EOL; } } if ($parens > $condition) { if ($this->_debug === true) { echo "\t* using parenthesis *".PHP_EOL; } $parenOpener = $parens; $condition = 0; } else if ($condition > 0) { if ($this->_debug === true) { echo "\t* using condition *".PHP_EOL; } $parenOpener = $condition; $parens = 0; } $exact = false; $lastOpenTagConditions = array_keys($tokens[$lastOpenTag]['conditions']); $lastOpenTagCondition = array_pop($lastOpenTagConditions); if ($condition > 0 && $lastOpenTagCondition === $condition) { if ($this->_debug === true) { echo "\t* open tag is inside condition; using open tag *".PHP_EOL; } $checkIndent = ($tokens[$lastOpenTag]['column'] - 1); if (isset($adjustments[$condition]) === true) { $checkIndent += $adjustments[$condition]; } $currentIndent = $checkIndent; if ($this->_debug === true) { $type = $tokens[$lastOpenTag]['type']; echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $lastOpenTag ($type)".PHP_EOL; } } else if ($condition > 0 && isset($tokens[$condition]['scope_opener']) === true && isset($setIndents[$tokens[$condition]['scope_opener']]) === true ) { $checkIndent = $setIndents[$tokens[$condition]['scope_opener']]; if (isset($adjustments[$condition]) === true) { $checkIndent += $adjustments[$condition]; } $currentIndent = $checkIndent; if ($this->_debug === true) { $type = $tokens[$condition]['type']; echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $condition ($type)".PHP_EOL; } } else { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $parenOpener, true); $checkIndent = ($tokens[$first]['column'] - 1); if (isset($adjustments[$first]) === true) { $checkIndent += $adjustments[$first]; } if ($this->_debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* first token on line $line is $first ($type) *".PHP_EOL; } if ($first === $tokens[$parenCloser]['parenthesis_opener']) { // This is unlikely to be the start of the statement, so look // back further to find it. $first--; } $prev = $phpcsFile->findStartOfStatement($first, T_COMMA); if ($prev !== $first) { // This is not the start of the statement. if ($this->_debug === true) { $line = $tokens[$prev]['line']; $type = $tokens[$prev]['type']; echo "\t* previous is $type on line $line *".PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); $prev = $phpcsFile->findStartOfStatement($first, T_COMMA); $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); if ($this->_debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL; } } if (isset($tokens[$first]['scope_closer']) === true && $tokens[$first]['scope_closer'] === $first ) { if ($this->_debug === true) { echo "\t* first token is a scope closer *".PHP_EOL; } if (isset($tokens[$first]['scope_condition']) === true) { $scopeCloser = $first; $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['scope_condition'], true); $currentIndent = ($tokens[$first]['column'] - 1); if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } // Make sure it is divisible by our expected indent. if ($tokens[$tokens[$scopeCloser]['scope_condition']]['code'] !== T_CLOSURE) { $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); } $setIndents[$first] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$first]['type']; echo "\t=> indent set to $currentIndent by token $first ($type)".PHP_EOL; } }//end if } else { // Don't force current indent to divisible because there could be custom // rules in place between parenthesis, such as with arrays. $currentIndent = ($tokens[$first]['column'] - 1); if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } $setIndents[$first] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$first]['type']; echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)".PHP_EOL; } }//end if }//end if } else if ($this->_debug === true) { echo "\t * ignoring single-line definition *".PHP_EOL; }//end if }//end if // Closing short array bracket should just be indented to at least // the same level as where it was opened (but can be more). if ($tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY || ($checkToken !== null && $tokens[$checkToken]['code'] === T_CLOSE_SHORT_ARRAY) ) { if ($checkToken !== null) { $arrayCloser = $checkToken; } else { $arrayCloser = $i; } if ($this->_debug === true) { $line = $tokens[$arrayCloser]['line']; echo "Closing short array bracket found on line $line".PHP_EOL; } $arrayOpener = $tokens[$arrayCloser]['bracket_opener']; if ($tokens[$arrayCloser]['line'] !== $tokens[$arrayOpener]['line']) { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $arrayOpener, true); $checkIndent = ($tokens[$first]['column'] - 1); if (isset($adjustments[$first]) === true) { $checkIndent += $adjustments[$first]; } $exact = false; if ($this->_debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* first token on line $line is $first ($type) *".PHP_EOL; } if ($first === $tokens[$arrayCloser]['bracket_opener']) { // This is unlikely to be the start of the statement, so look // back further to find it. $first--; } $prev = $phpcsFile->findStartOfStatement($first, T_COMMA); if ($prev !== $first) { // This is not the start of the statement. if ($this->_debug === true) { $line = $tokens[$prev]['line']; $type = $tokens[$prev]['type']; echo "\t* previous is $type on line $line *".PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); $prev = $phpcsFile->findStartOfStatement($first, T_COMMA); $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); if ($this->_debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL; } } if (isset($tokens[$first]['scope_closer']) === true && $tokens[$first]['scope_closer'] === $first ) { // The first token is a scope closer and would have already // been processed and set the indent level correctly, so // don't adjust it again. if ($this->_debug === true) { echo "\t* first token is a scope closer; ignoring closing short array bracket *".PHP_EOL; } if (isset($setIndents[$first]) === true) { $currentIndent = $setIndents[$first]; if ($this->_debug === true) { echo "\t=> indent reset to $currentIndent".PHP_EOL; } } } else { // Don't force current indent to be divisible because there could be custom // rules in place for arrays. $currentIndent = ($tokens[$first]['column'] - 1); if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } $setIndents[$first] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$first]['type']; echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)".PHP_EOL; } }//end if } else if ($this->_debug === true) { echo "\t * ignoring single-line definition *".PHP_EOL; }//end if }//end if // Adjust lines within scopes while auto-fixing. if ($checkToken !== null && $exact === false && (empty($tokens[$checkToken]['conditions']) === false || (isset($tokens[$checkToken]['scope_opener']) === true && $tokens[$checkToken]['scope_opener'] === $checkToken)) ) { if (empty($tokens[$checkToken]['conditions']) === false) { end($tokens[$checkToken]['conditions']); $condition = key($tokens[$checkToken]['conditions']); } else { $condition = $tokens[$checkToken]['scope_condition']; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $condition, true); if (isset($adjustments[$first]) === true && (($adjustments[$first] < 0 && $tokenIndent > $currentIndent) || ($adjustments[$first] > 0 && $tokenIndent < $currentIndent)) ) { $padding = ($tokenIndent + $adjustments[$first]); if ($padding > 0) { if ($this->tabIndent === true) { $numTabs = floor($padding / $this->_tabWidth); $numSpaces = ($padding - ($numTabs * $this->_tabWidth)); $padding = str_repeat("\t", $numTabs).str_repeat(' ', $numSpaces); } else { $padding = str_repeat(' ', $padding); } } else { $padding = ''; } if ($checkToken === $i) { $phpcsFile->fixer->replaceToken($checkToken, $padding.$trimmed); } else { // Easier to just replace the entire indent. $phpcsFile->fixer->replaceToken(($checkToken - 1), $padding); } if ($this->_debug === true) { $length = strlen($padding); $line = $tokens[$checkToken]['line']; $type = $tokens[$checkToken]['type']; echo "Indent adjusted to $length for $type on line $line".PHP_EOL; } $adjustments[$checkToken] = $adjustments[$first]; if ($this->_debug === true) { $line = $tokens[$checkToken]['line']; $type = $tokens[$checkToken]['type']; echo "\t=> Add adjustment of ".$adjustments[$checkToken]." for token $checkToken ($type) on line $line".PHP_EOL; } }//end if }//end if // Scope closers reset the required indent to the same level as the opening condition. if (($checkToken !== null && isset($openScopes[$checkToken]) === true || (isset($tokens[$checkToken]['scope_condition']) === true && isset($tokens[$checkToken]['scope_closer']) === true && $tokens[$checkToken]['scope_closer'] === $checkToken && $tokens[$checkToken]['line'] !== $tokens[$tokens[$checkToken]['scope_opener']]['line'])) || ($checkToken === null && isset($openScopes[$i]) === true || (isset($tokens[$i]['scope_condition']) === true && isset($tokens[$i]['scope_closer']) === true && $tokens[$i]['scope_closer'] === $i && $tokens[$i]['line'] !== $tokens[$tokens[$i]['scope_opener']]['line'])) ) { if ($this->_debug === true) { if ($checkToken === null) { $type = $tokens[$tokens[$i]['scope_condition']]['type']; $line = $tokens[$i]['line']; } else { $type = $tokens[$tokens[$checkToken]['scope_condition']]['type']; $line = $tokens[$checkToken]['line']; } echo "Close scope ($type) on line $line".PHP_EOL; } $scopeCloser = $checkToken; if ($scopeCloser === null) { $scopeCloser = $i; } else { array_pop($openScopes); } if (isset($tokens[$scopeCloser]['scope_condition']) === true) { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['scope_condition'], true); $currentIndent = ($tokens[$first]['column'] - 1); if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } // Make sure it is divisible by our expected indent. if ($tokens[$tokens[$scopeCloser]['scope_condition']]['code'] !== T_CLOSURE) { $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); } $setIndents[$scopeCloser] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$scopeCloser]['type']; echo "\t=> indent set to $currentIndent by token $scopeCloser ($type)".PHP_EOL; } // We only check the indent of scope closers if they are // curly braces because other constructs tend to have different rules. if ($tokens[$scopeCloser]['code'] === T_CLOSE_CURLY_BRACKET) { $exact = true; } else { $checkToken = null; } }//end if }//end if // Handle scope for JS object notation. if ($phpcsFile->tokenizerType === 'JS' && (($checkToken !== null && $tokens[$checkToken]['code'] === T_CLOSE_OBJECT && $tokens[$checkToken]['line'] !== $tokens[$tokens[$checkToken]['bracket_opener']]['line']) || ($checkToken === null && $tokens[$i]['code'] === T_CLOSE_OBJECT && $tokens[$i]['line'] !== $tokens[$tokens[$i]['bracket_opener']]['line'])) ) { if ($this->_debug === true) { $line = $tokens[$i]['line']; echo "Close JS object on line $line".PHP_EOL; } $scopeCloser = $checkToken; if ($scopeCloser === null) { $scopeCloser = $i; } else { array_pop($openScopes); } $parens = 0; if (isset($tokens[$scopeCloser]['nested_parenthesis']) === true && empty($tokens[$scopeCloser]['nested_parenthesis']) === false ) { end($tokens[$scopeCloser]['nested_parenthesis']); $parens = key($tokens[$scopeCloser]['nested_parenthesis']); if ($this->_debug === true) { $line = $tokens[$parens]['line']; echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL; } } $condition = 0; if (isset($tokens[$scopeCloser]['conditions']) === true && empty($tokens[$scopeCloser]['conditions']) === false ) { end($tokens[$scopeCloser]['conditions']); $condition = key($tokens[$scopeCloser]['conditions']); if ($this->_debug === true) { $line = $tokens[$condition]['line']; $type = $tokens[$condition]['type']; echo "\t* token is inside condition $condition ($type) on line $line *".PHP_EOL; } } if ($parens > $condition) { if ($this->_debug === true) { echo "\t* using parenthesis *".PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $parens, true); $condition = 0; } else if ($condition > 0) { if ($this->_debug === true) { echo "\t* using condition *".PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $condition, true); $parens = 0; } else { if ($this->_debug === true) { $line = $tokens[$tokens[$scopeCloser]['bracket_opener']]['line']; echo "\t* token is not in parenthesis or condition; using opener on line $line *".PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $tokens[$scopeCloser]['bracket_opener'], true); }//end if $currentIndent = ($tokens[$first]['column'] - 1); if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } if ($parens > 0 || $condition > 0) { $checkIndent = ($tokens[$first]['column'] - 1); if (isset($adjustments[$first]) === true) { $checkIndent += $adjustments[$first]; } if ($condition > 0) { $checkIndent += $this->indent; $currentIndent += $this->indent; $exact = true; } } else { $checkIndent = $currentIndent; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $checkIndent = (int) (ceil($checkIndent / $this->indent) * $this->indent); $setIndents[$first] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$first]['type']; echo "\t=> checking indent of $checkIndent; main indent set to $currentIndent by token $first ($type)".PHP_EOL; } }//end if if ($checkToken !== null && isset(Tokens::$scopeOpeners[$tokens[$checkToken]['code']]) === true && in_array($tokens[$checkToken]['code'], $this->nonIndentingScopes) === false && isset($tokens[$checkToken]['scope_opener']) === true ) { $exact = true; $lastOpener = null; if (empty($openScopes) === false) { end($openScopes); $lastOpener = current($openScopes); } // A scope opener that shares a closer with another token (like multiple // CASEs using the same BREAK) needs to reduce the indent level so its // indent is checked correctly. It will then increase the indent again // (as all openers do) after being checked. if ($lastOpener !== null && isset($tokens[$lastOpener]['scope_closer']) === true && $tokens[$lastOpener]['level'] === $tokens[$checkToken]['level'] && $tokens[$lastOpener]['scope_closer'] === $tokens[$checkToken]['scope_closer'] ) { $currentIndent -= $this->indent; $setIndents[$lastOpener] = $currentIndent; if ($this->_debug === true) { $line = $tokens[$i]['line']; $type = $tokens[$lastOpener]['type']; echo "Shared closer found on line $line".PHP_EOL; echo "\t=> indent set to $currentIndent by token $lastOpener ($type)".PHP_EOL; } } if ($tokens[$checkToken]['code'] === T_CLOSURE && $tokenIndent > $currentIndent ) { // The opener is indented more than needed, which is fine. // But just check that it is divisible by our expected indent. $checkIndent = (int) (ceil($tokenIndent / $this->indent) * $this->indent); $exact = false; if ($this->_debug === true) { $line = $tokens[$i]['line']; echo "Closure found on line $line".PHP_EOL; echo "\t=> checking indent of $checkIndent; main indent remains at $currentIndent".PHP_EOL; } } }//end if // Method prefix indentation has to be exact or else if will break // the rest of the function declaration, and potentially future ones. if ($checkToken !== null && isset(Tokens::$methodPrefixes[$tokens[$checkToken]['code']]) === true && $tokens[($checkToken + 1)]['code'] !== T_DOUBLE_COLON ) { $exact = true; } // JS property indentation has to be exact or else if will break // things like function and object indentation. if ($checkToken !== null && $tokens[$checkToken]['code'] === T_PROPERTY) { $exact = true; } // PHP tags needs to be indented to exact column positions // so they don't cause problems with indent checks for the code // within them, but they don't need to line up with the current indent. if ($checkToken !== null && ($tokens[$checkToken]['code'] === T_OPEN_TAG || $tokens[$checkToken]['code'] === T_OPEN_TAG_WITH_ECHO || $tokens[$checkToken]['code'] === T_CLOSE_TAG) ) { $exact = true; $checkIndent = ($tokens[$checkToken]['column'] - 1); $checkIndent = (int) (ceil($checkIndent / $this->indent) * $this->indent); } // Check the line indent. if ($checkIndent === null) { $checkIndent = $currentIndent; } // If the line starts with "->" we assume this is an indented chained // method invocation, so we add one level of indentation. if ($checkToken !== null && $tokens[$checkToken]['code'] === T_OBJECT_OPERATOR) { $checkIndent += $this->indent; } // Comments starting with a star have an extra whitespace. if ($checkToken !== null && $tokens[$checkToken]['code'] === T_COMMENT) { $content = trim($tokens[$checkToken]['content']); if ($content{0} === '*') { $checkIndent += 1; } } $adjusted = false; if ($checkToken !== null && isset($this->_ignoreIndentationTokens[$tokens[$checkToken]['code']]) === false && (($tokenIndent !== $checkIndent && $exact === true) || ($tokenIndent < $checkIndent && $exact === false)) ) { if ($tokenIndent > $checkIndent) { // Ignore multi line statements. $before = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($checkToken - 1), null, true); if ($before !== false && in_array( $tokens[$before]['code'], array( T_SEMICOLON, T_CLOSE_CURLY_BRACKET, T_OPEN_CURLY_BRACKET, T_COLON, ) ) === false ) { continue; } } // Skip array closing indentation errors, this is handled by the // ArraySniff. if (($tokens[$checkToken]['code'] === T_CLOSE_PARENTHESIS && isset($tokens[$checkToken]['parenthesis_owner']) === true && $tokens[$tokens[$checkToken]['parenthesis_owner']]['code'] === T_ARRAY) || $tokens[$checkToken]['code'] === T_CLOSE_SHORT_ARRAY ) { continue; } $type = 'IncorrectExact'; $error = 'Line indented incorrectly; expected '; if ($exact === false) { $error .= 'at least '; $type = 'Incorrect'; } if ($this->tabIndent === true) { $error .= '%s tabs, found %s'; $data = array( floor($checkIndent / $this->_tabWidth), floor($tokenIndent / $this->_tabWidth), ); } else { $error .= '%s spaces, found %s'; $data = array( $checkIndent, $tokenIndent, ); } if ($this->_debug === true) { $line = $tokens[$checkToken]['line']; $message = vsprintf($error, $data); echo "[Line $line] $message".PHP_EOL; } $fix = $phpcsFile->addFixableError($error, $checkToken, $type, $data); if ($fix === true || $this->_debug === true) { $padding = ''; if ($this->tabIndent === true) { $numTabs = floor($checkIndent / $this->_tabWidth); if ($numTabs > 0) { $numSpaces = ($checkIndent - ($numTabs * $this->_tabWidth)); $padding = str_repeat("\t", $numTabs).str_repeat(' ', $numSpaces); } } else if ($checkIndent > 0) { $padding = str_repeat(' ', $checkIndent); } if ($checkToken === $i) { $accepted = $phpcsFile->fixer->replaceToken($checkToken, $padding.$trimmed); } else { // Easier to just replace the entire indent. $accepted = $phpcsFile->fixer->replaceToken(($checkToken - 1), $padding); } if ($accepted === true) { $adjustments[$checkToken] = ($checkIndent - $tokenIndent); if ($this->_debug === true) { $line = $tokens[$checkToken]['line']; $type = $tokens[$checkToken]['type']; echo "\t=> Add adjustment of ".$adjustments[$checkToken]." for token $checkToken ($type) on line $line".PHP_EOL; } } } else { // Assume the change would be applied and continue // checking indents under this assumption. This gives more // technically accurate error messages. $adjustments[$checkToken] = ($checkIndent - $tokenIndent); }//end if }//end if if ($checkToken !== null) { $i = $checkToken; } // Completely skip here/now docs as the indent is a part of the // content itself. if ($tokens[$i]['code'] === T_START_HEREDOC || $tokens[$i]['code'] === T_START_NOWDOC ) { $i = $phpcsFile->findNext(array(T_END_HEREDOC, T_END_NOWDOC), ($i + 1)); continue; } // Completely skip multi-line strings as the indent is a part of the // content itself. if ($tokens[$i]['code'] === T_CONSTANT_ENCAPSED_STRING || $tokens[$i]['code'] === T_DOUBLE_QUOTED_STRING ) { $i = $phpcsFile->findNext($tokens[$i]['code'], ($i + 1), null, true); $i--; continue; } // Completely skip doc comments as they tend to have complex // indentation rules. if ($tokens[$i]['code'] === T_DOC_COMMENT_OPEN_TAG) { $i = $tokens[$i]['comment_closer']; continue; } // Open tags reset the indent level. if ($tokens[$i]['code'] === T_OPEN_TAG || $tokens[$i]['code'] === T_OPEN_TAG_WITH_ECHO ) { if ($this->_debug === true) { $line = $tokens[$i]['line']; echo "Open PHP tag found on line $line".PHP_EOL; } if ($checkToken === null) { $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true); $currentIndent = (strlen($tokens[$first]['content']) - strlen(ltrim($tokens[$first]['content']))); } else { $currentIndent = ($tokens[$i]['column'] - 1); } $lastOpenTag = $i; if (isset($adjustments[$i]) === true) { $currentIndent += $adjustments[$i]; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $setIndents[$i] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$i]['type']; echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL; } continue; }//end if // Close tags reset the indent level, unless they are closing a tag // opened on the same line. if ($tokens[$i]['code'] === T_CLOSE_TAG) { if ($this->_debug === true) { $line = $tokens[$i]['line']; echo "Close PHP tag found on line $line".PHP_EOL; } if ($tokens[$lastOpenTag]['line'] !== $tokens[$i]['line']) { $currentIndent = ($tokens[$i]['column'] - 1); $lastCloseTag = $i; } else { if ($lastCloseTag === null) { $currentIndent = 0; } else { $currentIndent = ($tokens[$lastCloseTag]['column'] - 1); } } if (isset($adjustments[$i]) === true) { $currentIndent += $adjustments[$i]; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $setIndents[$i] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$i]['type']; echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL; } continue; }//end if // Anon classes and functions set the indent based on their own indent level. if ($tokens[$i]['code'] === T_CLOSURE || $tokens[$i]['code'] === T_ANON_CLASS) { $closer = $tokens[$i]['scope_closer']; if ($tokens[$i]['line'] === $tokens[$closer]['line']) { if ($this->_debug === true) { $type = str_replace('_', ' ', strtolower(substr($tokens[$i]['type'], 2))); $line = $tokens[$i]['line']; echo "* ignoring single-line $type on line $line".PHP_EOL; } $i = $closer; continue; } if ($this->_debug === true) { $type = str_replace('_', ' ', strtolower(substr($tokens[$i]['type'], 2))); $line = $tokens[$i]['line']; echo "Open $type on line $line".PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true); $currentIndent = (($tokens[$first]['column'] - 1) + $this->indent); if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (floor($currentIndent / $this->indent) * $this->indent); $i = $tokens[$i]['scope_opener']; $setIndents[$i] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$i]['type']; echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL; } continue; }//end if // Scope openers increase the indent level. if (isset($tokens[$i]['scope_condition']) === true && isset($tokens[$i]['scope_opener']) === true && $tokens[$i]['scope_opener'] === $i ) { $closer = $tokens[$i]['scope_closer']; if ($tokens[$i]['line'] === $tokens[$closer]['line']) { if ($this->_debug === true) { $line = $tokens[$i]['line']; $type = $tokens[$i]['type']; echo "* ignoring single-line $type on line $line".PHP_EOL; } $i = $closer; continue; } $condition = $tokens[$tokens[$i]['scope_condition']]['code']; if (isset(Tokens::$scopeOpeners[$condition]) === true && in_array($condition, $this->nonIndentingScopes) === false ) { if ($this->_debug === true) { $line = $tokens[$i]['line']; $type = $tokens[$tokens[$i]['scope_condition']]['type']; echo "Open scope ($type) on line $line".PHP_EOL; } $currentIndent += $this->indent; $setIndents[$i] = $currentIndent; $openScopes[$tokens[$i]['scope_closer']] = $tokens[$i]['scope_condition']; if ($this->_debug === true) { $type = $tokens[$i]['type']; echo "\t=> indent set to $currentIndent by token $i ($type)".PHP_EOL; } continue; } }//end if // JS objects set the indent level. if ($phpcsFile->tokenizerType === 'JS' && $tokens[$i]['code'] === T_OBJECT ) { $closer = $tokens[$i]['bracket_closer']; if ($tokens[$i]['line'] === $tokens[$closer]['line']) { if ($this->_debug === true) { $line = $tokens[$i]['line']; echo "* ignoring single-line JS object on line $line".PHP_EOL; } $i = $closer; continue; } if ($this->_debug === true) { $line = $tokens[$i]['line']; echo "Open JS object on line $line".PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $i, true); $currentIndent = (($tokens[$first]['column'] - 1) + $this->indent); if (isset($adjustments[$first]) === true) { $currentIndent += $adjustments[$first]; } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $setIndents[$first] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$first]['type']; echo "\t=> indent set to $currentIndent by token $first ($type)".PHP_EOL; } continue; }//end if // Closing an anon class or function. if (isset($tokens[$i]['scope_condition']) === true && $tokens[$i]['scope_closer'] === $i && ($tokens[$tokens[$i]['scope_condition']]['code'] === T_CLOSURE || $tokens[$tokens[$i]['scope_condition']]['code'] === T_ANON_CLASS) ) { if ($this->_debug === true) { $type = str_replace('_', ' ', strtolower(substr($tokens[$tokens[$i]['scope_condition']]['type'], 2))); $line = $tokens[$i]['line']; echo "Close $type on line $line".PHP_EOL; } $prev = false; $object = 0; if ($phpcsFile->tokenizerType === 'JS') { $conditions = $tokens[$i]['conditions']; krsort($conditions, SORT_NUMERIC); foreach ($conditions as $token => $condition) { if ($condition === T_OBJECT) { $object = $token; break; } } if ($this->_debug === true && $object !== 0) { $line = $tokens[$object]['line']; echo "\t* token is inside JS object $object on line $line *".PHP_EOL; } } $parens = 0; if (isset($tokens[$i]['nested_parenthesis']) === true && empty($tokens[$i]['nested_parenthesis']) === false ) { end($tokens[$i]['nested_parenthesis']); $parens = key($tokens[$i]['nested_parenthesis']); if ($this->_debug === true) { $line = $tokens[$parens]['line']; echo "\t* token has nested parenthesis $parens on line $line *".PHP_EOL; } } $condition = 0; if (isset($tokens[$i]['conditions']) === true && empty($tokens[$i]['conditions']) === false ) { end($tokens[$i]['conditions']); $condition = key($tokens[$i]['conditions']); if ($this->_debug === true) { $line = $tokens[$condition]['line']; $type = $tokens[$condition]['type']; echo "\t* token is inside condition $condition ($type) on line $line *".PHP_EOL; } } if ($parens > $object && $parens > $condition) { if ($this->_debug === true) { echo "\t* using parenthesis *".PHP_EOL; } $prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($parens - 1), null, true); $object = 0; $condition = 0; } else if ($object > 0 && $object >= $condition) { if ($this->_debug === true) { echo "\t* using object *".PHP_EOL; } $prev = $object; $parens = 0; $condition = 0; } else if ($condition > 0) { if ($this->_debug === true) { echo "\t* using condition *".PHP_EOL; } $prev = $condition; $object = 0; $parens = 0; }//end if if ($prev === false) { $prev = $phpcsFile->findPrevious(array(T_EQUAL, T_RETURN), ($tokens[$i]['scope_condition'] - 1), null, false, null, true); if ($prev === false) { $prev = $i; if ($this->_debug === true) { echo "\t* could not find a previous T_EQUAL or T_RETURN token; will use current token *".PHP_EOL; } } } if ($this->_debug === true) { $line = $tokens[$prev]['line']; $type = $tokens[$prev]['type']; echo "\t* previous token is $type on line $line *".PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); if ($this->_debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* first token on line $line is $first ($type) *".PHP_EOL; } $prev = $phpcsFile->findStartOfStatement($first); if ($prev !== $first) { // This is not the start of the statement. if ($this->_debug === true) { $line = $tokens[$prev]['line']; $type = $tokens[$prev]['type']; echo "\t* amended previous is $type on line $line *".PHP_EOL; } $first = $phpcsFile->findFirstOnLine(T_WHITESPACE, $prev, true); if ($this->_debug === true) { $line = $tokens[$first]['line']; $type = $tokens[$first]['type']; echo "\t* amended first token is $first ($type) on line $line *".PHP_EOL; } } $currentIndent = ($tokens[$first]['column'] - 1); if ($object > 0 || $condition > 0) { $currentIndent += $this->indent; } if (isset($tokens[$first]['scope_closer']) === true && $tokens[$first]['scope_closer'] === $first ) { if ($this->_debug === true) { echo "\t* first token is a scope closer *".PHP_EOL; } if ($condition === 0 || $tokens[$condition]['scope_opener'] < $first) { $currentIndent = $setIndents[$first]; } else if ($this->_debug === true) { echo "\t* ignoring scope closer *".PHP_EOL; } } // Make sure it is divisible by our expected indent. $currentIndent = (int) (ceil($currentIndent / $this->indent) * $this->indent); $setIndents[$first] = $currentIndent; if ($this->_debug === true) { $type = $tokens[$first]['type']; echo "\t=> indent set to $currentIndent by token $first ($type)".PHP_EOL; } }//end if }//end for // Don't process the rest of the file. return $phpcsFile->numTokens; }//end process() }//end class