* @author Marc McIntyre * @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$ * @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. * * Token Information * * Each token within the stack contains information about itself: * * * 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) * ), * ); * * * Conditional Tokens * * In addition to the standard token fields, conditions contain information to * determine where their scope begins and ends: * * * 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 * ); * * * The condition, the scope opener and the scope closer each contain this * information. * * Parenthesis Tokens * * 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. * * * array( * 'parenthesis_opener' => 34, * 'parenthesis_closer' => 40, * ); * * * 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. * * * array( * 'parenthesis_opener' => 34, * 'parenthesis_closer' => 40, * 'parenthesis_owner' => 33, * ); * * * 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. * * * 'nested_parentheisis' => array( * 12 => 15 * 11 => 14 * ); * * * Extended Tokens * * PHP_CodeSniffer extends and augments some of the tokens created by * token_get_all(). A full list of these tokens can be seen in the * Tokens.php file. * * @category PHP * @package PHP_CodeSniffer * @author Greg Sherwood * @author Marc McIntyre * @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); // 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); } $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: * * * 0 => array( * 'name' => '$var', // The variable name. * 'pass_by_reference' => false, // Passed by reference. * 'type_hint' => string, // Type hint for array or custom type * ) * * * 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: * * 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. * ); * * * @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: * * * array( * 'scope' => 'public', // public private or protected * 'is_static' => false, // true if the static keyword was found. * ); * * * @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 ?>