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