View source
<?php
namespace Drupal\Sniffs\WhiteSpace;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHP_CodeSniffer\Config;
class ScopeIndentSniff implements Sniff {
public $indent = 2;
public $exact = true;
public $tabIndent = false;
private $tabWidth = null;
public $ignoreIndentationTokens = [];
private $ignoreIndentation = [];
protected $nonIndentingScopes = [];
private $debug = false;
public function register() {
if (defined('PHP_CODESNIFFER_IN_TESTS') === true) {
$this->debug = false;
}
return [
T_OPEN_TAG,
];
}
public function process(File $phpcsFile, $stackPtr) {
$debug = Config::getConfigData('scope_indent_debug');
if ($debug !== null) {
$this->debug = (bool) $debug;
}
if ($this->tabWidth === null) {
if (isset($phpcsFile->config->tabWidth) === false || $phpcsFile->config->tabWidth === 0) {
$this->tabWidth = 4;
}
else {
$this->tabWidth = $phpcsFile->config->tabWidth;
}
}
$lastOpenTag = $stackPtr;
$lastCloseTag = null;
$openScopes = [];
$adjustments = [];
$setIndents = [];
$disableExactEnd = 0;
$tokenIndent = 0;
$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->ignoreIndentation) === true) {
$this->ignoreIndentation = [
T_INLINE_HTML => true,
];
foreach ($this->ignoreIndentationTokens as $token) {
if (is_int($token) === false) {
if (defined($token) === false) {
continue;
}
$token = constant($token);
}
$this->ignoreIndentation[$token] = true;
}
}
$this->exact = (bool) $this->exact;
$this->tabIndent = (bool) $this->tabIndent;
$checkAnnotations = $phpcsFile->config->annotations;
for ($i = $stackPtr + 1; $i < $phpcsFile->numTokens; $i++) {
if ($checkAnnotations === true && $tokens[$i]['code'] === T_PHPCS_SET && isset($tokens[$i]['sniffCode']) === true && $tokens[$i]['sniffCode'] === 'Generic.WhiteSpace.ScopeIndent' && $tokens[$i]['sniffProperty'] === 'exact') {
$value = $tokens[$i]['sniffPropertyValue'];
if ($value === 'true') {
$value = true;
}
else {
if ($value === 'false') {
$value = false;
}
else {
$value = (bool) $value;
}
}
$this->exact = $value;
if ($this->debug === true) {
$line = $tokens[$i]['line'];
if ($this->exact === true) {
$value = 'true';
}
else {
$value = 'false';
}
echo "* token {$i} on line {$line} set exact flag to {$value} *" . PHP_EOL;
}
}
$checkToken = null;
$checkIndent = null;
$exact = $this->exact;
if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS && isset($tokens[$i]['parenthesis_closer']) === true) {
$disableExactEnd = max($disableExactEnd, $tokens[$i]['parenthesis_closer']);
if ($this->debug === true) {
$line = $tokens[$i]['line'];
$type = $tokens[$disableExactEnd]['type'];
echo "Opening parenthesis found on line {$line}" . PHP_EOL;
echo "\t=> disabling exact indent checking until {$disableExactEnd} ({$type})" . PHP_EOL;
}
}
if ($exact === true && $i < $disableExactEnd) {
$exact = false;
}
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);
}
}
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) {
$parens = $tokens[$parenCloser]['nested_parenthesis'];
end($parens);
$parens = key($parens);
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 && (isset($tokens[$parenCloser]['parenthesis_owner']) === false || $parens > 0)) {
$condition = $tokens[$parenCloser]['conditions'];
end($condition);
$condition = key($condition);
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'] && $tokens[$first - 1]['line'] === $tokens[$first]['line']) {
$first--;
if ($this->debug === true) {
$line = $tokens[$first]['line'];
$type = $tokens[$first]['type'];
echo "\t* first token is the parenthesis opener *" . PHP_EOL;
echo "\t* amended first token is {$first} ({$type}) on line {$line} *" . PHP_EOL;
}
}
$prev = $phpcsFile
->findStartOfStatement($first, T_COMMA);
if ($prev !== $first) {
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,
T_INLINE_HTML,
], $prev, true);
if ($first !== false) {
$prev = $phpcsFile
->findStartOfStatement($first, T_COMMA);
$first = $phpcsFile
->findFirstOnLine([
T_WHITESPACE,
T_INLINE_HTML,
], $prev, true);
}
else {
$first = $prev;
}
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];
}
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;
}
}
}
else {
$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;
}
}
}
}
}
else {
if ($this->debug === true) {
echo "\t * ignoring single-line definition *" . PHP_EOL;
}
}
}
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']) {
$first--;
}
$prev = $phpcsFile
->findStartOfStatement($first, [
T_COMMA,
T_DOUBLE_ARROW,
]);
if ($prev !== $first) {
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,
T_DOUBLE_ARROW,
]);
$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;
}
}
else {
if ($tokens[$first]['code'] === T_WHITESPACE) {
$first = $phpcsFile
->findNext(T_WHITESPACE, $first + 1, null, true);
}
}
if (isset($tokens[$first]['scope_closer']) === true && $tokens[$first]['scope_closer'] === $first) {
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 {
$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;
}
}
}
else {
if ($this->debug === true) {
echo "\t * ignoring single-line definition *" . PHP_EOL;
}
}
}
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) {
$condition = $tokens[$checkToken]['conditions'];
end($condition);
$condition = key($condition);
}
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)) {
$length = $tokenIndent + $adjustments[$first];
if ($phpcsFile->fixer->enabled === true) {
$tokenIndent = $length;
$this
->adjustIndent($phpcsFile, $checkToken, $length, $adjustments[$first]);
}
if ($this->debug === true) {
$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;
}
}
}
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) {
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;
}
$conditionToken = array_pop($openScopes);
if ($this->debug === true) {
$line = $tokens[$conditionToken]['line'];
$type = $tokens[$conditionToken]['type'];
echo "\t=> removed open scope {$conditionToken} ({$type}) on line {$line}" . PHP_EOL;
}
if (isset($tokens[$scopeCloser]['scope_condition']) === true) {
$first = $phpcsFile
->findFirstOnLine([
T_WHITESPACE,
T_INLINE_HTML,
], $tokens[$scopeCloser]['scope_condition'], true);
if ($this->debug === true) {
$line = $tokens[$first]['line'];
$type = $tokens[$first]['type'];
echo "\t* first token is {$first} ({$type}) on line {$line} *" . PHP_EOL;
}
while ($tokens[$first]['code'] === T_CONSTANT_ENCAPSED_STRING && $tokens[$first - 1]['code'] === T_CONSTANT_ENCAPSED_STRING) {
$first = $phpcsFile
->findFirstOnLine(T_WHITESPACE, $first - 1, true);
if ($this->debug === true) {
$line = $tokens[$first]['line'];
$type = $tokens[$first]['type'];
echo "\t* found multi-line string; amended first token is {$first} ({$type}) on line {$line} *" . PHP_EOL;
}
}
$currentIndent = $tokens[$first]['column'] - 1;
if (isset($adjustments[$first]) === true) {
$currentIndent += $adjustments[$first];
}
$setIndents[$scopeCloser] = $currentIndent;
if ($this->debug === true) {
$type = $tokens[$scopeCloser]['type'];
echo "\t=> indent set to {$currentIndent} by token {$scopeCloser} ({$type})" . PHP_EOL;
}
if ($tokens[$scopeCloser]['code'] === T_CLOSE_CURLY_BRACKET) {
$exact = true;
}
else {
$checkToken = null;
}
}
}
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 {
$conditionToken = array_pop($openScopes);
if ($this->debug === true) {
$line = $tokens[$conditionToken]['line'];
$type = $tokens[$conditionToken]['type'];
echo "\t=> removed open scope {$conditionToken} ({$type}) on line {$line}" . PHP_EOL;
}
}
$parens = 0;
if (isset($tokens[$scopeCloser]['nested_parenthesis']) === true && empty($tokens[$scopeCloser]['nested_parenthesis']) === false) {
$parens = $tokens[$scopeCloser]['nested_parenthesis'];
end($parens);
$parens = key($parens);
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) {
$condition = $tokens[$scopeCloser]['conditions'];
end($condition);
$condition = key($condition);
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);
}
}
$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;
}
$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;
}
}
if ($checkToken !== null && isset(Tokens::$scopeOpeners[$tokens[$checkToken]['code']]) === true && in_array($tokens[$checkToken]['code'], $this->nonIndentingScopes, true) === false && isset($tokens[$checkToken]['scope_opener']) === true) {
$exact = true;
$lastOpener = null;
if (empty($openScopes) === false) {
end($openScopes);
$lastOpener = current($openScopes);
}
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) {
$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;
}
}
}
if ($checkToken !== null && isset(Tokens::$methodPrefixes[$tokens[$checkToken]['code']]) === true && $tokens[$checkToken + 1]['code'] !== T_DOUBLE_COLON) {
$next = $phpcsFile
->findNext(Tokens::$emptyTokens, $checkToken + 1, null, true);
if ($next === false || $tokens[$next]['code'] !== T_CLOSURE) {
if ($this->debug === true) {
$line = $tokens[$checkToken]['line'];
$type = $tokens[$checkToken]['type'];
echo "\t* method prefix ({$type}) found on line {$line}; indent set to exact *" . PHP_EOL;
}
$exact = true;
}
}
if ($checkToken !== null && $tokens[$checkToken]['code'] === T_PROPERTY) {
$exact = true;
}
if ($checkToken !== null && ($tokens[$checkToken]['code'] === T_OPEN_TAG || $tokens[$checkToken]['code'] === T_OPEN_TAG_WITH_ECHO)) {
$checkIndent = $tokens[$checkToken]['column'] - 1;
if (empty($tokens[$checkToken]['conditions']) === false) {
$close = $phpcsFile
->findPrevious(T_CLOSE_TAG, $checkToken - 1);
if ($close !== false && $tokens[$checkToken]['conditions'] === $tokens[$close]['conditions']) {
$conditions = array_keys($tokens[$checkToken]['conditions']);
$lastCondition = array_pop($conditions);
$lastOpener = $tokens[$lastCondition]['scope_opener'];
$lastCloser = $tokens[$lastCondition]['scope_closer'];
if ($tokens[$lastCloser]['line'] !== $tokens[$checkToken]['line'] && isset($setIndents[$lastOpener]) === true) {
$checkIndent = $setIndents[$lastOpener];
}
}
}
$checkIndent = (int) (ceil($checkIndent / $this->indent) * $this->indent);
}
if ($checkToken !== null && $tokens[$checkToken]['code'] === T_CLOSE_TAG) {
$exact = true;
$checkIndent = $currentIndent;
$checkIndent = (int) (ceil($checkIndent / $this->indent) * $this->indent);
}
if ($checkToken !== null && $tokens[$checkToken]['code'] === T_ELSE) {
$exact = true;
}
if ($checkToken !== null && $tokens[$checkToken]['code'] === T_OBJECT_OPERATOR && $exact === true) {
$exact = false;
}
if ($checkIndent === null) {
$checkIndent = $currentIndent;
}
if ($checkToken !== null && $tokens[$checkToken]['code'] === T_OBJECT_OPERATOR) {
$exact = true;
$checkIndent += $this->indent;
}
if ($checkToken !== null && $tokens[$checkToken]['code'] === T_COMMENT) {
$content = trim($tokens[$checkToken]['content']);
if ($content[0] === '*') {
$checkIndent += 1;
}
}
if ($checkToken !== null && isset($this->ignoreIndentation[$tokens[$checkToken]['code']]) === false && ($tokenIndent !== $checkIndent && $exact === true || $tokenIndent < $checkIndent && $exact === false)) {
if ($tokenIndent > $checkIndent) {
$before = $phpcsFile
->findPrevious(Tokens::$emptyTokens, $checkToken - 1, null, true);
if ($before !== false && in_array($tokens[$before]['code'], [
T_SEMICOLON,
T_CLOSE_CURLY_BRACKET,
T_OPEN_CURLY_BRACKET,
T_COLON,
T_OPEN_TAG,
]) === false) {
continue;
}
}
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 = [
floor($checkIndent / $this->tabWidth),
floor($tokenIndent / $this->tabWidth),
];
}
else {
$error .= '%s spaces, found %s';
$data = [
$checkIndent,
$tokenIndent,
];
}
if ($this->debug === true) {
$line = $tokens[$checkToken]['line'];
$message = vsprintf($error, $data);
echo "[Line {$line}] {$message}" . PHP_EOL;
}
$adjustments[$checkToken] = $checkIndent - $tokenIndent;
$fix = $phpcsFile
->addFixableError($error, $checkToken, $type, $data);
if ($fix === true || $this->debug === true) {
$accepted = $this
->adjustIndent($phpcsFile, $checkToken, $checkIndent, $checkIndent - $tokenIndent);
if ($accepted === true && $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;
}
}
}
if ($checkToken !== null) {
$i = $checkToken;
}
if ($tokens[$i]['code'] === T_OPEN_SHORT_ARRAY) {
$disableExactEnd = max($disableExactEnd, $tokens[$i]['bracket_closer']);
if ($this->debug === true) {
$line = $tokens[$i]['line'];
$type = $tokens[$disableExactEnd]['type'];
echo "Opening short array bracket found on line {$line}" . PHP_EOL;
if ($disableExactEnd === $tokens[$i]['bracket_closer']) {
echo "\t=> disabling exact indent checking until {$disableExactEnd} ({$type})" . PHP_EOL;
}
else {
echo "\t=> continuing to disable exact indent checking until {$disableExactEnd} ({$type})" . PHP_EOL;
}
}
}
if ($tokens[$i]['code'] === T_START_HEREDOC || $tokens[$i]['code'] === T_START_NOWDOC) {
if ($this->debug === true) {
$line = $tokens[$i]['line'];
$type = $tokens[$disableExactEnd]['type'];
echo "Here/nowdoc found on line {$line}" . PHP_EOL;
}
$i = $phpcsFile
->findNext([
T_END_HEREDOC,
T_END_NOWDOC,
], $i + 1);
$next = $phpcsFile
->findNext(Tokens::$emptyTokens, $i + 1, null, true);
if ($tokens[$next]['code'] === T_COMMA) {
$i = $next;
}
if ($this->debug === true) {
$line = $tokens[$i]['line'];
$type = $tokens[$i]['type'];
echo "\t* skipping to token {$i} ({$type}) on line {$line} *" . PHP_EOL;
}
continue;
}
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;
}
if ($tokens[$i]['code'] === T_DOC_COMMENT_OPEN_TAG) {
$i = $tokens[$i]['comment_closer'];
continue;
}
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];
}
$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;
}
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];
}
$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;
}
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);
if ($this->debug === true) {
$line = $tokens[$first]['line'];
$type = $tokens[$first]['type'];
echo "\t* first token is {$first} ({$type}) on line {$line} *" . PHP_EOL;
}
while ($tokens[$first]['code'] === T_CONSTANT_ENCAPSED_STRING && $tokens[$first - 1]['code'] === T_CONSTANT_ENCAPSED_STRING) {
$first = $phpcsFile
->findFirstOnLine(T_WHITESPACE, $first - 1, true);
if ($this->debug === true) {
$line = $tokens[$first]['line'];
$type = $tokens[$first]['type'];
echo "\t* found multi-line string; amended first token is {$first} ({$type}) on line {$line} *" . PHP_EOL;
}
}
$currentIndent = $tokens[$first]['column'] - 1 + $this->indent;
$openScopes[$tokens[$i]['scope_closer']] = $tokens[$i]['scope_condition'];
if ($this->debug === true) {
$closerToken = $tokens[$i]['scope_closer'];
$closerLine = $tokens[$closerToken]['line'];
$closerType = $tokens[$closerToken]['type'];
$conditionToken = $tokens[$i]['scope_condition'];
$conditionLine = $tokens[$conditionToken]['line'];
$conditionType = $tokens[$conditionToken]['type'];
echo "\t=> added open scope {$closerToken} ({$closerType}) on line {$closerLine}, pointing to condition {$conditionToken} ({$conditionType}) on line {$conditionLine}" . PHP_EOL;
}
if (isset($adjustments[$first]) === true) {
$currentIndent += $adjustments[$first];
}
$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;
}
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, true) === 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) {
$closerToken = $tokens[$i]['scope_closer'];
$closerLine = $tokens[$closerToken]['line'];
$closerType = $tokens[$closerToken]['type'];
$conditionToken = $tokens[$i]['scope_condition'];
$conditionLine = $tokens[$conditionToken]['line'];
$conditionType = $tokens[$conditionToken]['type'];
echo "\t=> added open scope {$closerToken} ({$closerType}) on line {$closerLine}, pointing to condition {$conditionToken} ({$conditionType}) on line {$conditionLine}" . PHP_EOL;
}
if ($this->debug === true) {
$type = $tokens[$i]['type'];
echo "\t=> indent set to {$currentIndent} by token {$i} ({$type})" . PHP_EOL;
}
continue;
}
}
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];
}
$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;
}
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) {
$parens = $tokens[$i]['nested_parenthesis'];
end($parens);
$parens = key($parens);
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) {
$condition = $tokens[$i]['conditions'];
end($condition);
$condition = key($condition);
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;
}
}
}
if ($prev === false) {
$prev = $phpcsFile
->findPrevious([
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) {
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;
}
}
}
$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;
}
}
}
return $phpcsFile->numTokens;
}
protected function adjustIndent(File $phpcsFile, $stackPtr, $length, $change) {
$tokens = $phpcsFile
->getTokens();
if ($tokens[$stackPtr]['code'] === T_INLINE_HTML) {
return false;
}
$padding = '';
if ($length > 0) {
if ($this->tabIndent === true) {
$numTabs = (int) floor($length / $this->tabWidth);
if ($numTabs > 0) {
$numSpaces = $length - $numTabs * $this->tabWidth;
$padding = str_repeat("\t", $numTabs) . str_repeat(' ', $numSpaces);
}
}
else {
$padding = str_repeat(' ', $length);
}
}
if ($tokens[$stackPtr]['column'] === 1) {
$trimmed = ltrim($tokens[$stackPtr]['content']);
$accepted = $phpcsFile->fixer
->replaceToken($stackPtr, $padding . $trimmed);
}
else {
$accepted = $phpcsFile->fixer
->replaceToken($stackPtr - 1, $padding);
}
if ($accepted === false) {
return false;
}
if ($tokens[$stackPtr]['code'] === T_DOC_COMMENT_OPEN_TAG) {
for ($x = $stackPtr + 1; $x < $tokens[$stackPtr]['comment_closer']; $x++) {
if ($tokens[$x]['column'] !== 1) {
continue;
}
$length = 0;
if ($tokens[$x]['code'] === T_DOC_COMMENT_WHITESPACE) {
$length = $tokens[$x]['length'];
}
$padding = $length + $change;
if ($padding > 0) {
if ($this->tabIndent === true) {
$numTabs = (int) floor($padding / $this->tabWidth);
$numSpaces = $padding - $numTabs * $this->tabWidth;
$padding = str_repeat("\t", $numTabs) . str_repeat(' ', $numSpaces);
}
else {
$padding = str_repeat(' ', $padding);
}
}
else {
$padding = '';
}
$phpcsFile->fixer
->replaceToken($x, $padding);
if ($this->debug === true) {
$length = strlen($padding);
$line = $tokens[$x]['line'];
$type = $tokens[$x]['type'];
echo "\t=> Indent adjusted to {$length} for {$type} on line {$line}" . PHP_EOL;
}
}
}
return true;
}
}