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 @@ -161,6 +161,8 @@ 'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php', 'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php', 'ArcanistRevertWorkflow' => 'workflow/ArcanistRevertWorkflow.php', + 'ArcanistRuboCopLinter' => 'lint/linter/ArcanistRuboCopLinter.php', + 'ArcanistRuboCopLinterTestCase' => 'lint/linter/__tests__/ArcanistRuboCopLinterTestCase.php', 'ArcanistRubyLinter' => 'lint/linter/ArcanistRubyLinter.php', 'ArcanistRubyLinterTestCase' => 'lint/linter/__tests__/ArcanistRubyLinterTestCase.php', 'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php', @@ -347,6 +349,8 @@ 'ArcanistRepositoryAPIMiscTestCase' => 'ArcanistTestCase', 'ArcanistRepositoryAPIStateTestCase' => 'ArcanistTestCase', 'ArcanistRevertWorkflow' => 'ArcanistWorkflow', + 'ArcanistRuboCopLinter' => 'ArcanistExternalLinter', + 'ArcanistRuboCopLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistRubyLinter' => 'ArcanistExternalLinter', 'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistScriptAndRegexLinter' => 'ArcanistLinter', diff --git a/src/lint/linter/ArcanistRuboCopLinter.php b/src/lint/linter/ArcanistRuboCopLinter.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/ArcanistRuboCopLinter.php @@ -0,0 +1,120 @@ +getExecutableCommand()); + + $matches = array(); + if (preg_match('/^(?P\d+\.\d+\.\d+)$/', $stdout, $matches)) { + return $matches['version']; + } else { + return false; + } + } + + public function getInstallInstructions() { + return pht('Install RuboCop using `%s`.', 'gem install rubocop'); + } + + protected function getMandatoryFlags() { + $options = array( + '--format=json', + ); + + if ($this->config) { + $options[] = '--config='.$this->config; + } + + return $options; + } + + public function getLinterConfigurationOptions() { + $options = array( + 'rubocop.config' => array( + 'type' => 'optional string', + 'help' => pht('A custom configuration file.'), + ), + ); + + return $options + parent::getLinterConfigurationOptions(); + } + + public function setLinterConfigurationValue($key, $value) { + switch ($key) { + case 'rubocop.config': + $this->config = $value; + return; + } + + return parent::setLinterConfigurationValue($key, $value); + } + + protected function parseLinterOutput($path, $err, $stdout, $stderr) { + $results = phutil_json_decode($stdout); + $messages = array(); + + foreach ($results['files'] as $file) { + foreach ($file['offenses'] as $offense) { + $message = id(new ArcanistLintMessage()) + ->setPath($file['path']) + ->setDescription($offense['message']) + ->setLine($offense['location']['line']) + ->setChar($offense['location']['column']) + ->setSeverity($this->getLintMessageSeverity($offense['severity'])) + ->setName($this->getLinterName()) + ->setCode($offense['cop_name']); + $messages[] = $message; + } + } + + return $messages; + } + + /* + Take the string from RuboCop's severity terminology and return an + ArcanistLintSeverity + */ + protected function getDefaultMessageSeverity($code) { + switch ($code) { + case 'convention': + case 'refactor': + case 'warning': + return ArcanistLintSeverity::SEVERITY_WARNING; + case 'error': + case 'fatal': + return ArcanistLintSeverity::SEVERITY_ERROR; + default: + return ArcanistLintSeverity::SEVERITY_ADVICE; + } + } + +} diff --git a/src/lint/linter/__tests__/ArcanistRuboCopLinterTestCase.php b/src/lint/linter/__tests__/ArcanistRuboCopLinterTestCase.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/ArcanistRuboCopLinterTestCase.php @@ -0,0 +1,10 @@ +executeTestsInDirectory(dirname(__FILE__).'/rubocop/'); + } + +} diff --git a/src/lint/linter/__tests__/rubocop/convention.lint-test b/src/lint/linter/__tests__/rubocop/convention.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/rubocop/convention.lint-test @@ -0,0 +1,4 @@ +def hello() +end +~~~~~~~~~~ +warning:1:10 diff --git a/src/lint/linter/__tests__/rubocop/error.lint-test b/src/lint/linter/__tests__/rubocop/error.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/rubocop/error.lint-test @@ -0,0 +1,4 @@ +def hello + puts 'world' +~~~~~~~~~~ +error:3:1 diff --git a/src/lint/linter/__tests__/rubocop/no_errors.lint-test b/src/lint/linter/__tests__/rubocop/no_errors.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/rubocop/no_errors.lint-test @@ -0,0 +1,4 @@ +def hello + puts 'hello world' +end +~~~~~~~~~~ diff --git a/src/lint/linter/__tests__/rubocop/warning.lint-test b/src/lint/linter/__tests__/rubocop/warning.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/rubocop/warning.lint-test @@ -0,0 +1,5 @@ +def hello(unused) + puts 'hi' +end +~~~~~~~~~ +warning:1:11