private static function _recurseScopeMap(&$tokens, $numTokens, $tokenizer, $eolChar, $stackPtr, $depth = 1, &$ignore = 0)
{
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo "=> Begin scope map recursion at token {$stackPtr} with depth {$depth}" . PHP_EOL;
}
$opener = null;
$currType = $tokens[$stackPtr]['code'];
$startLine = $tokens[$stackPtr]['line'];
// We will need this to restore the value if we end up
// returning a token ID that causes our calling function to go back
// over already ignored braces.
$originalIgnore = $ignore;
// If the start token for this scope opener is the same as
// the scope token, we have already found our opener.
if (isset($tokenizer->scopeOpeners[$currType]['start'][$currType]) === true) {
$opener = $stackPtr;
}
for ($i = $stackPtr + 1; $i < $numTokens; $i++) {
$tokenType = $tokens[$i]['code'];
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$i]['type'];
$line = $tokens[$i]['line'];
$content = PHP_CodeSniffer::prepareForOutput($tokens[$i]['content']);
echo str_repeat("\t", $depth);
echo "Process token {$i} on line {$line} [";
if ($opener !== null) {
echo "opener:{$opener};";
}
if ($ignore > 0) {
echo "ignore={$ignore};";
}
echo "]: {$type} => {$content}" . PHP_EOL;
}
//end if
// Very special case for IF statements in PHP that can be defined without
// scope tokens. E.g., if (1) 1; 1 ? (1 ? 1 : 1) : 1;
// If an IF statement below this one has an opener but no
// keyword, the opener will be incorrectly assigned to this IF statement.
if (($currType === T_IF || $currType === T_ELSE) && $opener === null && $tokens[$i]['code'] === T_SEMICOLON) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found semicolon before scope opener for {$stackPtr}:{$type}, bailing" . PHP_EOL;
}
return $i;
}
if ($opener === null && $ignore === 0 && $tokenType === T_CLOSE_CURLY_BRACKET && isset($tokenizer->scopeOpeners[$currType]['end'][$tokenType]) === true) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found curly brace closer before scope opener for {$stackPtr}:{$type}, bailing" . PHP_EOL;
}
return $i - 1;
}
if ($opener !== null && (isset($tokens[$i]['scope_opener']) === false || $tokenizer->scopeOpeners[$tokens[$stackPtr]['code']]['shared'] === true) && isset($tokenizer->scopeOpeners[$currType]['end'][$tokenType]) === true) {
if ($ignore > 0 && $tokenType === T_CLOSE_CURLY_BRACKET) {
// The last opening bracket must have been for a string
// offset or alike, so let's ignore it.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* finished ignoring curly brace *' . PHP_EOL;
}
$ignore--;
continue;
} else {
if ($tokens[$opener]['code'] === T_OPEN_CURLY_BRACKET && $tokenType !== T_CLOSE_CURLY_BRACKET) {
// The opener is a curly bracket so the closer must be a curly bracket as well.
// We ignore this closer to handle cases such as T_ELSE or T_ELSEIF being considered
// a closer of T_IF when it should not.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Ignoring non-curly scope closer for {$stackPtr}:{$type}" . PHP_EOL;
}
} else {
$scopeCloser = $i;
$todo = array($stackPtr, $opener);
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
$closerType = $tokens[$scopeCloser]['type'];
echo str_repeat("\t", $depth);
echo "=> Found scope closer ({$scopeCloser}:{$closerType}) for {$stackPtr}:{$type}" . PHP_EOL;
}
$validCloser = true;
if (($tokens[$stackPtr]['code'] === T_IF || $tokens[$stackPtr]['code'] === T_ELSEIF) && ($tokenType === T_ELSE || $tokenType === T_ELSEIF)) {
// To be a closer, this token must have an opener.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo "* closer needs to be tested *" . PHP_EOL;
}
$i = self::_recurseScopeMap($tokens, $numTokens, $tokenizer, $eolChar, $i, $depth + 1, $ignore);
if (isset($tokens[$scopeCloser]['scope_opener']) === false) {
$validCloser = false;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo "* closer is not valid (no opener found) *" . PHP_EOL;
}
} else {
if ($tokens[$tokens[$scopeCloser]['scope_opener']]['code'] !== $tokens[$opener]['code']) {
$validCloser = false;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
$type = $tokens[$tokens[$scopeCloser]['scope_opener']]['type'];
$openerType = $tokens[$opener]['type'];
echo "* closer is not valid (mismatched opener type; {$type} != {$openerType}) *" . PHP_EOL;
}
} else {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo "* closer was valid *" . PHP_EOL;
}
}
}
} else {
// The closer was not processed, so we need to
// complete that token as well.
$todo[] = $scopeCloser;
}
//end if
if ($validCloser === true) {
foreach ($todo as $token) {
$tokens[$token]['scope_condition'] = $stackPtr;
$tokens[$token]['scope_opener'] = $opener;
$tokens[$token]['scope_closer'] = $scopeCloser;
}
if ($tokenizer->scopeOpeners[$tokens[$stackPtr]['code']]['shared'] === true) {
// As we are going back to where we started originally, restore
// the ignore value back to its original value.
$ignore = $originalIgnore;
return $opener;
} else {
if ($scopeCloser === $i && isset($tokenizer->scopeOpeners[$tokenType]) === true) {
// Unset scope_condition here or else the token will appear to have
// already been processed, and it will be skipped. Normally we want that,
// but in this case, the token is both a closer and an opener, so
// it needs to act like an opener. This is also why we return the
// token before this one; so the closer has a chance to be processed
// a second time, but as an opener.
unset($tokens[$scopeCloser]['scope_condition']);
return $i - 1;
} else {
return $i;
}
}
} else {
continue;
}
//end if
}
}
//end if
}
//end if
// Is this an opening condition ?
if (isset($tokenizer->scopeOpeners[$tokenType]) === true) {
if ($opener === null) {
if ($tokenType === T_USE) {
// PHP use keywords are special because they can be
// used as blocks but also inline in function definitions.
// So if we find them nested inside another opener, just skip them.
continue;
}
if ($tokenType === T_FUNCTION && $tokens[$stackPtr]['code'] !== T_FUNCTION) {
// Probably a closure, so process it manually.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found function before scope opener for {$stackPtr}:{$type}, processing manually" . PHP_EOL;
}
if (isset($tokens[$i]['scope_closer']) === true) {
// We've already processed this closure.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* already processed, skipping *' . PHP_EOL;
}
$i = $tokens[$i]['scope_closer'];
continue;
}
$i = self::_recurseScopeMap($tokens, $numTokens, $tokenizer, $eolChar, $i, $depth + 1, $ignore);
continue;
}
//end if
// Found another opening condition but still haven't
// found our opener, so we are never going to find one.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found new opening condition before scope opener for {$stackPtr}:{$type}, ";
}
if (($tokens[$stackPtr]['code'] === T_IF || $tokens[$stackPtr]['code'] === T_ELSEIF || $tokens[$stackPtr]['code'] === T_ELSE) && ($tokens[$i]['code'] === T_ELSE || $tokens[$i]['code'] === T_ELSEIF)) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "continuing" . PHP_EOL;
}
return $i - 1;
} else {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo "backtracking" . PHP_EOL;
}
return $stackPtr;
}
}
//end if
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* token is an opening condition *' . PHP_EOL;
}
$isShared = $tokenizer->scopeOpeners[$tokenType]['shared'] === true;
if (isset($tokens[$i]['scope_condition']) === true) {
// We've been here before.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* already processed, skipping *' . PHP_EOL;
}
if ($isShared === false && isset($tokens[$i]['scope_closer']) === true) {
$i = $tokens[$i]['scope_closer'];
}
continue;
} else {
if ($currType === $tokenType && $isShared === false && $opener === null) {
// We haven't yet found our opener, but we have found another
// scope opener which is the same type as us, and we don't
// share openers, so we will never find one.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* it was another token\'s opener, bailing *' . PHP_EOL;
}
return $stackPtr;
} else {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* searching for opener *' . PHP_EOL;
}
if (isset($tokenizer->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
$oldIgnore = $ignore;
$ignore = 0;
}
// PHP has a max nesting level for functions. Stop before we hit that limit
// because too many loops means we've run into trouble anyway.
if ($depth > 50) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* reached maximum nesting level; aborting *' . PHP_EOL;
}
throw new PHP_CodeSniffer_Exception('Maximum nesting level reached; file could not be processed');
}
$oldDepth = $depth;
if ($isShared === true && isset($tokenizer->scopeOpeners[$tokenType]['with'][$currType]) === true) {
// Don't allow the depth to increment because this is
// possibly not a true nesting if we are sharing our closer.
// This can happen, for example, when a SWITCH has a large
// number of CASE statements with the same shared BREAK.
$depth--;
}
$i = self::_recurseScopeMap($tokens, $numTokens, $tokenizer, $eolChar, $i, $depth + 1, $ignore);
$depth = $oldDepth;
if (isset($tokenizer->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
$ignore = $oldIgnore;
}
}
}
//end if
}
//end if
if (isset($tokenizer->scopeOpeners[$currType]['start'][$tokenType]) === true && $opener === null) {
if ($tokenType === T_OPEN_CURLY_BRACKET) {
if (isset($tokens[$stackPtr]['parenthesis_closer']) === true && $i < $tokens[$stackPtr]['parenthesis_closer']) {
// We found a curly brace inside the condition of the
// current scope opener, so it must be a string offset.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* ignoring curly brace *' . PHP_EOL;
}
$ignore++;
} else {
// Make sure this is actually an opener and not a
// string offset (e.g., $var{0}).
for ($x = $i - 1; $x > 0; $x--) {
if (isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$x]['code']]) === true) {
continue;
} else {
// If the first non-whitespace/comment token is a
// variable or object operator then this is an opener
// for a string offset and not a scope.
if ($tokens[$x]['code'] === T_VARIABLE || $tokens[$x]['code'] === T_OBJECT_OPERATOR) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* ignoring curly brace *' . PHP_EOL;
}
$ignore++;
}
//end if
break;
}
//end if
}
//end for
}
//end if
}
//end if
if ($ignore === 0 || $tokenType !== T_OPEN_CURLY_BRACKET) {
// We found the opening scope token for $currType.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found scope opener for {$stackPtr}:{$type}" . PHP_EOL;
}
$opener = $i;
}
} else {
if ($tokenType === T_OPEN_PARENTHESIS) {
if (isset($tokens[$i]['parenthesis_owner']) === true) {
$owner = $tokens[$i]['parenthesis_owner'];
if (isset(PHP_CodeSniffer_Tokens::$scopeOpeners[$tokens[$owner]['code']]) === true && isset($tokens[$i]['parenthesis_closer']) === true) {
// If we get into here, then we opened a parenthesis for
// a scope (eg. an if or else if) so we need to update the
// start of the line so that when we check to see
// if the closing parenthesis is more than 3 lines away from
// the statement, we check from the closing parenthesis.
$startLine = $tokens[$tokens[$i]['parenthesis_closer']]['line'];
}
}
} else {
if ($tokenType === T_OPEN_CURLY_BRACKET && $opener !== null) {
// We opened something that we don't have a scope opener for.
// Examples of this are curly brackets for string offsets etc.
// We want to ignore this so that we don't have an invalid scope
// map.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* ignoring curly brace *' . PHP_EOL;
}
$ignore++;
} else {
if ($tokenType === T_CLOSE_CURLY_BRACKET && $ignore > 0) {
// We found the end token for the opener we were ignoring.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* finished ignoring curly brace *' . PHP_EOL;
}
$ignore--;
} else {
if ($opener === null && isset($tokenizer->scopeOpeners[$currType]) === true) {
// If we still haven't found the opener after 3 lines,
// we're not going to find it, unless we know it requires
// an opener (in which case we better keep looking) or the last
// token was empty (in which case we'll just confirm there is
// more code in this file and not just a big comment).
if ($tokens[$i]['line'] >= $startLine + 3 && isset(PHP_CodeSniffer_Tokens::$emptyTokens[$tokens[$i - 1]['code']]) === false) {
if ($tokenizer->scopeOpeners[$currType]['strict'] === true) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
$lines = $tokens[$i]['line'] - $startLine;
echo str_repeat("\t", $depth);
echo "=> Still looking for {$stackPtr}:{$type} scope opener after {$lines} lines" . PHP_EOL;
}
} else {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Couldn't find scope opener for {$stackPtr}:{$type}, bailing" . PHP_EOL;
}
return $stackPtr;
}
}
} else {
if ($opener !== null && $tokenType !== T_BREAK && isset($tokenizer->endScopeTokens[$tokenType]) === true) {
if (isset($tokens[$i]['scope_condition']) === false) {
if ($ignore > 0) {
// We found the end token for the opener we were ignoring.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* finished ignoring curly brace *' . PHP_EOL;
}
$ignore--;
} else {
// We found a token that closes the scope but it doesn't
// have a condition, so it belongs to another token and
// our token doesn't have a closer, so pretend this is
// the closer.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found (unexpected) scope closer for {$stackPtr}:{$type}" . PHP_EOL;
}
foreach (array($stackPtr, $opener) as $token) {
$tokens[$token]['scope_condition'] = $stackPtr;
$tokens[$token]['scope_opener'] = $opener;
$tokens[$token]['scope_closer'] = $i;
}
return $i - 1;
}
//end if
}
//end if
}
}
}
}
}
}
//end if
}
//end for
return $stackPtr;
}