function Tokenizer::recurseScopeMap
Recurses though the scope openers to build a scope map.
Parameters
int $stackPtr The position in the stack of the token that: opened the scope (eg. an IF token or FOR token).
int $depth How many scope levels down we are.:
int $ignore How many curly braces we are ignoring.:
Return value
int The position in the stack that closed the scope.
Throws
\PHP_CodeSniffer\Exceptions\TokenizerException If the nesting level gets too deep.
1 call to Tokenizer::recurseScopeMap()
- Tokenizer::createScopeMap in vendor/
squizlabs/ php_codesniffer/ src/ Tokenizers/ Tokenizer.php - Creates a scope map of tokens that open scopes.
File
-
vendor/
squizlabs/ php_codesniffer/ src/ Tokenizers/ Tokenizer.php, line 937
Class
Namespace
PHP_CodeSniffer\TokenizersCode
private function recurseScopeMap($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 = $this->tokens[$stackPtr]['code'];
$startLine = $this->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($this->scopeOpeners[$currType]['start'][$currType]) === true) {
$opener = $stackPtr;
}
for ($i = $stackPtr + 1; $i < $this->numTokens; $i++) {
$tokenType = $this->tokens[$i]['code'];
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->tokens[$i]['type'];
$line = $this->tokens[$i]['line'];
$content = Common::prepareForOutput($this->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.
// The same case also applies to USE statements, which don't have to have
// openers, so a following USE statement can cause an incorrect brace match.
if (($currType === T_IF || $currType === T_ELSE || $currType === T_USE) && $opener === null && ($this->tokens[$i]['code'] === T_SEMICOLON || $this->tokens[$i]['code'] === T_CLOSE_TAG)) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
if ($this->tokens[$i]['code'] === T_SEMICOLON) {
$closerType = 'semicolon';
}
else {
$closerType = 'close tag';
}
echo "=> Found {$closerType} before scope opener for {$stackPtr}:{$type}, bailing" . PHP_EOL;
}
return $i;
}
// Special case for PHP control structures that have no braces.
// If we find a curly brace closer before we find the opener,
// we're not going to find an opener. That closer probably belongs to
// a control structure higher up.
if ($opener === null && $ignore === 0 && $tokenType === T_CLOSE_CURLY_BRACKET && isset($this->scopeOpeners[$currType]['end'][$tokenType]) === true) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->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($this->tokens[$i]['scope_opener']) === false || $this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true) && isset($this->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 ($this->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 = $this->tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Ignoring non-curly scope closer for {$stackPtr}:{$type}" . PHP_EOL;
}
}
else {
$scopeCloser = $i;
$todo = [
$stackPtr,
$opener,
];
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->tokens[$stackPtr]['type'];
$closerType = $this->tokens[$scopeCloser]['type'];
echo str_repeat("\t", $depth);
echo "=> Found scope closer ({$scopeCloser}:{$closerType}) for {$stackPtr}:{$type}" . PHP_EOL;
}
$validCloser = true;
if (($this->tokens[$stackPtr]['code'] === T_IF || $this->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($i, $depth + 1, $ignore);
if (isset($this->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 ($this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['code'] !== $this->tokens[$opener]['code']) {
$validCloser = false;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
$type = $this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['type'];
$openerType = $this->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) {
$this->tokens[$token]['scope_condition'] = $stackPtr;
$this->tokens[$token]['scope_opener'] = $opener;
$this->tokens[$token]['scope_closer'] = $scopeCloser;
}
if ($this->scopeOpeners[$this->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($this->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($this->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($this->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_NAMESPACE) {
// PHP namespace keywords are special because they can be
// used as blocks but also inline as operators.
// So if we find them nested inside another opener, just skip them.
continue;
}
if ($tokenType === T_FUNCTION && $this->tokens[$stackPtr]['code'] !== T_FUNCTION) {
// Probably a closure, so process it manually.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found function before scope opener for {$stackPtr}:{$type}, processing manually" . PHP_EOL;
}
if (isset($this->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 = $this->tokens[$i]['scope_closer'];
continue;
}
$i = self::recurseScopeMap($i, $depth + 1, $ignore);
continue;
}
//end if
if ($tokenType === T_CLASS) {
// Probably an anonymous class inside another anonymous class,
// so process it manually.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found class before scope opener for {$stackPtr}:{$type}, processing manually" . PHP_EOL;
}
if (isset($this->tokens[$i]['scope_closer']) === true) {
// We've already processed this anon class.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* already processed, skipping *' . PHP_EOL;
}
$i = $this->tokens[$i]['scope_closer'];
continue;
}
$i = self::recurseScopeMap($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 = $this->tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found new opening condition before scope opener for {$stackPtr}:{$type}, ";
}
if (($this->tokens[$stackPtr]['code'] === T_IF || $this->tokens[$stackPtr]['code'] === T_ELSEIF || $this->tokens[$stackPtr]['code'] === T_ELSE) && ($this->tokens[$i]['code'] === T_ELSE || $this->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 = $this->scopeOpeners[$tokenType]['shared'] === true;
if (isset($this->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($this->tokens[$i]['scope_closer']) === true) {
$i = $this->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($this->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 TokenizerException('Maximum nesting level reached; file could not be processed');
}
$oldDepth = $depth;
if ($isShared === true && isset($this->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($i, $depth + 1, $ignore);
$depth = $oldDepth;
if (isset($this->scopeOpeners[$tokenType]['end'][T_CLOSE_CURLY_BRACKET]) === true) {
$ignore = $oldIgnore;
}
}
}
//end if
}
//end if
if (isset($this->scopeOpeners[$currType]['start'][$tokenType]) === true && $opener === null) {
if ($tokenType === T_OPEN_CURLY_BRACKET) {
if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true && $i < $this->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 inside condition *' . 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(Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
continue;
}
else {
// If the first non-whitespace/comment token looks like this
// brace is a string offset, or this brace is mid-way through
// a new statement, it isn't a scope opener.
$disallowed = Tokens::$assignmentTokens;
$disallowed += [
T_DOLLAR => true,
T_VARIABLE => true,
T_OBJECT_OPERATOR => true,
T_NULLSAFE_OBJECT_OPERATOR => true,
T_COMMA => true,
T_OPEN_PARENTHESIS => true,
];
if (isset($disallowed[$this->tokens[$x]['code']]) === true) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
echo str_repeat("\t", $depth);
echo '* ignoring curly brace *' . PHP_EOL;
}
$ignore++;
}
break;
}
//end if
}
//end for
}
//end if
}
//end if
if ($ignore === 0 || $tokenType !== T_OPEN_CURLY_BRACKET) {
$openerNested = isset($this->tokens[$i]['nested_parenthesis']);
$ownerNested = isset($this->tokens[$stackPtr]['nested_parenthesis']);
if ($openerNested === true && $ownerNested === false || $openerNested === false && $ownerNested === true || $openerNested === true && $this->tokens[$i]['nested_parenthesis'] !== $this->tokens[$stackPtr]['nested_parenthesis']) {
// We found the a token that looks like the opener, but it's nested differently.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->tokens[$i]['type'];
echo str_repeat("\t", $depth);
echo "* ignoring possible opener {$i}:{$type} as nested parenthesis don't match *" . PHP_EOL;
}
}
else {
// We found the opening scope token for $currType.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found scope opener for {$stackPtr}:{$type}" . PHP_EOL;
}
$opener = $i;
}
}
//end if
}
else {
if ($tokenType === T_SEMICOLON && $opener === null && (isset($this->tokens[$stackPtr]['parenthesis_closer']) === false || $i > $this->tokens[$stackPtr]['parenthesis_closer'])) {
// Found the end of a statement but still haven't
// found our opener, so we are never going to find one.
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found end of statement before scope opener for {$stackPtr}:{$type}, continuing" . PHP_EOL;
}
return $i - 1;
}
else {
if ($tokenType === T_OPEN_PARENTHESIS) {
if (isset($this->tokens[$i]['parenthesis_owner']) === true) {
$owner = $this->tokens[$i]['parenthesis_owner'];
if (isset(Tokens::$scopeOpeners[$this->tokens[$owner]['code']]) === true && isset($this->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 n lines away from
// the statement, we check from the closing parenthesis.
$startLine = $this->tokens[$this->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($this->scopeOpeners[$currType]) === true) {
// If we still haven't found the opener after 30 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 ($this->tokens[$i]['line'] >= $startLine + 30 && isset(Tokens::$emptyTokens[$this->tokens[$i - 1]['code']]) === false) {
if ($this->scopeOpeners[$currType]['strict'] === true) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$type = $this->tokens[$stackPtr]['type'];
$lines = $this->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 = $this->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($this->endScopeTokens[$tokenType]) === true) {
if (isset($this->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 = $this->tokens[$stackPtr]['type'];
echo str_repeat("\t", $depth);
echo "=> Found (unexpected) scope closer for {$stackPtr}:{$type}" . PHP_EOL;
}
foreach ([
$stackPtr,
$opener,
] as $token) {
$this->tokens[$token]['scope_condition'] = $stackPtr;
$this->tokens[$token]['scope_opener'] = $opener;
$this->tokens[$token]['scope_closer'] = $i;
}
return $i - 1;
}
//end if
}
//end if
}
}
}
}
}
}
}
//end if
}
//end for
return $stackPtr;
}