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.flags` 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; | |||||
} | |||||
} |