diff --git a/src/lint/linter/ArcanistCSSLintLinter.php b/src/lint/linter/ArcanistCSSLintLinter.php index 7314025d..1f07a4b9 100644 --- a/src/lint/linter/ArcanistCSSLintLinter.php +++ b/src/lint/linter/ArcanistCSSLintLinter.php @@ -1,112 +1,104 @@ getEngine()->getConfigurationManager(); - - $options = $config->getConfigFromAnySource('lint.csslint.options'); // TODO: Deprecation warning. - - return $options; + $config = $this->getEngine()->getConfigurationManager(); + return $config->getConfigFromAnySource('lint.csslint.options'); } public function getDefaultBinary() { // TODO: Deprecation warning. $config = $this->getEngine()->getConfigurationManager(); - $bin = $config->getConfigFromAnySource('lint.csslint.bin'); - if ($bin) { - return $bin; - } - - return 'csslint'; + return $config->getConfigFromAnySource('lint.csslint.bin', 'csslint'); } public function getInstallInstructions() { return pht('Install CSSLint using `npm install -g csslint`.'); } public function shouldExpectCommandErrors() { return true; } protected function parseLinterOutput($path, $err, $stdout, $stderr) { $report_dom = new DOMDocument(); $ok = @$report_dom->loadXML($stdout); if (!$ok) { return false; } $files = $report_dom->getElementsByTagName('file'); $messages = array(); foreach ($files as $file) { foreach ($file->childNodes as $child) { if (!($child instanceof DOMElement)) { continue; } $data = $this->getData($path); $lines = explode("\n", $data); $name = $child->getAttribute('reason'); $severity = ($child->getAttribute('severity') == 'warning') ? ArcanistLintSeverity::SEVERITY_WARNING : ArcanistLintSeverity::SEVERITY_ERROR; $message = new ArcanistLintMessage(); $message->setPath($path); $message->setLine($child->getAttribute('line')); $message->setChar($child->getAttribute('char')); $message->setCode('CSSLint'); $message->setDescription($child->getAttribute('reason')); $message->setSeverity($severity); if ($child->hasAttribute('line') && $child->getAttribute('line') > 0) { $line = $lines[$child->getAttribute('line') - 1]; $text = substr($line, $child->getAttribute('char') - 1); $message->setOriginalText($text); } $messages[] = $message; } } return $messages; } protected function getLintCodeFromLinterConfigurationKey($code) { // NOTE: We can't figure out which rule generated each message, so we // can not customize severities. I opened a pull request to add this // ability; see: // // https://github.com/stubbornella/csslint/pull/409 throw new Exception( pht( "CSSLint does not currently support custom severity levels, because ". "rules can't be identified from messages in output. ". "See Pull Request #409.")); } } diff --git a/src/lint/linter/ArcanistCSharpLinter.php b/src/lint/linter/ArcanistCSharpLinter.php index 81d1f360..0d6f3c22 100644 --- a/src/lint/linter/ArcanistCSharpLinter.php +++ b/src/lint/linter/ArcanistCSharpLinter.php @@ -1,243 +1,235 @@ >'; $options["binary"] = 'string'; return $options; } public function setLinterConfigurationValue($key, $value) { switch ($key) { case 'discovery': $this->discoveryMap = $value; return; case 'binary': $this->cslintHintPath = $value; return; } parent::setLinterConfigurationValue($key, $value); } public function getLintCodeFromLinterConfigurationKey($code) { return $code; } public function setCustomSeverityMap(array $map) { foreach ($map as $code => $severity) { if (substr($code, 0, 2) === "SA" && $severity == "disabled") { throw new Exception( "In order to keep StyleCop integration with IDEs and other tools ". "consistent with Arcanist results, you aren't permitted to ". "disable StyleCop rules within '.arclint'. ". "Instead configure the severity using the StyleCop settings dialog ". "(usually accessible from within your IDE). StyleCop settings ". "for your project will be used when linting for Arcanist."); } } return parent::setCustomSeverityMap($map); } - public function getLintSeverityMap() { - return array(); - } - - public function getLintNameMap() { - return array(); - } - /** * Determines what executables and lint paths to use. Between platforms * this also changes whether the lint engine is run under .NET or Mono. It * also ensures that all of the required binaries are available for the lint * to run successfully. * * @return void */ private function loadEnvironment() { if ($this->loaded) { return; } // Determine runtime engine (.NET or Mono). if (phutil_is_windows()) { $this->runtimeEngine = ""; } else if (Filesystem::binaryExists("mono")) { $this->runtimeEngine = "mono "; } else { throw new Exception("Unable to find Mono and you are not on Windows!"); } // Determine cslint path. $cslint = $this->cslintHintPath; if ($cslint !== null && file_exists($cslint)) { $this->cslintEngine = Filesystem::resolvePath($cslint); } else if (Filesystem::binaryExists("cslint.exe")) { $this->cslintEngine = "cslint.exe"; } else { throw new Exception("Unable to locate cslint."); } // Determine cslint version. $ver_future = new ExecFuture( "%C -v", $this->runtimeEngine.$this->cslintEngine); list($err, $stdout, $stderr) = $ver_future->resolve(); if ($err !== 0) { throw new Exception( "You are running an old version of cslint. Please ". "upgrade to version ".self::SUPPORTED_VERSION."."); } $ver = (int)$stdout; if ($ver < self::SUPPORTED_VERSION) { throw new Exception( "You are running an old version of cslint. Please ". "upgrade to version ".self::SUPPORTED_VERSION."."); } elseif ($ver > self::SUPPORTED_VERSION) { throw new Exception( "Arcanist does not support this version of cslint (it is ". "newer). You can try upgrading Arcanist with `arc upgrade`."); } $this->loaded = true; } public function lintPath($path) { } public function willLintPaths(array $paths) { $this->loadEnvironment(); $futures = array(); // Bulk linting up into futures, where the number of files // is based on how long the command is. $current_paths = array(); foreach ($paths as $path) { // If the current paths for the command, plus the next path // is greater than 6000 characters (less than the Windows // command line limit), then finalize this future and add it. $total = 0; foreach ($current_paths as $current_path) { $total += strlen($current_path) + 3; // Quotes and space. } if ($total + strlen($path) > 6000) { // %s won't pass through the JSON correctly // under Windows. This is probably because not only // does the JSON have quotation marks in the content, // but because there'll be a lot of escaping and // double escaping because the JSON also contains // regular expressions. cslint supports passing the // settings JSON through base64-encoded to mitigate // this issue. $futures[] = new ExecFuture( "%C --settings-base64=%s -r=. %Ls", $this->runtimeEngine.$this->cslintEngine, base64_encode(json_encode($this->discoveryMap)), $current_paths); $current_paths = array(); } // Append the path to the current paths array. $current_paths[] = $this->getEngine()->getFilePathOnDisk($path); } // If we still have paths left in current paths, then we need to create // a future for those too. if (count($current_paths) > 0) { $futures[] = new ExecFuture( "%C --settings-base64=%s -r=. %Ls", $this->runtimeEngine.$this->cslintEngine, base64_encode(json_encode($this->discoveryMap)), $current_paths); $current_paths = array(); } $this->futures = $futures; } public function didRunLinters() { if ($this->futures) { foreach (Futures($this->futures)->limit(8) as $future) { $this->resolveFuture($future); } } } protected function resolveFuture(Future $future) { list($stdout) = $future->resolvex(); $all_results = json_decode($stdout); foreach ($all_results as $results) { if ($results === null || $results->Issues === null) { return; } foreach ($results->Issues as $issue) { $message = new ArcanistLintMessage(); $message->setPath($results->FileName); $message->setLine($issue->LineNumber); $message->setCode($issue->Index->Code); $message->setName($issue->Index->Name); $message->setChar($issue->Column); $message->setOriginalText($issue->OriginalText); $message->setReplacementText($issue->ReplacementText); $message->setDescription( vsprintf($issue->Index->Message, $issue->Parameters)); $severity = ArcanistLintSeverity::SEVERITY_ADVICE; switch ($issue->Index->Severity) { case 0: $severity = ArcanistLintSeverity::SEVERITY_ADVICE; break; case 1: $severity = ArcanistLintSeverity::SEVERITY_AUTOFIX; break; case 2: $severity = ArcanistLintSeverity::SEVERITY_WARNING; break; case 3: $severity = ArcanistLintSeverity::SEVERITY_ERROR; break; case 4: $severity = ArcanistLintSeverity::SEVERITY_DISABLED; break; } $severity_override = $this->getLintMessageSeverity($issue->Index->Code); if ($severity_override !== null) { $severity = $severity_override; } $message->setSeverity($severity); $this->addLintMessage($message); } } } protected function getDefaultMessageSeverity($code) { return null; } } diff --git a/src/lint/linter/ArcanistCppcheckLinter.php b/src/lint/linter/ArcanistCppcheckLinter.php index 33b9f490..471a24ca 100644 --- a/src/lint/linter/ArcanistCppcheckLinter.php +++ b/src/lint/linter/ArcanistCppcheckLinter.php @@ -1,129 +1,121 @@ getEngine()->getConfigurationManager(); // You will for sure want some options. The below default tends to be ok - $options = $config->getConfigFromAnySource( + return $config->getConfigFromAnySource( 'lint.cppcheck.options', '-j2 --inconclusive --enable=performance,style,portability,information'); - - return $options; } public function getLintPath() { $config = $this->getEngine()->getConfigurationManager(); $prefix = $config->getConfigFromAnySource('lint.cppcheck.prefix'); $bin = $config->getConfigFromAnySource('lint.cppcheck.bin', 'cppcheck'); if ($prefix !== null) { if (!Filesystem::pathExists($prefix.'/'.$bin)) { throw new ArcanistUsageException( "Unable to find cppcheck binary in a specified directory. Make ". "sure that 'lint.cppcheck.prefix' and 'lint.cppcheck.bin' keys are ". "set correctly. If you'd rather use a copy of cppcheck installed ". "globally, you can just remove these keys from your .arcconfig."); } - $bin = csprintf("%s/%s", $prefix, $bin); - - return $bin; + return csprintf("%s/%s", $prefix, $bin); } // Look for globally installed cppcheck list($err) = exec_manual('which %s', $bin); if ($err) { throw new ArcanistUsageException( "cppcheck does not appear to be installed on this system. Install ". "it (from http://cppcheck.sourceforge.net/) or configure ". "'lint.cppcheck.prefix' in your .arcconfig to point to the ". "directory where it resides." ); } return $bin; } public function lintPath($path) { $bin = $this->getLintPath(); $options = $this->getLintOptions(); list($rc, $stdout, $stderr) = exec_manual( "%C %C --inline-suppr --xml-version=2 -q %s", $bin, $options, $this->getEngine()->getFilePathOnDisk($path)); if ($rc === 1) { throw new Exception("cppcheck failed to run correctly:\n".$stderr); } $dom = new DOMDocument(); libxml_clear_errors(); if ($dom->loadXML($stderr) === false || libxml_get_errors()) { throw new ArcanistUsageException('cppcheck Linter failed to load ' . 'output. Something happened when running cppcheck. ' . "Output:\n$stderr" . "\nTry running lint with --trace flag to get more details."); } $errors = $dom->getElementsByTagName('error'); foreach ($errors as $error) { $loc_node = $error->getElementsByTagName('location'); if (!$loc_node) { continue; } $location = $loc_node->item(0); if (!$location) { continue; } $file = $location->getAttribute('file'); if ($file != Filesystem::resolvePath($path)) { continue; } $line = $location->getAttribute('line'); $id = $error->getAttribute('id'); $severity = $error->getAttribute('severity'); $msg = $error->getAttribute('msg'); $inconclusive = $error->getAttribute('inconclusive'); $verbose_msg = $error->getAttribute('verbose'); $severity_code = ArcanistLintSeverity::SEVERITY_WARNING; if ($inconclusive) { $severity_code = ArcanistLintSeverity::SEVERITY_ADVICE; } else if (stripos($severity, 'error') !== false) { $severity_code = ArcanistLintSeverity::SEVERITY_ERROR; } $message = new ArcanistLintMessage(); $message->setPath($path); $message->setLine($line); $message->setCode($severity); $message->setName($id); $message->setDescription($msg); $message->setSeverity($severity_code); $this->addLintMessage($message); } } } diff --git a/src/lint/linter/ArcanistCpplintLinter.php b/src/lint/linter/ArcanistCpplintLinter.php index e13d3fcb..b2eeca85 100644 --- a/src/lint/linter/ArcanistCpplintLinter.php +++ b/src/lint/linter/ArcanistCpplintLinter.php @@ -1,96 +1,88 @@ getEngine()->getConfigurationManager(); - $options = $config->getConfigFromAnySource('lint.cpplint.options', ''); - - return $options; + return $config->getConfigFromAnySource('lint.cpplint.options', ''); } public function getLintPath() { $config = $this->getEngine()->getConfigurationManager(); $prefix = $config->getConfigFromAnySource('lint.cpplint.prefix'); $bin = $config->getConfigFromAnySource('lint.cpplint.bin', 'cpplint.py'); if ($prefix !== null) { if (!Filesystem::pathExists($prefix.'/'.$bin)) { throw new ArcanistUsageException( "Unable to find cpplint.py binary in a specified directory. Make ". "sure that 'lint.cpplint.prefix' and 'lint.cpplint.bin' keys are ". "set correctly. If you'd rather use a copy of cpplint installed ". "globally, you can just remove these keys from your .arcconfig."); } - $bin = csprintf("%s/%s", $prefix, $bin); - - return $bin; + return csprintf("%s/%s", $prefix, $bin); } // Look for globally installed cpplint.py list($err) = exec_manual('which %s', $bin); if ($err) { throw new ArcanistUsageException( "cpplint.py does not appear to be installed on this system. Install ". "it (e.g., with 'wget \"http://google-styleguide.googlecode.com/". "svn/trunk/cpplint/cpplint.py\"') or configure 'lint.cpplint.prefix' ". "in your .arcconfig to point to the directory where it resides. ". "Also don't forget to chmod a+x cpplint.py!"); } return $bin; } public function lintPath($path) { $bin = $this->getLintPath(); $options = $this->getLintOptions(); $f = new ExecFuture("%C %C -", $bin, $options); $f->write($this->getData($path)); list($err, $stdout, $stderr) = $f->resolve(); if ($err === 2) { throw new Exception("cpplint failed to run correctly:\n".$stderr); } $lines = explode("\n", $stderr); $messages = array(); foreach ($lines as $line) { $line = trim($line); $matches = null; $regex = '/^-:(\d+):\s*(.*)\s*\[(.*)\] \[(\d+)\]$/'; if (!preg_match($regex, $line, $matches)) { continue; } foreach ($matches as $key => $match) { $matches[$key] = trim($match); } $message = new ArcanistLintMessage(); $message->setPath($path); $message->setLine($matches[1]); $message->setCode($matches[3]); $message->setName($matches[3]); $message->setDescription($matches[2]); $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); $this->addLintMessage($message); } } } diff --git a/src/lint/linter/ArcanistFlake8Linter.php b/src/lint/linter/ArcanistFlake8Linter.php index a7ed986a..01ecd49c 100644 --- a/src/lint/linter/ArcanistFlake8Linter.php +++ b/src/lint/linter/ArcanistFlake8Linter.php @@ -1,124 +1,120 @@ getEngine()->getConfigurationManager(); - - $options = $config->getConfigFromAnySource('lint.flake8.options', ''); - - return $options; + return $config->getConfigFromAnySource('lint.flake8.options', ''); } public function getDefaultBinary() { $config = $this->getEngine()->getConfigurationManager(); $prefix = $config->getConfigFromAnySource('lint.flake8.prefix'); $bin = $config->getConfigFromAnySource('lint.flake8.bin', 'flake8'); if ($prefix || ($bin != 'flake8')) { return $prefix.'/'.$bin; } return 'flake8'; } public function getInstallInstructions() { return pht('Install flake8 using `easy_install flake8`.'); } public function supportsReadDataFromStdin() { return true; } public function getReadDataFromStdinFilename() { return '-'; } public function shouldExpectCommandErrors() { return true; } protected function parseLinterOutput($path, $err, $stdout, $stderr) { $lines = phutil_split_lines($stdout, $retain_endings = false); $messages = array(); foreach ($lines as $line) { $matches = null; // stdin:2: W802 undefined name 'foo' # pyflakes // stdin:3:1: E302 expected 2 blank lines, found 1 # pep8 $regexp = '/^(.*?):(\d+):(?:(\d+):)? (\S+) (.*)$/'; if (!preg_match($regexp, $line, $matches)) { continue; } foreach ($matches as $key => $match) { $matches[$key] = trim($match); } $message = new ArcanistLintMessage(); $message->setPath($path); $message->setLine($matches[2]); if (!empty($matches[3])) { $message->setChar($matches[3]); } $message->setCode($matches[4]); $message->setName($this->getLinterName().' '.$matches[3]); $message->setDescription($matches[5]); $message->setSeverity($this->getLintMessageSeverity($matches[4])); $messages[] = $message; } if ($err && !$messages) { return false; } return $messages; } protected function getDefaultMessageSeverity($code) { if (preg_match('/^C/', $code)) { // "C": Cyclomatic complexity return ArcanistLintSeverity::SEVERITY_ADVICE; } else if (preg_match('/^W/', $code)) { // "W": PEP8 Warning return ArcanistLintSeverity::SEVERITY_WARNING; } else { // "E": PEP8 Error // "F": PyFlakes Error return ArcanistLintSeverity::SEVERITY_ERROR; } } protected function getLintCodeFromLinterConfigurationKey($code) { if (!preg_match('/^(E|W|C|F)\d+$/', $code)) { throw new Exception( pht( 'Unrecognized lint message code "%s". Expected a valid flake8 '. 'lint code like "%s", or "%s", or "%s", or "%s".', $code, "E225", "W291", "F811", "C901")); } return $code; } } diff --git a/src/lint/linter/ArcanistPEP8Linter.php b/src/lint/linter/ArcanistPEP8Linter.php index 5b7c9edb..2f67a859 100644 --- a/src/lint/linter/ArcanistPEP8Linter.php +++ b/src/lint/linter/ArcanistPEP8Linter.php @@ -1,129 +1,124 @@ getExecutableCommand()); return $stdout.$this->getCommandFlags(); } public function getDefaultFlags() { // TODO: Warn that all of this is deprecated. - $config = $this->getEngine()->getConfigurationManager(); - $options = $config->getConfigFromAnySource('lint.pep8.options'); - - if ($options === null) { - $options = $this->getConfig('options'); - } - - return $options; + return $config->getConfigFromAnySource( + 'lint.pep8.options', + $this->getConfig('options')); } public function shouldUseInterpreter() { return ($this->getDefaultBinary() !== 'pep8'); } public function getDefaultInterpreter() { return 'python2.6'; } public function getDefaultBinary() { if (Filesystem::binaryExists('pep8')) { return 'pep8'; } $config = $this->getEngine()->getConfigurationManager(); $old_prefix = $config->getConfigFromAnySource('lint.pep8.prefix'); $old_bin = $config->getConfigFromAnySource('lint.pep8.bin'); if ($old_prefix || $old_bin) { // TODO: Deprecation warning. $old_bin = nonempty($old_bin, 'pep8'); return $old_prefix.'/'.$old_bin; } $arc_root = dirname(phutil_get_library_root('arcanist')); return $arc_root.'/externals/pep8/pep8.py'; } public function getInstallInstructions() { return pht('Install PEP8 using `easy_install pep8`.'); } public function shouldExpectCommandErrors() { return true; } protected function parseLinterOutput($path, $err, $stdout, $stderr) { $lines = phutil_split_lines($stdout, $retain_endings = false); $messages = array(); foreach ($lines as $line) { $matches = null; if (!preg_match('/^(.*?):(\d+):(\d+): (\S+) (.*)$/', $line, $matches)) { continue; } foreach ($matches as $key => $match) { $matches[$key] = trim($match); } $message = new ArcanistLintMessage(); $message->setPath($path); $message->setLine($matches[2]); $message->setChar($matches[3]); $message->setCode($matches[4]); $message->setName('PEP8 '.$matches[4]); $message->setDescription($matches[5]); $message->setSeverity($this->getLintMessageSeverity($matches[4])); $messages[] = $message; } if ($err && !$messages) { return false; } return $messages; } protected function getDefaultMessageSeverity($code) { if (preg_match('/^W/', $code)) { return ArcanistLintSeverity::SEVERITY_WARNING; } else { // TODO: Once severities/.arclint are more usable, restore this to // "ERROR". // return ArcanistLintSeverity::SEVERITY_ERROR; return ArcanistLintSeverity::SEVERITY_WARNING; } } protected function getLintCodeFromLinterConfigurationKey($code) { if (!preg_match('/^(E|W)\d+$/', $code)) { throw new Exception( pht( 'Unrecognized lint message code "%s". Expected a valid PEP8 '. 'lint code like "%s" or "%s".', $code, "E101", "W291")); } return $code; } } diff --git a/src/lint/linter/ArcanistPhpcsLinter.php b/src/lint/linter/ArcanistPhpcsLinter.php index acb53b4b..efbb3fca 100644 --- a/src/lint/linter/ArcanistPhpcsLinter.php +++ b/src/lint/linter/ArcanistPhpcsLinter.php @@ -1,125 +1,120 @@ getEngine()->getConfigurationManager(); $options = $config->getConfigFromAnySource('lint.phpcs.options'); $standard = $config->getConfigFromAnySource('lint.phpcs.standard'); $options .= !empty($standard) ? ' --standard=' . $standard : ''; return $options; } public function getDefaultBinary() { // TODO: Deprecation warnings. $config = $this->getEngine()->getConfigurationManager(); - $bin = $config->getConfigFromAnySource('lint.phpcs.bin'); - if ($bin) { - return $bin; - } - - return 'phpcs'; + return $config->getConfigFromAnySource('lint.phpcs.bin', 'phpcs'); } public function shouldExpectCommandErrors() { return true; } protected function parseLinterOutput($path, $err, $stdout, $stderr) { // NOTE: Some version of PHPCS after 1.4.6 stopped printing a valid, empty // XML document to stdout in the case of no errors. If PHPCS exits with // error 0, just ignore output. if (!$err) { return array(); } $report_dom = new DOMDocument(); $ok = @$report_dom->loadXML($stdout); if (!$ok) { return false; } $files = $report_dom->getElementsByTagName('file'); $messages = array(); foreach ($files as $file) { foreach ($file->childNodes as $child) { if (!($child instanceof DOMElement)) { continue; } if ($child->tagName == 'error') { $prefix = 'E'; } else { $prefix = 'W'; } $code = 'PHPCS.'.$prefix.'.'.$child->getAttribute('source'); $message = new ArcanistLintMessage(); $message->setPath($path); $message->setLine($child->getAttribute('line')); $message->setChar($child->getAttribute('column')); $message->setCode($code); $message->setDescription($child->nodeValue); $message->setSeverity($this->getLintMessageSeverity($code)); $messages[] = $message; } } return $messages; } protected function getDefaultMessageSeverity($code) { if (preg_match('/^PHPCS\\.W\\./', $code)) { return ArcanistLintSeverity::SEVERITY_WARNING; } else { return ArcanistLintSeverity::SEVERITY_ERROR; } } protected function getLintCodeFromLinterConfigurationKey($code) { if (!preg_match('/^PHPCS\\.(E|W)\\./', $code)) { throw new Exception( "Invalid severity code '{$code}', should begin with 'PHPCS.'."); } return $code; } } diff --git a/src/lint/linter/ArcanistPhutilLibraryLinter.php b/src/lint/linter/ArcanistPhutilLibraryLinter.php index d48c9dfc..f67b8fe4 100644 --- a/src/lint/linter/ArcanistPhutilLibraryLinter.php +++ b/src/lint/linter/ArcanistPhutilLibraryLinter.php @@ -1,203 +1,199 @@ 'Unknown Symbol', self::LINT_DUPLICATE_SYMBOL => 'Duplicate Symbol', self::LINT_ONE_CLASS_PER_FILE => 'One Class Per File', ); } public function getLinterName() { return 'PHL'; } - public function getLintSeverityMap() { - return array(); - } - 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( 'php %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}'."); } } } } $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."); } } } } } private function raiseLintInLibrary($library, $path, $offset, $code, $desc) { $root = phutil_get_library_root($library); $this->activePath = $root.'/'.$path; $this->raiseLintAtOffset($offset, $code, $desc); } public function lintPath($path) { return; } public function getCacheGranularity() { return self::GRANULARITY_GLOBAL; } } diff --git a/src/lint/linter/ArcanistPyFlakesLinter.php b/src/lint/linter/ArcanistPyFlakesLinter.php index da6a10b2..25674364 100644 --- a/src/lint/linter/ArcanistPyFlakesLinter.php +++ b/src/lint/linter/ArcanistPyFlakesLinter.php @@ -1,112 +1,99 @@ getEngine()->getConfigurationManager(); $pyflakes_path = $config->getConfigFromAnySource('lint.pyflakes.path'); $pyflakes_prefix = $config->getConfigFromAnySource('lint.pyflakes.prefix'); // Default to just finding pyflakes in the users path $pyflakes_bin = 'pyflakes'; $python_path = array(); // If a pyflakes path was specified, then just use that as the // pyflakes binary and assume that the libraries will be imported // correctly. // // If no pyflakes path was specified and a pyflakes prefix was // specified, then use the binary from this prefix and add it to // the PYTHONPATH environment variable so that the libs are imported // correctly. This is useful when pyflakes is installed into a // non-default location. if ($pyflakes_path !== null) { $pyflakes_bin = $pyflakes_path; } else if ($pyflakes_prefix !== null) { $pyflakes_bin = $pyflakes_prefix.'/bin/pyflakes'; $python_path[] = $pyflakes_prefix.'/lib/python2.7/site-packages'; $python_path[] = $pyflakes_prefix.'/lib/python2.7/dist-packages'; $python_path[] = $pyflakes_prefix.'/lib/python2.6/site-packages'; $python_path[] = $pyflakes_prefix.'/lib/python2.6/dist-packages'; } $python_path[] = ''; $python_path = implode(':', $python_path); $options = $this->getPyFlakesOptions(); $f = new ExecFuture( '/usr/bin/env PYTHONPATH=%s$PYTHONPATH %s %C', $python_path, $pyflakes_bin, $options); $f->write($this->getData($path)); try { list($stdout, $_) = $f->resolvex(); } catch (CommandException $e) { // PyFlakes will return an exit code of 1 if warnings/errors // are found but print nothing to stderr in this case. Therefore, // if we see any output on stderr or a return code other than 1 or 0, // pyflakes failed. if ($e->getError() !== 1 || $e->getStderr() !== '') { throw $e; } else { $stdout = $e->getStdout(); } } $lines = explode("\n", $stdout); $messages = array(); foreach ($lines as $line) { $matches = null; if (!preg_match('/^(.*?):(\d+): (.*)$/', $line, $matches)) { continue; } foreach ($matches as $key => $match) { $matches[$key] = trim($match); } $severity = ArcanistLintSeverity::SEVERITY_WARNING; $description = $matches[3]; $error_regexp = '/(^undefined|^duplicate|before assignment$)/'; if (preg_match($error_regexp, $description)) { $severity = ArcanistLintSeverity::SEVERITY_ERROR; } $message = new ArcanistLintMessage(); $message->setPath($path); $message->setLine($matches[2]); $message->setCode($this->getLinterName()); $message->setDescription($description); $message->setSeverity($severity); $this->addLintMessage($message); } } } diff --git a/src/lint/linter/ArcanistPyLintLinter.php b/src/lint/linter/ArcanistPyLintLinter.php index 6bdca57f..c0fc82e3 100644 --- a/src/lint/linter/ArcanistPyLintLinter.php +++ b/src/lint/linter/ArcanistPyLintLinter.php @@ -1,255 +1,243 @@ getEngine()->getConfigurationManager(); $error_regexp = $config->getConfigFromAnySource('lint.pylint.codes.error'); $warning_regexp = $config->getConfigFromAnySource('lint.pylint.codes.warning'); $advice_regexp = $config->getConfigFromAnySource('lint.pylint.codes.advice'); if (!$error_regexp && !$warning_regexp && !$advice_regexp) { throw new ArcanistUsageException( "You are invoking the PyLint linter but have not configured any of ". "'lint.pylint.codes.error', 'lint.pylint.codes.warning', or ". "'lint.pylint.codes.advice'. Consult the documentation for ". "ArcanistPyLintLinter."); } $code_map = array( ArcanistLintSeverity::SEVERITY_ERROR => $error_regexp, ArcanistLintSeverity::SEVERITY_WARNING => $warning_regexp, ArcanistLintSeverity::SEVERITY_ADVICE => $advice_regexp, ); foreach ($code_map as $sev => $codes) { if ($codes === null) { continue; } if (!is_array($codes)) { $codes = array($codes); } foreach ($codes as $code_re) { if (preg_match("/{$code_re}/", $code)) { return $sev; } } } // If the message code doesn't match any of the provided regex's, // then just disable it. return ArcanistLintSeverity::SEVERITY_DISABLED; } private function getPyLintPath() { $pylint_bin = "pylint"; // Use the PyLint prefix specified in the config file $config = $this->getEngine()->getConfigurationManager(); $prefix = $config->getConfigFromAnySource('lint.pylint.prefix'); if ($prefix !== null) { $pylint_bin = $prefix."/bin/".$pylint_bin; } if (!Filesystem::pathExists($pylint_bin)) { list($err) = exec_manual('which %s', $pylint_bin); if ($err) { throw new ArcanistUsageException( "PyLint does not appear to be installed on this system. Install it ". "(e.g., with 'sudo easy_install pylint') or configure ". "'lint.pylint.prefix' in your .arcconfig to point to the directory ". "where it resides."); } } return $pylint_bin; } private function getPyLintPythonPath() { // Get non-default install locations for pylint and its dependencies // libraries. $config = $this->getEngine()->getConfigurationManager(); $prefixes = array( $config->getConfigFromAnySource('lint.pylint.prefix'), $config->getConfigFromAnySource('lint.pylint.logilab_astng.prefix'), $config->getConfigFromAnySource('lint.pylint.logilab_common.prefix'), ); // Add the libraries to the python search path $python_path = array(); foreach ($prefixes as $prefix) { if ($prefix !== null) { $python_path[] = $prefix.'/lib/python2.7/site-packages'; $python_path[] = $prefix.'/lib/python2.7/dist-packages'; $python_path[] = $prefix.'/lib/python2.6/site-packages'; $python_path[] = $prefix.'/lib/python2.6/dist-packages'; } } $working_copy = $this->getEngine()->getWorkingCopy(); $config_paths = $config->getConfigFromAnySource('lint.pylint.pythonpath'); if ($config_paths !== null) { foreach ($config_paths as $config_path) { if ($config_path !== null) { $python_path[] = Filesystem::resolvePath($config_path, $working_copy->getProjectRoot()); } } } $python_path[] = ''; return implode(":", $python_path); } private function getPyLintOptions() { // '-rn': don't print lint report/summary at end // '-iy': show message codes for lint warnings/errors $options = array('-rn', '-iy'); $working_copy = $this->getEngine()->getWorkingCopy(); $config = $this->getEngine()->getConfigurationManager(); // Specify an --rcfile, either absolute or relative to the project root. // Stupidly, the command line args above are overridden by rcfile, so be // careful. $rcfile = $config->getConfigFromAnySource('lint.pylint.rcfile'); if ($rcfile !== null) { $rcfile = Filesystem::resolvePath( $rcfile, $working_copy->getProjectRoot()); $options[] = csprintf('--rcfile=%s', $rcfile); } // Add any options defined in the config file for PyLint $config_options = $config->getConfigFromAnySource('lint.pylint.options'); if ($config_options !== null) { $options = array_merge($options, $config_options); } return implode(" ", $options); } - public function willLintPaths(array $paths) { - return; - } - public function getLinterName() { return 'PyLint'; } - public function getLintSeverityMap() { - return array(); - } - - public function getLintNameMap() { - return array(); - } - public function lintPath($path) { $pylint_bin = $this->getPyLintPath(); $python_path = $this->getPyLintPythonPath(); $options = $this->getPyLintOptions(); $path_on_disk = $this->getEngine()->getFilePathOnDisk($path); try { list($stdout, $_) = execx( '/usr/bin/env PYTHONPATH=%s$PYTHONPATH %s %C %s', $python_path, $pylint_bin, $options, $path_on_disk); } catch (CommandException $e) { if ($e->getError() == 32) { // According to ##man pylint## the exit status of 32 means there was a // usage error. That's bad, so actually exit abnormally. throw $e; } else { // The other non-zero exit codes mean there were messages issued, // which is expected, so don't exit. $stdout = $e->getStdout(); } } $lines = explode("\n", $stdout); $messages = array(); foreach ($lines as $line) { $matches = null; if (!preg_match( '/([A-Z]\d+): *(\d+)(?:|,\d*): *(.*)$/', $line, $matches)) { continue; } foreach ($matches as $key => $match) { $matches[$key] = trim($match); } $message = new ArcanistLintMessage(); $message->setPath($path); $message->setLine($matches[2]); $message->setCode($matches[1]); $message->setName($this->getLinterName()." ".$matches[1]); $message->setDescription($matches[3]); $message->setSeverity($this->getMessageCodeSeverity($matches[1])); $this->addLintMessage($message); } } }