PHP_CodeSniffer_File::_recurseScopeMap PHP Method

_recurseScopeMap() private static method

Recurses though the scope openers to build a scope map.
private static _recurseScopeMap ( array &$tokens, integer $numTokens, object $tokenizer, string $eolChar, integer $stackPtr, integer $depth = 1, integer &$ignore ) : integer
$tokens array The array of tokens to process.
$numTokens integer The size of the tokens array.
$tokenizer object The tokenizer being used to process this file.
$eolChar string The EOL character to use for splitting strings.
$stackPtr integer The position in the stack of the token that opened the scope (eg. an IF token or FOR token).
$depth integer How many scope levels down we are.
$ignore integer How many curly braces we are ignoring.
return integer The position in the stack that closed the scope.
    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;
    }