-
-
Save rmccue/1657281 to your computer and use it in GitHub Desktop.
Find Unset Local Variables
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Find unset PHP variables | |
* | |
* Deals with scope fairly correctly. | |
* | |
* Known bugs: | |
* - Does not yet support `if: ... endif;` (etc) | |
* - Does not yet support {$a}/${a} (coming soon) | |
*/ | |
// By default, parse ourself! | |
if (empty($path_to_file)) { | |
$path_to_file = __FILE__; | |
} | |
$file = file_get_contents($path_to_file); | |
// Tokenize | |
$tokens = token_get_all($file); | |
// Setup | |
$errors = array(); // array(array($var, $line), ...) | |
$last = null; // Last T_VARIABLE | |
$last_line = null; // Where $last was defined | |
$expecting = false; // Are we expecting a new variable? (After => in foreach, or "as") | |
$ignore_closeblock = false; | |
$inherits_scope = false; | |
$add_on_new_scope = null; | |
// $defined acts as a stack | |
// A new array is created for each scope | |
$defined = array(array()); | |
$current_scope = 0; | |
foreach ($tokens as $token) { | |
if (is_string($token)) { | |
// Not sure when it'd be null, but just in case | |
if ($token === '=' && $last !== null) { | |
$defined[$current_scope][] = $last; | |
$last = null; | |
$last_line = null; | |
} | |
elseif ($token === '{') { | |
if ($inherits_scope) { | |
$defined[] = &$defined[$current_scope]; | |
} | |
else { | |
$defined[] = array(); | |
} | |
$current_scope++; | |
if ($add_on_new_scope !== null) { | |
$defined[$current_scope][] = $add_on_new_scope; | |
$add_on_new_scope = null; | |
} | |
} | |
elseif ($token === '}' && !$ignore_closeblock) { | |
array_pop($defined); | |
$current_scope--; | |
} | |
elseif ($last !== null) { | |
$errors[] = array($last, $last_line); | |
$last = null; | |
$last_line = null; | |
} | |
continue; | |
} | |
switch ($token[0]) { | |
case T_WHITESPACE: | |
continue; | |
// We're about to expect a new variable | |
case T_DOUBLE_ARROW: // This includes array('a' => 'b') too, that should be fixed | |
case T_AS: | |
case T_EMPTY: | |
case T_ISSET: | |
$expecting = true; | |
continue; | |
// We found a variable! | |
case T_VARIABLE: | |
// Prefixed with $ | |
$name = substr($token[1], 1); | |
// Are we expecting one? | |
if ($expecting) { | |
$add_on_new_scope = $name; | |
$expecting = false; | |
continue; | |
} | |
// Do we know about it? | |
if (in_array(substr($token[1], 1), $defined[$current_scope])) { | |
continue; | |
} | |
// Whoops, we already had a variable, what's this doing here? | |
// (Unlikely to ever get here) | |
if ($last !== null) { | |
$errors[] = array($last, $last_line); | |
} | |
$last = $name; | |
$last_line = $token[2]; | |
continue; | |
// Includes a { in it, ignore that for closing blocks | |
case T_DOLLAR_OPEN_CURLY_BRACES: | |
case T_CURLY_OPEN: | |
case T_STRING_VARNAME: | |
$ignore_closeblock = true; | |
// more here pls | |
continue; | |
// All types of assignment | |
case T_AND_EQUAL: | |
case T_MOD_EQUAL: | |
case T_MUL_EQUAL: | |
case T_SL_EQUAL: | |
case T_SR_EQUAL: | |
case T_XOR_EQUAL: | |
$defined[$current_scope][] = $last; | |
$last = null; | |
$last_line = null; | |
continue; | |
// These have no scope, so we fake one | |
case T_DO: | |
case T_WHILE: | |
case T_IF: | |
case T_ELSE: | |
case T_FOR: | |
case T_FOREACH: | |
$inherits_scope = true; | |
default: | |
if ($last !== null) { | |
$errors[] = array($last, $last_line); | |
$last = null; | |
$last_line = null; | |
} | |
} | |
} | |
foreach ($errors as $error) { | |
printf('L%2$d: $%1$s' . PHP_EOL, $error[0], $error[1]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added
T_EMPTY
andT_ISSET
to tokens that expect an undefined (more that they don't care, really).