/** * @group xhpast */ function xhpast_get_parser_future($data) { if (!xhpast_is_available()) { throw new Exception(xhpast_get_build_instructions()); } $future = new ExecFuture('%s', xhpast_get_binary_path()); $future->write($data); return $future; }
/** * @group xhpast */ function xhpast_get_parser_future($data) { if (!xhpast_is_available()) { try { // Try to build XHPAST automatically. If we can't then just ask the user // to build it themselves. xhpast_build(); } catch (CommandException $e) { throw new Exception(xhpast_get_build_instructions()); } } $future = new ExecFuture('%s', xhpast_get_binary_path()); $future->write($data); return $future; }
public function willLintPaths(array $paths) { if (!xhpast_is_available()) { throw new Exception(xhpast_get_build_instructions()); } // NOTE: For now, we completely ignore paths and just lint every library in // its entirety. This is simpler and relatively fast because we don't do any // detailed checks and all the data we need for this comes out of module // caches. $bootloader = PhutilBootloader::getInstance(); $libs = $bootloader->getAllLibraries(); // Load the up-to-date map for each library, without loading the library // itself. This means lint results will accurately reflect the state of // the working copy. $arc_root = dirname(phutil_get_library_root('arcanist')); $bin = "{$arc_root}/scripts/phutil_rebuild_map.php"; $symbols = array(); foreach ($libs as $lib) { // Do these one at a time since they individually fanout to saturate // available system resources. $future = new ExecFuture('%s --show --quiet --ugly -- %s', $bin, phutil_get_library_root($lib)); $symbols[$lib] = $future->resolveJSON(); } $all_symbols = array(); foreach ($symbols as $library => $map) { // Check for files which declare more than one class/interface in the same // file, or mix function definitions with class/interface definitions. We // must isolate autoloadable symbols to one per file so the autoloader // can't end up in an unresolvable cycle. foreach ($map as $file => $spec) { $have = idx($spec, 'have', array()); $have_classes = idx($have, 'class', array()) + idx($have, 'interface', array()); $have_functions = idx($have, 'function'); if ($have_functions && $have_classes) { $function_list = implode(', ', array_keys($have_functions)); $class_list = implode(', ', array_keys($have_classes)); $this->raiseLintInLibrary($library, $file, end($have_functions), self::LINT_ONE_CLASS_PER_FILE, "File '{$file}' mixes function ({$function_list}) and " . "class/interface ({$class_list}) definitions in the same file. " . "A file which declares a class or an interface MUST " . "declare nothing else."); } else { if (count($have_classes) > 1) { $class_list = implode(', ', array_keys($have_classes)); $this->raiseLintInLibrary($library, $file, end($have_classes), self::LINT_ONE_CLASS_PER_FILE, "File '{$file}' declares more than one class or interface " . "({$class_list}). A file which declares a class or interface MUST " . "declare nothing else."); } } } // Check for duplicate symbols: two files providing the same class or // function. foreach ($map as $file => $spec) { $have = idx($spec, 'have', array()); foreach (array('class', 'function', 'interface') as $type) { $libtype = $type == 'interface' ? 'class' : $type; foreach (idx($have, $type, array()) as $symbol => $offset) { if (empty($all_symbols[$libtype][$symbol])) { $all_symbols[$libtype][$symbol] = array('library' => $library, 'file' => $file, 'offset' => $offset); continue; } $osrc = $all_symbols[$libtype][$symbol]['file']; $olib = $all_symbols[$libtype][$symbol]['library']; $this->raiseLintInLibrary($library, $file, $offset, self::LINT_DUPLICATE_SYMBOL, "Definition of {$type} '{$symbol}' in '{$file}' in library " . "'{$library}' duplicates prior definition in '{$osrc}' in " . "library '{$olib}'."); } } } } foreach ($symbols as $library => $map) { // Check for unknown symbols: uses of classes, functions or interfaces // which are not defined anywhere. We reference the list of all symbols // we built up earlier. foreach ($map as $file => $spec) { $need = idx($spec, 'need', array()); foreach (array('class', 'function', 'interface') as $type) { $libtype = $type == 'interface' ? 'class' : $type; foreach (idx($need, $type, array()) as $symbol => $offset) { if (!empty($all_symbols[$libtype][$symbol])) { // Symbol is defined somewhere. continue; } $this->raiseLintInLibrary($library, $file, $offset, self::LINT_UNKNOWN_SYMBOL, "Use of unknown {$type} '{$symbol}'. This symbol is not defined " . "in any loaded phutil library."); } } } } }
public function willLintPaths(array $paths) { if (!xhpast_is_available()) { throw new Exception(xhpast_get_build_instructions()); } // NOTE: For now, we completely ignore paths and just lint every library in // its entirety. This is simpler and relatively fast because we don't do any // detailed checks and all the data we need for this comes out of module // caches. $bootloader = PhutilBootloader::getInstance(); $libs = $bootloader->getAllLibraries(); // Load the up-to-date map for each library, without loading the library // itself. This means lint results will accurately reflect the state of // the working copy. $symbols = array(); foreach ($libs as $lib) { $root = phutil_get_library_root($lib); try { $symbols[$lib] = id(new PhutilLibraryMapBuilder($root))->buildFileSymbolMap(); } catch (XHPASTSyntaxErrorException $ex) { // If the library contains a syntax error then there isn't much that we // can do. continue; } } $all_symbols = array(); foreach ($symbols as $library => $map) { // Check for files which declare more than one class/interface in the same // file, or mix function definitions with class/interface definitions. We // must isolate autoloadable symbols to one per file so the autoloader // can't end up in an unresolvable cycle. foreach ($map as $file => $spec) { $have = idx($spec, 'have', array()); $have_classes = idx($have, 'class', array()) + idx($have, 'interface', array()); $have_functions = idx($have, 'function'); if ($have_functions && $have_classes) { $function_list = implode(', ', array_keys($have_functions)); $class_list = implode(', ', array_keys($have_classes)); $this->raiseLintInLibrary($library, $file, end($have_functions), self::LINT_ONE_CLASS_PER_FILE, "File '{$file}' mixes function ({$function_list}) and " . "class/interface ({$class_list}) definitions in the same file. " . "A file which declares a class or an interface MUST " . "declare nothing else."); } else { if (count($have_classes) > 1) { $class_list = implode(', ', array_keys($have_classes)); $this->raiseLintInLibrary($library, $file, end($have_classes), self::LINT_ONE_CLASS_PER_FILE, "File '{$file}' declares more than one class or interface " . "({$class_list}). A file which declares a class or interface MUST " . "declare nothing else."); } } } // Check for duplicate symbols: two files providing the same class or // function. foreach ($map as $file => $spec) { $have = idx($spec, 'have', array()); foreach (array('class', 'function', 'interface') as $type) { $libtype = $type == 'interface' ? 'class' : $type; foreach (idx($have, $type, array()) as $symbol => $offset) { if (empty($all_symbols[$libtype][$symbol])) { $all_symbols[$libtype][$symbol] = array('library' => $library, 'file' => $file, 'offset' => $offset); continue; } $osrc = $all_symbols[$libtype][$symbol]['file']; $olib = $all_symbols[$libtype][$symbol]['library']; $this->raiseLintInLibrary($library, $file, $offset, self::LINT_DUPLICATE_SYMBOL, "Definition of {$type} '{$symbol}' in '{$file}' in library " . "'{$library}' duplicates prior definition in '{$osrc}' in " . "library '{$olib}'."); } } } } $types = array('class', 'function', 'interface', 'class/interface'); foreach ($symbols as $library => $map) { // Check for unknown symbols: uses of classes, functions or interfaces // which are not defined anywhere. We reference the list of all symbols // we built up earlier. foreach ($map as $file => $spec) { $need = idx($spec, 'need', array()); foreach ($types as $type) { $libtype = $type; if ($type == 'interface' || $type == 'class/interface') { $libtype = 'class'; } foreach (idx($need, $type, array()) as $symbol => $offset) { if (!empty($all_symbols[$libtype][$symbol])) { // Symbol is defined somewhere. continue; } $libphutil_root = dirname(phutil_get_library_root('phutil')); $this->raiseLintInLibrary($library, $file, $offset, self::LINT_UNKNOWN_SYMBOL, "Use of unknown {$type} '{$symbol}'. Common causes are:\n\n" . " - Your libphutil/ is out of date.\n" . " This is the most common cause.\n" . " Update this copy of libphutil: {$libphutil_root}\n" . "\n" . " - Some other library is out of date.\n" . " Update the library this symbol appears in.\n" . "\n" . " - This symbol is misspelled.\n" . " Spell the symbol name correctly.\n" . " Symbol name spelling is case-sensitive.\n" . "\n" . " - This symbol was added recently.\n" . " Run `arc liberate` on the library it was added to.\n" . "\n" . " - This symbol is external. Use `@phutil-external-symbol`.\n" . " Use `grep` to find usage examples of this directive.\n" . "\n" . "*** ALTHOUGH USUALLY EASY TO FIX, THIS IS A SERIOUS ERROR.\n" . "*** THIS ERROR IS YOUR FAULT. YOU MUST RESOLVE IT."); } } } } }
public function willLintPaths(array $paths) { if ($paths) { if (!xhpast_is_available()) { throw new Exception(xhpast_get_build_instructions()); } } $modules = array(); $moduleinfo = array(); $project_root = $this->getEngine()->getWorkingCopy()->getProjectRoot(); foreach ($paths as $path) { $absolute_path = $project_root . '/' . $path; $library_root = phutil_get_library_root_for_path($absolute_path); if (!$library_root) { continue; } if ($this->isPhutilLibraryMetadata($path)) { continue; } $library_name = phutil_get_library_name_for_root($library_root); if (!is_dir($path)) { $path = dirname($path); } $path = Filesystem::resolvePath($path, $project_root); if ($path == $library_root) { continue; } $module_name = Filesystem::readablePath($path, $library_root); $module_key = $library_name . ':' . $module_name; if (empty($modules[$module_key])) { $modules[$module_key] = $module_key; $this->setModuleInfo($module_key, array('library' => $library_name, 'root' => $library_root, 'module' => $module_name)); } } if (!$modules) { return; } $modules = array_keys($modules); $arc_root = phutil_get_library_root('arcanist'); $bin = dirname($arc_root) . '/scripts/phutil_analyzer.php'; $futures = array(); foreach ($modules as $mkey => $key) { $disk_path = $this->getModulePathOnDisk($key); if (Filesystem::pathExists($disk_path)) { $futures[$key] = new ExecFuture('%s %s', $bin, $disk_path); } else { // This can occur in git when you add a module in HEAD and then remove // it in unstaged changes in the working copy. Just ignore it. unset($modules[$mkey]); } } $requirements = array(); foreach (Futures($futures)->limit(16) as $key => $future) { $requirements[$key] = $future->resolveJSON(); } $dependencies = array(); $futures = array(); foreach ($requirements as $key => $requirement) { foreach ($requirement['messages'] as $message) { list($where, $text, $code, $description) = $message; if ($where) { $where = array($where); } $this->raiseLintInModule($key, $code, $description, $where, $text); } foreach ($requirement['requires']['module'] as $req_module => $where) { if (isset($requirements[$req_module])) { $dependencies[$req_module] = $requirements[$req_module]; } else { list($library_name, $module_name) = explode(':', $req_module); $library_root = phutil_get_library_root($library_name); $this->setModuleInfo($req_module, array('library' => $library_name, 'root' => $library_root, 'module' => $module_name)); $disk_path = $this->getModulePathOnDisk($req_module); if (Filesystem::pathExists($disk_path)) { $futures[$req_module] = new ExecFuture('%s %s', $bin, $disk_path); } else { $dependencies[$req_module] = array(); } } } } foreach (Futures($futures)->limit(16) as $key => $future) { $dependencies[$key] = $future->resolveJSON(); } foreach ($requirements as $key => $spec) { $deps = array_intersect_key($dependencies, $spec['requires']['module']); $this->lintModule($key, $spec, $deps); } }