Пример #1
0
 public function getReport(XRef_IParsedFile $pf)
 {
     if ($pf->getFileType() != $this->supportedFileType) {
         return;
     }
     $this->report = array();
     // config_constants are initialized here and not in constructor only for
     // unittest. TODO: make unittest reload plugins after config changes
     $this->config_constants = array();
     foreach (XRef::getConfigValue("lint.add-constant", array()) as $const_name) {
         $this->config_constants[$const_name] = true;
     }
     // lower/mixed-case string literals:
     // for (foo=0; $foo<10; )
     // $bar[x]  - is it $bar['x'] or $bar[$x] ?
     $seen_strings = array();
     // report every literal once per file, don't be noisy
     $global_constants = array();
     // list of all constants defined in global space in this file
     $class_constants = array();
     // list of all class constants names defined in this file
     // list of token indexes that contains allowed string literals
     $ignore_tokens = array();
     foreach ($pf->getConstants() as $c) {
         if ($c->className) {
             $class_constants[$c->name] = 1;
         } else {
             $global_constants[$c->name] = 1;
             // TODO: this is a namespaced name
         }
         $ignore_tokens[$c->index] = 1;
         // don't parse/analyze this token
     }
     $tokens = $pf->getTokens();
     $tokens_count = count($tokens);
     $is_inside_interpolated_string = false;
     for ($i = 0; $i < $tokens_count; ++$i) {
         $t = $tokens[$i];
         if (isset($ignore_tokens[$i])) {
             continue;
         }
         // skip namespaces declarations and imports:
         // namespace foo\bar;
         // use foo\bar as foobar;
         if ($t->kind == T_NAMESPACE || $t->kind == T_USE) {
             do {
                 $t = $t->nextNS();
             } while ($t->text != ';' && $t->text != '{');
             $i = $t->index;
             continue;
         }
         // skip class names completely
         // class Foo extends Bar implements Baz, Qux
         if ($t->kind == T_CLASS || $t->kind == T_INTERFACE || $t->kind == T_TRAIT) {
             do {
                 $t = $t->nextNS();
             } while ($t->text != ';' && $t->text != '{');
             $i = $t->index;
             continue;
         }
         if ($t->kind == T_INSTANCEOF || $t->kind == T_NEW) {
             // ok, class name:
             // $foo instanceof Foo
             // $foo = new Foo;
             do {
                 $t = $t->nextNS();
             } while ($t->kind == T_STRING || $t->kind == T_NS_SEPARATOR);
             $i = $t->index;
             continue;
         }
         if ($t->kind == XRef::T_ONE_CHAR && $t->text == '"') {
             $is_inside_interpolated_string = !$is_inside_interpolated_string;
         }
         if ($t->kind == T_STRING) {
             if (isset($seen_strings[$t->text])) {
                 // report each string only once
                 continue;
             }
             // skip know constants defined in this file (const foo=1),
             // system constants (SORT_DESC) and config-defined constants
             if (isset($global_constants[$t->text]) || isset($this->system_constants[$t->text]) || isset($this->config_constants[$t->text])) {
                 continue;
             }
             // PHP predefined case-insensitive constants?
             $str_upper = strtoupper($t->text);
             if ($str_upper == "TRUE" || $str_upper == "FALSE" || $str_upper == "NULL") {
                 continue;
             }
             // skip all fully-qualified names (names with namespaces), if any
             // Foo\Bar\Baz
             $n = $t->nextNS();
             while ($n->kind == T_STRING || $n->kind == T_NS_SEPARATOR) {
                 $n = $n->nextNS();
             }
             // add constants defined in this file to list of known strings and don't report them
             //  define('foo', <expression>)
             if ($t->text == 'define') {
                 if ($n->text == '(') {
                     $nn = $n->nextNS();
                     if ($nn->kind == T_CONSTANT_ENCAPSED_STRING) {
                         $string = $nn->text;
                         $nn = $nn->nextNS();
                         if ($nn->text == ',' && strlen($string) > 2) {
                             // remove the first and the last quotes
                             $string = substr($string, 1, strlen($string) - 2);
                             $global_constants[$string] = 1;
                             continue;
                         }
                     }
                 }
             }
             if ($n->text == '(') {
                 // ok, function call: Foo(...)
                 $i = $n->index;
                 continue;
             }
             if ($n->kind == T_DOUBLE_COLON) {
                 // ok, class name: foo::$something
                 $i = $n->index;
                 continue;
             }
             if ($n->text == ':') {
                 // ok, label (e.g. goto foo; foo: ...);
                 $i = $n->index;
                 continue;
             }
             // some kind of variable declared with class?
             // catch (Foo $x);
             // function bar(Foo\Bar &$x)
             if ($n->text == '&') {
                 $n = $n->nextNS();
             }
             if ($n->kind == T_VARIABLE) {
                 $i = $n->index;
                 continue;
             }
             $p = $t->prevNS();
             if ($p->kind == T_DOUBLE_COLON || $p->kind == T_OBJECT_OPERATOR) {
                 // ok, static or instance member: $bar->foo, Bar::foo
                 continue;
             }
             if ($p->kind == T_GOTO) {
                 // ok, label for goto
                 continue;
             }
             // declare(ticks=1) ?
             if ($t->text == 'ticks' || $t->text == 'encoding') {
                 if ($p->text == '(') {
                     $pp = $p->prevNS();
                     if (strtolower($pp->text) == 'declare') {
                         // ok, skip this
                         continue;
                     }
                 }
             }
             if ($is_inside_interpolated_string) {
                 // exception: it's ok to index arrays inside interpolated (double-quoted) strings
                 // with bare words:
                 //      "Foo is $data[foo]";        // ok
                 //      "Foo is {$data['foo']}";    // ok
                 //      "Foo is {$data[foo]}";      // NOT ok
                 if ($p->text == '[') {
                     $pp = $p->prev();
                     if ($pp && $pp->kind == T_VARIABLE) {
                         $pp = $pp->prev();
                         if ($pp && $pp->kind != T_CURLY_OPEN) {
                             // ok, allow "$data[something]"
                             continue;
                         }
                     }
                 }
             }
             // is it some known class constant used without prefix?
             //  class Foo { const BAR = 1; }
             //  echo BAR; // should be Foo::BAR (or self::BAR inside the class)
             if (isset($class_constants[$t->text])) {
                 $this->addDefect($t, self::E_UNPREFIXED_CLASS_CONSTANT);
                 $seen_strings[$t->text] = 1;
                 continue;
             }
             if ($t->text == $str_upper) {
                 // ok, all-uppercase, SOME_CONSTANT, I hope
                 continue;
             }
             $this->addDefect($t, self::E_LOWER_CASE_STRING_LITERAL);
             $seen_strings[$t->text] = 1;
         }
     }
     return $this->report;
 }