Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14053301
D15124.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
8 KB
Referenced Files
None
Subscribers
None
D15124.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 16, 1:59 PM (1 d, 6 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6725307
Default Alt Text
D15124.diff (8 KB)
Attached To
Mode
D15124: Add linter to support eslint
Attached
Detach File
Event Timeline
Log In to Comment