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 @@ -89,6 +89,8 @@ 'ArcanistDuplicateKeysInArrayXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDuplicateKeysInArrayXHPASTLinterRule.php', 'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDuplicateSwitchCaseXHPASTLinterRule.php', 'ArcanistDynamicDefineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php', + 'ArcanistESLintLinter' => 'lint/linter/ArcanistESLintLinter.php', + 'ArcanistESLintLinterTestCase' => 'lint/linter/__tests__/ArcanistESLintLinterTestCase.php', 'ArcanistElseIfUsageXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php', 'ArcanistEmptyFileXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php', 'ArcanistEmptyStatementXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyStatementXHPASTLinterRule.php', @@ -378,6 +380,8 @@ 'ArcanistDuplicateKeysInArrayXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistDynamicDefineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistESLintLinter' => 'ArcanistExternalLinter', + 'ArcanistESLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistElseIfUsageXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistEmptyFileXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistEmptyStatementXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 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,128 @@ +eslintenv) { + $options[] = '--env='.$this->eslintenv; + } + + if ($this->eslintconfig) { + $options[] = '--config='.$this->eslintconfig; + } + + return $options; + } + + public function getLinterConfigurationOptions() { + $options = array( + 'eslint.eslintenv' => array( + 'type' => 'optional string', + 'help' => pht('enables specific environments.'), + ), + 'eslint.eslintconfig' => array( + 'type' => 'optional string', + 'help' => pht('config file to use the default is .eslint.'), + ), + ); + + return $options + parent::getLinterConfigurationOptions(); + } + + public function setLinterConfigurationValue($key, $value) { + + switch ($key) { + case 'eslint.eslintenv': + $this->eslintenv = $value; + return; + case 'eslint.eslintconfig': + $this->eslintconfig = $value; + return; + } + + return parent::setLinterConfigurationValue($key, $value); + } + + 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->setName($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 @@ -63,7 +63,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,14 @@ +setLinterConfigurationValue( + 'eslint.eslintconfig', + dirname(__FILE__).'/eslint/.eslintrc'); + $this->executeTestsInDirectory(dirname(__FILE__).'/eslint/', $linter); + } + +} diff --git a/src/lint/linter/__tests__/eslint/.eslintrc b/src/lint/linter/__tests__/eslint/.eslintrc new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/eslint/.eslintrc @@ -0,0 +1,24 @@ +{ + "rules": { + "indent": [ + 1, + "tab" + ], + "quotes": [ + 1, + "double" + ], + "linebreak-style": [ + 2, + "unix" + ], + "semi": [ + 2, + "always" + ] + }, + "env": { + "es6": true, + "browser": true + }, +} 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,93 @@ +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; + +~~~~~~~~~~ +warning:1:23 +warning:2:20 +warning:3:22 +warning:4:28 +warning:5:26 +warning:6:22 +warning:7:21 +warning:13:9 +warning:13:39 +warning:14:9 +warning:14:24 +warning:18:16 +warning:22:45 +warning:24:9 +warning:25:9 +warning:29:3 +warning:29:23 +error:29:35 +warning:30:3 +warning:37:13 +warning:37:24 +warning:38:3 +warning:39:5 +error:39:34 +warning:40:5 +warning:40:16 +warning:41:7 +warning:42:7 +warning:50:3 +warning:51:3 +warning:51:14 +warning:52:5 +warning:53:5