/** * @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("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("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; }