Skip to content
This repository has been archived by the owner on Aug 7, 2023. It is now read-only.

Gracefully handle invalid points #761

Merged
merged 11 commits into from
Dec 6, 2016
209 changes: 207 additions & 2 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') + '</' + elName + '> ' + (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');

Expand All @@ -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);

Expand Down Expand Up @@ -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, '', '', '<!-- If at all possible, please include code to reproduce this issue! -->', '', '', '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. ' + ('<a href="' + newIssueURL + '">Report this!</a>'),
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);
};
}();
112 changes: 15 additions & 97 deletions lib/main.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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', {
Expand Down Expand Up @@ -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);
Expand All @@ -153,8 +128,6 @@ module.exports = {
provideLinter: function provideLinter() {
var _this2 = this;

var Helpers = require('atom-linter');

return {
name: 'ESLint',
grammarScopes: scopes,
Expand Down Expand Up @@ -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') + '</' + elName + '> ' + (0, _escapeHtml2.default)(message));
} else {
ret.text = message;
}
if (linterFix) {
ret.fix = linterFix;
}

return ret;
});
return (0, _helpers.processESLintMessages)(response, textEditor, showRule, _this2.worker);
});
}
};
Expand Down
Loading