diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -53,6 +53,7 @@ 'ArcanistCapabilityNotSupportedException' => 'workflow/exception/ArcanistCapabilityNotSupportedException.php', 'ArcanistCastSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCastSpacingXHPASTLinterRule.php', 'ArcanistCastSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistCastSpacingXHPASTLinterRuleTestCase.php', + 'ArcanistCheckstyleLinter' => 'lint/linter/ArcanistCheckstyleLinter.php', 'ArcanistCheckstyleXMLLintRenderer' => 'lint/renderer/ArcanistCheckstyleXMLLintRenderer.php', 'ArcanistChmodLinter' => 'lint/linter/ArcanistChmodLinter.php', 'ArcanistChmodLinterTestCase' => 'lint/linter/__tests__/ArcanistChmodLinterTestCase.php', @@ -451,6 +452,7 @@ 'ArcanistCapabilityNotSupportedException' => 'Exception', 'ArcanistCastSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistCastSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistCheckstyleLinter' => 'ArcanistExternalLinter', 'ArcanistCheckstyleXMLLintRenderer' => 'ArcanistLintRenderer', 'ArcanistChmodLinter' => 'ArcanistLinter', 'ArcanistChmodLinterTestCase' => 'ArcanistLinterTestCase', diff --git a/src/lint/linter/ArcanistCheckstyleLinter.php b/src/lint/linter/ArcanistCheckstyleLinter.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/ArcanistCheckstyleLinter.php @@ -0,0 +1,135 @@ +getExecutableCommand()); + + $matches = array(); + $regex = '/^Checkstyle version: (?P\d+\.\d+)$/'; + if (preg_match($regex, $stdout, $matches)) { + return $matches['version']; + } + return false; + } + + public function getInstallInstructions() { + return pht('Ensure java is configured as interpreter and '. + 'the checkstyle jar is configured as the binary. If you need to pass '. + 'additional additional JVM arguments include them with the '. + '`interpreter` field. Use the `flags` field to configure checkstyle '. + 'arguments, including the `-c my_styles.xml` for the styles to verify.'); + } + + protected function getMandatoryFlags() { + return array('-f', 'xml'); + } + + public function shouldExpectCommandErrors() { + return false; + } + + public function shouldUseInterpreter() { + return true; + } + + public function getDefaultInterpreter() { + return 'java'; + } + + public function getDefaultBinary() { + return 'checkstyle.jar'; + } + + protected function parseLinterOutput($path, $err, $stdout, $stderr) { + $dom = new DOMDocument(); + $ok = @$dom->loadXML($stdout); + + if (!$ok) { + return false; + } + + $files = $dom->getElementsByTagName('file'); + $messages = array(); + foreach ($files as $file) { + $errors = $file->getElementsByTagName('error'); + foreach ($errors as $error) { + $message = new ArcanistLintMessage(); + $message->setPath($file->getAttribute('name')); + $message->setLine($error->getAttribute('line')); + $message->setCode($this->getLinterName()); + + // source is the module's fully-qualified classname + // attempt to simplify it for readability + $source = $error->getAttribute('source'); + $simple_classname_matches = array(); + $simple_classname_regex = '/.*\.()$/'; + if (preg_match($simple_classname_regex, $source, + $simple_classname_matches)) { + $source = $simple_classname_matches['simplename']; + } + $message->setName($source); + + // checkstyle's XMLLogger escapes these five characters + $description = $error->getAttribute('message'); + $description = str_replace( + ['<', '>', ''', '"', '&'], + ['<', '>', '\'', '"', '&'], + $description); + $message->setDescription($description); + + $column = $error->getAttribute('column'); + if ($column) { + $message->setChar($column); + } + + $severity = $error->getAttribute('severity'); + switch ($severity) { + case 'error': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ERROR); + break; + case 'warning': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); + break; + case 'info': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ADVICE); + break; + case 'ignore': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_DISABLED); + break; + + // The above four are the only valid checkstyle severities, + // this is for completion as well as preparing for future severities + default: + $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); + break; + } + + $messages[] = $message; + } + } + + return $messages; + } +}