Viewing file: File.php (72.26 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php /** * A PHP_CodeSniffer_File object represents a PHP source file and the tokens * associated with it. * * PHP version 5 * * @category PHP * @package PHP_CodeSniffer * @author Greg Sherwood <gsherwood@squiz.net> * @author Marc McIntyre <mmcintyre@squiz.net> * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence * @version CVS: $Id: File.php,v 1.68 2008/07/01 01:25:08 squiz Exp $ * @link http://pear.php.net/package/PHP_CodeSniffer */
/** * A PHP_CodeSniffer_File object represents a PHP source file and the tokens * associated with it. * * It provides a means for traversing the token stack, along with * other token related operations. If a PHP_CodeSniffer_Sniff finds and error or * warning within a PHP_CodeSniffer_File, you can raise an error using the * addError() or addWarning() methods. * * <b>Token Information</b> * * Each token within the stack contains information about itself: * * <code> * array( * 'code' => 301, // the token type code (see token_get_all()) * 'content' => 'if', // the token content * 'type' => 'T_IF', // the token name * 'line' => 56, // the line number when the token is located * 'column' => 12, // the column in the line where this token * // starts (starts from 1) * 'level' => 2 // the depth a token is within the scopes open * 'conditions' => array( // a list of scope condition token * // positions => codes that * 2 => 50, // openened the scopes that this token exists * 9 => 353, // in (see conditional tokens section below) * ), * ); * </code> * * <b>Conditional Tokens</b> * * In addition to the standard token fields, conditions contain information to * determine where their scope begins and ends: * * <code> * array( * 'scope_condition' => 38, // the token position of the condition * 'scope_opener' => 41, // the token position that started the scope * 'scope_closer' => 70, // the token position that ended the scope * ); * </code> * * The condition, the scope opener and the scope closer each contain this * information. * * <b>Parenthesis Tokens</b> * * Each parenthesis token (T_OPEN_PARENTHESIS and T_CLOSE_PARENTHESIS) has a * reference to their opening and closing parenthesis, one being itself, the * other being its oposite. * * <code> * array( * 'parenthesis_opener' => 34, * 'parenthesis_closer' => 40, * ); * </code> * * Some tokens can "own" a set of parethesis. For example a T_FUNCTION token * has parenthesis around its argument list. These tokens also have the * parenthesis_opener and and parenthesis_closer indicies. Not all parethesis * have owners, for example parenthesis used for arithmetic operations and * function calls. The parenthesis tokens that have an owner have the following * auxilery array indicies. * * <code> * array( * 'parenthesis_opener' => 34, * 'parenthesis_closer' => 40, * 'parenthesis_owner' => 33, * ); * </code> * * Each token within a set of parenthesis also has an array indicy * 'nested_parenthesis' which is an array of the * left parenthesis => right parenthesis token positions. * * <code> * 'nested_parentheisis' => array( * 12 => 15 * 11 => 14 * ); * </code> * * <b>Extended Tokens</b> * * PHP_CodeSniffer extends and augments some of the tokens created by * <i>token_get_all()</i>. A full list of these tokens can be seen in the * <i>Tokens.php</i> file. * * @category PHP * @package PHP_CodeSniffer * @author Greg Sherwood <gsherwood@squiz.net> * @author Marc McIntyre <mmcintyre@squiz.net> * @copyright 2006 Squiz Pty Ltd (ABN 77 084 670 600) * @license http://matrix.squiz.net/developer/tools/php_cs/licence BSD Licence * @version Release: 1.1.0 * @link http://pear.php.net/package/PHP_CodeSniffer */ class PHP_CodeSniffer_File {
/** * The absolute path to the file associated with this object. * * @var string */ private $_file = '';
/** * The EOL character this file uses. * * @var string */ public $eolChar = '';
/** * The tokenizer being used for this file. * * @var object */ public $tokenizer = null;
/** * The tokenizer being used for this file. * * @var string */ public $tokenizerType = 'PHP';
/** * The number of tokens in this file. * * Stored here to save calling count() everywhere. * * @var int */ public $numTokens = 0;
/** * The tokens stack map. * * Note that the tokens in this array differ in format to the tokens * produced by token_get_all(). Tokens are initially produced with * token_get_all(), then augmented so that it's easier to process them. * * @var array() * @see Tokens.php */ private $_tokens = array();
/** * The errors raised from PHP_CodeSniffer_Sniffs. * * @var array() * @see getErrors() */ private $_errors = array();
/** * The warnings raised form PHP_CodeSniffer_Sniffs. * * @var array() * @see getWarnings() */ private $_warnings = array();
/** * The total number of errors raised. * * @var int */ private $_errorCount = 0;
/** * The total number of warnings raised. * * @var int */ private $_warningCount = 0;
/** * An array of sniffs listening to this file's processing. * * @var array(PHP_CodeSniffer_Sniff) */ private $_listeners = array();
/** * A constant to represent an error in PHP_CodeSniffer. * * @var int */ const ERROR = 0;
/** * A constant to represent a warning in PHP_CodeSniffer. * * @var int */ const WARNING = 1;
/** * An array of extensions mapping to the tokenizer to use. * * This value gets set by PHP_CodeSniffer when the object is created. * * @var array */ protected $tokenizers = array();
/** * Constructs a PHP_CodeSniffer_File. * * @param string $file The absolute path to the file * to process. * @param array(string) $listeners The initial listeners listening * to processing of this file. * @param array $tokenizers An array of extensions mapping * to the tokenizer to use. * * @throws PHP_CodeSniffer_Exception If the register() method does * not return an array. */ public function __construct($file, array $listeners, array $tokenizers) { foreach ($listeners as $listenerClass) { $listener = new $listenerClass(); $tokens = $listener->register();
if (is_array($tokens) === false) { $msg = "Sniff $listenerClass register() method must return an array"; throw new PHP_CodeSniffer_Exception($msg); }
$this->addTokenListener($listener, $tokens); }
$this->_file = trim($file); $this->tokenizers = $tokenizers;
}//end __construct()
/** * Adds a listener to the token stack that listens to the specific tokens. * * When PHP_CodeSniffer encounters on the the tokens specified in $tokens, it * invokes the process method of the sniff. * * @param PHP_CodeSniffer_Sniff $listener The listener to add to the * listener stack. * @param array(int) $tokens The token types the listener wishes to * listen to. * * @return void */ public function addTokenListener(PHP_CodeSniffer_Sniff $listener, array $tokens) { foreach ($tokens as $token) { if (isset($this->_listeners[$token]) === false) { $this->_listeners[$token] = array(); }
if (in_array($listener, $this->_listeners[$token], true) === false) { $this->_listeners[$token][] = $listener; } }
}//end addTokenListener()
/** * Removes a listener from listening from the specified tokens. * * @param PHP_CodeSniffer_Sniff $listener The listener to remove from the * listener stack. * @param array(int) $tokens The token types the listener wishes to * stop listen to. * * @return void */ public function removeTokenListener(PHP_CodeSniffer_Sniff $listener, array $tokens) { foreach ($tokens as $token) { if (isset($this->_listeners[$token]) === false) { continue; }
if (in_array($listener, $this->_listeners[$token]) === true) { foreach ($this->_listeners[$token] as $pos => $value) { if ($value === $listener) { unset($this->_listeners[$token][$pos]); } } } }
}//end removeTokenListener()
/** * Returns the token stack for this file. * * @return array() */ public function getTokens() { return $this->_tokens;
}//end getTokens()
/** * Starts the stack traversal and tells listeners when tokens are found. * * @param string $contents The contents to parse. If NULL, the content * is taken from the file system. * * @return void */ public function start($contents=null) { $this->_parse($contents);
if (PHP_CODESNIFFER_VERBOSITY > 2) { echo "\t*** START TOKEN PROCESSING ***".PHP_EOL; }
// Foreach of the listeners that have registed to listen for this // token, get them to process it. foreach ($this->_tokens as $stackPtr => $token) { if (PHP_CODESNIFFER_VERBOSITY > 2) { $type = $token['type']; $content = str_replace($this->eolChar, '\n', $token['content']); echo "\t\tProcess token $stackPtr: $type => $content".PHP_EOL; }
$tokenType = $token['code']; if (isset($this->_listeners[$tokenType]) === true) { foreach ($this->_listeners[$tokenType] as $listener) { // Make sure this sniff supports the tokenizer // we are currently using. $vars = get_class_vars(get_class($listener)); if (isset($vars['supportedTokenizers']) === true) { if (in_array($this->tokenizerType, $vars['supportedTokenizers']) === false) { continue; } } else { // The default supported tokenizer is PHP. if ($this->tokenizerType !== 'PHP') { continue; } }
if (PHP_CODESNIFFER_VERBOSITY > 2) { $startTime = microtime(true); echo "\t\t\tProcessing ".get_class($listener).'... '; }
$listener->process($this, $stackPtr);
if (PHP_CODESNIFFER_VERBOSITY > 2) { $timeTaken = round((microtime(true) - $startTime), 4); echo "DONE in $timeTaken seconds".PHP_EOL; } }//end foreach }//end if }//end foreach
if (PHP_CODESNIFFER_VERBOSITY > 2) { echo "\t*** END TOKEN PROCESSING ***".PHP_EOL; }
// We don't need the tokens any more, so get rid of them // to save some memory. $this->_tokens = null; $this->_listeners = null;
}//end start()
/** * Tokenizes the file and preapres it for the test run. * * @param string $contents The contents to parse. If NULL, the content * is taken from the file system. * * @return void */ private function _parse($contents=null) { $this->eolChar = self::detectLineEndings($this->_file, $contents); $this->eolChar = "\n"; // Determine the tokenizer from the file extension. $fileParts = explode('.', $this->_file); $extension = array_pop($fileParts); if (isset($this->tokenizers[$extension]) === true) { $tokenizerClass = 'PHP_CodeSniffer_Tokenizers_'.$this->tokenizers[$extension]; $this->tokenizerType = $this->tokenizers[$extension]; } else { // Revert to default. $tokenizerClass = 'PHP_CodeSniffer_Tokenizers_'.$this->tokenizerType; }
$tokenizer = new $tokenizerClass(); $this->tokenizer = $tokenizer;
if ($contents === null) { $contents = file_get_contents($this->_file); } $contents = str_replace("\r", "", $contents);
$this->_tokens = self::tokenizeString($contents, $tokenizer, $this->eolChar); $this->numTokens = count($this->_tokens);
if (PHP_CODESNIFFER_VERBOSITY > 0) { if ($this->numTokens === 0) { $numLines = 0; } else { $numLines = $this->_tokens[($this->numTokens - 1)]['line']; }
echo "[$this->numTokens tokens in $numLines lines]... "; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo PHP_EOL; } }
}//end _parse()
/** * Opens a file and detects the EOL character being used. * * @param string $file The full path to the file. * @param string $contents The contents to parse. If NULL, the content * is taken from the file system. * * @return string * @throws PHP_CodeSniffer_Exception If $file could not be opened. */ public static function detectLineEndings($file, $contents=null) { if ($contents === null) { // Determine the newline character being used in this file. // Will be either \r, \r\n or \n. $handle = fopen($file, 'r'); if ($handle === false) { $error = 'File could not be opened; could not auto-detect line endings'; throw new PHP_CodeSniffer_Exception($error); }
$firstLine = fgets($handle); fclose($handle);
$eolChar = substr($firstLine, -1); if ($eolChar === "\n") { $secondLastChar = substr($firstLine, -2, 1); if ($secondLastChar === "\r") { $eolChar = "\r\n"; } } } else { if (preg_match("/\r\n?|\n/", $contents, $matches) !== 1) { $error = 'Could not auto-detect line endings from content'; throw new PHP_CodeSniffer_Exception($error); }
$eolChar = $matches[0]; }//end if return $eolChar;
}//end detectLineEndings()
/** * Adds an error to the error stack. * * @param string $error The error message. * @param int $stackPtr The stack position where the error occured. * * @return void */ public function addError($error, $stackPtr) { if ($stackPtr === null) { $lineNum = 1; $column = 1; } else { $lineNum = $this->_tokens[$stackPtr]['line']; $column = $this->_tokens[$stackPtr]['column']; }
if (isset($this->_errors[$lineNum]) === false) { $this->errors[$lineNum] = array(); }
if (isset($this->_errors[$lineNum][$column]) === false) { $this->errors[$lineNum][$column] = array(); }
$this->_errors[$lineNum][$column][] = $error; $this->_errorCount++;
}//end addError()
/** * Adds an warning to the warning stack. * * @param string $warning The error message. * @param int $stackPtr The stack position where the error occured. * * @return void */ public function addWarning($warning, $stackPtr) { if ($stackPtr === null) { $lineNum = 1; $column = 1; } else { $lineNum = $this->_tokens[$stackPtr]['line']; $column = $this->_tokens[$stackPtr]['column']; }
if (isset($this->_warnings[$lineNum]) === false) { $this->_warnings[$lineNum] = array(); }
if (isset($this->_warnings[$lineNum][$column]) === false) { $this->_warnings[$lineNum][$column] = array(); }
$this->_warnings[$lineNum][$column][] = $warning; $this->_warningCount++;
}//end addWarning()
/** * Returns the number of errors raised. * * @return int */ public function getErrorCount() { return $this->_errorCount;
}//end getErrorCount()
/** * Returns the number of warnings raised. * * @return int */ public function getWarningCount() { return $this->_warningCount;
}//end getWarningCount()
/** * Returns the errors raised from processing this file. * * @return array */ public function getErrors() { return $this->_errors;
}//end getErrors()
/** * Returns the warnings raised from processing this file. * * @return array */ public function getWarnings() { return $this->_warnings;
}//end getWarnings()
/** * Returns the absolute filename of this file. * * @return string */ public function getFilename() { return $this->_file;
}//end getFilename()
/** * Creates an array of tokens when given some PHP code. * * Starts by using token_get_all() but does a lot of extra processing * to insert information about the context of the token. * * @param string $string The string to tokenize. * @param object $tokenizer A tokenizer class to use to tokenize the string. * @param string $eolChar The EOL character to use for splitting strings. * * @return array */ public static function tokenizeString($string, $tokenizer, $eolChar='\n') { $tokens = $tokenizer->tokenizeString($string, $eolChar);
self::_createLineMap($tokens, $tokenizer, $eolChar); self::_createBracketMap($tokens, $tokenizer, $eolChar); self::_createParenthesisMap($tokens, $tokenizer, $eolChar); self::_createParenthesisNestingMap($tokens, $tokenizer, $eolChar); self::_createScopeMap($tokens, $tokenizer, $eolChar);
// If we know the width of each tab, convert tabs // into spaces so sniffs can use one method of checking. if (PHP_CODESNIFFER_TAB_WIDTH > 0) { self::_convertTabs($tokens, $tokenizer, $eolChar); }
// Column map requires the line map to be complete. self::_createColumnMap($tokens, $tokenizer, $eolChar); self::_createLevelMap($tokens, $tokenizer, $eolChar);
return $tokens;
}//end tokenizeString()
/** * Creates a map of tokens => line numbers for each token. * * @param array &$tokens The array of tokens to process. * @param object $tokenizer The tokenizer being used to process this file. * @param string $eolChar The EOL character to use for splitting strings. * * @return void */ private static function _createLineMap(&$tokens, $tokenizer, $eolChar) { $lineNumber = 1; $count = count($tokens);
for ($i = 0; $i < $count; $i++) { $tokens[$i]['line'] = $lineNumber; $lineNumber += substr_count($tokens[$i]['content'], $eolChar); }
}//end _createLineMap()
/** * Converts tabs into spaces. * * Each tab can represent between 1 and $width spaces, so * this cannot be a straight string replace. * * @param array &$tokens The array of tokens to process. * @param object $tokenizer The tokenizer being used to process this file. * @param string $eolChar The EOL character to use for splitting strings. * * @return void */ private static function _convertTabs(&$tokens, $tokenizer, $eolChar) { $currColumn = 1; $count = count($tokens);
for ($i = 0; $i < $count; $i++) { $tokenContent = $tokens[$i]['content'];
if (strpos($tokenContent, "\t") === false) { // There are no tabs in this content. $currColumn += (strlen($tokenContent) - 1); } else { // We need to determine the length of each tab. $tabs = preg_split("|(\t)|", $tokenContent, -1, PREG_SPLIT_DELIM_CAPTURE); $tabNum = 0; $adjustedTab = false; $tabsToSpaces = array(); $newContent = '';
foreach ($tabs as $content) { if ($content === '') { continue; }
if (strpos($content, "\t") === false) { // This piece of content is not a tab. $currColumn += strlen($content); $newContent .= $content; } else { $lastCurrColumn = $currColumn; $tabNum++;
// Move the pointer to the next tab stop. if (($currColumn % PHP_CODESNIFFER_TAB_WIDTH) === 0) { // This is the first tab, and we are already at a // tab stop, so this tab counts as a single space. $currColumn++; $adjustedTab = true; } else { $currColumn++; while (($currColumn % PHP_CODESNIFFER_TAB_WIDTH) != 0) { $currColumn++; }
$currColumn++; }
$newContent .= str_repeat(' ', ($currColumn - $lastCurrColumn)); }//end if }//end foreach
if ($tabNum === 1 && $adjustedTab === true) { $currColumn--; }
$tokens[$i]['content'] = $newContent; }//end if
if (isset($tokens[($i + 1)]['line']) === true && $tokens[($i + 1)]['line'] !== $tokens[$i]['line']) { $currColumn = 1; } else { $currColumn++; } }//end for
}//end _convertTabs()
/** * Creates a column map. * * The column map indicates where the token started on the line where it * exists. * * @param array &$tokens The array of tokens to process. * @param object $tokenizer The tokenizer being used to process this file. * @param string $eolChar The EOL character to use for splitting strings. * * @return void */ private static function _createColumnMap(&$tokens, $tokenizer, $eolChar) { $currColumn = 1; $count = count($tokens);
for ($i = 0; $i < $count; $i++) { $tokens[$i]['column'] = $currColumn; if (isset($tokens[($i + 1)]['line']) === true && $tokens[($i + 1)]['line'] !== $tokens[$i]['line']) { $currColumn = 1; } else { $currColumn += strlen($tokens[$i]['content']); } }
}//end _createColumnMap()
/** * Creates a map for opening and closing of square brackets. * * Each bracket token (T_OPEN_SQUARE_BRACKET and T_CLOSE_SQUARE_BRACKET) * has a reference to their opening and closing bracket * (bracket_opener and bracket_closer). * * @param array &$tokens The array of tokens to process. * @param object $tokenizer The tokenizer being used to process this file. * @param string $eolChar The EOL character to use for splitting strings. * * @return void */ private static function _createBracketMap(&$tokens, $tokenizer, $eolChar) { $openers = array(); $numTokens = count($tokens); $owners = array();
for ($i = 0; $i < $numTokens; $i++) { if ($tokens[$i]['code'] === T_OPEN_SQUARE_BRACKET) { $openers[] = $i; } else if ($tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET) { if (empty($openers) === false) { $opener = array_pop($openers); $tokens[$i]['bracket_opener'] = $opener; $tokens[$i]['bracket_closer'] = $i; $tokens[$opener]['bracket_opener'] = $opener; $tokens[$opener]['bracket_closer'] = $i; } } }
}//end _createBracketMap()
/** * Creates a map for opening and closing of parenthesis. * * Each parenthesis token (T_OPEN_PARENTHESIS and T_CLOSE_PARENTHESIS) has a * reference to their opening and closing parenthesis (parenthesis_opener * and parenthesis_closer). * * @param array &$tokens The array of tokens to process. * @param object $tokenizer The tokenizer being used to process this file. * @param string $eolChar The EOL character to use for splitting strings. * * @return void */ private static function _createParenthesisMap(&$tokens, $tokenizer, $eolChar) { $openers = array(); $numTokens = count($tokens); $openOwner = null;
for ($i = 0; $i < $numTokens; $i++) { if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$parenthesisOpeners) === true) { $tokens[$i]['parenthesis_opener'] = null; $tokens[$i]['parenthesis_closer'] = null; $tokens[$i]['parenthesis_owner'] = $i; $openOwner = $i; } else if ($tokens[$i]['code'] === T_OPEN_PARENTHESIS) { $openers[] = $i; $tokens[$i]['parenthesis_opener'] = $i; if ($openOwner !== null) { $tokens[$openOwner]['parenthesis_opener'] = $i; $tokens[$i]['parenthesis_owner'] = $openOwner; $openOwner = null; } } else if ($tokens[$i]['code'] === T_CLOSE_PARENTHESIS) { // Did we set an owner for this set of parenthesis? $numOpeners = count($openers); if ($numOpeners !== 0) { $opener = array_pop($openers); if (isset($tokens[$opener]['parenthesis_owner']) === true) { $owner = $tokens[$opener]['parenthesis_owner'];
$tokens[$owner]['parenthesis_closer'] = $i; $tokens[$i]['parenthesis_owner'] = $owner; }
$tokens[$i]['parenthesis_opener'] = $opener; $tokens[$i]['parenthesis_closer'] = $i; $tokens[$opener]['parenthesis_closer'] = $i; } }//end if }//end for
}//end _createParenthesisMap()
/** * Creates a map for the parenthesis tokens that surround other tokens. * * @param array &$tokens The array of tokens to process. * @param object $tokenizer The tokenizer being used to process this file. * @param string $eolChar The EOL character to use for splitting strings. * * @return void */ private static function _createParenthesisNestingMap(&$tokens, $tokenizer, $eolChar) { $numTokens = count($tokens); $map = array(); for ($i = 0; $i < $numTokens; $i++) { if (isset($tokens[$i]['parenthesis_opener']) === true && $i === $tokens[$i]['parenthesis_opener']) { if (empty($map) === false) { $tokens[$i]['nested_parenthesis'] = $map; }
if (isset($tokens[$i]['parenthesis_closer']) === true) { $map[$tokens[$i]['parenthesis_opener']] = $tokens[$i]['parenthesis_closer']; } } else if (isset($tokens[$i]['parenthesis_closer']) === true && $i === $tokens[$i]['parenthesis_closer']) { array_pop($map); if (empty($map) === false) { $tokens[$i]['nested_parenthesis'] = $map; } } else { if (empty($map) === false) { $tokens[$i]['nested_parenthesis'] = $map; } } }
}//end _createParenthesisNestingMap()
/** * Creates a scope map of tokens that open scopes. * * @param array &$tokens The array of tokens to process. * @param object $tokenizer The tokenizer being used to process this file. * @param string $eolChar The EOL character to use for splitting strings. * * @return void * @see _recurseScopeMap() */ private static function _createScopeMap(&$tokens, $tokenizer, $eolChar) { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t*** START SCOPE MAP ***".PHP_EOL; }
$numTokens = count($tokens); for ($i = 0; $i < $numTokens; $i++) { // Check to see if the current token starts a new scope. if (isset($tokenizer->scopeOpeners[$tokens[$i]['code']]) === true) { if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$i]['type']; $content = str_replace($eolChar, '\n', $tokens[$i]['content']); echo "\tStart scope map at $i: $type => $content".PHP_EOL; }
$i = self::_recurseScopeMap($tokens, $numTokens, $tokenizer, $eolChar, $i); } }
if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t*** END SCOPE MAP ***".PHP_EOL; }
}//end _createScopeMap()
/** * Recurses though the scope openers to build a scope map. * * @param array &$tokens The array of tokens to process. * @param int $numTokens The size of the tokens array. * @param object $tokenizer The tokenizer being used to process this file. * @param string $eolChar The EOL character to use for splitting strings. * @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. * * @return int The position in the stack that closed the scope. */ private static function _recurseScopeMap(&$tokens, $numTokens, $tokenizer, $eolChar, $stackPtr, $depth=1) { $opener = null; $currType = $tokens[$stackPtr]['code']; $startLine = $tokens[$stackPtr]['line']; $ignore = false;
// If the start token for this scope opener is the same as // the scope token, we have already found our opener. if ($currType === $tokenizer->scopeOpeners[$currType]['start']) { $opener = $stackPtr; }
for ($i = ($stackPtr + 1); $i < $numTokens; $i++) { $tokenType = $tokens[$i]['code'];
if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$i]['type']; $content = str_replace($eolChar, '\n', $tokens[$i]['content']); echo str_repeat("\t", $depth); echo "Process token $i ["; if ($opener !== null) { echo "opener:$opener;"; }
if ($ignore === true) { echo 'ignore;'; }
echo "]: $type => $content".PHP_EOL; }
// Is this an opening condition ? if (isset($tokenizer->scopeOpeners[$tokenType]) === true) { if ($opener === null) { // 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 "=> Couldn't find scope opener for $stackPtr ($type), bailing".PHP_EOL; }
return $stackPtr; }
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; }
$i = self::_recurseScopeMap($tokens, $numTokens, $tokenizer, $eolChar, $i, ($depth + 1)); }//end if }//end if start scope
if ($tokenType === $tokenizer->scopeOpeners[$currType]['start'] && $opener === null) { if ($tokenType === T_OPEN_CURLY_BRACKET) { // Make sure this is actually an opener and not a // string offset (e.g., $var{0}). for ($x = ($i - 1); $x > 0; $x--) { if (in_array($tokens[$x]['code'], PHP_CodeSniffer_Tokens::$emptyTokens) === 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 = true; }//end if
break; }//end if }//end for }//end if
if ($ignore === false) { // 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 === $tokenizer->scopeOpeners[$currType]['end'] && $opener !== null) { if ($ignore === true && $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 = false; } else { if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$stackPtr]['type']; echo str_repeat("\t", $depth); echo "=> Found scope closer for $stackPtr ($type)".PHP_EOL; }
foreach (array($stackPtr, $opener, $i) as $token) { $tokens[$token]['scope_condition'] = $stackPtr; $tokens[$token]['scope_opener'] = $opener; $tokens[$token]['scope_closer'] = $i; }
if ($tokenizer->scopeOpeners[$tokens[$stackPtr]['code']]['shared'] === true) { return $opener; } else { return $i; } }//end if } else if ($tokenType === T_OPEN_PARENTHESIS) { if (isset($tokens[$i]['parenthesis_owner']) === true) { $owner = $tokens[$i]['parenthesis_owner']; if (in_array($tokens[$owner]['code'], PHP_CodeSniffer_Tokens::$scopeOpeners) === 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). We can just skip to // the closing parenthesis. $i = $tokens[$i]['parenthesis_closer'];
// 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'];
if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", $depth); echo '* skipping parenthesis *'.PHP_EOL; } } } } 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 = true; } 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. if ($tokens[$i]['line'] >= ($startLine + 3)) { 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 && in_array($tokenType, $tokenizer->endScopeTokens) === true) { if (isset($tokens[$i]['scope_condition']) === false) { if ($ignore === true) { // 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 = false; } 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;
}//end _recurseScopeMap()
/** * Constructs the level map. * * The level map adds a 'level' indice to each token which indicates the * depth that a token within a set of scope blocks. It also adds a * 'condition' indice which is an array of the scope conditions that opened * each of the scopes - position 0 being the first scope opener. * * @param array &$tokens The array of tokens to process. * @param object $tokenizer The tokenizer being used to process this file. * @param string $eolChar The EOL character to use for splitting strings. * * @return void */ private static function _createLevelMap(&$tokens, $tokenizer, $eolChar) { if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t*** START LEVEL MAP ***".PHP_EOL; }
$numTokens = count($tokens); $level = 0; $conditions = array(); $lastOpener = null; $openers = array();
for ($i = 0; $i < $numTokens; $i++) { if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$i]['type']; $line = $tokens[$i]['line']; $content = str_replace($eolChar, '\n', $tokens[$i]['content']); echo str_repeat("\t", ($level + 1)); echo "Process token $i on line $line [lvl:$level;"; if (empty($conditions) !== true) { $condString = 'conds;'; foreach ($conditions as $condition) { $condString .= token_name($condition).','; }
echo rtrim($condString, ',').';'; }
echo "]: $type => $content".PHP_EOL; }
$tokens[$i]['level'] = $level; $tokens[$i]['conditions'] = $conditions;
if (isset($tokens[$i]['scope_condition']) === true) { // Check to see if this token opened the scope. if ($tokens[$i]['scope_opener'] === $i) { $stackPtr = $tokens[$i]['scope_condition']; if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$stackPtr]['type']; echo str_repeat("\t", ($level + 1)); echo "=> Found scope opener for $stackPtr ($type)".PHP_EOL; }
$stackPtr = $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 && $tokens[$lastOpener]['scope_closer'] === $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 = $tokens[$tokens[$i]['scope_condition']]['code']; $opener = $tokens[$lastOpener]['scope_condition']; if (in_array($tokens[$opener]['code'], $tokenizer->scopeOpeners[$thisType]['with']) === true) { $badToken = $tokens[$lastOpener]['scope_condition']; if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$badToken]['type']; echo str_repeat("\t", ($level + 1)); echo "* shared closer, cleaning up $badToken ($type) *".PHP_EOL; }
for ($x = $tokens[$i]['scope_condition']; $x <= $i; $x++) { $oldConditions = $tokens[$x]['conditions']; $oldLevel = $tokens[$x]['level']; $tokens[$x]['level']--; unset($tokens[$x]['conditions'][$badToken]); if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$x]['type']; $oldConds = ''; foreach ($oldConditions as $condition) { $oldConds .= token_name($condition).','; }
$oldConds = rtrim($oldConds, ',');
$newConds = ''; foreach ($tokens[$x]['conditions'] as $condition) { $newConds .= token_name($condition).','; }
$newConds = rtrim($newConds, ',');
$newLevel = $tokens[$x]['level']; echo str_repeat("\t", ($level + 1)); echo "* cleaned $x ($type) *".PHP_EOL; echo str_repeat("\t", ($level + 2)); echo "=> level changed from $oldLevel to $newLevel".PHP_EOL; echo str_repeat("\t", ($level + 2)); echo "=> conditions changed from $oldConds to $newConds".PHP_EOL; }//end if }//end for
unset($conditions[$badToken]); if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$badToken]['type']; echo str_repeat("\t", ($level + 1)); echo "* token $badToken ($type) removed from conditions array *".PHP_EOL; }
unset ($openers[$lastOpener]);
$level--; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", ($level + 2)); echo '* level decreased *'.PHP_EOL; } }//end if }//end if
$level++; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", ($level + 1)); echo '* level increased *'.PHP_EOL; }
$conditions[$stackPtr] = $tokens[$stackPtr]['code']; if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$stackPtr]['type']; echo str_repeat("\t", ($level + 1)); echo "* token $stackPtr ($type) added to conditions array *".PHP_EOL; }
$lastOpener = $tokens[$i]['scope_opener']; if ($lastOpener !== null) { $openers[$lastOpener] = $lastOpener; } } else if ($tokens[$i]['scope_closer'] === $i) { $removedCondition = false; foreach (array_reverse($openers) as $opener) { if ($tokens[$opener]['scope_closer'] === $i) { $oldOpener = array_pop($openers); if (empty($openers) === false) { $lastOpener = array_pop($openers); $openers[$lastOpener] = $lastOpener; } else { $lastOpener = null; }
if (PHP_CODESNIFFER_VERBOSITY > 1) { $type = $tokens[$oldOpener]['type']; echo str_repeat("\t", ($level + 1)); echo "=> Found scope closer for $oldOpener ($type)".PHP_EOL; }
if ($removedCondition === false) { $oldCondition = array_pop($conditions); if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", ($level + 1)); echo '* token '.token_name($oldCondition).' removed from conditions array *'.PHP_EOL; }
$level--; if (PHP_CODESNIFFER_VERBOSITY > 1) { echo str_repeat("\t", ($level + 2)); echo '* level decreased *'.PHP_EOL; } }
$tokens[$i]['level'] = $level; $tokens[$i]['conditions'] = $conditions; }//end if }//end foreach }//end if }//end if }//end for
if (PHP_CODESNIFFER_VERBOSITY > 1) { echo "\t*** END LEVEL MAP ***".PHP_EOL; }
}//end _createLevelMap()
/** * Returns the declaration names for T_CLASS, T_INTERFACE and T_FUNCTION tokens. * * @param int $stackPtr The position of the declaration token which * declared the class, interface or function. * * @return string The name of the class, interface or function. * @throws PHP_CodeSniffer_Exception If the specified token is not of type * T_FUNCTION, T_CLASS or T_INTERFACE. */ public function getDeclarationName($stackPtr) { $tokenCode = $this->_tokens[$stackPtr]['code']; if ($tokenCode !== T_FUNCTION && $tokenCode !== T_CLASS && $tokenCode !== T_INTERFACE) { throw new PHP_CodeSniffer_Exception('Token type is not T_FUNCTION, T_CLASS OR T_INTERFACE'); }
$token = $this->findNext(T_STRING, $stackPtr); return $this->_tokens[$token]['content'];
}//end getDeclarationName()
/** * Returns the method parameters for the specified T_FUNCTION token. * * Each parameter is in the following format: * * <code> * 0 => array( * 'name' => '$var', // The variable name. * 'pass_by_reference' => false, // Passed by reference. * 'type_hint' => string, // Type hint for array or custom type * ) * </code> * * Parameters with default values have and additional array indice of * 'default' with the value of the default as a string. * * @param int $stackPtr The position in the stack of the T_FUNCTION token * to acquire the parameters for. * * @return array() * @throws PHP_CodeSniffer_Exception If the specified $stackPtr is not of * type T_FUNCTION. */ public function getMethodParameters($stackPtr) { if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION) { throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION'); }
$opener = $this->_tokens[$stackPtr]['parenthesis_opener']; $closer = $this->_tokens[$stackPtr]['parenthesis_closer'];
$vars = array(); $currVar = null; $defaultStart = null; $paramCount = 0; $passByReference = false; $typeHint = '';
for ($i = ($opener + 1); $i <= $closer; $i++) { // Check to see if this token has a parenthesis opener. If it does // its likely to be an array, which might have arguments in it, which // we cause problems in our parsing below, so lets just skip to the // end of it. if (isset($this->_tokens[$i]['parenthesis_opener']) === true) { // Don't do this if its the close parenthesis for the method. if ($i !== $this->_tokens[$i]['parenthesis_closer']) { $i = ($this->_tokens[$i]['parenthesis_closer'] + 1); } }
switch ($this->_tokens[$i]['code']) { case T_BITWISE_AND: $passByReference = true; break; case T_VARIABLE: $currVar = $i; break; case T_ARRAY_HINT: $typeHint = $this->_tokens[$i]['content']; break; case T_STRING: // This is a string, so it may be a type hint, but it could // also be a constant used as a default value. $prevComma = $this->findPrevious(T_COMMA, $i, $opener); if ($prevComma !== false) { $nextEquals = $this->findNext(T_EQUAL, $prevComma, $i); if ($nextEquals !== false) { break; } }
$typeHint = $this->_tokens[$i]['content']; break; case T_CLOSE_PARENTHESIS: case T_COMMA: // If it's null, then there must be no parameters for this // method. if ($currVar === null) { continue; }
$vars[$paramCount] = array(); $vars[$paramCount]['name'] = $this->_tokens[$currVar]['content'];
if ($defaultStart !== null) { $vars[$paramCount]['default'] = $this->getTokensAsString($defaultStart, ($i - $defaultStart)); }
$vars[$paramCount]['pass_by_reference'] = $passByReference; $vars[$paramCount]['type_hint'] = $typeHint;
// Reset the vars, as we are about to process the next parameter. $defaultStart = null; $passByReference = false; $typeHint = '';
$paramCount++; break; case T_EQUAL: $defaultStart = ($i + 1); break; }//end switch }//end for
return $vars;
}//end getMethodParameters()
/** * Returns the visibility and implementation properies of a method. * * The format of the array is: * <code> * array( * 'scope' => 'public', // public private or protected * 'scope_specified' => true, // true is scope keyword was found. * 'is_abstract' => false, // true if the abstract keyword was found. * 'is_final' => false, // true if the final keyword was found. * 'is_static' => false, // true if the static keyword was found. * ); * </code> * * @param int $stackPtr The position in the stack of the T_FUNCTION token to * acquire the properties for. * * @return array * @throws PHP_CodeSniffer_Exception If the specified position is not a * T_FUNCTION token. */ public function getMethodProperties($stackPtr) { if ($this->_tokens[$stackPtr]['code'] !== T_FUNCTION) { throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_FUNCTION'); }
$valid = array( T_PUBLIC, T_PRIVATE, T_PROTECTED, T_STATIC, T_FINAL, T_ABSTRACT, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, );
$scope = 'public'; $scopeSpecified = false; $isAbstract = false; $isFinal = false; $isStatic = false;
for ($i = ($stackPtr - 1); $i > 0; $i--) { if (in_array($this->_tokens[$i]['code'], $valid) === false) { break; }
switch ($this->_tokens[$i]['code']) { case T_PUBLIC: $scope = 'public'; $scopeSpecified = true; break; case T_PRIVATE: $scope = 'private'; $scopeSpecified = true; break; case T_PROTECTED: $scope = 'protected'; $scopeSpecified = true; break; case T_ABSTRACT: $isAbstract = true; break; case T_FINAL: $isFinal = true; break; case T_STATIC: $isStatic = true; break; }//end switch }//end for
return array( 'scope' => $scope, 'scope_specified' => $scopeSpecified, 'is_abstract' => $isAbstract, 'is_final' => $isFinal, 'is_static' => $isStatic, );
}//end getMethodProperties()
/** * Returns the visibility and implementation properies of the class member * variable found at the specified position in the stack. * * The format of the array is: * * <code> * array( * 'scope' => 'public', // public private or protected * 'is_static' => false, // true if the static keyword was found. * ); * </code> * * @param int $stackPtr The position in the stack of the T_VARIABLE token to * acquire the properties for. * * @return array * @throws PHP_CodeSniffer_Exception If the specified position is not a * T_VARIABLE token, or if the position is not * a class member variable. */ public function getMemberProperties($stackPtr) { if ($this->_tokens[$stackPtr]['code'] !== T_VARIABLE) { throw new PHP_CodeSniffer_Exception('$stackPtr must be of type T_VARIABLE'); }
end($this->_tokens[$stackPtr]['conditions']); $ptr = key($this->_tokens[$stackPtr]['conditions']); if (isset($this->_tokens[$ptr]) === false || $this->_tokens[$ptr]['code'] !== T_CLASS) { if (isset($this->_tokens[$ptr]) === true && $this->_tokens[$ptr]['code'] === T_INTERFACE) { $error = 'Possible parse error: interfaces may not include member vars'; $this->addWarning($error, $stackPtr); return array(); } else { throw new PHP_CodeSniffer_Exception('$stackPtr is not a class member var'); } }
$valid = array( T_PUBLIC, T_PRIVATE, T_PROTECTED, T_STATIC, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, );
$scope = 'public'; $scopeSpecified = false; $isStatic = false;
for ($i = ($stackPtr - 1); $i > 0; $i--) { if (in_array($this->_tokens[$i]['code'], $valid) === false) { break; }
switch ($this->_tokens[$i]['code']) { case T_PUBLIC: $scope = 'public'; $scopeSpecified = true; break; case T_PRIVATE: $scope = 'private'; $scopeSpecified = true; break; case T_PROTECTED: $scope = 'protected'; $scopeSpecified = true; break; case T_STATIC: $isStatic = true; break; } }//end for
return array( 'scope' => $scope, 'scope_specified' => $scopeSpecified, 'is_static' => $isStatic, );
}//end getMemberProperties()
/** * Determine if the passed token is a reference operator. * * Returns true if the specified token position represents a reference. * Returns false if the token represents a bitwise operator. * * @param int $stackPtr The position of the T_BITWISE_AND token. * * @return boolean */ public function isReference($stackPtr) { if ($this->_tokens[$stackPtr]['code'] !== T_BITWISE_AND) { return false; }
$tokenBefore = $this->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
if ($this->_tokens[$tokenBefore]['code'] === T_FUNCTION) { // Function returns a reference. return true; }
if ($this->_tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) { // Inside a foreach loop, this is a reference. return true; }
if ($this->_tokens[$tokenBefore]['code'] === T_AS) { // Inside a foreach loop, this is a reference. return true; }
if (in_array($this->_tokens[$tokenBefore]['code'], PHP_CodeSniffer_Tokens::$assignmentTokens) === true) { // This is directly after an assignment. It's a reference. Even if // it is part of an operation, the other tests will handle it. return true; }
if (isset($this->_tokens[$stackPtr]['nested_parenthesis']) === true) { $brackets = $this->_tokens[$stackPtr]['nested_parenthesis']; $lastBracket = array_pop($brackets); if (isset($this->_tokens[$lastBracket]['parenthesis_owner']) === true) { $owner = $this->_tokens[$this->_tokens[$lastBracket]['parenthesis_owner']]; if ($owner['code'] === T_FUNCTION) { // Inside a function declaration, this is a reference. return true; } } }
return false;
}//end isReference()
/** * Returns the content of the tokens from the specified start position in * the token stack for the specified legnth. * * @param int $start The position to start from in the token stack. * @param int $length The length of tokens to traverse from the start pos. * * @return string The token contents. */ public function getTokensAsString($start, $length) { $str = ''; $end = ($start + $length); for ($i = $start; $i < $end; $i++) { $str .= $this->_tokens[$i]['content']; }
return $str;
}//end getTokensAsString()
/** * Returns the position of the next specified token(s). * * If a value is specified, the next token of the specified type(s) * containing the specified value will be returned. * * Returns false if no token can be found. * * @param int|array $types The type(s) of tokens to search for. * @param int $start The position to start searching from in the * token stack. * @param int $end The end position to fail if no token is found. * if not specified or null, end will default to * the start of the token stack. * @param bool $exclude If true, find the next token that are NOT of * the types specified in $types. * @param string $value The value that the token(s) must be equal to. * If value is ommited, tokens with any value will * be returned. * @param bool $local If true, tokens outside the current statement * will not be cheked. IE. checking will stop * at the next semi-colon found. * * @return int | bool * @see findNext() */ public function findPrevious($types, $start, $end=null, $exclude=false, $value=null, $local=false) { $types = (array) $types;
if ($end === null) { $end = 0; }
for ($i = $start; $i >= $end; $i--) { $found = (bool) $exclude; foreach ($types as $type) { if ($this->_tokens[$i]['code'] === $type) { $found = !$exclude; break; } }
if ($found === true) { if ($value === null) { return $i; } else if ($this->_tokens[$i]['content'] === $value) { return $i; } }
if ($local === true && $this->_tokens[$i]['code'] === T_SEMICOLON) { break; } }//end for
return false;
}//end findPrevious()
/** * Returns the position of the next specified token(s). * * If a value is specified, the next token of the specified type(s) * containing the specified value will be returned. * * Returns false if no token can be found. * * @param int|array $types The type(s) of tokens to search for. * @param int $start The position to start searching from in the * token stack. * @param int $end The end position to fail if no token is found. * if not specified or null, end will default to * the end of the token stack. * @param bool $exclude If true, find the next token that is NOT of * a type specified in $types. * @param string $value The value that the token(s) must be equal to. * If value is ommited, tokens with any value will * be returned. * @param bool $local If true, tokens outside the current statement * will not be cheked. IE. checking will stop * at the next semi-colon found. * * @return int | bool * @see findPrevious() */ public function findNext($types, $start, $end=null, $exclude=false, $value=null, $local=false) { $types = (array) $types;
if ($end === null || $end > $this->numTokens) { $end = $this->numTokens; }
for ($i = $start; $i < $end; $i++) { $found = (bool) $exclude; foreach ($types as $type) { if ($this->_tokens[$i]['code'] === $type) { $found = !$exclude; break; } }
if ($found === true) { if ($value === null) { return $i; } else if ($this->_tokens[$i]['content'] === $value) { return $i; } }
if ($local === true && $this->_tokens[$i]['code'] === T_SEMICOLON) { break; } }//end for
return false;
}//end findNext()
/** * Returns the position of the first token on a line, matching given type. * * Returns false if no token can be found. * * @param int|array $types The type(s) of tokens to search for. * @param int $start The position to start searching from in the * token stack. The first token matching on * this line before this token will be returned. * @param bool $exclude If true, find the token that is NOT of * the types specified in $types. * @param string $value The value that the token must be equal to. * If value is ommited, tokens with any value will * be returned. * * @return int | bool */ public function findFirstOnLine($types, $start, $exclude=false, $value=null) { if (is_array($types) === false) { $types = array($types); }
$foundToken = false;
for ($i = $start; $i >= 0; $i--) { if ($this->_tokens[$i]['line'] < $this->_tokens[$start]['line']) { break; }
$found = $exclude; foreach ($types as $type) { if ($exclude === false) { if ($this->_tokens[$i]['code'] === $type) { $found = true; break; } } else { if ($this->_tokens[$i]['code'] === $type) { $found = false; break; } } }
if ($found === true) { if ($value === null) { $foundToken = $i; } else if ($this->_tokens[$i]['content'] === $value) { $foundToken = $i; } } }//end for
return $foundToken;
}//end findFirstOnLine()
/** * Determine if the passed token has a condition of one of the passed types. * * @param int $stackPtr The position of the token we are checking. * @param int|array $types The type(s) of tokens to search for. * * @return boolean */ public function hasCondition($stackPtr, $types) { // Check for the existence of the token. if (isset($this->_tokens[$stackPtr]) === false) { return false; }
// Make sure the token has conditions. if (isset($this->_tokens[$stackPtr]['conditions']) === false) { return false; }
$types = (array) $types; $conditions = $this->_tokens[$stackPtr]['conditions'];
foreach ($types as $type) { if (in_array($type, $conditions) === true) { // We found a token with the required type. return true; } }
return false;
}//end hasCondition()
}//end class
?>
|