Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. Tokenizer.php

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

Tokenizer

Namespace

PHP_CodeSniffer\Tokenizers

Code

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;
}
RSS feed
Powered by Drupal