Page MenuHomePhabricator

D12198.id29320.diff
No OneTemporary

D12198.id29320.diff

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
@@ -69,6 +69,8 @@
'ArcanistDifferentialRevisionHash' => 'differential/constants/ArcanistDifferentialRevisionHash.php',
'ArcanistDifferentialRevisionStatus' => 'differential/constants/ArcanistDifferentialRevisionStatus.php',
'ArcanistDownloadWorkflow' => 'workflow/ArcanistDownloadWorkflow.php',
+ 'ArcanistESLintLinter' => 'lint/linter/ArcanistESLintLinter.php',
+ 'ArcanistESLintLinterTestCase' => 'lint/linter/__tests__/ArcanistESLintLinterTestCase.php',
'ArcanistEventType' => 'events/constant/ArcanistEventType.php',
'ArcanistExportWorkflow' => 'workflow/ArcanistExportWorkflow.php',
'ArcanistExternalLinter' => 'lint/linter/ArcanistExternalLinter.php',
@@ -268,6 +270,8 @@
'ArcanistDifferentialCommitMessageParserException' => 'Exception',
'ArcanistDifferentialDependencyGraph' => 'AbstractDirectedGraph',
'ArcanistDownloadWorkflow' => 'ArcanistWorkflow',
+ 'ArcanistESLintLinter' => 'ArcanistExternalLinter',
+ 'ArcanistESLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistEventType' => 'PhutilEventType',
'ArcanistExportWorkflow' => 'ArcanistWorkflow',
'ArcanistExternalLinter' => 'ArcanistFutureLinter',
diff --git a/src/lint/linter/ArcanistESLintLinter.php b/src/lint/linter/ArcanistESLintLinter.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/ArcanistESLintLinter.php
@@ -0,0 +1,69 @@
+<?php
+
+final class ArcanistESLintLinter extends ArcanistExternalLinter {
+
+ public function getInfoName() {
+ return 'ESLint';
+ }
+
+ public function getInfoURI() {
+ return 'https://www.eslint.org';
+ }
+
+ public function getInfoDescription() {
+ return pht('ESLint is a linter for JavaScript source files.');
+ }
+
+ public function getLinterName() {
+ return 'ESLint';
+ }
+
+ public function getLinterConfigurationName() {
+ return 'eslint';
+ }
+
+ public function getDefaultBinary() {
+ return 'eslint';
+ }
+
+ public function getInstallInstructions() {
+ return pht('Install ESLint using `npm install -g eslint`.');
+ }
+
+ protected function canCustomizeLintSeverities() {
+ return true;
+ }
+
+ protected function parseLinterOutput($path, $err, $stdout, $stderr) {
+ $lines = phutil_split_lines($stdout, false);
+
+ $messages = array();
+ foreach ($lines as $line) {
+ // Clean up nasty ESLint output
+ $clean_line = $output = preg_replace('!\s+!', ' ', $line);
+ $parts = explode(' ', ltrim($clean_line));
+
+ if (isset($parts[1]) &&
+ ($parts[1] === 'error' || $parts[1] === 'warning')) {
+ $severity = $parts[1] === 'error' ?
+ ArcanistLintSeverity::SEVERITY_ERROR :
+ ArcanistLintSeverity::SEVERITY_WARNING;
+
+ list($line, $char) = explode(':', $parts[0]);
+
+ $message = new ArcanistLintMessage();
+ $message->setPath($path);
+ $message->setLine($line);
+ $message->setChar($char);
+ $message->setCode($this->getLinterName());
+ $message->setDescription(implode(' ', $parts));
+ $message->setSeverity($severity);
+
+ $messages[] = $message;
+ }
+ }
+
+ return $messages;
+ }
+
+}
diff --git a/src/lint/linter/ArcanistExternalLinter.php b/src/lint/linter/ArcanistExternalLinter.php
--- a/src/lint/linter/ArcanistExternalLinter.php
+++ b/src/lint/linter/ArcanistExternalLinter.php
@@ -52,7 +52,7 @@
* this method and return true so execution continues when it exits with
* a nonzero status.
*
- * @param bool Return true to continue on nonzero error code.
+ * @return bool Return true to continue on nonzero error code.
* @task bin
*/
public function shouldExpectCommandErrors() {
diff --git a/src/lint/linter/__tests__/ArcanistESLintLinterTestCase.php b/src/lint/linter/__tests__/ArcanistESLintLinterTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/__tests__/ArcanistESLintLinterTestCase.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ArcanistESLintLinterTestCase
+ extends ArcanistExternalLinterTestCase {
+
+ public function testLinter() {
+ $this->executeTestsInDirectory(dirname(__FILE__).'/eslint/');
+ }
+
+}
diff --git a/src/lint/linter/__tests__/eslint/eslint.lint-test b/src/lint/linter/__tests__/eslint/eslint.lint-test
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/__tests__/eslint/eslint.lint-test
@@ -0,0 +1,95 @@
+var express = require('express');
+var path = require('path');
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
+var routes = require('./routes/index');
+var users = require('./routes/users');
+
+var app = express();
+var app = express();
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'));
+app.set('view engine', 'jade');
+
+// uncomment after placing your favicon in /public
+//app.use(favicon(__dirname + '/public/favicon.ico'));
+app.use(logger('dev'));
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
+app.use(cookieParser());
+app.use(express.static(path.join(__dirname, 'public')));
+
+app.use('/', routes);
+app.use('/users', users);
+
+// catch 404 and forward to error handler
+app.use(function(req, res, next) {
+ var err = new Error('Not Found');
+ err.status = 404;
+});
+
+// error handlers
+
+// development error handler
+// will print stacktrace
+if (app.get('env') === 'development') {
+ app.use(function(err, req, res) {
+ res.status(err.status || 500);
+ res.render('error', {
+ message: err.message,
+ error: err
+ });
+ });
+}
+
+// production error handler
+// no stacktraces leaked to user
+app.use(function(err, req, res) {
+ res.status(err.status || 500);
+ res.render('error', {
+ message: err.message,
+ error: {}
+ });
+});
+
+
+module.exports = app;
+
+~~~~~~~~~~
+error:1:14
+error:1:22
+error:2:11
+error:2:19
+error:3:13
+error:3:21
+error:4:19
+error:4:27
+error:5:17
+error:5:25
+error:6:13
+error:6:21
+error:7:12
+error:7:20
+error:10:4
+error:13:8
+error:13:27
+error:13:38
+error:14:8
+error:14:23
+error:18:15
+error:22:33
+error:22:44
+error:24:8
+error:25:8
+error:28:8
+error:28:27
+error:29:22
+error:37:12
+error:37:23
+error:38:10
+error:40:15
+error:49:8
+error:51:13
+error:58:0

File Metadata

Mime Type
text/plain
Expires
Fri, Apr 25, 12:34 AM (4 d, 18 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7692540
Default Alt Text
D12198.id29320.diff (6 KB)

Event Timeline