You are here

VariableAnalysisSniff.php in Coder 8.2

File

coder_sniffer/DrupalPractice/Sniffs/CodeAnalysis/VariableAnalysisSniff.php
View source
<?php

/**
 * This file is part of the VariableAnalysis addon for PHP_CodeSniffer.
 *
 * @category  PHP
 * @package   PHP_CodeSniffer
 * @author    Sam Graham <php-codesniffer-variableanalysis BLAHBLAH illusori.co.uk>
 * @copyright 2011-2012 Sam Graham <php-codesniffer-variableanalysis BLAHBLAH illusori.co.uk>
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD License
 * @link      http://pear.php.net/package/PHP_CodeSniffer
 */
namespace DrupalPractice\Sniffs\CodeAnalysis;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;

/**
 * Holds details of a scope.
 *
 * @category  PHP
 * @package   PHP_CodeSniffer
 * @author    Sam Graham <php-codesniffer-variableanalysis BLAHBLAH illusori.co.uk>
 * @copyright 2011-2012 Sam Graham <php-codesniffer-plugins BLAHBLAH illusori.co.uk>
 * @link      http://pear.php.net/package/PHP_CodeSniffer
 */
class ScopeInfo {
  public $owner;
  public $opener;
  public $closer;
  public $variables = array();

  /**
   * Constructor.
   *
   * @param int $currScope
   */
  function __construct($currScope) {

    // TODO: extract opener/closer.
    $this->owner = $currScope;
  }

}

//end class

/**
 * Holds details of a variable within a scope.
 *
 * @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
 */
class VariableInfo {
  public $name;

  /**
   * What scope the variable has: local, param, static, global, bound
   */
  public $scopeType;
  public $typeHint;
  public $passByReference = false;
  public $firstDeclared;
  public $firstInitialized;
  public $firstRead;
  public $ignoreUnused = false;
  public $lastAssignment;
  static $scopeTypeDescriptions = array(
    'local' => 'variable',
    'param' => 'function parameter',
    'static' => 'static variable',
    'global' => 'global variable',
    'bound' => 'bound variable',
  );

  /**
   * Constructor.
   *
   * @param string $varName
   */
  function __construct($varName) {
    $this->name = $varName;
  }

}

//end class

/**
 * 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
 */
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
  }

}

//end class

Classes

Namesort descending Description
ScopeInfo Holds details of a scope.
VariableAnalysisSniff Checks the for undefined function variables.
VariableInfo Holds details of a variable within a scope.