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 @@ -154,6 +154,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', @@ -335,6 +337,8 @@ 'ArcanistRepositoryAPIMiscTestCase' => 'ArcanistTestCase', 'ArcanistRepositoryAPIStateTestCase' => 'ArcanistTestCase', 'ArcanistRevertWorkflow' => 'ArcanistWorkflow', + 'ArcanistRuboCopLinter' => 'ArcanistExternalLinter', + 'ArcanistRuboCopLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistRubyLinter' => 'ArcanistExternalLinter', 'ArcanistRubyLinterTestCase' => 'ArcanistArcanistLinterTestCase', '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,127 @@ +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 `gem install rubocop`.'); + } + + public function shouldExpectCommandErrors() { + return true; + } + + public function supportsReadDataFromStdin() { + return false; + } + + protected function getMandatoryFlags() { + $options = array( + '--format=json', + '--force-exclusion', + '--no-color', + ); + + 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 = new ArcanistLintMessage(); + + $message->setPath($file['path']); + $message->setDescription($offense['message']); + + $message->setLine($offense['location']['line']); + $message->setChar($offense['location']['column']); + + switch ($offense['severity']) { + case 'warning': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); + break; + case 'error': + case 'fatal': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ERROR); + break; + default: + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ADVICE); + break; + } + + $message->setName($this->getLinterName()); + $message->setCode($offense['cop_name']); + + $messages[] = $message; + } + } + + return $messages; + } +} 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,12 @@ +executeTestsInDirectory( + dirname(__FILE__).'/rubocop/', + new ArcanistRuboCopLinter()); + } + +} 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 +~~~~~~~~~~ +advice: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