diff --git a/lib/helpers.js b/lib/helpers.js index 39aec3ad..f82ca0f7 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -4,10 +4,164 @@ Object.defineProperty(exports, "__esModule", { value: true }); +exports.processESLintMessages = exports.generateDebugString = exports.getDebugInfo = undefined; + +var getDebugInfo = exports.getDebugInfo = function () { + var _ref = _asyncToGenerator(function* (worker) { + var textEditor = atom.workspace.getActiveTextEditor(); + var filePath = textEditor.getPath(); + var packagePath = atom.packages.resolvePackagePath('linter-eslint'); + // eslint-disable-next-line import/no-dynamic-require + var linterEslintMeta = require((0, _path.join)(packagePath, 'package.json')); + var config = atom.config.get('linter-eslint'); + var hoursSinceRestart = Math.round(process.uptime() / 3600 * 10) / 10; + var returnVal = void 0; + try { + var response = yield worker.request('job', { + type: 'debug', + config: config, + filePath: filePath + }); + returnVal = { + atomVersion: atom.getVersion(), + linterEslintVersion: linterEslintMeta.version, + linterEslintConfig: config, + // eslint-disable-next-line import/no-dynamic-require + eslintVersion: require((0, _path.join)(response.path, 'package.json')).version, + hoursSinceRestart: hoursSinceRestart, + platform: process.platform, + eslintType: response.type, + eslintPath: response.path + }; + } catch (error) { + atom.notifications.addError('' + error); + } + return returnVal; + }); + + return function getDebugInfo(_x3) { + return _ref.apply(this, arguments); + }; +}(); + +var generateDebugString = exports.generateDebugString = function () { + var _ref2 = _asyncToGenerator(function* (worker) { + var debug = yield getDebugInfo(worker); + var details = ['Atom version: ' + debug.atomVersion, 'linter-eslint version: ' + debug.linterEslintVersion, 'ESLint version: ' + debug.eslintVersion, 'Hours since last Atom restart: ' + debug.hoursSinceRestart, 'Platform: ' + debug.platform, 'Using ' + debug.eslintType + ' ESLint from ' + debug.eslintPath, 'linter-eslint configuration: ' + JSON.stringify(debug.linterEslintConfig, null, 2)]; + return details.join('\n'); + }); + + return function generateDebugString(_x4) { + return _ref2.apply(this, arguments); + }; +}(); + +/** + * Given a raw response from ESLint, this processes the messages into a format + * compatible with the Linter API. + * @param {Object} response The raw response from ESLint + * @param {TextEditor} textEditor The Atom::TextEditor of the file the messages belong to + * @param {bool} showRule Whether to show the rule in the messages + * @param {Object} worker The current Worker process to send Debug jobs to + * @return {Promise} The messages transformed into Linter messages + */ +var processESLintMessages = exports.processESLintMessages = function () { + var _ref4 = _asyncToGenerator(function* (response, textEditor, showRule, worker) { + return Promise.all(response.map(function () { + var _ref5 = _asyncToGenerator(function* (_ref6) { + var message = _ref6.message, + line = _ref6.line, + severity = _ref6.severity, + ruleId = _ref6.ruleId, + column = _ref6.column, + fix = _ref6.fix, + endLine = _ref6.endLine, + endColumn = _ref6.endColumn; + + var filePath = textEditor.getPath(); + var textBuffer = textEditor.getBuffer(); + var linterFix = null; + if (fix) { + var fixRange = new _atom.Range(textBuffer.positionForCharacterIndex(fix.range[0]), textBuffer.positionForCharacterIndex(fix.range[1])); + linterFix = { + range: fixRange, + newText: fix.text + }; + } + var msgCol = void 0; + var msgEndLine = void 0; + var msgEndCol = void 0; + var eslintFullRange = false; + + /* + Note: ESLint positions are 1-indexed, while Atom expects 0-indexed, + positions. We are subtracting 1 from these values here so we don't have to + keep doing so in later uses. + */ + var msgLine = line - 1; + if (typeof endColumn !== 'undefined' && typeof endLine !== 'undefined') { + eslintFullRange = true; + // Here we always want the column to be a number + msgCol = Math.max(0, column - 1); + msgEndLine = endLine - 1; + msgEndCol = endColumn - 1; + } else { + // We want msgCol to remain undefined if it was initially so + // `rangeFromLineNumber` will give us a range over the entire line + msgCol = typeof column !== 'undefined' ? column - 1 : column; + } + + var ret = void 0; + var range = void 0; + try { + if (eslintFullRange) { + validatePoint(textEditor, msgLine, msgCol); + validatePoint(textEditor, msgEndLine, msgEndCol); + range = [[msgLine, msgCol], [msgEndLine, msgEndCol]]; + } else { + range = (0, _atomLinter.rangeFromLineNumber)(textEditor, msgLine, msgCol); + } + ret = { + filePath: filePath, + type: severity === 1 ? 'Warning' : 'Error', + range: range + }; + + if (showRule) { + var elName = ruleId ? 'a' : 'span'; + var href = ruleId ? ' href=' + (0, _eslintRuleDocumentation2.default)(ruleId).url : ''; + ret.html = '<' + elName + href + ' class="badge badge-flexible eslint">' + ((ruleId || 'Fatal') + ' ' + (0, _escapeHtml2.default)(message)); + } else { + ret.text = message; + } + if (linterFix) { + ret.fix = linterFix; + } + } catch (err) { + if (!err.message.startsWith('Line number ') && !err.message.startsWith('Column start ')) { + // This isn't an invalid point error from `rangeFromLineNumber`, re-throw it + throw err; + } + ret = yield generateInvalidTrace(msgLine, msgCol, msgEndLine, msgEndCol, eslintFullRange, filePath, textEditor, ruleId, message, worker); + } + + return ret; + }); + + return function (_x19) { + return _ref5.apply(this, arguments); + }; + }())); + }); + + return function processESLintMessages(_x15, _x16, _x17, _x18) { + return _ref4.apply(this, arguments); + }; +}(); + exports.spawnWorker = spawnWorker; exports.showError = showError; exports.idsToIgnoredRules = idsToIgnoredRules; -exports.validatePoint = validatePoint; var _child_process = require('child_process'); @@ -17,13 +171,27 @@ var _processCommunication = require('process-communication'); var _path = require('path'); +var _escapeHtml = require('escape-html'); + +var _escapeHtml2 = _interopRequireDefault(_escapeHtml); + +var _eslintRuleDocumentation = require('eslint-rule-documentation'); + +var _eslintRuleDocumentation2 = _interopRequireDefault(_eslintRuleDocumentation); + +var _atomLinter = require('atom-linter'); + var _atom = require('atom'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -var RULE_OFF_SEVERITY = 0; +function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } // eslint-disable-next-line import/no-extraneous-dependencies, import/extensions + + +var RULE_OFF_SEVERITY = 0; + function spawnWorker() { var env = Object.create(process.env); @@ -83,3 +251,40 @@ function validatePoint(textEditor, line, col) { throw new Error(line + ':' + col + ' isn\'t a valid point!'); } } + +var generateInvalidTrace = function () { + var _ref3 = _asyncToGenerator(function* (msgLine, msgCol, msgEndLine, msgEndCol, eslintFullRange, filePath, textEditor, ruleId, message, worker) { + var errMsgRange = msgLine + 1 + ':' + msgCol; + if (eslintFullRange) { + errMsgRange += ' - ' + (msgEndLine + 1) + ':' + (msgEndCol + 1); + } + var rangeText = 'Requested ' + (eslintFullRange ? 'start point' : 'range') + ': ' + errMsgRange; + var issueURL = 'https://github.com/AtomLinter/linter-eslint/issues/new'; + var titleText = 'Invalid position given by \'' + ruleId + '\''; + var title = encodeURIComponent(titleText); + var body = encodeURIComponent(['ESLint returned a point that did not exist in the document being edited.', 'Rule: `' + ruleId + '`', rangeText, '', '', '', '', '', 'Debug information:', '```json', JSON.stringify((yield getDebugInfo(worker)), null, 2), '```'].join('\n')); + var newIssueURL = issueURL + '?title=' + title + '&body=' + body; + return { + type: 'Error', + severity: 'error', + html: (0, _escapeHtml2.default)(titleText) + '. See the trace for details. ' + ('Report this!'), + filePath: filePath, + range: (0, _atomLinter.rangeFromLineNumber)(textEditor, 0), + trace: [{ + type: 'Trace', + text: 'Original message: ' + ruleId + ' - ' + message, + filePath: filePath, + severity: 'info' + }, { + type: 'Trace', + text: rangeText, + filePath: filePath, + severity: 'info' + }] + }; + }); + + return function generateInvalidTrace(_x5, _x6, _x7, _x8, _x9, _x10, _x11, _x12, _x13, _x14) { + return _ref3.apply(this, arguments); + }; +}(); diff --git a/lib/main.js b/lib/main.js index cd4c98e4..afb38494 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,28 +1,15 @@ 'use strict'; 'use babel'; -var _path = require('path'); - -var _path2 = _interopRequireDefault(_path); - -var _escapeHtml = require('escape-html'); - -var _escapeHtml2 = _interopRequireDefault(_escapeHtml); - -var _eslintRuleDocumentation = require('eslint-rule-documentation'); - -var _eslintRuleDocumentation2 = _interopRequireDefault(_eslintRuleDocumentation); +// eslint-disable-next-line import/no-extraneous-dependencies, import/extensions var _atom = require('atom'); var _helpers = require('./helpers'); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } // Configuration - - -// eslint-disable-next-line import/no-extraneous-dependencies, import/extensions var scopes = []; var showRule = void 0; var ignoredRulesWhenModified = void 0; @@ -72,28 +59,17 @@ module.exports = { })); this.subscriptions.add(atom.commands.add('atom-text-editor', { - 'linter-eslint:debug': function linterEslintDebug() { - var textEditor = atom.workspace.getActiveTextEditor(); - var filePath = textEditor.getPath(); - // eslint-disable-next-line import/no-dynamic-require - var linterEslintMeta = require(_path2.default.join(atom.packages.resolvePackagePath('linter-eslint'), 'package.json')); - var config = atom.config.get('linter-eslint'); - var configString = JSON.stringify(config, null, 2); - var hoursSinceRestart = process.uptime() / 3600; - _this.worker.request('job', { - type: 'debug', - config: config, - filePath: filePath - }).then(function (response) { - var detail = ['atom version: ' + atom.getVersion(), 'linter-eslint version: ' + linterEslintMeta.version, - // eslint-disable-next-line import/no-dynamic-require - 'eslint version: ' + require(_path2.default.join(response.path, 'package.json')).version, 'hours since last atom restart: ' + Math.round(hoursSinceRestart * 10) / 10, 'platform: ' + process.platform, 'Using ' + response.type + ' eslint from ' + response.path, 'linter-eslint configuration: ' + configString].join('\n'); - var notificationOptions = { detail: detail, dismissable: true }; + 'linter-eslint:debug': function () { + var _ref = _asyncToGenerator(function* () { + var debugString = yield (0, _helpers.generateDebugString)(_this.worker); + var notificationOptions = { detail: debugString, dismissable: true }; atom.notifications.addInfo('linter-eslint debugging information', notificationOptions); - }).catch(function (response) { - atom.notifications.addError('' + response); }); - } + + return function linterEslintDebug() { + return _ref.apply(this, arguments); + }; + }() })); this.subscriptions.add(atom.commands.add('atom-text-editor', { @@ -130,10 +106,9 @@ module.exports = { })); var initializeWorker = function initializeWorker() { - var _spawnWorker = (0, _helpers.spawnWorker)(); - - var worker = _spawnWorker.worker; - var subscription = _spawnWorker.subscription; + var _spawnWorker = (0, _helpers.spawnWorker)(), + worker = _spawnWorker.worker, + subscription = _spawnWorker.subscription; _this.worker = worker; _this.subscriptions.add(subscription); @@ -153,8 +128,6 @@ module.exports = { provideLinter: function provideLinter() { var _this2 = this; - var Helpers = require('atom-linter'); - return { name: 'ESLint', grammarScopes: scopes, @@ -189,62 +162,7 @@ module.exports = { */ return null; } - return response.map(function (_ref) { - var message = _ref.message; - var line = _ref.line; - var severity = _ref.severity; - var ruleId = _ref.ruleId; - var column = _ref.column; - var fix = _ref.fix; - var endLine = _ref.endLine; - var endColumn = _ref.endColumn; - - var textBuffer = textEditor.getBuffer(); - var linterFix = null; - if (fix) { - var fixRange = new _atom.Range(textBuffer.positionForCharacterIndex(fix.range[0]), textBuffer.positionForCharacterIndex(fix.range[1])); - linterFix = { - range: fixRange, - newText: fix.text - }; - } - var range = void 0; - var msgLine = line - 1; - try { - if (typeof endColumn !== 'undefined' && typeof endLine !== 'undefined') { - // Here we always want the column to be a number - var msgCol = Math.max(0, column - 1); - (0, _helpers.validatePoint)(textEditor, msgLine, msgCol); - (0, _helpers.validatePoint)(textEditor, endLine - 1, endColumn - 1); - range = [[msgLine, msgCol], [endLine - 1, endColumn - 1]]; - } else { - // We want msgCol to remain undefined if it was initially so - // `rangeFromLineNumber` will give us a range over the entire line - var _msgCol = typeof column !== 'undefined' ? column - 1 : column; - range = Helpers.rangeFromLineNumber(textEditor, msgLine, _msgCol); - } - } catch (err) { - throw new Error('Cannot mark location in editor for (' + ruleId + ') - (' + message + ')' + (' at line (' + line + ') column (' + column + ')')); - } - var ret = { - filePath: filePath, - type: severity === 1 ? 'Warning' : 'Error', - range: range - }; - - if (showRule) { - var elName = ruleId ? 'a' : 'span'; - var href = ruleId ? ' href=' + (0, _eslintRuleDocumentation2.default)(ruleId).url : ''; - ret.html = '<' + elName + href + ' class="badge badge-flexible eslint">' + ((ruleId || 'Fatal') + ' ' + (0, _escapeHtml2.default)(message)); - } else { - ret.text = message; - } - if (linterFix) { - ret.fix = linterFix; - } - - return ret; - }); + return (0, _helpers.processESLintMessages)(response, textEditor, showRule, _this2.worker); }); } }; diff --git a/lib/worker-helpers.js b/lib/worker-helpers.js index fbf71f91..4f55b01c 100644 --- a/lib/worker-helpers.js +++ b/lib/worker-helpers.js @@ -97,9 +97,8 @@ function findESLintDirectory(modulesDir, config, projectPath) { } function getESLintFromDirectory(modulesDir, config, projectPath) { - var _findESLintDirectory = findESLintDirectory(modulesDir, config, projectPath); - - var ESLintDirectory = _findESLintDirectory.path; + var _findESLintDirectory = findESLintDirectory(modulesDir, config, projectPath), + ESLintDirectory = _findESLintDirectory.path; try { // eslint-disable-next-line import/no-dynamic-require diff --git a/lib/worker.js b/lib/worker.js index 0d4965d3..edeeba69 100644 --- a/lib/worker.js +++ b/lib/worker.js @@ -51,12 +51,12 @@ function fixJob(argv, eslint) { } (0, _processCommunication.create)().onRequest('job', function (_ref, job) { - var contents = _ref.contents; - var type = _ref.type; - var config = _ref.config; - var filePath = _ref.filePath; - var projectPath = _ref.projectPath; - var rules = _ref.rules; + var contents = _ref.contents, + type = _ref.type, + config = _ref.config, + filePath = _ref.filePath, + projectPath = _ref.projectPath, + rules = _ref.rules; global.__LINTER_ESLINT_RESPONSE = []; diff --git a/spec/linter-eslint-spec.js b/spec/linter-eslint-spec.js index 877f3c48..71e346a9 100644 --- a/spec/linter-eslint-spec.js +++ b/spec/linter-eslint-spec.js @@ -325,11 +325,11 @@ describe('The eslint provider for Linter', () => { const checkNotificaton = (notification) => { if (notification.getMessage() === 'linter-eslint debugging information') { const detail = notification.getDetail() - expect(detail.includes(`atom version: ${atom.getVersion()}`)).toBe(true) + expect(detail.includes(`Atom version: ${atom.getVersion()}`)).toBe(true) expect(detail.includes('linter-eslint version:')).toBe(true) - expect(detail.includes(`platform: ${process.platform}`)).toBe(true) + expect(detail.includes(`Platform: ${process.platform}`)).toBe(true) expect(detail.includes('linter-eslint configuration:')).toBe(true) - expect(detail.includes('Using local project eslint')).toBe(true) + expect(detail.includes('Using local project ESLint')).toBe(true) done = true } } diff --git a/src/helpers.js b/src/helpers.js index c3e89b50..96fb01be 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -3,9 +3,12 @@ import ChildProcess from 'child_process' import { createFromProcess } from 'process-communication' import { join } from 'path' +import escapeHTML from 'escape-html' +import ruleURI from 'eslint-rule-documentation' +import { rangeFromLineNumber } from 'atom-linter' // eslint-disable-next-line import/no-extraneous-dependencies, import/extensions -import { Disposable } from 'atom' +import { Disposable, Range } from 'atom' const RULE_OFF_SEVERITY = 0 @@ -57,10 +60,198 @@ export function idsToIgnoredRules(ruleIds = []) { }, {}) } -export function validatePoint(textEditor, line, col) { +function validatePoint(textEditor, line, col) { const buffer = textEditor.getBuffer() // Clip the given point to a valid one, and check if it equals the original if (!buffer.clipPosition([line, col]).isEqual([line, col])) { throw new Error(`${line}:${col} isn't a valid point!`) } } + +export async function getDebugInfo(worker) { + const textEditor = atom.workspace.getActiveTextEditor() + const filePath = textEditor.getPath() + const packagePath = atom.packages.resolvePackagePath('linter-eslint') + // eslint-disable-next-line import/no-dynamic-require + const linterEslintMeta = require(join(packagePath, 'package.json')) + const config = atom.config.get('linter-eslint') + const hoursSinceRestart = Math.round((process.uptime() / 3600) * 10) / 10 + let returnVal + try { + const response = await worker.request('job', { + type: 'debug', + config, + filePath + }) + returnVal = { + atomVersion: atom.getVersion(), + linterEslintVersion: linterEslintMeta.version, + linterEslintConfig: config, + // eslint-disable-next-line import/no-dynamic-require + eslintVersion: require(join(response.path, 'package.json')).version, + hoursSinceRestart, + platform: process.platform, + eslintType: response.type, + eslintPath: response.path, + } + } catch (error) { + atom.notifications.addError(`${error}`) + } + return returnVal +} + +export async function generateDebugString(worker) { + const debug = await getDebugInfo(worker) + const details = [ + `Atom version: ${debug.atomVersion}`, + `linter-eslint version: ${debug.linterEslintVersion}`, + `ESLint version: ${debug.eslintVersion}`, + `Hours since last Atom restart: ${debug.hoursSinceRestart}`, + `Platform: ${debug.platform}`, + `Using ${debug.eslintType} ESLint from ${debug.eslintPath}`, + `linter-eslint configuration: ${JSON.stringify(debug.linterEslintConfig, null, 2)}` + ] + return details.join('\n') +} + +const generateInvalidTrace = async ( + msgLine, msgCol, msgEndLine, msgEndCol, + eslintFullRange, filePath, textEditor, ruleId, message, worker +) => { + let errMsgRange = `${msgLine + 1}:${msgCol}` + if (eslintFullRange) { + errMsgRange += ` - ${msgEndLine + 1}:${msgEndCol + 1}` + } + const rangeText = `Requested ${eslintFullRange ? 'start point' : 'range'}: ${errMsgRange}` + const issueURL = 'https://github.com/AtomLinter/linter-eslint/issues/new' + const titleText = `Invalid position given by '${ruleId}'` + const title = encodeURIComponent(titleText) + const body = encodeURIComponent([ + 'ESLint returned a point that did not exist in the document being edited.', + `Rule: \`${ruleId}\``, + rangeText, + '', '', + '', + '', '', + 'Debug information:', + '```json', + JSON.stringify(await getDebugInfo(worker), null, 2), + '```' + ].join('\n')) + const newIssueURL = `${issueURL}?title=${title}&body=${body}` + return { + type: 'Error', + severity: 'error', + html: `${escapeHTML(titleText)}. See the trace for details. ` + + `Report this!`, + filePath, + range: rangeFromLineNumber(textEditor, 0), + trace: [ + { + type: 'Trace', + text: `Original message: ${ruleId} - ${message}`, + filePath, + severity: 'info', + }, + { + type: 'Trace', + text: rangeText, + filePath, + severity: 'info', + }, + ] + } +} + +/** + * Given a raw response from ESLint, this processes the messages into a format + * compatible with the Linter API. + * @param {Object} response The raw response from ESLint + * @param {TextEditor} textEditor The Atom::TextEditor of the file the messages belong to + * @param {bool} showRule Whether to show the rule in the messages + * @param {Object} worker The current Worker process to send Debug jobs to + * @return {Promise} The messages transformed into Linter messages + */ +export async function processESLintMessages(response, textEditor, showRule, worker) { + return Promise.all(response.map(async ({ + message, line, severity, ruleId, column, fix, endLine, endColumn + }) => { + const filePath = textEditor.getPath() + const textBuffer = textEditor.getBuffer() + let linterFix = null + if (fix) { + const fixRange = new Range( + textBuffer.positionForCharacterIndex(fix.range[0]), + textBuffer.positionForCharacterIndex(fix.range[1]) + ) + linterFix = { + range: fixRange, + newText: fix.text + } + } + let msgCol + let msgEndLine + let msgEndCol + let eslintFullRange = false + + /* + Note: ESLint positions are 1-indexed, while Atom expects 0-indexed, + positions. We are subtracting 1 from these values here so we don't have to + keep doing so in later uses. + */ + const msgLine = line - 1 + if (typeof endColumn !== 'undefined' && typeof endLine !== 'undefined') { + eslintFullRange = true + // Here we always want the column to be a number + msgCol = Math.max(0, column - 1) + msgEndLine = endLine - 1 + msgEndCol = endColumn - 1 + } else { + // We want msgCol to remain undefined if it was initially so + // `rangeFromLineNumber` will give us a range over the entire line + msgCol = typeof column !== 'undefined' ? column - 1 : column + } + + let ret + let range + try { + if (eslintFullRange) { + validatePoint(textEditor, msgLine, msgCol) + validatePoint(textEditor, msgEndLine, msgEndCol) + range = [[msgLine, msgCol], [msgEndLine, msgEndCol]] + } else { + range = rangeFromLineNumber(textEditor, msgLine, msgCol) + } + ret = { + filePath, + type: severity === 1 ? 'Warning' : 'Error', + range + } + + if (showRule) { + const elName = ruleId ? 'a' : 'span' + const href = ruleId ? ` href=${ruleURI(ruleId).url}` : '' + ret.html = `<${elName}${href} class="badge badge-flexible eslint">` + + `${ruleId || 'Fatal'} ${escapeHTML(message)}` + } else { + ret.text = message + } + if (linterFix) { + ret.fix = linterFix + } + } catch (err) { + if (!err.message.startsWith('Line number ') && + !err.message.startsWith('Column start ') + ) { + // This isn't an invalid point error from `rangeFromLineNumber`, re-throw it + throw err + } + ret = await generateInvalidTrace( + msgLine, msgCol, msgEndLine, msgEndCol, + eslintFullRange, filePath, textEditor, ruleId, message, worker + ) + } + + return ret + })) +} diff --git a/src/main.js b/src/main.js index f120032d..be0a4041 100644 --- a/src/main.js +++ b/src/main.js @@ -1,13 +1,12 @@ 'use babel' -import Path from 'path' -import escapeHTML from 'escape-html' -import ruleURI from 'eslint-rule-documentation' - // eslint-disable-next-line import/no-extraneous-dependencies, import/extensions -import { CompositeDisposable, Range } from 'atom' +import { CompositeDisposable, } from 'atom' -import { spawnWorker, showError, idsToIgnoredRules, validatePoint } from './helpers' +import { + spawnWorker, showError, idsToIgnoredRules, processESLintMessages, + generateDebugString +} from './helpers' // Configuration const scopes = [] @@ -62,34 +61,10 @@ module.exports = { })) this.subscriptions.add(atom.commands.add('atom-text-editor', { - 'linter-eslint:debug': () => { - const textEditor = atom.workspace.getActiveTextEditor() - const filePath = textEditor.getPath() - // eslint-disable-next-line import/no-dynamic-require - const linterEslintMeta = require(Path.join(atom.packages.resolvePackagePath('linter-eslint'), 'package.json')) - const config = atom.config.get('linter-eslint') - const configString = JSON.stringify(config, null, 2) - const hoursSinceRestart = process.uptime() / 3600 - this.worker.request('job', { - type: 'debug', - config, - filePath - }).then((response) => { - const detail = [ - `atom version: ${atom.getVersion()}`, - `linter-eslint version: ${linterEslintMeta.version}`, - // eslint-disable-next-line import/no-dynamic-require - `eslint version: ${require(Path.join(response.path, 'package.json')).version}`, - `hours since last atom restart: ${Math.round(hoursSinceRestart * 10) / 10}`, - `platform: ${process.platform}`, - `Using ${response.type} eslint from ${response.path}`, - `linter-eslint configuration: ${configString}` - ].join('\n') - const notificationOptions = { detail, dismissable: true } - atom.notifications.addInfo('linter-eslint debugging information', notificationOptions) - }).catch((response) => { - atom.notifications.addError(`${response}`) - }) + 'linter-eslint:debug': async () => { + const debugString = await generateDebugString(this.worker) + const notificationOptions = { detail: debugString, dismissable: true } + atom.notifications.addInfo('linter-eslint debugging information', notificationOptions) } })) @@ -147,8 +122,6 @@ module.exports = { this.subscriptions.dispose() }, provideLinter() { - const Helpers = require('atom-linter') - return { name: 'ESLint', grammarScopes: scopes, @@ -183,62 +156,7 @@ module.exports = { */ return null } - return response.map(({ - message, line, severity, ruleId, column, fix, endLine, endColumn } - ) => { - const textBuffer = textEditor.getBuffer() - let linterFix = null - if (fix) { - const fixRange = new Range( - textBuffer.positionForCharacterIndex(fix.range[0]), - textBuffer.positionForCharacterIndex(fix.range[1]) - ) - linterFix = { - range: fixRange, - newText: fix.text - } - } - let range - const msgLine = line - 1 - try { - if (typeof endColumn !== 'undefined' && typeof endLine !== 'undefined') { - // Here we always want the column to be a number - const msgCol = Math.max(0, column - 1) - validatePoint(textEditor, msgLine, msgCol) - validatePoint(textEditor, endLine - 1, endColumn - 1) - range = [[msgLine, msgCol], [endLine - 1, endColumn - 1]] - } else { - // We want msgCol to remain undefined if it was initially so - // `rangeFromLineNumber` will give us a range over the entire line - const msgCol = typeof column !== 'undefined' ? column - 1 : column - range = Helpers.rangeFromLineNumber(textEditor, msgLine, msgCol) - } - } catch (err) { - throw new Error( - `Cannot mark location in editor for (${ruleId}) - (${message})` + - ` at line (${line}) column (${column})` - ) - } - const ret = { - filePath, - type: severity === 1 ? 'Warning' : 'Error', - range - } - - if (showRule) { - const elName = ruleId ? 'a' : 'span' - const href = ruleId ? ` href=${ruleURI(ruleId).url}` : '' - ret.html = `<${elName}${href} class="badge badge-flexible eslint">` + - `${ruleId || 'Fatal'} ${escapeHTML(message)}` - } else { - ret.text = message - } - if (linterFix) { - ret.fix = linterFix - } - - return ret - }) + return processESLintMessages(response, textEditor, showRule, this.worker) }) } }