#!/www/server/bin/php true, 'files' => array(), ); foreach ($argv as $arg) { if ($arg[0] == "-") { for ($i = 1; $i < strlen($arg); $i++) { switch ($option = $arg[$i]) { case 'a': $opts['checkcounters'] = $opts['checklists'] = $opts['checkparams'] = true; break; case 'c': $opts['checkcounters'] = true; break; case 'd': $opts['debug'] = true; break; case 'h': $error = ""; case 'l': $opts['checklists'] = true; break; case 'p': $opts['checkparams'] = true; break; case 'q': $opts['quiet'] = true; break; case 's': $opts['short'] = true; break; default: $error = "$progname: Invalid option -- $option\n"; } } } else $opts['files'][] = $arg; } if (isset($error)) { fputs(STDERR, "$error$usage"); exit(1); } if (empty($opts['files'])) $opts['files'] = array('php://stdin'); # Use stdin if none given $phplint = new phplint($opts); if ($phplint->warnings && !$opts['quiet']) echo join("\n", $phplint->warnings) . "\n"; exit($phplint->warnings ? 1 : 0); ######################################################################## class phplint { var $warnings = array(); # Do not report if one of the following tokens is encountered before variable var $var_declaration = array(T_VAR => 1, T_STATIC => 1, T_PUBLIC => 1, T_PROTECTED => 1, T_PRIVATE => 1); var $var_static = array(T_PAAMAYIM_NEKUDOTAYIM => 1); # Tokens to ignore, generated in constructor var $filter_token = array(); function phplint($p) { $this->p = $p; foreach (explode(' ', "T_WHITESPACE T_COMMENT T_ML_COMMENT T_DOC_COMMENT") as $token) { if (defined($token)) $this->filter_token[constant($token)] = true; } foreach ((array)$p['files'] as $file) { $filedata = file_get_contents($file); # Ignore non-PHP files (accept #! or < at start of file) if (preg_match('/^(#!|<)/', $filedata)) $this->check($file, $filedata); } } function check($file, $string) { $vars = $stack = array(); $this->tokens = array_merge( array_filter(@token_get_all($string), array($this, "filter_tokens")), array("}") # Sentinel to trigger check at end of file ); $this->source = array_map("trim", explode("\n", "\n$string")); $body = true; foreach ($this->tokens as $i => $token) { if (is_array($token)) { switch ($token[0]) { case T_CLASS: case T_FUNCTION: $stack[] = array($stacktop = $curly, $vars); $vars = array(); $body = $list = false; break; case T_LIST: $list = true; break; case T_VARIABLE: list($prev, $next) = $this->getnextprev($i); $token = array($token[0], $token[1], $token[2], $body, $list, $prev, $next); $vars[$token[1]][] = $token; break; case T_STRING: if (preg_match('/^(compact|extract)$/i', $token[1])) { list($prev, $next) = $this->getnextprev($i); if ($prev != T_PAAMAYIM_NEKUDOTAYIM && $next == "(") $this->out($file, $token[2], "$token[1]() is deprecated"); } break; case T_CURLY_OPEN: case T_DOLLAR_OPEN_CURLY_BRACES: $curly++; break; } } else { switch ($token) { case '{': $body = true; $curly++; break; case '}': if ((--$curly <= $stacktop) && ($curly || $stack)) { foreach ((array)$vars as $info) { list(, $name, $line, $body, $list, $prev, $next) = $info[0]; #var_dump(count($info), $body, $list, token_name((int)$prev), token_name((int)$next), $name); if ( (count($info) == 1) && ($this->p['checkparams'] || $body) && ($this->p['checklists'] || !$list) && ($this->p['checkcounters'] || ($prev != T_INC && $next != T_INC && ($prev != "!" || $next != "["))) && (!$this->var_static[$prev]) && (!$this->var_declaration[$prev]) && !preg_match('/^\$(this|GLOBALS|_SERVER|_GET|_POST|_REQUEST|_FILES|_COOKIE|argv|argc|dummy.*)$/', $name) && !preg_match('/NOPHPLINT/', $this->source[$line]) ) { if ($this->p['debug']) var_dump($info); $this->out($file, $line, $body ? "$name used only once" : "$name parameter not used"); } } list($stacktop, $vars) = array_pop($stack); } break; case ')': $list = false; break; } } } } function filter_tokens($token) { return !$this->filter_token[$token[0]]; } function getnextprev($pos) { return array($this->tokens[$pos - 1][0], $this->tokens[$pos + 1][0]); } function out($file, $line, $msg) { $this->warnings[] = $line ? ("$file:$line $msg" . ($this->p['short'] ? "" : (": " . $this->source[$line]))) : "$file $msg"; } }