Source for file Tokenizer.php
Documentation is available at Tokenizer.php
* The base tokenizer class.
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
namespace PHP_CodeSniffer\Tokenizers;
use PHP_CodeSniffer\Exceptions\RuntimeException;
use PHP_CodeSniffer\Util;
* The config data for the run.
* @var \PHP_CodeSniffer\Config
protected $config = null;
* The EOL char used in the content.
protected $eolChar = array ();
* A token-based representation of the content.
protected $tokens = array ();
* Known lengths of tokens.
public $knownLengths = array ();
* A list of lines being ignored due to error suppression comments.
public $ignoredLines = array ();
* Initialise and run the tokenizer.
* @param string $content The content to tokenize,
* @param \PHP_CodeSniffer\Config | null $config The config data for the run.
* @param string $eolChar The EOL char used in the content.
* @throws TokenizerException If the file appears to be minified.
public function __construct ($content, $config, $eolChar= '\n')
$this->eolChar = $eolChar;
$this->tokens = $this->tokenize ($content);
$this->createPositionMap ();
$this->createParenthesisNestingMap ();
// Allow the tokenizer to do additional processing if required.
$this->processAdditional ();
* Checks the content to see if it looks minified.
* @param string $content The content to tokenize,
* @param string $eolChar The EOL char used in the content.
protected function isMinifiedContent ($content, $eolChar= '\n')
// Minified files often have a very large number of characters per line
// and cause issues when tokenizing.
$average = ($numChars / $numLines);
}//end isMinifiedContent()
* Gets the array of tokens.
public function getTokens ()
* Creates an array of tokens when given some content.
* @param string $string The string to tokenize.
abstract protected function tokenize ($string);
* Performs additional processing after main tokenizing.
abstract protected function processAdditional ();
* Sets token position information.
* Can also convert tabs into spaces. Each tab can represent between
* 1 and $width spaces, so this cannot be a straight string replace.
private function createPositionMap ()
$eolLen = (strlen($this->eolChar) * -1 );
$inTests = defined('PHP_CODESNIFFER_IN_TESTS');
$checkAnnotations = $this->config->annotations;
$this->tokensWithTabs = array (
T_CONSTANT_ENCAPSED_STRING => true ,
$this->numTokens = count($this->tokens);
for ($i = 0; $i < $this->numTokens; $i++ ) {
$this->tokens[$i]['line'] = $lineNumber;
$this->tokens[$i]['column'] = $currColumn;
if (isset ($this->knownLengths[$this->tokens[$i]['code']]) === true ) {
// There are no tabs in the tokens we know the length of.
$length = $this->knownLengths[$this->tokens[$i]['code']];
} else if ($this->config->tabWidth === 0
|| isset ($this->tokensWithTabs[$this->tokens[$i]['code']]) === false
|| strpos($this->tokens[$i]['content'], "\t") === false
// There are no tabs in this content, or we aren't replacing them.
if ($checkEncoding === true ) {
// Not using the default encoding, so take a bit more care.
$length = @iconv_strlen($this->tokens[$i]['content'], $this->config->encoding );
// String contained invalid characters, so revert to default.
$length = strlen($this->tokens[$i]['content']);
$length = strlen($this->tokens[$i]['content']);
$this->replaceTabsInToken ($this->tokens[$i]);
$length = $this->tokens[$i]['length'];
$this->tokens[$i]['length'] = $length;
if (isset ($this->knownLengths[$this->tokens[$i]['code']]) === false
&& strpos($this->tokens[$i]['content'], $this->eolChar) !== false
// Newline chars are not counted in the token length.
$this->tokens[$i]['length'] += $eolLen;
if ($checkAnnotations === true
&& ($this->tokens[$i]['code'] === T_COMMENT
|| ($inTests === true && $this->tokens[$i]['code'] === T_INLINE_HTML ))
if (strpos($this->tokens[$i]['content'], '@codingStandards') !== false ) {
&& strpos($this->tokens[$i]['content'], '@codingStandardsIgnoreStart') !== false
} else if ($ignoring === true
&& strpos($this->tokens[$i]['content'], '@codingStandardsIgnoreEnd') !== false
// Ignore this comment too.
$this->ignoredLines[$this->tokens[$i]['line']] = true;
} else if ($ignoring === false
&& strpos($this->tokens[$i]['content'], '@codingStandardsIgnoreLine') !== false
$this->ignoredLines[($this->tokens[$i]['line'] + 1 )] = true;
// Ignore this comment too.
$this->ignoredLines[$this->tokens[$i]['line']] = true;
if ($ignoring === true ) {
$this->ignoredLines[$this->tokens[$i]['line']] = true;
}//end createPositionMap()
* Replaces tabs in original token content with spaces.
* Each tab can represent between 1 and $config->tabWidth spaces,
* so this cannot be a straight string replace. The original content
* is placed into an orig_content index and the new token length is also
* set in the length index.
* @param array $token The token to replace tabs inside.
* @param string $prefix The character to use to represent the start of a tab.
* @param string $padding The character to use to represent the end of a tab.
public function replaceTabsInToken (&$token, $prefix= ' ', $padding= ' ')
$currColumn = $token['column'];
$tabWidth = $this->config->tabWidth;
// String only contains tabs, so we can shortcut the process.
$numTabs = strlen($token['content']);
$firstTabSize = ($tabWidth - (($currColumn - 1 ) % $tabWidth));
$length = ($firstTabSize + ($tabWidth * ($numTabs - 1 )));
$newContent = $prefix. str_repeat($padding, ($length - 1 ));
// We need to determine the length of each tab.
$tabs = explode("\t", $token['content']);
$numTabs = (count($tabs) - 1 );
foreach ($tabs as $content) {
if ($checkEncoding === true ) {
// Not using the default encoding, so take a bit more care.
$contentLength = @iconv_strlen($content, $this->config->encoding );
if ($contentLength === false ) {
// String contained invalid characters, so revert to default.
$contentLength = strlen($content);
$contentLength = strlen($content);
$currColumn += $contentLength;
$length += $contentLength;
// The last piece of content does not have a tab after it.
if ($tabNum === $numTabs) {
// Process the tab that comes after the content.
$lastCurrColumn = $currColumn;
// Move the pointer to the next tab stop.
if (($currColumn % $tabWidth) === 0 ) {
// This is the first tab, and we are already at a
// tab stop, so this tab counts as a single space.
while (($currColumn % $tabWidth) !== 0 ) {
$length += ($currColumn - $lastCurrColumn);
$newContent .= $prefix. str_repeat($padding, ($currColumn - $lastCurrColumn - 1 ));
$token['orig_content'] = $token['content'];
$token['content'] = $newContent;
$token['length'] = $length;
}//end replaceTabsInToken()
* Creates a map of brackets positions.
private function createTokenMap ()
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo "\t*** START TOKEN MAP ***".PHP_EOL;
$squareOpeners = array ();
$this->numTokens = count($this->tokens);
for ($i = 0; $i < $this->numTokens; $i++ ) {
if (isset (Util\Tokens ::$parenthesisOpeners[$this->tokens[$i]['code']]) === true ) {
$this->tokens[$i]['parenthesis_opener'] = null;
$this->tokens[$i]['parenthesis_closer'] = null;
$this->tokens[$i]['parenthesis_owner'] = $i;
$this->tokens[$i]['parenthesis_opener'] = $i;
if ($openOwner !== null ) {
$this->tokens[$openOwner]['parenthesis_opener'] = $i;
$this->tokens[$i]['parenthesis_owner'] = $openOwner;
// Did we set an owner for this set of parenthesis?
$numOpeners = count($openers);
if (isset ($this->tokens[$opener]['parenthesis_owner']) === true ) {
$owner = $this->tokens[$opener]['parenthesis_owner'];
$this->tokens[$owner]['parenthesis_closer'] = $i;
$this->tokens[$i]['parenthesis_owner'] = $owner;
$this->tokens[$i]['parenthesis_opener'] = $opener;
$this->tokens[$i]['parenthesis_closer'] = $i;
$this->tokens[$opener]['parenthesis_closer'] = $i;
switch ($this->tokens[$i]['code']) {
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo " => Found square bracket opener at $i".PHP_EOL;
if (isset ($this->tokens[$i]['scope_closer']) === false ) {
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo " => Found curly bracket opener at $i".PHP_EOL;
if (empty ($squareOpeners) === false ) {
$this->tokens[$i]['bracket_opener'] = $opener;
$this->tokens[$i]['bracket_closer'] = $i;
$this->tokens[$opener]['bracket_opener'] = $opener;
$this->tokens[$opener]['bracket_closer'] = $i;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo " \t=> Found square bracket closer at $i for $opener".PHP_EOL;
if (empty ($curlyOpeners) === false
&& isset ($this->tokens[$i]['scope_opener']) === false
$this->tokens[$i]['bracket_opener'] = $opener;
$this->tokens[$i]['bracket_closer'] = $i;
$this->tokens[$opener]['bracket_opener'] = $opener;
$this->tokens[$opener]['bracket_closer'] = $i;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo " \t=> Found curly bracket closer at $i for $opener".PHP_EOL;
// Cleanup for any openers that we didn't find closers for.
// This typically means there was a syntax error breaking things.
foreach ($openers as $opener) {
unset ($this->tokens[$opener]['parenthesis_opener']);
unset ($this->tokens[$opener]['parenthesis_owner']);
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo "\t*** END TOKEN MAP ***".PHP_EOL;
* Creates a map for the parenthesis tokens that surround other tokens.
private function createParenthesisNestingMap ()
for ($i = 0; $i < $this->numTokens; $i++ ) {
if (isset ($this->tokens[$i]['parenthesis_opener']) === true
&& $i === $this->tokens[$i]['parenthesis_opener']
if (empty ($map) === false ) {
$this->tokens[$i]['nested_parenthesis'] = $map;
if (isset ($this->tokens[$i]['parenthesis_closer']) === true ) {
$map[$this->tokens[$i]['parenthesis_opener']]
= $this->tokens[$i]['parenthesis_closer'];
} else if (isset ($this->tokens[$i]['parenthesis_closer']) === true
&& $i === $this->tokens[$i]['parenthesis_closer']
if (empty ($map) === false ) {
$this->tokens[$i]['nested_parenthesis'] = $map;
if (empty ($map) === false ) {
$this->tokens[$i]['nested_parenthesis'] = $map;
}//end createParenthesisNestingMap()
* Creates a scope map of tokens that open scopes.
private function createScopeMap ()
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo "\t*** START SCOPE MAP ***".PHP_EOL;
for ($i = 0; $i < $this->numTokens; $i++ ) {
// Check to see if the current token starts a new scope.
if (isset ($this->scopeOpeners[$this->tokens[$i]['code']]) === true ) {
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$i]['type'];
$content = Util\Common ::prepareForOutput ($this->tokens[$i]['content']);
echo " \tStart scope map at $i:$type => $content".PHP_EOL;
if (isset ($this->tokens[$i]['scope_condition']) === true ) {
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo "\t* already processed, skipping *".PHP_EOL;
$i = $this->recurseScopeMap ($i);
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo "\t*** END SCOPE MAP ***".PHP_EOL;
* Recurses though the scope openers to build a scope map.
* @param int $stackPtr The position in the stack of the token that
* opened the scope (eg. an IF token or FOR token).
* @param int $depth How many scope levels down we are.
* @param int $ignore How many curly braces we are ignoring.
* @return int The position in the stack that closed the scope.
private function recurseScopeMap ($stackPtr, $depth=1 , &$ignore=0 )
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo " => Begin scope map recursion at token $stackPtr with depth $depth".PHP_EOL;
$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 ) {
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 = Util\Common ::prepareForOutput ($this->tokens[$i]['content']);
echo " Process token $i on line $line [";
echo " ]: $type => $content".PHP_EOL;
// 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 )
|| $this->tokens[$i]['code'] === T_CLOSE_TAG )
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$stackPtr]['type'];
$closerType = 'semicolon';
$closerType = 'close tag';
echo " => Found $closerType before scope opener for $stackPtr:$type, bailing".PHP_EOL;
// 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.
&& isset ($this->scopeOpeners[$currType]['end'][$tokenType]) === true
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$stackPtr]['type'];
echo " => Found curly brace closer before scope opener for $stackPtr:$type, bailing".PHP_EOL;
&& (isset ($this->tokens[$i]['scope_opener']) === false
|| $this->scopeOpeners[$this->tokens[$stackPtr]['code']]['shared'] === true )
&& isset ($this->scopeOpeners[$currType]['end'][$tokenType]) === true
// The last opening bracket must have been for a string
// offset or alike, so let's ignore it.
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* finished ignoring curly brace *'.PHP_EOL;
// 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 " => Ignoring non-curly scope closer for $stackPtr:$type".PHP_EOL;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$stackPtr]['type'];
$closerType = $this->tokens[$scopeCloser]['type'];
echo " => Found scope closer ($scopeCloser:$closerType) for $stackPtr:$type".PHP_EOL;
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 "* closer needs to be tested *".PHP_EOL;
$i = self ::recurseScopeMap ($i, ($depth + 1 ), $ignore);
if (isset ($this->tokens[$scopeCloser]['scope_opener']) === false ) {
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo "* closer is not valid (no opener found) *".PHP_EOL;
} else if ($this->tokens[$this->tokens[$scopeCloser]['scope_opener']]['code'] !== $this->tokens[$opener]['code']) {
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$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 "* closer was valid *".PHP_EOL;
// The closer was not processed, so we need to
// complete that token as well.
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;
} 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']);
// Is this an opening condition ?
if (isset ($this->scopeOpeners[$tokenType]) === true ) {
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.
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 " => 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 '* already processed, skipping *'.PHP_EOL;
$i = $this->tokens[$i]['scope_closer'];
$i = self ::recurseScopeMap ($i, ($depth + 1 ), $ignore);
// 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 " => 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;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo "backtracking".PHP_EOL;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
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 '* already processed, skipping *'.PHP_EOL;
&& isset ($this->tokens[$i]['scope_closer']) === true
$i = $this->tokens[$i]['scope_closer'];
} else if ($currType === $tokenType
// 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 '* it was another token\'s opener, bailing *'.PHP_EOL;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* searching for opener *'.PHP_EOL;
// 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 (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* reached maximum nesting level; aborting *'.PHP_EOL;
throw new RuntimeException ('Maximum nesting level reached; file could not be processed');
&& 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.
$i = self ::recurseScopeMap ($i, ($depth + 1 ), $ignore);
if (isset ($this->scopeOpeners[$currType]['start'][$tokenType]) === true
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 '* ignoring curly brace inside condition *'.PHP_EOL;
// 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 (Util\Tokens ::$emptyTokens[$this->tokens[$x]['code']]) === true ) {
// 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 = Util\Tokens ::$assignmentTokens;
T_OBJECT_OPERATOR => true ,
if (isset ($disallowed[$this->tokens[$x]['code']]) === true ) {
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* ignoring curly brace *'.PHP_EOL;
// We found the opening scope token for $currType.
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$stackPtr]['type'];
echo " => Found scope opener for $stackPtr:$type".PHP_EOL;
if (isset ($this->tokens[$i]['parenthesis_owner']) === true ) {
$owner = $this->tokens[$i]['parenthesis_owner'];
if (isset (Util\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 3 lines away from
// the statement, we check from the closing parenthesis.
$startLine = $this->tokens[$this->tokens[$i]['parenthesis_closer']]['line'];
// 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
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* ignoring curly brace *'.PHP_EOL;
// We found the end token for the opener we were ignoring.
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* finished ignoring curly brace *'.PHP_EOL;
} else if ($opener === null
&& isset ($this->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 ($this->tokens[$i]['line'] >= ($startLine + 3 )
&& isset (Util\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 " => Still looking for $stackPtr:$type scope opener after $lines lines".PHP_EOL;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$stackPtr]['type'];
echo " => Couldn't find scope opener for $stackPtr:$type, bailing".PHP_EOL;
} else if ($opener !== null
&& $tokenType !== T_BREAK
&& isset ($this->endScopeTokens[$tokenType]) === true
if (isset ($this->tokens[$i]['scope_condition']) === false ) {
// We found the end token for the opener we were ignoring.
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* finished ignoring curly brace *'.PHP_EOL;
// 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
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$stackPtr]['type'];
echo " => Found (unexpected) scope closer for $stackPtr:$type".PHP_EOL;
foreach (array ($stackPtr, $opener) as $token) {
$this->tokens[$token]['scope_condition'] = $stackPtr;
$this->tokens[$token]['scope_opener'] = $opener;
$this->tokens[$token]['scope_closer'] = $i;
* Constructs the level map.
* The level map adds a 'level' index to each token which indicates the
* depth that a token within a set of scope blocks. It also adds a
* 'condition' index which is an array of the scope conditions that opened
* each of the scopes - position 0 being the first scope opener.
private function createLevelMap ()
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo "\t*** START LEVEL MAP ***".PHP_EOL;
$this->numTokens = count($this->tokens);
for ($i = 0; $i < $this->numTokens; $i++ ) {
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$i]['type'];
$line = $this->tokens[$i]['line'];
$len = $this->tokens[$i]['length'];
$col = $this->tokens[$i]['column'];
$content = Util\Common ::prepareForOutput ($this->tokens[$i]['content']);
echo " Process token $i on line $line [col:$col;len:$len;lvl:$level;";
if (empty ($conditions) !== true ) {
foreach ($conditions as $condition) {
$condString .= Util\Tokens ::tokenName ($condition). ',';
echo rtrim($condString, ','). ';';
echo " ]: $type => $content".PHP_EOL;
$this->tokens[$i]['level'] = $level;
$this->tokens[$i]['conditions'] = $conditions;
if (isset ($this->tokens[$i]['scope_condition']) === true ) {
// Check to see if this token opened the scope.
if ($this->tokens[$i]['scope_opener'] === $i) {
$stackPtr = $this->tokens[$i]['scope_condition'];
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$stackPtr]['type'];
echo " => Found scope opener for $stackPtr:$type".PHP_EOL;
$stackPtr = $this->tokens[$i]['scope_condition'];
// If we find a scope opener that has a shared closer,
// then we need to go back over the condition map that we
// just created and fix ourselves as we just added some
// conditions where there was none. This happens for T_CASE
// statements that are using the same break statement.
if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $this->tokens[$i]['scope_closer']) {
// This opener shares its closer with the previous opener,
// but we still need to check if the two openers share their
// closer with each other directly (like CASE and DEFAULT)
// or if they are just sharing because one doesn't have a
// closer (like CASE with no BREAK using a SWITCHes closer).
$thisType = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
$opener = $this->tokens[$lastOpener]['scope_condition'];
$isShared = isset ($this->scopeOpeners[$thisType]['with'][$this->tokens[$opener]['code']]);
reset($this->scopeOpeners[$thisType]['end']);
reset($this->scopeOpeners[$this->tokens[$opener]['code']]['end']);
$sameEnd = (current($this->scopeOpeners[$thisType]['end']) === current($this->scopeOpeners[$this->tokens[$opener]['code']]['end']));
if ($isShared === true && $sameEnd === true ) {
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$badToken]['type'];
echo " * shared closer, cleaning up $badToken:$type *".PHP_EOL;
for ($x = $this->tokens[$i]['scope_condition']; $x <= $i; $x++ ) {
$oldConditions = $this->tokens[$x]['conditions'];
$oldLevel = $this->tokens[$x]['level'];
$this->tokens[$x]['level']--;
unset ($this->tokens[$x]['conditions'][$badToken]);
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$x]['type'];
foreach ($oldConditions as $condition) {
$oldConds .= Util\Tokens ::tokenName ($condition). ',';
$oldConds = rtrim($oldConds, ',');
foreach ($this->tokens[$x]['conditions'] as $condition) {
$newConds .= Util\Tokens ::tokenName ($condition). ',';
$newConds = rtrim($newConds, ',');
$newLevel = $this->tokens[$x]['level'];
echo " * cleaned $x:$type *".PHP_EOL;
echo " => level changed from $oldLevel to $newLevel".PHP_EOL;
echo " => conditions changed from $oldConds to $newConds".PHP_EOL;
unset ($conditions[$badToken]);
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$badToken]['type'];
echo " * token $badToken:$type removed from conditions array *".PHP_EOL;
unset ($openers[$lastOpener]);
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* level decreased *'.PHP_EOL;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* level increased *'.PHP_EOL;
$conditions[$stackPtr] = $this->tokens[$stackPtr]['code'];
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$stackPtr]['type'];
echo " * token $stackPtr:$type added to conditions array *".PHP_EOL;
$lastOpener = $this->tokens[$i]['scope_opener'];
if ($lastOpener !== null ) {
$openers[$lastOpener] = $lastOpener;
} else if ($lastOpener !== null && $this->tokens[$lastOpener]['scope_closer'] === $i) {
if ($this->tokens[$opener]['scope_closer'] === $i) {
if (empty ($openers) === false ) {
$openers[$lastOpener] = $lastOpener;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$oldOpener]['type'];
echo " => Found scope closer for $oldOpener:$type".PHP_EOL;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* token '.Util\Tokens ::tokenName ($oldCondition). ' removed from conditions array *'.PHP_EOL;
// Make sure this closer actually belongs to us.
// Either the condition also has to think this is the
// closer, or it has to allow sharing with us.
$condition = $this->tokens[$this->tokens[$i]['scope_condition']]['code'];
if ($condition !== $oldCondition) {
if (isset ($this->scopeOpeners[$oldCondition]['with'][$condition]) === false ) {
$badToken = $this->tokens[$oldOpener]['scope_condition'];
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = Util\Tokens ::tokenName ($oldCondition);
echo " * scope closer was bad, cleaning up $badToken:$type *".PHP_EOL;
for ($x = ($oldOpener + 1 ); $x <= $i; $x++ ) {
$oldConditions = $this->tokens[$x]['conditions'];
$oldLevel = $this->tokens[$x]['level'];
$this->tokens[$x]['level']--;
unset ($this->tokens[$x]['conditions'][$badToken]);
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
$type = $this->tokens[$x]['type'];
foreach ($oldConditions as $condition) {
$oldConds .= Util\Tokens ::tokenName ($condition). ',';
$oldConds = rtrim($oldConds, ',');
foreach ($this->tokens[$x]['conditions'] as $condition) {
$newConds .= Util\Tokens ::tokenName ($condition). ',';
$newConds = rtrim($newConds, ',');
$newLevel = $this->tokens[$x]['level'];
echo " * cleaned $x:$type *".PHP_EOL;
echo " => level changed from $oldLevel to $newLevel".PHP_EOL;
echo " => conditions changed from $oldConds to $newConds".PHP_EOL;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo '* level decreased *'.PHP_EOL;
$this->tokens[$i]['level'] = $level;
$this->tokens[$i]['conditions'] = $conditions;
if (PHP_CODESNIFFER_VERBOSITY > 1 ) {
echo "\t*** END LEVEL MAP ***".PHP_EOL;
Documentation generated on Mon, 11 Mar 2019 15:27:49 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|