Page MenuHomePhabricator

D15124.diff
No OneTemporary

D15124.diff

Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -126,6 +126,8 @@
'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase.php',
'ArcanistDynamicDefineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php',
'ArcanistDynamicDefineXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDynamicDefineXHPASTLinterRuleTestCase.php',
+ 'ArcanistESLintLinter' => 'lint/linter/ArcanistESLintLinter.php',
+ 'ArcanistESLintLinterTestCase' => 'lint/linter/__tests__/ArcanistESLintLinterTestCase.php',
'ArcanistElseIfUsageXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php',
'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistElseIfUsageXHPASTLinterRuleTestCase.php',
'ArcanistEmptyFileXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php',
@@ -540,6 +542,8 @@
'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistDynamicDefineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDynamicDefineXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
+ 'ArcanistESLintLinter' => 'ArcanistExternalLinter',
+ 'ArcanistESLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistElseIfUsageXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistEmptyFileXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
Index: src/lint/linter/ArcanistESLintLinter.php
===================================================================
--- /dev/null
+++ src/lint/linter/ArcanistESLintLinter.php
@@ -0,0 +1,149 @@
+<?php
+
+/**
+ * Uses ESLint to detect errors and potential problems in JavaScript code.
+ */
+final class ArcanistESLintLinter extends ArcanistExternalLinter {
+
+ private $eslintignore;
+ private $eslintrc;
+
+ public function getInfoName() {
+ return 'Pluggable JavaScript linter';
+ }
+
+ public function getInfoURI() {
+ return 'http://eslint.org/';
+ }
+
+ public function getInfoDescription() {
+ return pht(
+ 'Use `%s` to detect issues with JavaScript source files.',
+ 'eslint');
+ }
+
+ public function getLinterName() {
+ return 'ESLint';
+ }
+
+ public function getLinterConfigurationName() {
+ return 'eslint';
+ }
+
+ protected function getDefaultMessageSeverity($code) {
+ if ($code == 1) {
+ return ArcanistLintSeverity::SEVERITY_WARNING;
+ } else {
+ return ArcanistLintSeverity::SEVERITY_ERROR;
+ }
+ }
+
+ public function getDefaultBinary() {
+ return 'eslint';
+ }
+
+ public function getVersion() {
+ list($stdout, $stderr) = execx(
+ '%C --version',
+ $this->getExecutableCommand());
+
+ $matches = array();
+ $regex = '/^v(?P<version>\d+\.\d+\.\d+)$/';
+ if (preg_match($regex, $stdout, $matches)) {
+ return $matches['version'];
+ } else {
+ return false;
+ }
+ }
+
+ public function getInstallInstructions() {
+ return pht('Install ESLint using `%s`.', 'npm install -g eslint');
+ }
+
+ protected function getMandatoryFlags() {
+ $options = array();
+
+ $options[] = '--format=json';
+
+ if ($this->eslintrc) {
+ $options[] = '--config='.$this->eslintrc;
+ }
+
+ if ($this->eslintignore) {
+ $options[] = '--ignore-path='.$this->eslintignore;
+ }
+
+ return $options;
+ }
+
+ public function getLinterConfigurationOptions() {
+ $options = array(
+ 'eslint.eslintignore' => array(
+ 'type' => 'optional string',
+ 'help' => pht('Pass in a custom eslintignore file path.'),
+ ),
+ 'eslint.eslintrc' => array(
+ 'type' => 'optional string',
+ 'help' => pht('Custom configuration file.'),
+ ),
+ );
+
+ return $options + parent::getLinterConfigurationOptions();
+ }
+
+ public function setLinterConfigurationValue($key, $value) {
+ switch ($key) {
+ case 'eslint.eslintignore':
+ $this->eslintignore = $value;
+ return;
+
+ case 'eslint.eslintrc':
+ $this->eslintrc = $value;
+ return;
+ }
+
+ return parent::setLinterConfigurationValue($key, $value);
+ }
+
+ protected function parseLinterOutput($path, $err, $stdout, $stderr) {
+ $errors = null;
+ try {
+ $errors = phutil_json_decode($stdout);
+ } catch (PhutilJSONParserException $ex) {
+ // Something went wrong and we can't decode the output. Exit abnormally.
+ throw new PhutilProxyException(
+ pht('ESLint returned unparseable output.'),
+ $ex);
+ }
+
+ $messages = array();
+ foreach (idx($errors[0], 'messages') as $err) {
+ $message = new ArcanistLintMessage();
+ $message->setPath($path);
+ $message->setLine(idx($err, 'line'));
+ $message->setChar(idx($err, 'column'));
+ $message->setDescription(idx($err, 'message'));
+ $message->setSeverity(
+ $this->getLintMessageSeverity(idx($err, 'severity')));
+
+ // In case of a parsing error, eslint does not specify what rule failed
+ // Instead it sets fatal to true
+ if (idx($err, 'fatal')) {
+ $message->setCode('fatal');
+ $message->setName('ParsingError');
+ } else {
+ $message->setCode(idx($err, 'ruleId'));
+ $message->setName(idx($err, 'nodeType'));
+ }
+
+ $messages[] = $message;
+ }
+
+ return $messages;
+ }
+
+ protected function getLintCodeFromLinterConfigurationKey($code) {
+ return $code;
+ }
+
+}
Index: src/lint/linter/__tests__/.eslintrc
===================================================================
--- /dev/null
+++ src/lint/linter/__tests__/.eslintrc
@@ -0,0 +1,3 @@
+{
+ "extends": "eslint:recommended"
+}
Index: src/lint/linter/__tests__/ArcanistESLintLinterTestCase.php
===================================================================
--- /dev/null
+++ src/lint/linter/__tests__/ArcanistESLintLinterTestCase.php
@@ -0,0 +1,18 @@
+<?php
+
+final class ArcanistESLintLinterTestCase
+ extends ArcanistExternalLinterTestCase {
+
+ protected function getLinter() {
+ $linter = parent::getLinter();
+ $linter->setLinterConfigurationValue(
+ 'eslint.eslintrc',
+ dirname(realpath(__FILE__)).'/.eslintrc');
+ return $linter;
+ }
+
+ public function testLinter() {
+ $this->executeTestsInDirectory(dirname(__FILE__).'/eslint/');
+ }
+
+}
Index: src/lint/linter/__tests__/eslint/eslint.lint-test
===================================================================
--- /dev/null
+++ src/lint/linter/__tests__/eslint/eslint.lint-test
@@ -0,0 +1,11 @@
+function f() {
+ for (ii = 0; ii < 3; ii++) {
+ g()
+ }
+}
+~~~~~~~~~~
+error:1:10
+error:2:8
+error:2:16
+error:2:24
+error:3:5
Index: src/lint/linter/__tests__/eslint/expected-conditional.lint-test
===================================================================
--- /dev/null
+++ src/lint/linter/__tests__/eslint/expected-conditional.lint-test
@@ -0,0 +1,7 @@
+var foo;
+if (foo = 'bar') {
+ foo += 'baz';
+}
+~~~~~~~~~~
+error:2:1
+error:2:5
Index: src/lint/linter/__tests__/eslint/missing-semicolon.lint-test
===================================================================
--- /dev/null
+++ src/lint/linter/__tests__/eslint/missing-semicolon.lint-test
@@ -0,0 +1,4 @@
+console.log('foobar')
+~~~~~~~~~~
+error:1:1
+error:1:1
Index: src/lint/linter/__tests__/eslint/parse-failure.lint-test
===================================================================
--- /dev/null
+++ src/lint/linter/__tests__/eslint/parse-failure.lint-test
@@ -0,0 +1,4 @@
+function main() {
+
+~~~~~~~~~~
+error:3:2
Index: src/lint/linter/__tests__/eslint/unnecessary-semicolon.lint-test
===================================================================
--- /dev/null
+++ src/lint/linter/__tests__/eslint/unnecessary-semicolon.lint-test
@@ -0,0 +1,6 @@
+function main() {
+ return 'Hello, World!';
+};
+~~~~~~~~~~
+error:1:10
+error:3:2

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 4, 6:34 PM (1 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6725307
Default Alt Text
D15124.diff (8 KB)

Event Timeline