Chris@0: Chris@0: * @copyright 2011-2012 Sam Graham Chris@0: * @license http://www.opensource.org/licenses/bsd-license.php BSD License Chris@0: * @link http://pear.php.net/package/PHP_CodeSniffer Chris@0: */ Chris@0: Chris@17: namespace DrupalPractice\Sniffs\CodeAnalysis; Chris@17: Chris@17: use PHP_CodeSniffer\Files\File; Chris@17: use PHP_CodeSniffer\Sniffs\Sniff; Chris@17: Chris@0: /** Chris@0: * Holds details of a scope. Chris@0: * Chris@0: * @category PHP Chris@0: * @package PHP_CodeSniffer Chris@0: * @author Sam Graham Chris@0: * @copyright 2011-2012 Sam Graham Chris@0: * @link http://pear.php.net/package/PHP_CodeSniffer Chris@0: */ Chris@0: class ScopeInfo Chris@0: { Chris@0: public $owner; Chris@0: public $opener; Chris@0: public $closer; Chris@0: public $variables = array(); Chris@0: Chris@0: Chris@0: /** Chris@0: * Constructor. Chris@0: * Chris@0: * @param int $currScope Chris@0: */ Chris@0: function __construct($currScope) Chris@0: { Chris@0: // TODO: extract opener/closer. Chris@0: $this->owner = $currScope; Chris@0: Chris@0: }//end __construct() Chris@0: Chris@0: Chris@0: }//end class Chris@0: Chris@0: Chris@0: /** Chris@0: * Holds details of a variable within a scope. Chris@0: * Chris@0: * @category PHP Chris@0: * @package PHP_CodeSniffer Chris@0: * @author Sam Graham Chris@0: * @copyright 2011 Sam Graham Chris@0: * @link http://pear.php.net/package/PHP_CodeSniffer Chris@0: */ Chris@0: class VariableInfo Chris@0: { Chris@0: public $name; Chris@0: /** Chris@0: * What scope the variable has: local, param, static, global, bound Chris@0: */ Chris@0: public $scopeType; Chris@0: public $typeHint; Chris@0: public $passByReference = false; Chris@0: public $firstDeclared; Chris@0: public $firstInitialized; Chris@0: public $firstRead; Chris@0: public $ignoreUnused = false; Chris@0: public $lastAssignment; Chris@0: Chris@0: static $scopeTypeDescriptions = array( Chris@0: 'local' => 'variable', Chris@0: 'param' => 'function parameter', Chris@0: 'static' => 'static variable', Chris@0: 'global' => 'global variable', Chris@0: 'bound' => 'bound variable', Chris@0: ); Chris@0: Chris@0: Chris@0: /** Chris@0: * Constructor. Chris@0: * Chris@0: * @param string $varName Chris@0: */ Chris@0: function __construct($varName) Chris@0: { Chris@0: $this->name = $varName; Chris@0: Chris@0: }//end __construct() Chris@0: Chris@0: Chris@0: }//end class Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks the for undefined function variables. Chris@0: * Chris@0: * This sniff checks that all function variables Chris@0: * are defined in the function body. Chris@0: * Chris@0: * @category PHP Chris@0: * @package PHP_CodeSniffer Chris@0: * @author Sam Graham Chris@0: * @copyright 2011 Sam Graham Chris@0: * @link http://pear.php.net/package/PHP_CodeSniffer Chris@0: */ Chris@17: class VariableAnalysisSniff implements Sniff Chris@0: { Chris@0: /** Chris@0: * The current phpcsFile being checked. Chris@0: * Chris@0: * @var phpcsFile Chris@0: */ Chris@0: protected $currentFile = null; Chris@0: Chris@0: /** Chris@0: * A list of scopes encountered so far and the variables within them. Chris@0: */ Chris@0: private $_scopes = array(); Chris@0: Chris@0: /** Chris@0: * A regexp for matching variable names in double-quoted strings. Chris@0: */ Chris@0: private $_double_quoted_variable_regexp = '|(? array(5), Chris@0: 'addFunction' => array(3), Chris@0: 'addTask' => array(3), Chris@0: 'addTaskBackground' => array(3), Chris@0: 'addTaskHigh' => array(3), Chris@0: 'addTaskHighBackground' => array(3), Chris@0: 'addTaskLow' => array(3), Chris@0: 'addTaskLowBackground' => array(3), Chris@0: 'addTaskStatus' => array(2), Chris@0: 'apc_dec' => array(3), Chris@0: 'apc_fetch' => array(2), Chris@0: 'apc_inc' => array(3), Chris@0: 'areConfusable' => array(3), Chris@0: 'array_multisort' => array(1), Chris@0: 'array_pop' => array(1), Chris@0: 'array_push' => array(1), Chris@0: 'array_replace' => array(1), Chris@0: 'array_replace_recursive' => array( Chris@0: 1, Chris@0: 2, Chris@0: 3, Chris@0: '...', Chris@0: ), Chris@0: 'array_shift' => array(1), Chris@0: 'array_splice' => array(1), Chris@0: 'array_unshift' => array(1), Chris@0: 'array_walk' => array(1), Chris@0: 'array_walk_recursive' => array(1), Chris@0: 'arsort' => array(1), Chris@0: 'asort' => array(1), Chris@0: 'asort' => array(1), Chris@0: 'bindColumn' => array(2), Chris@0: 'bindParam' => array(2), Chris@0: 'bind_param' => array( Chris@0: 2, Chris@0: 3, Chris@0: '...', Chris@0: ), Chris@0: 'bind_result' => array( Chris@0: 1, Chris@0: 2, Chris@0: '...', Chris@0: ), Chris@0: 'call_user_method' => array(2), Chris@0: 'call_user_method_array' => array(2), Chris@0: 'curl_multi_exec' => array(2), Chris@0: 'curl_multi_info_read' => array(2), Chris@0: 'current' => array(1), Chris@0: 'dbplus_curr' => array(2), Chris@0: 'dbplus_first' => array(2), Chris@0: 'dbplus_info' => array(3), Chris@0: 'dbplus_last' => array(2), Chris@0: 'dbplus_next' => array(2), Chris@0: 'dbplus_prev' => array(2), Chris@0: 'dbplus_tremove' => array(3), Chris@0: 'dns_get_record' => array( Chris@0: 3, Chris@0: 4, Chris@0: ), Chris@0: 'domxml_open_file' => array(3), Chris@0: 'domxml_open_mem' => array(3), Chris@0: 'each' => array(1), Chris@0: 'enchant_dict_quick_check' => array(3), Chris@0: 'end' => array(1), Chris@0: 'ereg' => array(3), Chris@0: 'eregi' => array(3), Chris@0: 'exec' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'exif_thumbnail' => array( Chris@0: 1, Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'expect_expectl' => array(3), Chris@0: 'extract' => array(1), Chris@0: 'filter' => array(3), Chris@0: 'flock' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'fscanf' => array( Chris@0: 2, Chris@0: 3, Chris@0: '...', Chris@0: ), Chris@0: 'fsockopen' => array( Chris@0: 3, Chris@0: 4, Chris@0: ), Chris@0: 'ftp_alloc' => array(3), Chris@0: 'get' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'getByKey' => array(4), Chris@0: 'getMulti' => array(2), Chris@0: 'getMultiByKey' => array(3), Chris@0: 'getimagesize' => array(2), Chris@0: 'getmxrr' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'gnupg_decryptverify' => array(3), Chris@0: 'gnupg_verify' => array(4), Chris@0: 'grapheme_extract' => array(5), Chris@0: 'headers_sent' => array( Chris@0: 1, Chris@0: 2, Chris@0: ), Chris@0: 'http_build_url' => array(4), Chris@0: 'http_get' => array(3), Chris@0: 'http_head' => array(3), Chris@0: 'http_negotiate_charset' => array(2), Chris@0: 'http_negotiate_content_type' => array(2), Chris@0: 'http_negotiate_language' => array(2), Chris@0: 'http_post_data' => array(4), Chris@0: 'http_post_fields' => array(5), Chris@0: 'http_put_data' => array(4), Chris@0: 'http_put_file' => array(4), Chris@0: 'http_put_stream' => array(4), Chris@0: 'http_request' => array(5), Chris@0: 'isSuspicious' => array(2), Chris@0: 'is_callable' => array(3), Chris@0: 'key' => array(1), Chris@0: 'krsort' => array(1), Chris@0: 'ksort' => array(1), Chris@0: 'ldap_get_option' => array(3), Chris@0: 'ldap_parse_reference' => array(3), Chris@0: 'ldap_parse_result' => array( Chris@0: 3, Chris@0: 4, Chris@0: 5, Chris@0: 6, Chris@0: ), Chris@0: 'localtime' => array(2), Chris@0: 'm_completeauthorizations' => array(2), Chris@0: 'maxdb_stmt_bind_param' => array( Chris@0: 3, Chris@0: 4, Chris@0: '...', Chris@0: ), Chris@0: 'maxdb_stmt_bind_result' => array( Chris@0: 2, Chris@0: 3, Chris@0: '...', Chris@0: ), Chris@0: 'mb_convert_variables' => array( Chris@0: 3, Chris@0: 4, Chris@0: '...', Chris@0: ), Chris@0: 'mb_parse_str' => array(2), Chris@0: 'mqseries_back' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'mqseries_begin' => array( Chris@0: 3, Chris@0: 4, Chris@0: ), Chris@0: 'mqseries_close' => array( Chris@0: 4, Chris@0: 5, Chris@0: ), Chris@0: 'mqseries_cmit' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'mqseries_conn' => array( Chris@0: 2, Chris@0: 3, Chris@0: 4, Chris@0: ), Chris@0: 'mqseries_connx' => array( Chris@0: 2, Chris@0: 3, Chris@0: 4, Chris@0: 5, Chris@0: ), Chris@0: 'mqseries_disc' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'mqseries_get' => array( Chris@0: 3, Chris@0: 4, Chris@0: 5, Chris@0: 6, Chris@0: 7, Chris@0: 8, Chris@0: 9, Chris@0: ), Chris@0: 'mqseries_inq' => array( Chris@0: 6, Chris@0: 8, Chris@0: 9, Chris@0: 10, Chris@0: ), Chris@0: 'mqseries_open' => array( Chris@0: 2, Chris@0: 4, Chris@0: 5, Chris@0: 6, Chris@0: ), Chris@0: 'mqseries_put' => array( Chris@0: 3, Chris@0: 4, Chris@0: 6, Chris@0: 7, Chris@0: ), Chris@0: 'mqseries_put1' => array( Chris@0: 2, Chris@0: 3, Chris@0: 4, Chris@0: 6, Chris@0: 7, Chris@0: ), Chris@0: 'mqseries_set' => array( Chris@0: 9, Chris@0: 10, Chris@0: ), Chris@0: 'msg_receive' => array( Chris@0: 3, Chris@0: 5, Chris@0: 8, Chris@0: ), Chris@0: 'msg_send' => array(6), Chris@0: 'mssql_bind' => array(3), Chris@0: 'natcasesort' => array(1), Chris@0: 'natsort' => array(1), Chris@0: 'ncurses_color_content' => array( Chris@0: 2, Chris@0: 3, Chris@0: 4, Chris@0: ), Chris@0: 'ncurses_getmaxyx' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'ncurses_getmouse' => array(1), Chris@0: 'ncurses_getyx' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'ncurses_instr' => array(1), Chris@0: 'ncurses_mouse_trafo' => array( Chris@0: 1, Chris@0: 2, Chris@0: ), Chris@0: 'ncurses_mousemask' => array(2), Chris@0: 'ncurses_pair_content' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'ncurses_wmouse_trafo' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'newt_button_bar' => array(1), Chris@0: 'newt_form_run' => array(2), Chris@0: 'newt_get_screen_size' => array( Chris@0: 1, Chris@0: 2, Chris@0: ), Chris@0: 'newt_grid_get_size' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'newt_reflow_text' => array( Chris@0: 5, Chris@0: 6, Chris@0: ), Chris@0: 'newt_win_entries' => array(7), Chris@0: 'newt_win_menu' => array(8), Chris@0: 'next' => array(1), Chris@0: 'oci_bind_array_by_name' => array(3), Chris@0: 'oci_bind_by_name' => array(3), Chris@0: 'oci_define_by_name' => array(3), Chris@0: 'oci_fetch_all' => array(2), Chris@0: 'ocifetchinto' => array(2), Chris@0: 'odbc_fetch_into' => array(2), Chris@0: 'openssl_csr_export' => array(2), Chris@0: 'openssl_csr_new' => array(2), Chris@0: 'openssl_open' => array(2), Chris@0: 'openssl_pkcs12_export' => array(2), Chris@0: 'openssl_pkcs12_read' => array(2), Chris@0: 'openssl_pkey_export' => array(2), Chris@0: 'openssl_private_decrypt' => array(2), Chris@0: 'openssl_private_encrypt' => array(2), Chris@0: 'openssl_public_decrypt' => array(2), Chris@0: 'openssl_public_encrypt' => array(2), Chris@0: 'openssl_random_pseudo_bytes' => array(2), Chris@0: 'openssl_seal' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'openssl_sign' => array(2), Chris@0: 'openssl_x509_export' => array(2), Chris@0: 'ovrimos_fetch_into' => array(2), Chris@0: 'parse' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'parseCurrency' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'parse_str' => array(2), Chris@0: 'parsekit_compile_file' => array(2), Chris@0: 'parsekit_compile_string' => array(2), Chris@0: 'passthru' => array(2), Chris@0: 'pcntl_sigprocmask' => array(3), Chris@0: 'pcntl_sigtimedwait' => array(2), Chris@0: 'pcntl_sigwaitinfo' => array(2), Chris@0: 'pcntl_wait' => array(1), Chris@0: 'pcntl_waitpid' => array(2), Chris@0: 'pfsockopen' => array( Chris@0: 3, Chris@0: 4, Chris@0: ), Chris@0: 'php_check_syntax' => array(2), Chris@0: 'poll' => array( Chris@0: 1, Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'preg_filter' => array(5), Chris@0: 'preg_match' => array(3), Chris@0: 'preg_match_all' => array(3), Chris@0: 'preg_replace' => array(5), Chris@0: 'preg_replace_callback' => array(5), Chris@0: 'prev' => array(1), Chris@0: 'proc_open' => array(3), Chris@0: 'query' => array(3), Chris@0: 'queryExec' => array(2), Chris@0: 'reset' => array(1), Chris@0: 'rsort' => array(1), Chris@0: 'settype' => array(1), Chris@0: 'shuffle' => array(1), Chris@0: 'similar_text' => array(3), Chris@0: 'socket_create_pair' => array(4), Chris@0: 'socket_getpeername' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'socket_getsockname' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'socket_recv' => array(2), Chris@0: 'socket_recvfrom' => array( Chris@0: 2, Chris@0: 5, Chris@0: 6, Chris@0: ), Chris@0: 'socket_select' => array( Chris@0: 1, Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'sort' => array(1), Chris@0: 'sortWithSortKeys' => array(1), Chris@0: 'sqlite_exec' => array(3), Chris@0: 'sqlite_factory' => array(3), Chris@0: 'sqlite_open' => array(3), Chris@0: 'sqlite_popen' => array(3), Chris@0: 'sqlite_query' => array(4), Chris@0: 'sqlite_query' => array(4), Chris@0: 'sqlite_unbuffered_query' => array(4), Chris@0: 'sscanf' => array( Chris@0: 3, Chris@0: '...', Chris@0: ), Chris@0: 'str_ireplace' => array(4), Chris@0: 'str_replace' => array(4), Chris@0: 'stream_open' => array(4), Chris@0: 'stream_select' => array( Chris@0: 1, Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'stream_socket_accept' => array(3), Chris@0: 'stream_socket_client' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'stream_socket_recvfrom' => array(4), Chris@0: 'stream_socket_server' => array( Chris@0: 2, Chris@0: 3, Chris@0: ), Chris@0: 'system' => array(2), Chris@0: 'uasort' => array(1), Chris@0: 'uksort' => array(1), Chris@0: 'unbufferedQuery' => array(3), Chris@0: 'usort' => array(1), Chris@0: 'wincache_ucache_dec' => array(3), Chris@0: 'wincache_ucache_get' => array(2), Chris@0: 'wincache_ucache_inc' => array(3), Chris@0: 'xdiff_string_merge3' => array(4), Chris@0: 'xdiff_string_patch' => array(4), Chris@0: 'xml_parse_into_struct' => array( Chris@0: 3, Chris@0: 4, Chris@0: ), Chris@0: 'xml_set_object' => array(2), Chris@0: 'xmlrpc_decode_request' => array(2), Chris@0: 'xmlrpc_set_type' => array(1), Chris@0: 'xslt_set_object' => array(2), Chris@0: 'yaml_parse' => array(3), Chris@0: 'yaml_parse_file' => array(3), Chris@0: 'yaml_parse_url' => array(3), Chris@0: 'yaz_ccl_parse' => array(3), Chris@0: 'yaz_hits' => array(2), Chris@0: 'yaz_scan_result' => array(2), Chris@0: 'yaz_wait' => array(1), Chris@0: ); Chris@0: Chris@0: /** Chris@0: * Allows an install to extend the list of known pass-by-reference functions Chris@0: * by defining generic.codeanalysis.variableanalysis.sitePassByRefFunctions. Chris@0: */ Chris@0: public $sitePassByRefFunctions = null; Chris@0: Chris@0: /** Chris@0: * Allows exceptions in a catch block to be unused without provoking unused-var warning. Chris@0: * Set generic.codeanalysis.variableanalysis.allowUnusedCaughtExceptions to a true value. Chris@0: */ Chris@0: public $allowUnusedCaughtExceptions = true; Chris@0: Chris@0: /** Chris@0: * Allow function parameters to be unused without provoking unused-var warning. Chris@0: * Set generic.codeanalysis.variableanalysis.allowUnusedFunctionParameters to a true value. Chris@0: */ Chris@0: public $allowUnusedFunctionParameters = true; Chris@0: Chris@0: /** Chris@0: * A list of names of placeholder variables that you want to ignore from Chris@0: * unused variable warnings, ie things like $junk. Chris@0: */ Chris@0: public $validUnusedVariableNames = null; Chris@0: Chris@0: Chris@0: /** Chris@0: * Returns an array of tokens this test wants to listen for. Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public function register() Chris@0: { Chris@0: // Magic to modfy $_passByRefFunctions with any site-specific settings. Chris@0: if (empty($this->sitePassByRefFunctions) === false) { Chris@0: foreach (preg_split('/\s+/', trim($this->sitePassByRefFunctions)) as $line) { Chris@0: list ($function, $args) = explode(':', $line); Chris@0: $this->_passByRefFunctions[$function] = explode(',', $args); Chris@0: } Chris@0: } Chris@0: Chris@0: if (empty($this->validUnusedVariableNames) === false) { Chris@0: $this->validUnusedVariableNames = preg_split('/\s+/', trim($this->validUnusedVariableNames)); Chris@0: } Chris@0: Chris@0: return array( Chris@0: T_VARIABLE, Chris@0: T_DOUBLE_QUOTED_STRING, Chris@0: T_HEREDOC, Chris@0: T_CLOSE_CURLY_BRACKET, Chris@0: T_STRING, Chris@0: ); Chris@0: Chris@0: }//end register() Chris@0: Chris@0: Chris@0: /** Chris@0: * Processes this test, when one of its tokens is encountered. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. Chris@17: * @param int $stackPtr The position of the current token Chris@17: * in the stack passed in $tokens. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@17: public function process(File $phpcsFile, $stackPtr) Chris@0: { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: // Debug code. Chris@0: // if ($token['content'] == '$param') { Chris@0: // echo "Found token on line {$token['line']}.\n" . print_r($token, true); Chris@0: // } Chris@0: // End: Debug code. Chris@0: if ($this->currentFile !== $phpcsFile) { Chris@0: $this->currentFile = $phpcsFile; Chris@0: } Chris@0: Chris@0: if ($token['code'] === T_VARIABLE) { Chris@0: return $this->processVariable($phpcsFile, $stackPtr); Chris@0: } Chris@0: Chris@0: if (($token['code'] === T_DOUBLE_QUOTED_STRING) Chris@0: || ($token['code'] === T_HEREDOC) Chris@0: ) { Chris@0: return $this->processVariableInString($phpcsFile, $stackPtr); Chris@0: } Chris@0: Chris@0: if (($token['code'] === T_STRING) && ($token['content'] === 'compact')) { Chris@0: return $this->processCompact($phpcsFile, $stackPtr); Chris@0: } Chris@0: Chris@0: if (($token['code'] === T_CLOSE_CURLY_BRACKET) Chris@0: && isset($token['scope_condition']) === true Chris@0: ) { Chris@0: return $this->processScopeClose($phpcsFile, $token['scope_condition']); Chris@0: } Chris@0: Chris@0: }//end process() Chris@0: Chris@0: Chris@0: /** Chris@0: * Remove special characters from the variable name. Chris@0: * Chris@0: * @param string $varName Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: function normalizeVarName($varName) Chris@0: { Chris@0: $varName = preg_replace('/[{}$]/', '', $varName); Chris@0: return $varName; Chris@0: Chris@0: }//end normalizeVarName() Chris@0: Chris@0: Chris@0: /** Chris@0: * Generate a scope key based on the current file. Chris@0: * Chris@0: * @param string $currScope Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: function scopeKey($currScope) Chris@0: { Chris@0: if ($currScope === false) { Chris@0: $currScope = 'file'; Chris@0: } Chris@0: Chris@0: if (isset($this->currentFile) === true) { Chris@0: return ($this->currentFile->getFilename()).':'.$currScope; Chris@0: } else { Chris@0: return ('unknown file').':'.$currScope; Chris@0: } Chris@0: Chris@0: }//end scopeKey() Chris@0: Chris@0: Chris@0: /** Chris@0: * Warning: this is an autovivifying get. Chris@0: * Chris@0: * @param string|false $currScope Chris@0: * @param bool $autoCreate Chris@0: * Chris@0: * @return ScopeInfo Chris@0: */ Chris@0: function getScopeInfo($currScope, $autoCreate = true) Chris@0: { Chris@0: $scopeKey = $this->scopeKey($currScope); Chris@0: if (isset($this->_scopes[$scopeKey]) === false) { Chris@0: if ($autoCreate === false) { Chris@0: return null; Chris@0: } Chris@0: Chris@0: $this->_scopes[$scopeKey] = new ScopeInfo($currScope); Chris@0: } Chris@0: Chris@0: return $this->_scopes[$scopeKey]; Chris@0: Chris@0: }//end getScopeInfo() Chris@0: Chris@0: Chris@0: /** Chris@0: * Get variable information for a given variable name. Chris@0: * Chris@0: * @param string $varName Chris@0: * Name of the variable. Chris@0: * @param int $currScope Chris@0: * Token stack pointer of the current scope. Chris@0: * @param bool $autoCreate Chris@0: * TRUE if the variable should be auto created. Chris@0: * Chris@0: * @return VariableInfo|null Chris@0: * Information about the variable. Chris@0: */ Chris@0: function getVariableInfo($varName, $currScope, $autoCreate = true) Chris@0: { Chris@0: $scopeInfo = $this->getScopeInfo($currScope, $autoCreate); Chris@0: if (isset($scopeInfo->variables[$varName]) === false) { Chris@0: if ($autoCreate === false) { Chris@0: return null; Chris@0: } Chris@0: Chris@0: $scopeInfo->variables[$varName] = new VariableInfo($varName); Chris@0: if (is_array($this->validUnusedVariableNames) === true Chris@0: && in_array($varName, $this->validUnusedVariableNames) === true Chris@0: ) { Chris@0: $scopeInfo->variables[$varName]->ignoreUnused = true; Chris@0: } Chris@0: } Chris@0: Chris@0: return $scopeInfo->variables[$varName]; Chris@0: Chris@0: }//end getVariableInfo() Chris@0: Chris@0: Chris@0: /** Chris@0: * Mark the given variable as being assigned. Chris@0: * Chris@0: * @param string $varName Chris@0: * @param int $stackPtr Chris@0: * @param string $currScope Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function markVariableAssignment($varName, $stackPtr, $currScope) Chris@0: { Chris@0: $varInfo = $this->getVariableInfo($varName, $currScope); Chris@0: if (isset($varInfo->scopeType) === false) { Chris@0: $varInfo->scopeType = 'local'; Chris@0: } Chris@0: Chris@0: if (isset($varInfo->firstInitialized) === true && ($varInfo->firstInitialized <= $stackPtr)) { Chris@0: $varInfo->lastAssignment = $stackPtr; Chris@0: return; Chris@0: } Chris@0: Chris@0: $varInfo->firstInitialized = $stackPtr; Chris@0: Chris@0: }//end markVariableAssignment() Chris@0: Chris@0: Chris@0: /** Chris@0: * Mark the given variable as being declared. Chris@0: * Chris@0: * @param string $varName Chris@0: * @param string $scopeType Chris@0: * @param string $typeHint Chris@0: * @param int $stackPtr Chris@0: * @param string $currScope Chris@0: * @param bool $permitMatchingRedeclaration Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function markVariableDeclaration($varName, $scopeType, $typeHint, $stackPtr, $currScope, $permitMatchingRedeclaration = false) Chris@0: { Chris@0: $varInfo = $this->getVariableInfo($varName, $currScope); Chris@0: if (isset($varInfo->scopeType) === true) { Chris@0: if (($permitMatchingRedeclaration === false) Chris@0: || ($varInfo->scopeType !== $scopeType) Chris@0: ) { Chris@0: // Issue redeclaration/reuse warning Chris@0: // Note: we check off scopeType not firstDeclared, this is so that Chris@0: // we catch declarations that come after implicit declarations like Chris@0: // use of a variable as a local. Chris@0: $this->currentFile->addWarning( Chris@0: "Redeclaration of %s %s as %s.", Chris@0: $stackPtr, Chris@0: 'VariableRedeclaration', Chris@0: array( Chris@0: VariableInfo::$scopeTypeDescriptions[$varInfo->scopeType], Chris@0: "\${$varName}", Chris@0: VariableInfo::$scopeTypeDescriptions[$scopeType], Chris@0: ) Chris@0: ); Chris@0: } Chris@0: } Chris@0: Chris@0: $varInfo->scopeType = $scopeType; Chris@0: if (isset($typeHint) === true) { Chris@0: $varInfo->typeHint = $typeHint; Chris@0: } Chris@0: Chris@0: if (isset($varInfo->firstDeclared) === true && ($varInfo->firstDeclared <= $stackPtr)) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // When a global variable is declared it also means we can consider it as Chris@0: // being initialized. Chris@0: if ($scopeType === 'global') { Chris@0: $varInfo->firstInitialized = $stackPtr; Chris@0: } Chris@0: Chris@0: $varInfo->firstDeclared = $stackPtr; Chris@0: Chris@0: }//end markVariableDeclaration() Chris@0: Chris@0: Chris@0: /** Chris@0: * Mark the given variable as being read. Chris@0: * Chris@0: * @param string $varName Chris@0: * @param int $stackPtr Chris@0: * @param string $currScope Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function markVariableRead($varName, $stackPtr, $currScope) Chris@0: { Chris@0: $varInfo = $this->getVariableInfo($varName, $currScope); Chris@0: if (isset($varInfo->firstRead) === true && ($varInfo->firstRead <= $stackPtr)) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $varInfo->firstRead = $stackPtr; Chris@0: Chris@0: }//end markVariableRead() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks if a variable has been initialized. Chris@0: * Chris@0: * @param string $varName Chris@0: * @param int $stackPtr Chris@0: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: function isVariableInitialized($varName, $stackPtr, $currScope) Chris@0: { Chris@0: $varInfo = $this->getVariableInfo($varName, $currScope); Chris@0: if (isset($varInfo->firstInitialized) === true && $varInfo->firstInitialized <= $stackPtr) { Chris@0: return true; Chris@0: } Chris@0: Chris@0: return false; Chris@0: Chris@0: }//end isVariableInitialized() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks if the given variable is undefined. Chris@0: * Chris@0: * @param string $varName Chris@0: * @param int $stackPtr Chris@0: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: function isVariableUndefined($varName, $stackPtr, $currScope) Chris@0: { Chris@0: $varInfo = $this->getVariableInfo($varName, $currScope, false); Chris@0: if (isset($varInfo->firstDeclared) === true && $varInfo->firstDeclared <= $stackPtr) { Chris@0: // TODO: do we want to check scopeType here? Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (isset($varInfo->firstInitialized) === true && $varInfo->firstInitialized <= $stackPtr) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: return true; Chris@0: Chris@0: }//end isVariableUndefined() Chris@0: Chris@0: Chris@0: /** Chris@0: * Marks a variable as read and throws a PHPCS warning if it is undefined. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param string $varName Chris@17: * @param int $stackPtr Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@17: function markVariableReadAndWarnIfUndefined(File $phpcsFile, $varName, $stackPtr, $currScope) Chris@0: { Chris@0: $this->markVariableRead($varName, $stackPtr, $currScope); Chris@0: Chris@0: if ($this->isVariableUndefined($varName, $stackPtr, $currScope) === true) { Chris@0: // We haven't been defined by this point. Chris@0: $phpcsFile->addWarning( Chris@0: "Variable %s is undefined.", Chris@0: $stackPtr, Chris@0: 'UndefinedVariable', Chris@0: array("\${$varName}") Chris@0: ); Chris@0: } Chris@0: Chris@0: return true; Chris@0: Chris@0: }//end markVariableReadAndWarnIfUndefined() Chris@0: Chris@0: Chris@0: /** Chris@0: * Returns the function declaration pointer. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@0: * Chris@0: * @return int|false Chris@0: */ Chris@17: function findFunctionPrototype(File $phpcsFile, $stackPtr) Chris@0: { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: if (($openPtr = $this->findContainingBrackets($phpcsFile, $stackPtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // Function names are T_STRING, and return-by-reference is T_BITWISE_AND, Chris@0: // so we look backwards from the opening bracket for the first thing that Chris@0: // isn't a function name, reference sigil or whitespace and check if Chris@0: // it's a function keyword. Chris@0: $functionPtr = $phpcsFile->findPrevious( Chris@0: array( Chris@0: T_STRING, Chris@0: T_WHITESPACE, Chris@0: T_BITWISE_AND, Chris@0: ), Chris@0: ($openPtr - 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if (($functionPtr !== false) Chris@0: && ($tokens[$functionPtr]['code'] === T_FUNCTION) Chris@0: ) { Chris@0: return $functionPtr; Chris@0: } Chris@0: Chris@0: return false; Chris@0: Chris@0: }//end findFunctionPrototype() Chris@0: Chris@0: Chris@0: /** Chris@0: * Find the scope the given pointer is in. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@0: * Chris@0: * @return int|false Chris@0: */ Chris@17: function findVariableScope(File $phpcsFile, $stackPtr) Chris@0: { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: $in_class = false; Chris@0: if (empty($token['conditions']) === false) { Chris@0: foreach (array_reverse($token['conditions'], true) as $scopePtr => $scopeCode) { Chris@0: if (($scopeCode === T_FUNCTION) || ($scopeCode === T_CLOSURE)) { Chris@0: return $scopePtr; Chris@0: } Chris@0: Chris@0: if (($scopeCode === T_CLASS) || ($scopeCode === T_INTERFACE) || ($scopeCode === T_TRAIT)) { Chris@0: $in_class = true; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: if (($scopePtr = $this->findFunctionPrototype($phpcsFile, $stackPtr)) !== false) { Chris@0: return $scopePtr; Chris@0: } Chris@0: Chris@0: if ($in_class === true) { Chris@0: // Member var of a class, we don't care. Chris@0: return false; Chris@0: } Chris@0: Chris@0: // File scope, hmm, lets use first token of file? Chris@0: return 0; Chris@0: Chris@0: }//end findVariableScope() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks if the next token is an assignment. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@17: function isNextThingAnAssign(File $phpcsFile, $stackPtr) Chris@0: { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // Is the next non-whitespace an assignment? Chris@0: $nextPtr = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true, null, true); Chris@0: if ($nextPtr !== false) { Chris@0: if ($tokens[$nextPtr]['code'] === T_EQUAL) { Chris@0: return $nextPtr; Chris@0: } Chris@0: Chris@0: // Special handling for initializing arrays on the fly, which is Chris@0: // also an assignment. Chris@0: if ($tokens[$nextPtr]['code'] === T_OPEN_SQUARE_BRACKET) { Chris@0: return $this->isNextThingAnAssign($phpcsFile, $tokens[$nextPtr]['bracket_closer']); Chris@0: } Chris@0: } Chris@0: Chris@0: return false; Chris@0: Chris@0: }//end isNextThingAnAssign() Chris@0: Chris@0: Chris@0: /** Chris@0: * Find the end of the assignment. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@0: * Chris@0: * @return int Chris@0: */ Chris@17: function findWhereAssignExecuted(File $phpcsFile, $stackPtr) Chris@0: { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // Write should be recorded at the next statement to ensure we treat Chris@0: // the assign as happening after the RHS execution. Chris@0: // eg: $var = $var + 1; -> RHS could still be undef. Chris@0: // However, if we're within a bracketed expression, Chris@0: // eg: echo (($var = 12) && ($var == 12)); Chris@0: // we take place at the closing bracket, if that's first. Chris@0: $semicolonPtr = $phpcsFile->findNext(T_SEMICOLON, ($stackPtr + 1), null, false, null, true); Chris@0: $closePtr = false; Chris@0: if (($openPtr = $this->findContainingBrackets($phpcsFile, $stackPtr)) !== false) { Chris@0: if (isset($tokens[$openPtr]['parenthesis_closer']) === true) { Chris@0: $closePtr = $tokens[$openPtr]['parenthesis_closer']; Chris@0: } Chris@0: } Chris@0: Chris@0: if ($semicolonPtr === false) { Chris@0: if ($closePtr === false) { Chris@0: // TODO: panic. Chris@0: return $stackPtr; Chris@0: } Chris@0: Chris@0: return $closePtr; Chris@0: } Chris@0: Chris@0: if ($closePtr !== false && $closePtr < $semicolonPtr) { Chris@0: return $closePtr; Chris@0: } Chris@0: Chris@0: return $semicolonPtr; Chris@0: Chris@0: }//end findWhereAssignExecuted() Chris@0: Chris@0: Chris@0: /** Chris@0: * Find the parenthesis if the pointer is in some. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@0: * Chris@0: * @return int|false Chris@0: */ Chris@17: function findContainingBrackets(File $phpcsFile, $stackPtr) Chris@0: { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: if (isset($tokens[$stackPtr]['nested_parenthesis']) === true) { Chris@0: $openPtrs = array_keys($tokens[$stackPtr]['nested_parenthesis']); Chris@0: return end($openPtrs); Chris@0: } Chris@0: Chris@0: return false; Chris@0: Chris@0: }//end findContainingBrackets() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks if the given pointer is in a function call. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@0: * Chris@0: * @return int|false Chris@0: */ Chris@17: function findFunctionCall(File $phpcsFile, $stackPtr) Chris@0: { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: if (($openPtr = $this->findContainingBrackets($phpcsFile, $stackPtr)) !== false) { Chris@0: // First non-whitespace thing and see if it's a T_STRING function name. Chris@0: $functionPtr = $phpcsFile->findPrevious( Chris@0: T_WHITESPACE, Chris@0: ($openPtr - 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if ($tokens[$functionPtr]['code'] === T_STRING) { Chris@0: return $functionPtr; Chris@0: } Chris@0: } Chris@0: Chris@0: return false; Chris@0: Chris@0: }//end findFunctionCall() Chris@0: Chris@0: Chris@0: /** Chris@0: * Get the arguments of a function call. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@0: * Chris@0: * @return array|false Chris@0: */ Chris@17: function findFunctionCallArguments(File $phpcsFile, $stackPtr) Chris@0: { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // Slight hack: also allow this to find args for array constructor. Chris@0: // TODO: probably should refactor into three functions: arg-finding and bracket-finding. Chris@0: if (($tokens[$stackPtr]['code'] !== T_STRING) && ($tokens[$stackPtr]['code'] !== T_ARRAY)) { Chris@0: // Assume $stackPtr is something within the brackets, find our function call. Chris@0: if (($stackPtr = $this->findFunctionCall($phpcsFile, $stackPtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: } Chris@0: Chris@0: // $stackPtr is the function name, find our brackets after it. Chris@0: $openPtr = $phpcsFile->findNext( Chris@0: T_WHITESPACE, Chris@0: ($stackPtr + 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if (($openPtr === false) || ($tokens[$openPtr]['code'] !== T_OPEN_PARENTHESIS)) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (isset($tokens[$openPtr]['parenthesis_closer']) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $closePtr = $tokens[$openPtr]['parenthesis_closer']; Chris@0: Chris@0: $argPtrs = array(); Chris@0: $lastPtr = $openPtr; Chris@0: $lastArgComma = $openPtr; Chris@0: while (($nextPtr = $phpcsFile->findNext(T_COMMA, ($lastPtr + 1), $closePtr)) !== false) { Chris@0: if ($this->findContainingBrackets($phpcsFile, $nextPtr) === $openPtr) { Chris@0: // Comma is at our level of brackets, it's an argument delimiter. Chris@0: array_push($argPtrs, range(($lastArgComma + 1), ($nextPtr - 1))); Chris@0: $lastArgComma = $nextPtr; Chris@0: } Chris@0: Chris@0: $lastPtr = $nextPtr; Chris@0: } Chris@0: Chris@0: array_push($argPtrs, range(($lastArgComma + 1), ($closePtr - 1))); Chris@0: Chris@0: return $argPtrs; Chris@0: Chris@0: }//end findFunctionCallArguments() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks the function prototype. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForFunctionPrototype( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // Are we a function or closure parameter? Chris@0: // It would be nice to get the list of function parameters from watching for Chris@0: // T_FUNCTION, but AbstractVariableSniff and AbstractScopeSniff define everything Chris@0: // we need to do that as private or final, so we have to do it this hackish way. Chris@0: if (($openPtr = $this->findContainingBrackets($phpcsFile, $stackPtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // Function names are T_STRING, and return-by-reference is T_BITWISE_AND, Chris@0: // so we look backwards from the opening bracket for the first thing that Chris@0: // isn't a function name, reference sigil or whitespace and check if Chris@0: // it's a function keyword. Chris@0: $functionPtr = $phpcsFile->findPrevious( Chris@0: array( Chris@0: T_STRING, Chris@0: T_WHITESPACE, Chris@0: T_BITWISE_AND, Chris@0: ), Chris@0: ($openPtr - 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if (($functionPtr !== false) Chris@0: && (($tokens[$functionPtr]['code'] === T_FUNCTION) Chris@0: || ($tokens[$functionPtr]['code'] === T_CLOSURE)) Chris@0: ) { Chris@0: // TODO: typeHint. Chris@0: $this->markVariableDeclaration($varName, 'param', null, $stackPtr, $functionPtr); Chris@0: // Are we pass-by-reference? Chris@0: $referencePtr = $phpcsFile->findPrevious( Chris@0: T_WHITESPACE, Chris@0: ($stackPtr - 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if (($referencePtr !== false) && ($tokens[$referencePtr]['code'] === T_BITWISE_AND)) { Chris@0: $varInfo = $this->getVariableInfo($varName, $functionPtr); Chris@0: $varInfo->passByReference = true; Chris@0: } Chris@0: Chris@0: // Are we optional with a default? Chris@0: if ($this->isNextThingAnAssign($phpcsFile, $stackPtr) !== false) { Chris@0: $this->markVariableAssignment($varName, $stackPtr, $functionPtr); Chris@0: } Chris@0: Chris@0: return true; Chris@0: }//end if Chris@0: Chris@0: // Is it a use keyword? Use is both a read and a define, fun! Chris@0: if (($functionPtr !== false) && ($tokens[$functionPtr]['code'] === T_USE)) { Chris@0: $this->markVariableRead($varName, $stackPtr, $currScope); Chris@0: if ($this->isVariableUndefined($varName, $stackPtr, $currScope) === true) { Chris@0: // We haven't been defined by this point. Chris@0: $phpcsFile->addWarning( Chris@0: "Variable %s is undefined.", Chris@0: $stackPtr, Chris@0: 'UndefinedVariable', Chris@0: array("\${$varName}") Chris@0: ); Chris@0: return true; Chris@0: } Chris@0: Chris@0: // $functionPtr is at the use, we need the function keyword for start of scope. Chris@0: $functionPtr = $phpcsFile->findPrevious( Chris@0: T_CLOSURE, Chris@0: ($functionPtr - 1), Chris@0: ($currScope + 1), Chris@0: false, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if ($functionPtr !== false) { Chris@0: // TODO: typeHints in use? Chris@0: $this->markVariableDeclaration($varName, 'bound', null, $stackPtr, $functionPtr); Chris@0: $this->markVariableAssignment($varName, $stackPtr, $functionPtr); Chris@0: Chris@0: // Are we pass-by-reference? Chris@0: $referencePtr = $phpcsFile->findPrevious( Chris@0: T_WHITESPACE, Chris@0: ($stackPtr - 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if (($referencePtr !== false) && ($tokens[$referencePtr]['code'] === T_BITWISE_AND)) { Chris@0: $varInfo = $this->getVariableInfo($varName, $functionPtr); Chris@0: $varInfo->passByReference = true; Chris@0: } Chris@0: Chris@0: return true; Chris@0: }//end if Chris@0: }//end if Chris@0: Chris@0: return false; Chris@0: Chris@0: }//end checkForFunctionPrototype() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks if we are in a catch() block. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForCatchBlock( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // Are we a catch block parameter? Chris@0: if (($openPtr = $this->findContainingBrackets($phpcsFile, $stackPtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // Function names are T_STRING, and return-by-reference is T_BITWISE_AND, Chris@0: // so we look backwards from the opening bracket for the first thing that Chris@0: // isn't a function name, reference sigil or whitespace and check if Chris@0: // it's a function keyword. Chris@0: $catchPtr = $phpcsFile->findPrevious( Chris@0: T_WHITESPACE, Chris@0: ($openPtr - 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if (($catchPtr !== false) Chris@0: && ($tokens[$catchPtr]['code'] === T_CATCH) Chris@0: ) { Chris@0: // Scope of the exception var is actually the function, not just the catch block. Chris@0: // TODO: typeHint. Chris@0: $this->markVariableDeclaration($varName, 'local', null, $stackPtr, $currScope, true); Chris@0: $this->markVariableAssignment($varName, $stackPtr, $currScope); Chris@0: if ($this->allowUnusedCaughtExceptions !== false) { Chris@0: $varInfo = $this->getVariableInfo($varName, $currScope); Chris@0: $varInfo->ignoreUnused = true; Chris@0: } Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: return false; Chris@0: Chris@0: }//end checkForCatchBlock() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks if $this is used within a class. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForThisWithinClass( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: // Are we $this within a class? Chris@0: if (($varName !== 'this') || empty($token['conditions']) === true) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: foreach (array_reverse($token['conditions'], true) as $scopePtr => $scopeCode) { Chris@0: if ($scopeCode === T_CLASS || $scopeCode === T_TRAIT) { Chris@0: return true; Chris@0: } Chris@0: } Chris@0: Chris@0: return false; Chris@0: Chris@0: }//end checkForThisWithinClass() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks if the variable is a PHP super global. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForSuperGlobal( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: // Are we a superglobal variable? Chris@0: if (in_array( Chris@0: $varName, Chris@0: array( Chris@0: 'GLOBALS', Chris@0: '_SERVER', Chris@0: '_GET', Chris@0: '_POST', Chris@0: '_FILES', Chris@0: '_COOKIE', Chris@0: '_SESSION', Chris@0: '_REQUEST', Chris@0: '_ENV', Chris@0: 'argv', Chris@0: 'argc', Chris@0: ) Chris@0: ) === true Chris@0: ) { Chris@0: return true; Chris@0: } Chris@0: Chris@0: return false; Chris@0: Chris@0: }//end checkForSuperGlobal() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks if the variable is a static class member. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForStaticMember( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: // Are we a static member? Chris@0: $doubleColonPtr = ($stackPtr - 1); Chris@0: if ($tokens[$doubleColonPtr]['code'] !== T_DOUBLE_COLON) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $classNamePtr = ($stackPtr - 2); Chris@0: if (($tokens[$classNamePtr]['code'] !== T_STRING) Chris@0: && ($tokens[$classNamePtr]['code'] !== T_SELF) Chris@0: && ($tokens[$classNamePtr]['code'] !== T_STATIC) Chris@0: ) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // Are we referring to self:: outside a class? Chris@0: // TODO: not sure this is our business or should be some other sniff. Chris@0: if (($tokens[$classNamePtr]['code'] === T_SELF) Chris@0: || ($tokens[$classNamePtr]['code'] === T_STATIC) Chris@0: ) { Chris@0: if ($tokens[$classNamePtr]['code'] === T_SELF) { Chris@0: $err_class = 'SelfOutsideClass'; Chris@0: $err_desc = 'self::'; Chris@0: } else { Chris@0: $err_class = 'StaticOutsideClass'; Chris@0: $err_desc = 'static::'; Chris@0: } Chris@0: Chris@0: if (empty($token['conditions']) === false) { Chris@0: foreach (array_reverse($token['conditions'], true) as $scopePtr => $scopeCode) { Chris@0: // Self within a closure is invalid. Chris@0: // Note: have to fetch code from $tokens, T_CLOSURE isn't set for conditions codes. Chris@0: if ($tokens[$scopePtr]['code'] === T_CLOSURE) { Chris@0: $phpcsFile->addError( Chris@0: "Use of {$err_desc}%s inside closure.", Chris@0: $stackPtr, Chris@0: $err_class, Chris@0: array("\${$varName}") Chris@0: ); Chris@0: return true; Chris@0: } Chris@0: Chris@0: if ($scopeCode === T_CLASS || $scopeCode === T_TRAIT) { Chris@0: return true; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: $phpcsFile->addError( Chris@0: "Use of {$err_desc}%s outside class definition.", Chris@0: $stackPtr, Chris@0: $err_class, Chris@0: array("\${$varName}") Chris@0: ); Chris@0: return true; Chris@0: }//end if Chris@0: Chris@0: return true; Chris@0: Chris@0: }//end checkForStaticMember() Chris@0: Chris@0: Chris@0: /** Chris@0: * Checks if the variable is being assigned to. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForAssignment( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // Is the next non-whitespace an assignment? Chris@0: if (($assignPtr = $this->isNextThingAnAssign($phpcsFile, $stackPtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // Plain ol' assignment. Simpl(ish). Chris@0: if (($writtenPtr = $this->findWhereAssignExecuted($phpcsFile, $assignPtr)) === false) { Chris@0: $writtenPtr = $stackPtr; Chris@0: // I dunno. Chris@0: } Chris@0: Chris@0: // Check for the ampersand '&' after the assignment, which means this Chris@0: // variable is taken by reference. Chris@0: $refPtr = $phpcsFile->findNext(T_WHITESPACE, ($assignPtr + 1), null, true); Chris@0: if ($tokens[$refPtr]['code'] === T_BITWISE_AND) { Chris@0: $varInfo = $this->getVariableInfo($varName, $currScope); Chris@0: $varInfo->passByReference = true; Chris@0: } Chris@0: Chris@0: $this->markVariableAssignment($varName, $writtenPtr, $currScope); Chris@0: return true; Chris@0: Chris@0: }//end checkForAssignment() Chris@0: Chris@0: Chris@0: /** Chris@0: * Check if this is a list language construct assignment. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForListAssignment( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // OK, are we within a list (...) construct? Chris@0: if (($openPtr = $this->findContainingBrackets($phpcsFile, $stackPtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $prevPtr = $phpcsFile->findPrevious(T_WHITESPACE, ($openPtr - 1), null, true, null, true); Chris@0: if (($prevPtr === false) || ($tokens[$prevPtr]['code'] !== T_LIST)) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // OK, we're a list (...) construct... are we being assigned to? Chris@0: $closePtr = $tokens[$openPtr]['parenthesis_closer']; Chris@0: if (($assignPtr = $this->isNextThingAnAssign($phpcsFile, $closePtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // Yes, we're being assigned. Chris@0: $writtenPtr = $this->findWhereAssignExecuted($phpcsFile, $assignPtr); Chris@0: $this->markVariableAssignment($varName, $writtenPtr, $currScope); Chris@0: return true; Chris@0: Chris@0: }//end checkForListAssignment() Chris@0: Chris@0: Chris@0: /** Chris@0: * Check if this variable is declared globally. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForGlobalDeclaration( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // Are we a global declaration? Chris@0: // Search backwards for first token that isn't whitespace, comma or variable. Chris@0: $globalPtr = $phpcsFile->findPrevious( Chris@0: array( Chris@0: T_WHITESPACE, Chris@0: T_VARIABLE, Chris@0: T_COMMA, Chris@0: ), Chris@0: ($stackPtr - 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if (($globalPtr === false) || ($tokens[$globalPtr]['code'] !== T_GLOBAL)) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // It's a global declaration. Chris@0: $this->markVariableDeclaration($varName, 'global', null, $stackPtr, $currScope); Chris@0: // Also mark this variable as being a reference, so that we don't get Chris@0: // unused variable warnings if it is never read. Chris@0: $varInfo = $this->getVariableInfo($varName, $currScope); Chris@0: $varInfo->passByReference = true; Chris@0: return true; Chris@0: Chris@0: }//end checkForGlobalDeclaration() Chris@0: Chris@0: Chris@0: /** Chris@0: * Check is this is a static variable declaration. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForStaticDeclaration( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // Are we a static declaration? Chris@0: // Static declarations are a bit more complicated than globals, since they Chris@0: // can contain assignments. The assignment is compile-time however so can Chris@0: // only be constant values, which makes life manageable. Chris@0: // Chris@0: // Just to complicate matters further, late static binding constants Chris@0: // take the form static::CONSTANT and are invalid within static variable Chris@0: // assignments, but we don't want to accidentally match their use of the Chris@0: // static keyword. Chris@0: // Chris@0: // Valid values are: Chris@0: // number T_MINUS T_LNUMBER T_DNUMBER Chris@0: // string T_CONSTANT_ENCAPSED_STRING Chris@0: // heredoc T_START_HEREDOC T_HEREDOC T_END_HEREDOC Chris@0: // nowdoc T_START_NOWDOC T_NOWDOC T_END_NOWDOC Chris@0: // define T_STRING Chris@0: // class constant T_STRING T_DOUBLE_COLON T_STRING Chris@0: // Search backwards for first token that isn't whitespace, comma, variable, Chris@0: // equals, or on the list of assignable constant values above. Chris@0: $staticPtr = $phpcsFile->findPrevious( Chris@0: array( Chris@0: T_WHITESPACE, Chris@0: T_VARIABLE, Chris@0: T_COMMA, Chris@0: T_EQUAL, Chris@0: T_MINUS, Chris@0: T_LNUMBER, Chris@0: T_DNUMBER, Chris@0: T_CONSTANT_ENCAPSED_STRING, Chris@0: T_STRING, Chris@0: T_DOUBLE_COLON, Chris@0: T_START_HEREDOC, Chris@0: T_HEREDOC, Chris@0: T_END_HEREDOC, Chris@0: T_START_NOWDOC, Chris@0: T_NOWDOC, Chris@0: T_END_NOWDOC, Chris@0: ), Chris@0: ($stackPtr - 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if (($staticPtr === false) || ($tokens[$staticPtr]['code'] !== T_STATIC)) { Chris@0: // Debug code. Chris@0: // if ($varName == 'static4') { Chris@0: // echo "Failing token:\n" . print_r($tokens[$staticPtr], true); Chris@0: // } Chris@0: // End: Debug code. Chris@0: return false; Chris@0: } Chris@0: Chris@0: // Is it a late static binding static::? Chris@0: // If so, this isn't the static keyword we're looking for, but since Chris@0: // static:: isn't allowed in a compile-time constant, we also know Chris@0: // we can't be part of a static declaration anyway, so there's no Chris@0: // need to look any further. Chris@0: $lateStaticBindingPtr = $phpcsFile->findNext(T_WHITESPACE, ($staticPtr + 1), null, true, null, true); Chris@0: if (($lateStaticBindingPtr !== false) && ($tokens[$lateStaticBindingPtr]['code'] === T_DOUBLE_COLON)) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // It's a static declaration. Chris@0: $this->markVariableDeclaration($varName, 'static', null, $stackPtr, $currScope); Chris@0: if ($this->isNextThingAnAssign($phpcsFile, $stackPtr) !== false) { Chris@0: $this->markVariableAssignment($varName, $stackPtr, $currScope); Chris@0: } Chris@0: Chris@0: return true; Chris@0: Chris@0: }//end checkForStaticDeclaration() Chris@0: Chris@0: Chris@0: /** Chris@0: * Check if this is a foreach loop variable. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForForeachLoopVar( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: // Are we a foreach loopvar? Chris@0: if (($openPtr = $this->findContainingBrackets($phpcsFile, $stackPtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // Is there an 'as' token between us and the opening bracket? Chris@0: if ($phpcsFile->findPrevious(T_AS, ($stackPtr - 1), $openPtr) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $this->markVariableAssignment($varName, $stackPtr, $currScope); Chris@0: Chris@0: // Workaround: We want to allow foreach ($array as $key => $value) where Chris@0: // $value is never read, so we just mark it read immediately here. Chris@0: if ($phpcsFile->findPrevious(T_DOUBLE_ARROW, ($stackPtr - 1), $openPtr) !== false) { Chris@0: $this->markVariableRead($varName, $stackPtr, $currScope); Chris@0: } Chris@0: Chris@0: // Foreach variables that are read as references like Chris@0: // foreach ($array as &$value) should not throw unused variable errors. Chris@0: if (($refPtr = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true)) !== false Chris@0: && $tokens[$refPtr]['code'] === T_BITWISE_AND Chris@0: ) { Chris@0: $varInfo = $this->getVariableInfo($varName, $currScope); Chris@0: $varInfo->passByReference = true; Chris@0: } Chris@0: Chris@0: return true; Chris@0: Chris@0: }//end checkForForeachLoopVar() Chris@0: Chris@0: Chris@0: /** Chris@0: * Check if this is a "&" function call. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForPassByReferenceFunctionCall( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: // Are we pass-by-reference to known pass-by-reference function? Chris@0: if (($functionPtr = $this->findFunctionCall($phpcsFile, $stackPtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // Is our function a known pass-by-reference function? Chris@0: $functionName = $tokens[$functionPtr]['content']; Chris@0: if (isset($this->_passByRefFunctions[$functionName]) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $refArgs = $this->_passByRefFunctions[$functionName]; Chris@0: Chris@0: if (($argPtrs = $this->findFunctionCallArguments($phpcsFile, $stackPtr)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: // We're within a function call arguments list, find which arg we are. Chris@0: $argPos = false; Chris@0: foreach ($argPtrs as $idx => $ptrs) { Chris@0: if (in_array($stackPtr, $ptrs) === true) { Chris@0: $argPos = ($idx + 1); Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@0: if ($argPos === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (in_array($argPos, $refArgs) === false) { Chris@0: // Our arg wasn't mentioned explicitly, are we after an elipsis catch-all? Chris@0: if (($elipsis = array_search('...', $refArgs)) === false) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: if ($argPos < $refArgs[($elipsis - 1)]) { Chris@0: return false; Chris@0: } Chris@0: } Chris@0: Chris@0: // Our argument position matches that of a pass-by-ref argument, Chris@0: // check that we're the only part of the argument expression. Chris@0: foreach ($argPtrs[($argPos - 1)] as $ptr) { Chris@0: if ($ptr === $stackPtr) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: if ($tokens[$ptr]['code'] !== T_WHITESPACE) { Chris@0: return false; Chris@0: } Chris@0: } Chris@0: Chris@0: // Just us, we can mark it as a write. Chris@0: $this->markVariableAssignment($varName, $stackPtr, $currScope); Chris@0: // It's a read as well for purposes of used-variables. Chris@0: $this->markVariableRead($varName, $stackPtr, $currScope); Chris@0: return true; Chris@0: Chris@0: }//end checkForPassByReferenceFunctionCall() Chris@0: Chris@0: Chris@0: /** Chris@0: * Check if the variable is an object property. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param string $varName Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: protected function checkForSymbolicObjectProperty( Chris@17: File $phpcsFile, Chris@0: $stackPtr, Chris@0: $varName, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: // Are we a symbolic object property/function derefeference? Chris@0: // Search backwards for first token that isn't whitespace, is it a "->" operator? Chris@0: $objectOperatorPtr = $phpcsFile->findPrevious( Chris@0: T_WHITESPACE, Chris@0: ($stackPtr - 1), Chris@0: null, Chris@0: true, Chris@0: null, Chris@0: true Chris@0: ); Chris@0: if (($objectOperatorPtr === false) || ($tokens[$objectOperatorPtr]['code'] !== T_OBJECT_OPERATOR)) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $this->markVariableReadAndWarnIfUndefined($phpcsFile, $varName, $stackPtr, $currScope); Chris@0: return true; Chris@0: Chris@0: }//end checkForSymbolicObjectProperty() Chris@0: Chris@0: Chris@0: /** Chris@0: * Called to process class member vars. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this Chris@17: * token was found. Chris@17: * @param int $stackPtr The position where the token was found. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: protected function processMemberVar( Chris@17: File $phpcsFile, Chris@0: $stackPtr Chris@0: ) { Chris@0: // TODO: don't care for now. Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: }//end processMemberVar() Chris@0: Chris@0: Chris@0: /** Chris@0: * Called to process normal member vars. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this Chris@17: * token was found. Chris@17: * @param int $stackPtr The position where the token was found. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: protected function processVariable( Chris@17: File $phpcsFile, Chris@0: $stackPtr Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: $varName = $this->normalizeVarName($token['content']); Chris@0: if (($currScope = $this->findVariableScope($phpcsFile, $stackPtr)) === false) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Debug code. Chris@0: // static $dump_token = false; Chris@0: // if ($varName == 'property') { Chris@0: // $dump_token = true; Chris@0: // } Chris@0: // if ($dump_token) { Chris@0: // echo "Found variable {$varName} on line {$token['line']} in scope {$currScope}.\n" . print_r($token, true); Chris@0: // echo "Prev:\n" . print_r($tokens[$stackPtr - 1], true); Chris@0: // } Chris@0: // Determine if variable is being assigned or read. Chris@0: // Read methods that preempt assignment: Chris@0: // Are we a $object->$property type symbolic reference? Chris@0: // Possible assignment methods: Chris@0: // Is a mandatory function/closure parameter Chris@0: // Is an optional function/closure parameter with non-null value Chris@0: // Is closure use declaration of a variable defined within containing scope Chris@0: // catch (...) block start Chris@0: // $this within a class (but not within a closure). Chris@0: // $GLOBALS, $_REQUEST, etc superglobals. Chris@0: // $var part of class::$var static member Chris@0: // Assignment via = Chris@0: // Assignment via list (...) = Chris@0: // Declares as a global Chris@0: // Declares as a static Chris@0: // Assignment via foreach (... as ...) { } Chris@0: // Pass-by-reference to known pass-by-reference function Chris@0: // Are we a $object->$property type symbolic reference? Chris@0: if ($this->checkForSymbolicObjectProperty($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Are we a function or closure parameter? Chris@0: if ($this->checkForFunctionPrototype($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Are we a catch parameter? Chris@0: if ($this->checkForCatchBlock($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Are we $this within a class? Chris@0: if ($this->checkForThisWithinClass($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Are we a $GLOBALS, $_REQUEST, etc superglobal? Chris@0: if ($this->checkForSuperGlobal($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // $var part of class::$var static member Chris@0: if ($this->checkForStaticMember($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Is the next non-whitespace an assignment? Chris@0: if ($this->checkForAssignment($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // OK, are we within a list (...) = construct? Chris@0: if ($this->checkForListAssignment($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Are we a global declaration? Chris@0: if ($this->checkForGlobalDeclaration($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Are we a static declaration? Chris@0: if ($this->checkForStaticDeclaration($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Are we a foreach loopvar? Chris@0: if ($this->checkForForeachLoopVar($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Are we pass-by-reference to known pass-by-reference function? Chris@0: if ($this->checkForPassByReferenceFunctionCall($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // OK, we don't appear to be a write to the var, assume we're a read. Chris@0: $this->markVariableReadAndWarnIfUndefined($phpcsFile, $varName, $stackPtr, $currScope); Chris@0: Chris@0: }//end processVariable() Chris@0: Chris@0: Chris@0: /** Chris@0: * Called to process variables found in double quoted strings. Chris@0: * Chris@0: * Note that there may be more than one variable in the string, which will Chris@0: * result only in one call for the string. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this Chris@17: * token was found. Chris@17: * @param int $stackPtr The position where the double quoted Chris@17: * string was found. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: protected function processVariableInString( Chris@17: File Chris@0: $phpcsFile, Chris@0: $stackPtr Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: $runMatch = preg_match_all($this->_double_quoted_variable_regexp, $token['content'], $matches); Chris@0: if ($runMatch === 0 || $runMatch === false) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $currScope = $this->findVariableScope($phpcsFile, $stackPtr); Chris@0: foreach ($matches[1] as $varName) { Chris@0: $varName = $this->normalizeVarName($varName); Chris@0: // Are we $this within a class? Chris@0: if ($this->checkForThisWithinClass($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: if ($this->checkForSuperGlobal($phpcsFile, $stackPtr, $varName, $currScope) === true) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: $this->markVariableReadAndWarnIfUndefined($phpcsFile, $varName, $stackPtr, $currScope); Chris@0: } Chris@0: Chris@0: }//end processVariableInString() Chris@0: Chris@0: Chris@0: /** Chris@0: * Check variables in a compact() call. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile Chris@17: * @param int $stackPtr Chris@17: * @param array $arguments Chris@17: * @param string $currScope Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: protected function processCompactArguments( Chris@17: File Chris@0: $phpcsFile, Chris@0: $stackPtr, Chris@0: $arguments, Chris@0: $currScope Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: Chris@0: foreach ($arguments as $argumentPtrs) { Chris@0: $argumentPtrs = array_values( Chris@0: array_filter( Chris@0: $argumentPtrs, Chris@0: function ($argumentPtr) use ($tokens) { Chris@0: return $tokens[$argumentPtr]['code'] !== T_WHITESPACE; Chris@0: } Chris@0: ) Chris@0: ); Chris@0: if (empty($argumentPtrs) === true) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: if (isset($tokens[$argumentPtrs[0]]) === false) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: $argument_first_token = $tokens[$argumentPtrs[0]]; Chris@0: if ($argument_first_token['code'] === T_ARRAY) { Chris@0: // It's an array argument, recurse. Chris@0: if (($array_arguments = $this->findFunctionCallArguments($phpcsFile, $argumentPtrs[0])) !== false) { Chris@0: $this->processCompactArguments($phpcsFile, $stackPtr, $array_arguments, $currScope); Chris@0: } Chris@0: Chris@0: continue; Chris@0: } Chris@0: Chris@0: if (count($argumentPtrs) > 1) { Chris@0: // Complex argument, we can't handle it, ignore. Chris@0: continue; Chris@0: } Chris@0: Chris@0: if ($argument_first_token['code'] === T_CONSTANT_ENCAPSED_STRING) { Chris@0: // Single-quoted string literal, ie compact('whatever'). Chris@0: // Substr is to strip the enclosing single-quotes. Chris@0: $varName = substr($argument_first_token['content'], 1, -1); Chris@0: $this->markVariableReadAndWarnIfUndefined($phpcsFile, $varName, $argumentPtrs[0], $currScope); Chris@0: continue; Chris@0: } Chris@0: Chris@0: if ($argument_first_token['code'] === T_DOUBLE_QUOTED_STRING) { Chris@0: // Double-quoted string literal. Chris@0: if (preg_match($this->_double_quoted_variable_regexp, $argument_first_token['content']) === 1) { Chris@0: // Bail if the string needs variable expansion, that's runtime stuff. Chris@0: continue; Chris@0: } Chris@0: Chris@0: // Substr is to strip the enclosing double-quotes. Chris@0: $varName = substr($argument_first_token['content'], 1, -1); Chris@0: $this->markVariableReadAndWarnIfUndefined($phpcsFile, $varName, $argumentPtrs[0], $currScope); Chris@0: continue; Chris@0: } Chris@0: }//end foreach Chris@0: Chris@0: }//end processCompactArguments() Chris@0: Chris@0: Chris@0: /** Chris@0: * Called to process variables named in a call to compact(). Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this Chris@17: * token was found. Chris@17: * @param int $stackPtr The position where the call to compact() Chris@17: * was found. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: protected function processCompact( Chris@17: File Chris@0: $phpcsFile, Chris@0: $stackPtr Chris@0: ) { Chris@0: $tokens = $phpcsFile->getTokens(); Chris@0: $token = $tokens[$stackPtr]; Chris@0: Chris@0: $currScope = $this->findVariableScope($phpcsFile, $stackPtr); Chris@0: Chris@0: if (($arguments = $this->findFunctionCallArguments($phpcsFile, $stackPtr)) !== false) { Chris@0: $this->processCompactArguments($phpcsFile, $stackPtr, $arguments, $currScope); Chris@0: } Chris@0: Chris@0: }//end processCompact() Chris@0: Chris@0: Chris@0: /** Chris@0: * Called to process the end of a scope. Chris@0: * Chris@0: * Note that although triggered by the closing curly brace of the scope, $stackPtr is Chris@0: * the scope conditional, not the closing curly brace. Chris@0: * Chris@17: * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where this Chris@17: * token was found. Chris@17: * @param int $stackPtr The position of the scope conditional. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: protected function processScopeClose( Chris@17: File Chris@0: $phpcsFile, Chris@0: $stackPtr Chris@0: ) { Chris@0: $scopeInfo = $this->getScopeInfo($stackPtr, false); Chris@0: if (is_null($scopeInfo) === true) { Chris@0: return; Chris@0: } Chris@0: Chris@0: foreach ($scopeInfo->variables as $varInfo) { Chris@0: if (($varInfo->ignoreUnused === true) || (isset($varInfo->firstRead) === true)) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: if (($this->allowUnusedFunctionParameters === true) && ($varInfo->scopeType === 'param')) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: if (($varInfo->passByReference === true) && isset($varInfo->lastAssignment) === true) { Chris@0: // If we're pass-by-reference then it's a common pattern to Chris@0: // use the variable to return data to the caller, so any Chris@0: // assignment also counts as "variable use" for the purposes Chris@0: // of "unused variable" warnings. Chris@0: continue; Chris@0: } Chris@0: Chris@0: if (isset($varInfo->firstDeclared) === true) { Chris@0: $phpcsFile->addWarning( Chris@0: "Unused %s %s.", Chris@0: $varInfo->firstDeclared, Chris@0: 'UnusedVariable', Chris@0: array( Chris@0: VariableInfo::$scopeTypeDescriptions[$varInfo->scopeType], Chris@0: "\${$varInfo->name}", Chris@0: ) Chris@0: ); Chris@0: } else if (isset($varInfo->firstInitialized) === true) { Chris@0: $phpcsFile->addWarning( Chris@0: "Unused %s %s.", Chris@0: $varInfo->firstInitialized, Chris@0: 'UnusedVariable', Chris@0: array( Chris@0: VariableInfo::$scopeTypeDescriptions[$varInfo->scopeType], Chris@0: "\${$varInfo->name}", Chris@0: ) Chris@0: ); Chris@0: }//end if Chris@0: }//end foreach Chris@0: Chris@0: }//end processScopeClose() Chris@0: Chris@0: Chris@0: }//end class