Changeset View
Changeset View
Standalone View
Standalone View
src/lint/linter/ArcanistCheckstyleLinter.php
- This file was added.
| <?php | |||||
| /** | |||||
| * This linter invokes checkstyle for verifying java coding standards. | |||||
| * The checkstyle report is given over stdout as a simple XML document | |||||
| * which maps fairly easily to ArcanistLintMessage. | |||||
| */ | |||||
| class ArcanistCheckstyleLinter extends ArcanistExternalLinter { | |||||
| private $simplifySourceClassname = false; | |||||
| public function getInfoName() { | |||||
| return 'Java checkstyle linter'; | |||||
| } | |||||
| public function getLinterName() { | |||||
| return 'CHECKSTYLE'; | |||||
| } | |||||
| public function getInfoURI() { | |||||
| return 'http://checkstyle.sourceforge.net'; | |||||
| } | |||||
| public function getInfoDescription() { | |||||
| return pht('Use `%s` to perform static analysis on Java code.', | |||||
| 'checkstyle'); | |||||
| } | |||||
| public function getLinterConfigurationName() { | |||||
| return 'checkstyle'; | |||||
| } | |||||
| public function getLinterConfigurationOptions() { | |||||
| $options = array( | |||||
| 'checkstyle.simplify-source-classname' => array( | |||||
| 'type' => 'optional bool', | |||||
| 'help' => pht( | |||||
| 'Lint messages reported by checkstyle indicate their source '. | |||||
| 'as the fully-qualified classname of the respective module. '. | |||||
| 'Set this value to true to simplify the classname, such as: '. | |||||
| '`com.company.pkg.IndentationCheck` => `IndentationCheck`.'), | |||||
| ), | |||||
| ); | |||||
| return $options + parent::getLinterConfigurationOptions(); | |||||
| } | |||||
| public function setLinterConfigurationValue($key, $value) { | |||||
| switch ($key) { | |||||
| case 'checkstyle.simplify-source-classname': | |||||
| $this->setSimplifySourceClassname($value); | |||||
| return; | |||||
| } | |||||
| return parent::setLinterConfigurationValue($key, $value); | |||||
| } | |||||
| public function setSimplifySourceClassname($simplify_source_classname) { | |||||
| $this->simplifySourceClassname = $simplify_source_classname; | |||||
| return $this; | |||||
| } | |||||
| public function getVersion() { | |||||
| list($stdout) = execx('%C -v', $this->getExecutableCommand()); | |||||
| $matches = array(); | |||||
| $regex = '/^Checkstyle version: (?P<version>\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 getDefaultBinary() { | |||||
| return 'checkstyle.jar'; | |||||
| } | |||||
| public function getDefaultInterpreter() { | |||||
| return 'java'; | |||||
| } | |||||
| protected function getMandatoryInterpreterFlags() { | |||||
| // since the binary is the checkstyle jar, this flag must | |||||
| // always be passed to the interpreter, and is guaranteed to be provided | |||||
| // as the first flag before the binary jar on the command line | |||||
| return array('-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'); | |||||
| if ($this->simplifySourceClassname) { | |||||
| $source = idx(array_slice(explode('.', $source), -1), 0); | |||||
| } | |||||
| $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; | |||||
| } | |||||
| } | |||||