/**
  * @return InspectionResult
  */
 public function run()
 {
     $result = new InspectionResult("Syntax errors");
     $good = 0;
     $bad = 0;
     foreach (new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->dir)), "#\\.php\$#") as $file) {
         exec("php -l {$file} 2>&1", $out);
         $lint = implode("<br>", $out);
         if (strpos($lint, "No syntax errors detected in") === 0) {
             $good++;
         } else {
             $bad++;
             $result->error($lint);
         }
     }
     $result->info("{$good} good PHP file(s) and {$bad} bad PHP file(s) found.");
     $result->info("Checked with <code>PHP " . `php -r 'echo PHP_VERSION;'` . "</code>");
     return $result;
 }
 /**
  * @return InspectionResult
  */
 public function run()
 {
     $result = new InspectionResult("Bad practice");
     $pluginYml = file_get_contents($this->dir . "plugin.yml");
     $manifest = yaml_parse($pluginYml);
     if ($manifest === false) {
         $result->error("Error parsing <code>plugin.yml</code>");
         return $result;
     }
     $mainFile = realpath($this->dir . "src/" . str_replace("\\", "/", $manifest["main"]) . ".php");
     /** @var \SplFileInfo $file */
     foreach (new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->dir)), "#\\.php\$#") as $file) {
         $file = $file->getPathname();
         $contents = file_get_contents($file);
         $isMain = $file === $mainFile;
         if (stripos($contents, "server::getinstance()") !== false) {
             $result->warning("<code>Server::getInstance()</code> scanned in file <code>" . substr($file, strlen($this->dir)) . "</code><br>\n\t\t\t\t\t<ul><li>The PHP extensions that PocketMine-MP uses have some issues with\n\t\t\t\t\tstatic properties. You are recommended try using other methods to get\n\t\t\t\t\tthe <code>Server</code> instance.</li>\n\t\t\t\t\t<li>" . ($isMain ? "You can use <code>\$this->getServer()</code> to get the server object instead." : (stripos($contents, "extends PluginTask") !== false ? "You can use <code>\$this->getOwner()->getServer()</code> to get\n\t\t\t\t\t\t\tthe <code>Server</code> instance instead." : "You can pass <code>\$this->getServer()</code> from the plugin object to\n\t\t\t\t\t\t\tyour current class's constructor.")) . "</li></ul>");
         }
         if ($isMain) {
             if (preg_match_all("#\\\$this->config[ \t\r\n]?=[ \t\r\n]?new[ \t\r\n]+config[ \t\r\n]?\\(#i", $contents)) {
                 $result->warning("<code>PluginBase::\$config</code> is already defined in PocketMine\n\t\t\t\t\t\tPluginBase class. Strange errors will occur if you override\n\t\t\t\t\t\t<code>\$this->config</code> yourself. For example, when you decide to use\n\t\t\t\t\t\t<code>\$this->saveDefaultConfig()</code> later, it will not work.<br>\n\t\t\t\t\t\t<ul><li>You are recommended to improve this by renaming <code>\$this->config</code>\n\t\t\t\t\t\tto something else, or to use <code>\$this->saveDefaultConfig()</code> and related\n\t\t\t\t\t\tfunctions.</li></ul>");
             }
         }
     }
     return $result;
 }
 /**
  * @return InspectionResult
  */
 public function run()
 {
     $result = new InspectionResult("Classpath");
     $pluginYml = yaml_parse_file($this->dir . "plugin.yml");
     if ($pluginYml === false) {
         $result->error("Error parsing <code>plugin.yml</code>");
         return $result;
     }
     if (!isset($pluginYml["main"])) {
         $result->error("Attribute <code>main</code> is missing in <code>plugin.yml</code>!");
         goto end;
     }
     $mainClass = $pluginYml["main"];
     $result->info("Main class scanned: {$mainClass}");
     $tokenHead = true;
     for ($i = 0; $i < strlen($mainClass); $i++) {
         $char = substr($mainClass, $i, 1);
         $ord = ord($char);
         if ($tokenHead) {
             $tokenHead = false;
             if (!(ord("A") <= $ord and $ord <= ord("Z") or ord("a") <= $ord and ord("z") or $char === "_")) {
                 $result->error("The first character of the class name or after a backslash must be <code>A-Z</code>, <code>a-z</code> or <code>_</code>. Invalid character <code>{$char}</code> at character {$i} of class name <code>{$mainClass}</code> found.");
             }
         } else {
             if (!(ord("A") <= $ord and $ord <= ord("Z") or ord("a") <= $ord and ord("z") or $char === "_" or ord("0") <= $ord and $ord <= ord("9") or $char === "\\")) {
                 $result->error("Invalid character <code>{$char}</code> found in classmain class name <code>{$mainClass}</code> at character {$i} found. A fully qualified class name must only contain <code>A-Z</code>, <code>a-z</code>, <code>_</code>, <code>0-9</code> and <code>\\</code>.");
             }
         }
     }
     $expectedSub = "src/" . str_replace("\\", "/", $mainClass) . ".php";
     $mainClassFile = $this->dir . $expectedSub;
     if (!is_file($mainClassFile)) {
         $result->error("Main class file expected at <code>{$expectedSub}</code> but it is not a file");
         goto end;
     }
     $result->info("Main class file found at <code>{$expectedSub}</code>");
     $code = preg_replace("/[\r\n\t ]+/m", " ", file_get_contents($mainClassFile));
     $tokens = explode("\\", $mainClass);
     $simpleName = $tokens[count($tokens) - 1];
     $namespace = implode("\\", array_slice($tokens, 0, -1));
     $namespaceDec = "namespace {$namespace}";
     if (strpos($code, $namespaceDec) === false) {
         $result->warning("Namespace declaration <code>{$namespaceDec}</code> not found");
     }
     $superclass = "(\\\\pocketmine\\\\plugin\\\\)?PluginBase";
     if (preg_match_all("#use pocketmine\\\\plugin\\\\PluginBase as ([A-Za-z0-9_]+) ?;#i", $code, $matches)) {
         $alias = $matches[1][0];
         $superclass = "({$superclass})|({$alias})";
     }
     if (!preg_match_all("#class {$simpleName} (implements ([A-Za-z0-9_]+, ?)?[A-Za-z0-9]+)?extends {$superclass}#i", $code)) {
         $result->error("Main class <code>{$simpleName}</code> is not declared as a subclass of pocketmine\\plugin\\PluginBase.");
     }
     $src = rtrim(realpath($this->dir . "src"), "/\\") . "/";
     foreach (new \RegexIterator(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($src)), '#.php$#i') as $file) {
         $file = realpath($file);
         $subpath = str_replace("\\", "/", substr($file, strlen($src), -4));
         $contents = file_get_contents($file);
         $namespace = implode("\\", array_slice($explosion = explode("/", $subpath), 0, -1));
         $namespaceEx = str_replace("\\", "\\\\", $namespace);
         if (preg_match_all("#namespace[\t \r\n]+" . $namespaceEx . "[\t \r\n]*[\\{\\;]#i", $contents) === 0) {
             $result->warning("Namespace declaration as <code>{$namespace}</code> for <code>src/{$subpath}.php</code> missing");
         }
         $class = $explosion[count($explosion) - 1];
         if (preg_match_all("#(class|interface|trait)[\t \r\n]+{$class}#i", $contents) === 0) {
             $result->warning("Class/interface/trait declaration as <code>{$namespace}\\{$class}</code> missing at <code>src/{$subpath}.php</code>");
         }
     }
     end:
     return $result;
 }