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 @@ -38,6 +38,8 @@ 'ArcanistCloseWorkflow' => 'workflow/ArcanistCloseWorkflow.php', 'ArcanistClosureLinter' => 'lint/linter/ArcanistClosureLinter.php', 'ArcanistClosureLinterTestCase' => 'lint/linter/__tests__/ArcanistClosureLinterTestCase.php', + 'ArcanistCoffeeLintLinter' => 'lint/linter/ArcanistCoffeeLintLinter.php', + 'ArcanistCoffeeLintLinterTestCase' => 'lint/linter/__tests__/ArcanistCoffeeLintLinterTestCase.php', 'ArcanistCommentRemover' => 'parser/ArcanistCommentRemover.php', 'ArcanistCommentRemoverTestCase' => 'parser/__tests__/ArcanistCommentRemoverTestCase.php', 'ArcanistCommitWorkflow' => 'workflow/ArcanistCommitWorkflow.php', @@ -226,6 +228,8 @@ 'ArcanistCloseWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistClosureLinter' => 'ArcanistExternalLinter', 'ArcanistClosureLinterTestCase' => 'ArcanistArcanistLinterTestCase', + 'ArcanistCoffeeLintLinter' => 'ArcanistExternalLinter', + 'ArcanistCoffeeLintLinterTestCase' => 'ArcanistArcanistLinterTestCase', 'ArcanistCommentRemoverTestCase' => 'ArcanistTestCase', 'ArcanistCommitWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistConduitLinter' => 'ArcanistLinter', diff --git a/src/lint/linter/ArcanistCoffeeLintLinter.php b/src/lint/linter/ArcanistCoffeeLintLinter.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/ArcanistCoffeeLintLinter.php @@ -0,0 +1,152 @@ +getExecutableCommand()); + + $matches = array(); + if (preg_match('/^(?P\d+\.\d+\.\d+)$/', $stdout, $matches)) { + return $matches['version']; + } else { + return false; + } + } + + public function getInstallInstructions() { + return pht('Install CoffeeLint using `npm install -g coffeelint`.'); + } + + public function shouldExpectCommandErrors() { + return true; + } + + public function supportsReadDataFromStdin() { + return true; + } + + public function getReadDataFromStdinFilename() { + return '--stdin'; + } + + protected function getMandatoryFlags() { + $options = array( + '--checkstyle', + '--nocolor', + '--quiet', + ); + + if ($this->config) { + $options[] = '--file='.$this->config; + } + + return $options; + } + + public function getLinterConfigurationOptions() { + $options = array( + 'coffeelint.config' => array( + 'type' => 'optional string', + 'help' => pht('A custom configuration file.'), + ), + ); + + return $options + parent::getLinterConfigurationOptions(); + } + + public function setLinterConfigurationValue($key, $value) { + switch ($key) { + case 'coffeelint.config': + $this->config = $value; + return; + } + + return parent::setLinterConfigurationValue($key, $value); + } + + protected function parseLinterOutput($path, $err, $stdout, $stderr) { + $report_dom = new DOMDocument(); + $ok = @$report_dom->loadXML($stdout); + + if (!$ok) { + return false; + } + + $files = $report_dom->getElementsByTagName('file'); + $messages = array(); + foreach ($files as $file) { + foreach ($file->getElementsByTagName('error') as $error) { + + // Column number is not provided in the output. + // See https://github.com/clutchski/coffeelint/issues/87 + + $message = new ArcanistLintMessage(); + $message->setPath($path); + $message->setLine($error->getAttribute('line')); + $message->setCode($this->getLinterName()); + $message->setDescription(preg_replace( + '/; context: .*$/', + '.', + $error->getAttribute('message'))); + + switch ($error->getAttribute('severity')) { + case 'warning': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_WARNING); + break; + + case 'error': + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ERROR); + break; + + default: + $message->setSeverity(ArcanistLintSeverity::SEVERITY_ADVICE); + break; + } + + $messages[] = $message; + } + } + + return $messages; + } + + protected function getLintCodeFromLinterConfigurationKey($code) { + + // NOTE: We can't figure out which rule generated each message, so we + // can not customize severities. + + throw new Exception( + pht( + "CoffeeLint does not currently support custom severity levels, ". + "because rules can't be identified from messages in output.")); + } + +} diff --git a/src/lint/linter/__tests__/ArcanistCoffeeLintLinterTestCase.php b/src/lint/linter/__tests__/ArcanistCoffeeLintLinterTestCase.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/ArcanistCoffeeLintLinterTestCase.php @@ -0,0 +1,12 @@ +executeTestsInDirectory( + dirname(__FILE__).'/coffeelint/', + new ArcanistCoffeeLintLinter()); + } + +} diff --git a/src/lint/linter/__tests__/coffeelint/camel_case_classes.lint-test b/src/lint/linter/__tests__/coffeelint/camel_case_classes.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/camel_case_classes.lint-test @@ -0,0 +1,3 @@ +class boaConstrictor +~~~~~~~~~~ +error:1: diff --git a/src/lint/linter/__tests__/coffeelint/duplicate_key.lint-test b/src/lint/linter/__tests__/coffeelint/duplicate_key.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/duplicate_key.lint-test @@ -0,0 +1,18 @@ +class SomeThing + getConfig: -> + one = 1 + one = 5 + @config = + keyA: one + keyB: one + keyA: 2 + getConfig: -> + @config = + foo: 1 + + @getConfig: -> + config = + foo: 1 +~~~~~~~~~~ +error:8: +error:9: diff --git a/src/lint/linter/__tests__/coffeelint/indentation.lint-test b/src/lint/linter/__tests__/coffeelint/indentation.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/indentation.lint-test @@ -0,0 +1,7 @@ +twoSpaces = () -> + fourSpaces = () -> + eightSpaces = () -> + 'this is valid CoffeeScript' +~~~~~~~~~~ +error:3: +error:4: diff --git a/src/lint/linter/__tests__/coffeelint/max_line_length.lint-test b/src/lint/linter/__tests__/coffeelint/max_line_length.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/max_line_length.lint-test @@ -0,0 +1,3 @@ +#-------------------------------------------------------------------------------- +~~~~~~~~~~ +error:1: diff --git a/src/lint/linter/__tests__/coffeelint/no_backticks.lint-test b/src/lint/linter/__tests__/coffeelint/no_backticks.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/no_backticks.lint-test @@ -0,0 +1,3 @@ +`with(document) alert(height);` +~~~~~~~~~~ +error:1: diff --git a/src/lint/linter/__tests__/coffeelint/no_debugger.lint-test b/src/lint/linter/__tests__/coffeelint/no_debugger.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/no_debugger.lint-test @@ -0,0 +1,3 @@ +debugger +~~~~~~~~~~ +warning:1: diff --git a/src/lint/linter/__tests__/coffeelint/no_tabs.lint-test b/src/lint/linter/__tests__/coffeelint/no_tabs.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/no_tabs.lint-test @@ -0,0 +1,6 @@ +x = () -> + y = () -> + return 1234 +~~~~~~~~~~ +error:2: +error:3: diff --git a/src/lint/linter/__tests__/coffeelint/no_throwing_strings.lint-test b/src/lint/linter/__tests__/coffeelint/no_throwing_strings.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/no_throwing_strings.lint-test @@ -0,0 +1,3 @@ +throw "i made a boo boo" +~~~~~~~~~~ +error:1: diff --git a/src/lint/linter/__tests__/coffeelint/no_trailing_semicolons.lint-test b/src/lint/linter/__tests__/coffeelint/no_trailing_semicolons.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/no_trailing_semicolons.lint-test @@ -0,0 +1,3 @@ +alert('end of line'); +~~~~~~~~~~ +error:1: diff --git a/src/lint/linter/__tests__/coffeelint/no_trailing_whitespace.lint-test b/src/lint/linter/__tests__/coffeelint/no_trailing_whitespace.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/coffeelint/no_trailing_whitespace.lint-test @@ -0,0 +1,4 @@ +x = 1234 +y = 1 +~~~~~~~~~~ +error:1: