eslint 3.16.0 → 3.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/conf/eslint-recommended.js +2 -0
- package/lib/ast-utils.js +3 -67
- package/lib/code-path-analysis/code-path-analyzer.js +2 -7
- package/lib/code-path-analysis/debug-helpers.js +17 -16
- package/lib/config/config-file.js +68 -38
- package/lib/eslint.js +5 -5
- package/lib/formatters/stylish.js +5 -4
- package/lib/ignored-paths.js +6 -0
- package/lib/internal-rules/internal-no-invalid-meta.js +2 -40
- package/lib/rules/array-callback-return.js +15 -5
- package/lib/rules/capitalized-comments.js +2 -1
- package/lib/rules/complexity.js +14 -8
- package/lib/rules/consistent-return.js +17 -10
- package/lib/rules/func-name-matching.js +18 -7
- package/lib/rules/func-names.js +20 -5
- package/lib/rules/keyword-spacing.js +19 -4
- package/lib/rules/line-comment-position.js +15 -5
- package/lib/rules/lines-around-comment.js +19 -0
- package/lib/rules/max-params.js +17 -4
- package/lib/rules/max-statements.js +11 -10
- package/lib/rules/no-compare-neg-zero.js +53 -0
- package/lib/rules/no-else-return.js +13 -1
- package/lib/rules/no-empty-function.js +9 -16
- package/lib/rules/no-extra-parens.js +64 -19
- package/lib/rules/no-extra-semi.js +13 -1
- package/lib/rules/no-global-assign.js +1 -1
- package/lib/rules/no-invalid-regexp.js +2 -1
- package/lib/rules/no-multiple-empty-lines.js +2 -4
- package/lib/rules/no-new-func.js +6 -8
- package/lib/rules/no-new.js +2 -6
- package/lib/rules/no-param-reassign.js +29 -6
- package/lib/rules/no-process-exit.js +2 -10
- package/lib/rules/no-restricted-properties.js +2 -0
- package/lib/rules/no-restricted-syntax.js +6 -22
- package/lib/rules/no-return-await.js +1 -1
- package/lib/rules/no-sync.js +8 -13
- package/lib/rules/no-unused-expressions.js +10 -1
- package/lib/rules/no-unused-vars.js +12 -12
- package/lib/rules/no-use-before-define.js +1 -1
- package/lib/rules/no-useless-escape.js +8 -2
- package/lib/rules/no-useless-return.js +13 -2
- package/lib/rules/nonblock-statement-body-position.js +114 -0
- package/lib/rules/object-shorthand.js +2 -1
- package/lib/rules/operator-assignment.js +1 -1
- package/lib/rules/padded-blocks.js +37 -28
- package/lib/rules/prefer-destructuring.js +1 -1
- package/lib/rules/semi.js +13 -1
- package/lib/rules/sort-vars.js +3 -5
- package/lib/rules/space-unary-ops.js +19 -1
- package/lib/rules/strict.js +8 -2
- package/lib/rules/yoda.js +2 -2
- package/lib/testers/rule-tester.js +44 -13
- package/lib/util/fix-tracker.js +121 -0
- package/lib/util/node-event-generator.js +274 -4
- package/lib/util/source-code-fixer.js +2 -2
- package/lib/util/source-code.js +99 -2
- package/lib/util/traverser.js +16 -25
- package/package.json +8 -8
@@ -5,6 +5,185 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const esquery = require("esquery");
|
13
|
+
const lodash = require("lodash");
|
14
|
+
|
15
|
+
//------------------------------------------------------------------------------
|
16
|
+
// Typedefs
|
17
|
+
//------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
/**
|
20
|
+
* An object describing an AST selector
|
21
|
+
* @typedef {Object} ASTSelector
|
22
|
+
* @property {string} rawSelector The string that was parsed into this selector
|
23
|
+
* @property {boolean} isExit `true` if this should be emitted when exiting the node rather than when entering
|
24
|
+
* @property {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
|
25
|
+
* @property {string[]|null} listenerTypes A list of node types that could possibly cause the selector to match,
|
26
|
+
* or `null` if all node types could cause a match
|
27
|
+
* @property {number} attributeCount The total number of classes, pseudo-classes, and attribute queries in this selector
|
28
|
+
* @property {number} identifierCount The total number of identifier queries in this selector
|
29
|
+
*/
|
30
|
+
|
31
|
+
//------------------------------------------------------------------------------
|
32
|
+
// Helpers
|
33
|
+
//------------------------------------------------------------------------------
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Gets the possible types of a selector
|
37
|
+
* @param {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
|
38
|
+
* @returns {string[]|null} The node types that could possibly trigger this selector, or `null` if all node types could trigger it
|
39
|
+
*/
|
40
|
+
function getPossibleTypes(parsedSelector) {
|
41
|
+
switch (parsedSelector.type) {
|
42
|
+
case "identifier":
|
43
|
+
return [parsedSelector.value];
|
44
|
+
|
45
|
+
case "matches": {
|
46
|
+
const typesForComponents = parsedSelector.selectors.map(getPossibleTypes);
|
47
|
+
|
48
|
+
if (typesForComponents.every(typesForComponent => typesForComponent)) {
|
49
|
+
return lodash.union.apply(null, typesForComponents);
|
50
|
+
}
|
51
|
+
return null;
|
52
|
+
}
|
53
|
+
|
54
|
+
case "compound": {
|
55
|
+
const typesForComponents = parsedSelector.selectors.map(getPossibleTypes).filter(typesForComponent => typesForComponent);
|
56
|
+
|
57
|
+
// If all of the components could match any type, then the compound could also match any type.
|
58
|
+
if (!typesForComponents.length) {
|
59
|
+
return null;
|
60
|
+
}
|
61
|
+
|
62
|
+
/*
|
63
|
+
* If at least one of the components could only match a particular type, the compound could only match
|
64
|
+
* the intersection of those types.
|
65
|
+
*/
|
66
|
+
return lodash.intersection.apply(null, typesForComponents);
|
67
|
+
}
|
68
|
+
|
69
|
+
case "child":
|
70
|
+
case "descendant":
|
71
|
+
case "sibling":
|
72
|
+
case "adjacent":
|
73
|
+
return getPossibleTypes(parsedSelector.right);
|
74
|
+
|
75
|
+
default:
|
76
|
+
return null;
|
77
|
+
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Counts the number of class, pseudo-class, and attribute queries in this selector
|
83
|
+
* @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
|
84
|
+
* @returns {number} The number of class, pseudo-class, and attribute queries in this selector
|
85
|
+
*/
|
86
|
+
function countClassAttributes(parsedSelector) {
|
87
|
+
switch (parsedSelector.type) {
|
88
|
+
case "child":
|
89
|
+
case "descendant":
|
90
|
+
case "sibling":
|
91
|
+
case "adjacent":
|
92
|
+
return countClassAttributes(parsedSelector.left) + countClassAttributes(parsedSelector.right);
|
93
|
+
|
94
|
+
case "compound":
|
95
|
+
case "not":
|
96
|
+
case "matches":
|
97
|
+
return parsedSelector.selectors.reduce((sum, childSelector) => sum + countClassAttributes(childSelector), 0);
|
98
|
+
|
99
|
+
case "attribute":
|
100
|
+
case "field":
|
101
|
+
case "nth-child":
|
102
|
+
case "nth-last-child":
|
103
|
+
return 1;
|
104
|
+
|
105
|
+
default:
|
106
|
+
return 0;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
/**
|
111
|
+
* Counts the number of identifier queries in this selector
|
112
|
+
* @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
|
113
|
+
* @returns {number} The number of identifier queries
|
114
|
+
*/
|
115
|
+
function countIdentifiers(parsedSelector) {
|
116
|
+
switch (parsedSelector.type) {
|
117
|
+
case "child":
|
118
|
+
case "descendant":
|
119
|
+
case "sibling":
|
120
|
+
case "adjacent":
|
121
|
+
return countIdentifiers(parsedSelector.left) + countIdentifiers(parsedSelector.right);
|
122
|
+
|
123
|
+
case "compound":
|
124
|
+
case "not":
|
125
|
+
case "matches":
|
126
|
+
return parsedSelector.selectors.reduce((sum, childSelector) => sum + countIdentifiers(childSelector), 0);
|
127
|
+
|
128
|
+
case "identifier":
|
129
|
+
return 1;
|
130
|
+
|
131
|
+
default:
|
132
|
+
return 0;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Compares the specificity of two selector objects, with CSS-like rules.
|
138
|
+
* @param {ASTSelector} selectorA An AST selector descriptor
|
139
|
+
* @param {ASTSelector} selectorB Another AST selector descriptor
|
140
|
+
* @returns {number}
|
141
|
+
* a value less than 0 if selectorA is less specific than selectorB
|
142
|
+
* a value greater than 0 if selectorA is more specific than selectorB
|
143
|
+
* a value less than 0 if selectorA and selectorB have the same specificity, and selectorA <= selectorB alphabetically
|
144
|
+
* a value greater than 0 if selectorA and selectorB have the same specificity, and selectorA > selectorB alphabetically
|
145
|
+
*/
|
146
|
+
function compareSpecificity(selectorA, selectorB) {
|
147
|
+
return selectorA.attributeCount - selectorB.attributeCount ||
|
148
|
+
selectorA.identifierCount - selectorB.identifierCount ||
|
149
|
+
(selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1);
|
150
|
+
}
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Parses a raw selector string, and throws a useful error if parsing fails.
|
154
|
+
* @param {string} rawSelector A raw AST selector
|
155
|
+
* @returns {Object} An object (from esquery) describing the matching behavior of this selector
|
156
|
+
* @throws {Error} An error if the selector is invalid
|
157
|
+
*/
|
158
|
+
function tryParseSelector(rawSelector) {
|
159
|
+
try {
|
160
|
+
return esquery.parse(rawSelector.replace(/:exit$/, ""));
|
161
|
+
} catch (err) {
|
162
|
+
if (typeof err.offset === "number") {
|
163
|
+
throw new Error(`Syntax error in selector "${rawSelector}" at position ${err.offset}: ${err.message}`);
|
164
|
+
}
|
165
|
+
throw err;
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Parses a raw selector string, and returns the parsed selector along with specificity and type information.
|
171
|
+
* @param {string} rawSelector A raw AST selector
|
172
|
+
* @returns {ASTSelector} A selector descriptor
|
173
|
+
*/
|
174
|
+
const parseSelector = lodash.memoize(rawSelector => {
|
175
|
+
const parsedSelector = tryParseSelector(rawSelector);
|
176
|
+
|
177
|
+
return {
|
178
|
+
rawSelector,
|
179
|
+
isExit: rawSelector.endsWith(":exit"),
|
180
|
+
parsedSelector,
|
181
|
+
listenerTypes: getPossibleTypes(parsedSelector),
|
182
|
+
attributeCount: countClassAttributes(parsedSelector),
|
183
|
+
identifierCount: countIdentifiers(parsedSelector)
|
184
|
+
};
|
185
|
+
});
|
186
|
+
|
8
187
|
//------------------------------------------------------------------------------
|
9
188
|
// Public Interface
|
10
189
|
//------------------------------------------------------------------------------
|
@@ -24,10 +203,97 @@
|
|
24
203
|
class NodeEventGenerator {
|
25
204
|
|
26
205
|
/**
|
27
|
-
|
28
|
-
|
206
|
+
* @param {EventEmitter} emitter - An event emitter which is the destination of events. This emitter must already
|
207
|
+
* have registered listeners for all of the events that it needs to listen for.
|
208
|
+
* @returns {NodeEventGenerator} new instance
|
209
|
+
*/
|
29
210
|
constructor(emitter) {
|
30
211
|
this.emitter = emitter;
|
212
|
+
this.currentAncestry = [];
|
213
|
+
this.enterSelectorsByNodeType = new Map();
|
214
|
+
this.exitSelectorsByNodeType = new Map();
|
215
|
+
this.anyTypeEnterSelectors = [];
|
216
|
+
this.anyTypeExitSelectors = [];
|
217
|
+
|
218
|
+
const eventNames = typeof emitter.eventNames === "function"
|
219
|
+
|
220
|
+
// Use the built-in eventNames() function if available (Node 6+)
|
221
|
+
? emitter.eventNames()
|
222
|
+
|
223
|
+
/*
|
224
|
+
* Otherwise, use the private _events property.
|
225
|
+
* Using a private property isn't ideal here, but this seems to
|
226
|
+
* be the best way to get a list of event names without overriding
|
227
|
+
* addEventListener, which would hurt performance. This property
|
228
|
+
* is widely used and unlikely to be removed in a future version
|
229
|
+
* (see https://github.com/nodejs/node/issues/1817). Also, future
|
230
|
+
* node versions will have eventNames() anyway.
|
231
|
+
*/
|
232
|
+
: Object.keys(emitter._events); // eslint-disable-line no-underscore-dangle
|
233
|
+
|
234
|
+
eventNames.forEach(rawSelector => {
|
235
|
+
const selector = parseSelector(rawSelector);
|
236
|
+
|
237
|
+
if (selector.listenerTypes) {
|
238
|
+
selector.listenerTypes.forEach(nodeType => {
|
239
|
+
const typeMap = selector.isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType;
|
240
|
+
|
241
|
+
if (!typeMap.has(nodeType)) {
|
242
|
+
typeMap.set(nodeType, []);
|
243
|
+
}
|
244
|
+
typeMap.get(nodeType).push(selector);
|
245
|
+
});
|
246
|
+
} else {
|
247
|
+
(selector.isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors).push(selector);
|
248
|
+
}
|
249
|
+
});
|
250
|
+
|
251
|
+
this.anyTypeEnterSelectors.sort(compareSpecificity);
|
252
|
+
this.anyTypeExitSelectors.sort(compareSpecificity);
|
253
|
+
this.enterSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
|
254
|
+
this.exitSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
|
255
|
+
}
|
256
|
+
|
257
|
+
/**
|
258
|
+
* Checks a selector against a node, and emits it if it matches
|
259
|
+
* @param {ASTNode} node The node to check
|
260
|
+
* @param {ASTSelector} selector An AST selector descriptor
|
261
|
+
* @returns {void}
|
262
|
+
*/
|
263
|
+
applySelector(node, selector) {
|
264
|
+
if (esquery.matches(node, selector.parsedSelector, this.currentAncestry)) {
|
265
|
+
this.emitter.emit(selector.rawSelector, node);
|
266
|
+
}
|
267
|
+
}
|
268
|
+
|
269
|
+
/**
|
270
|
+
* Applies all appropriate selectors to a node, in specificity order
|
271
|
+
* @param {ASTNode} node The node to check
|
272
|
+
* @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited
|
273
|
+
* @returns {void}
|
274
|
+
*/
|
275
|
+
applySelectors(node, isExit) {
|
276
|
+
const selectorsByNodeType = (isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType).get(node.type) || [];
|
277
|
+
const anyTypeSelectors = isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors;
|
278
|
+
|
279
|
+
/*
|
280
|
+
* selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor.
|
281
|
+
* Iterate through each of them, applying selectors in the right order.
|
282
|
+
*/
|
283
|
+
let selectorsByTypeIndex = 0;
|
284
|
+
let anyTypeSelectorsIndex = 0;
|
285
|
+
|
286
|
+
while (selectorsByTypeIndex < selectorsByNodeType.length || anyTypeSelectorsIndex < anyTypeSelectors.length) {
|
287
|
+
if (
|
288
|
+
selectorsByTypeIndex >= selectorsByNodeType.length ||
|
289
|
+
anyTypeSelectorsIndex < anyTypeSelectors.length &&
|
290
|
+
compareSpecificity(anyTypeSelectors[anyTypeSelectorsIndex], selectorsByNodeType[selectorsByTypeIndex]) < 0
|
291
|
+
) {
|
292
|
+
this.applySelector(node, anyTypeSelectors[anyTypeSelectorsIndex++]);
|
293
|
+
} else {
|
294
|
+
this.applySelector(node, selectorsByNodeType[selectorsByTypeIndex++]);
|
295
|
+
}
|
296
|
+
}
|
31
297
|
}
|
32
298
|
|
33
299
|
/**
|
@@ -36,7 +302,10 @@ class NodeEventGenerator {
|
|
36
302
|
* @returns {void}
|
37
303
|
*/
|
38
304
|
enterNode(node) {
|
39
|
-
|
305
|
+
if (node.parent) {
|
306
|
+
this.currentAncestry.unshift(node.parent);
|
307
|
+
}
|
308
|
+
this.applySelectors(node, false);
|
40
309
|
}
|
41
310
|
|
42
311
|
/**
|
@@ -45,7 +314,8 @@ class NodeEventGenerator {
|
|
45
314
|
* @returns {void}
|
46
315
|
*/
|
47
316
|
leaveNode(node) {
|
48
|
-
this.
|
317
|
+
this.applySelectors(node, true);
|
318
|
+
this.currentAncestry.shift();
|
49
319
|
}
|
50
320
|
}
|
51
321
|
|
@@ -94,8 +94,8 @@ SourceCodeFixer.applyFixes = function(sourceCode, messages) {
|
|
94
94
|
const start = fix.range[0];
|
95
95
|
const end = fix.range[1];
|
96
96
|
|
97
|
-
// Remain it as a problem if it's overlapped
|
98
|
-
if (lastPos >= start) {
|
97
|
+
// Remain it as a problem if it's overlapped or it's a negative range
|
98
|
+
if (lastPos >= start || start > end) {
|
99
99
|
remainingMessages.push(problem);
|
100
100
|
continue;
|
101
101
|
}
|
package/lib/util/source-code.js
CHANGED
@@ -10,7 +10,8 @@
|
|
10
10
|
|
11
11
|
const TokenStore = require("../token-store"),
|
12
12
|
Traverser = require("./traverser"),
|
13
|
-
astUtils = require("../ast-utils")
|
13
|
+
astUtils = require("../ast-utils"),
|
14
|
+
lodash = require("lodash");
|
14
15
|
|
15
16
|
//------------------------------------------------------------------------------
|
16
17
|
// Private
|
@@ -138,7 +139,26 @@ function SourceCode(text, ast) {
|
|
138
139
|
* This is done to avoid each rule needing to do so separately.
|
139
140
|
* @type string[]
|
140
141
|
*/
|
141
|
-
this.lines =
|
142
|
+
this.lines = [];
|
143
|
+
this.lineStartIndices = [0];
|
144
|
+
|
145
|
+
const lineEndingPattern = astUtils.createGlobalLinebreakMatcher();
|
146
|
+
let match;
|
147
|
+
|
148
|
+
/*
|
149
|
+
* Previously, this was implemented using a regex that
|
150
|
+
* matched a sequence of non-linebreak characters followed by a
|
151
|
+
* linebreak, then adding the lengths of the matches. However,
|
152
|
+
* this caused a catastrophic backtracking issue when the end
|
153
|
+
* of a file contained a large number of non-newline characters.
|
154
|
+
* To avoid this, the current implementation just matches newlines
|
155
|
+
* and uses match.index to get the correct line start indices.
|
156
|
+
*/
|
157
|
+
while ((match = lineEndingPattern.exec(this.text))) {
|
158
|
+
this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1], match.index));
|
159
|
+
this.lineStartIndices.push(match.index + match[0].length);
|
160
|
+
}
|
161
|
+
this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1]));
|
142
162
|
|
143
163
|
this.tokensAndComments = sortedMerge(ast.tokens, ast.comments);
|
144
164
|
|
@@ -312,6 +332,83 @@ SourceCode.prototype = {
|
|
312
332
|
const text = this.text.slice(first.range[1], second.range[0]);
|
313
333
|
|
314
334
|
return /\s/.test(text.replace(/\/\*.*?\*\//g, ""));
|
335
|
+
},
|
336
|
+
|
337
|
+
/**
|
338
|
+
* Converts a source text index into a (line, column) pair.
|
339
|
+
* @param {number} index The index of a character in a file
|
340
|
+
* @returns {Object} A {line, column} location object with a 0-indexed column
|
341
|
+
*/
|
342
|
+
getLocFromIndex(index) {
|
343
|
+
if (typeof index !== "number") {
|
344
|
+
throw new TypeError("Expected `index` to be a number.");
|
345
|
+
}
|
346
|
+
|
347
|
+
if (index < 0 || index > this.text.length) {
|
348
|
+
throw new RangeError(`Index out of range (requested index ${index}, but source text has length ${this.text.length}).`);
|
349
|
+
}
|
350
|
+
|
351
|
+
/*
|
352
|
+
* For an argument of this.text.length, return the location one "spot" past the last character
|
353
|
+
* of the file. If the last character is a linebreak, the location will be column 0 of the next
|
354
|
+
* line; otherwise, the location will be in the next column on the same line.
|
355
|
+
*
|
356
|
+
* See getIndexFromLoc for the motivation for this special case.
|
357
|
+
*/
|
358
|
+
if (index === this.text.length) {
|
359
|
+
return { line: this.lines.length, column: this.lines[this.lines.length - 1].length };
|
360
|
+
}
|
361
|
+
|
362
|
+
/*
|
363
|
+
* To figure out which line rangeIndex is on, determine the last index at which rangeIndex could
|
364
|
+
* be inserted into lineIndices to keep the list sorted.
|
365
|
+
*/
|
366
|
+
const lineNumber = lodash.sortedLastIndex(this.lineStartIndices, index);
|
367
|
+
|
368
|
+
return { line: lineNumber, column: index - this.lineStartIndices[lineNumber - 1] };
|
369
|
+
|
370
|
+
},
|
371
|
+
|
372
|
+
/**
|
373
|
+
* Converts a (line, column) pair into a range index.
|
374
|
+
* @param {Object} loc A line/column location
|
375
|
+
* @param {number} loc.line The line number of the location (1-indexed)
|
376
|
+
* @param {number} loc.column The column number of the location (0-indexed)
|
377
|
+
* @returns {number} The range index of the location in the file.
|
378
|
+
*/
|
379
|
+
getIndexFromLoc(loc) {
|
380
|
+
if (typeof loc !== "object" || typeof loc.line !== "number" || typeof loc.column !== "number") {
|
381
|
+
throw new TypeError("Expected `loc` to be an object with numeric `line` and `column` properties.");
|
382
|
+
}
|
383
|
+
|
384
|
+
if (loc.line <= 0) {
|
385
|
+
throw new RangeError(`Line number out of range (line ${loc.line} requested). Line numbers should be 1-based.`);
|
386
|
+
}
|
387
|
+
|
388
|
+
if (loc.line > this.lineStartIndices.length) {
|
389
|
+
throw new RangeError(`Line number out of range (line ${loc.line} requested, but only ${this.lineStartIndices.length} lines present).`);
|
390
|
+
}
|
391
|
+
|
392
|
+
const lineStartIndex = this.lineStartIndices[loc.line - 1];
|
393
|
+
const lineEndIndex = loc.line === this.lineStartIndices.length ? this.text.length : this.lineStartIndices[loc.line];
|
394
|
+
const positionIndex = lineStartIndex + loc.column;
|
395
|
+
|
396
|
+
/*
|
397
|
+
* By design, getIndexFromLoc({ line: lineNum, column: 0 }) should return the start index of
|
398
|
+
* the given line, provided that the line number is valid element of this.lines. Since the
|
399
|
+
* last element of this.lines is an empty string for files with trailing newlines, add a
|
400
|
+
* special case where getting the index for the first location after the end of the file
|
401
|
+
* will return the length of the file, rather than throwing an error. This allows rules to
|
402
|
+
* use getIndexFromLoc consistently without worrying about edge cases at the end of a file.
|
403
|
+
*/
|
404
|
+
if (
|
405
|
+
loc.line === this.lineStartIndices.length && positionIndex > lineEndIndex ||
|
406
|
+
loc.line < this.lineStartIndices.length && positionIndex >= lineEndIndex
|
407
|
+
) {
|
408
|
+
throw new RangeError(`Column number out of range (column ${loc.column} requested, but the length of line ${loc.line} is ${lineEndIndex - lineStartIndex}).`);
|
409
|
+
}
|
410
|
+
|
411
|
+
return positionIndex;
|
315
412
|
}
|
316
413
|
};
|
317
414
|
|
package/lib/util/traverser.js
CHANGED
@@ -14,41 +14,32 @@ const estraverse = require("estraverse");
|
|
14
14
|
// Helpers
|
15
15
|
//------------------------------------------------------------------------------
|
16
16
|
|
17
|
-
const KEY_BLACKLIST = [
|
17
|
+
const KEY_BLACKLIST = new Set([
|
18
18
|
"parent",
|
19
19
|
"leadingComments",
|
20
20
|
"trailingComments"
|
21
|
-
];
|
21
|
+
]);
|
22
22
|
|
23
23
|
/**
|
24
24
|
* Wrapper around an estraverse controller that ensures the correct keys
|
25
25
|
* are visited.
|
26
26
|
* @constructor
|
27
27
|
*/
|
28
|
-
|
29
|
-
|
30
|
-
const controller = Object.create(new estraverse.Controller()),
|
31
|
-
originalTraverse = controller.traverse;
|
32
|
-
|
33
|
-
// intercept call to traverse() and add the fallback key to the visitor
|
34
|
-
controller.traverse = function(node, visitor) {
|
28
|
+
class Traverser extends estraverse.Controller {
|
29
|
+
traverse(node, visitor) {
|
35
30
|
visitor.fallback = Traverser.getKeys;
|
36
|
-
return
|
37
|
-
}
|
38
|
-
|
39
|
-
|
31
|
+
return super.traverse(node, visitor);
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Calculates the keys to use for traversal.
|
36
|
+
* @param {ASTNode} node The node to read keys from.
|
37
|
+
* @returns {string[]} An array of keys to visit on the node.
|
38
|
+
* @private
|
39
|
+
*/
|
40
|
+
static getKeys(node) {
|
41
|
+
return Object.keys(node).filter(key => !KEY_BLACKLIST.has(key));
|
42
|
+
}
|
40
43
|
}
|
41
44
|
|
42
|
-
/**
|
43
|
-
* Calculates the keys to use for traversal.
|
44
|
-
* @param {ASTNode} node The node to read keys from.
|
45
|
-
* @returns {string[]} An array of keys to visit on the node.
|
46
|
-
* @private
|
47
|
-
*/
|
48
|
-
Traverser.getKeys = function(node) {
|
49
|
-
return Object.keys(node).filter(key => KEY_BLACKLIST.indexOf(key) === -1);
|
50
|
-
};
|
51
|
-
|
52
45
|
module.exports = Traverser;
|
53
|
-
|
54
|
-
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.18.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -36,11 +36,12 @@
|
|
36
36
|
"dependencies": {
|
37
37
|
"babel-code-frame": "^6.16.0",
|
38
38
|
"chalk": "^1.1.3",
|
39
|
-
"concat-stream": "^1.
|
39
|
+
"concat-stream": "^1.5.2",
|
40
40
|
"debug": "^2.1.1",
|
41
|
-
"doctrine": "^
|
41
|
+
"doctrine": "^2.0.0",
|
42
42
|
"escope": "^3.6.0",
|
43
43
|
"espree": "^3.4.0",
|
44
|
+
"esquery": "^1.0.0",
|
44
45
|
"estraverse": "^4.2.0",
|
45
46
|
"esutils": "^2.0.2",
|
46
47
|
"file-entry-cache": "^2.0.0",
|
@@ -78,14 +79,14 @@
|
|
78
79
|
"browserify": "^12.0.1",
|
79
80
|
"chai": "^3.5.0",
|
80
81
|
"cheerio": "^0.19.0",
|
81
|
-
"coveralls": "2.11.
|
82
|
+
"coveralls": "^2.11.16",
|
82
83
|
"dateformat": "^1.0.8",
|
83
84
|
"ejs": "^2.3.3",
|
84
|
-
"eslint-plugin-
|
85
|
+
"eslint-plugin-eslint-plugin": "^0.7.1",
|
86
|
+
"eslint-plugin-node": "^4.1.0",
|
85
87
|
"eslint-release": "^0.10.0",
|
86
88
|
"esprima": "^2.4.1",
|
87
89
|
"esprima-fb": "^15001.1001.0-dev-harmony-fb",
|
88
|
-
"gh-got": "^2.2.0",
|
89
90
|
"istanbul": "^0.4.0",
|
90
91
|
"jsdoc": "^3.3.0-beta1",
|
91
92
|
"karma": "^0.13.22",
|
@@ -94,11 +95,10 @@
|
|
94
95
|
"karma-mocha-reporter": "^2.0.3",
|
95
96
|
"karma-phantomjs-launcher": "^1.0.0",
|
96
97
|
"leche": "^2.1.1",
|
97
|
-
"linefix": "^0.1.1",
|
98
98
|
"load-perf": "^0.2.0",
|
99
99
|
"markdownlint": "^0.3.1",
|
100
100
|
"mocha": "^2.4.5",
|
101
|
-
"mock-fs": "^4.
|
101
|
+
"mock-fs": "^4.2.0",
|
102
102
|
"npm-license": "^0.3.2",
|
103
103
|
"phantomjs-prebuilt": "^2.1.7",
|
104
104
|
"proxyquire": "^1.7.10",
|