eslint 3.16.1 → 3.19.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 +103 -0
- package/README.md +1 -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/config/config-rule.js +14 -10
- package/lib/config/plugins.js +19 -8
- package/lib/eslint.js +11 -10
- package/lib/formatters/codeframe.js +4 -9
- 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/arrow-body-style.js +2 -2
- package/lib/rules/arrow-parens.js +9 -3
- package/lib/rules/capitalized-comments.js +2 -1
- package/lib/rules/comma-dangle.js +3 -2
- package/lib/rules/comma-spacing.js +4 -14
- package/lib/rules/comma-style.js +8 -14
- package/lib/rules/complexity.js +14 -8
- package/lib/rules/consistent-return.js +17 -10
- package/lib/rules/curly.js +2 -2
- package/lib/rules/dot-notation.js +12 -6
- 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/lines-around-directive.js +1 -1
- package/lib/rules/max-params.js +17 -4
- package/lib/rules/max-statements.js +11 -10
- package/lib/rules/new-parens.js +7 -21
- package/lib/rules/no-compare-neg-zero.js +53 -0
- package/lib/rules/no-cond-assign.js +4 -17
- package/lib/rules/no-else-return.js +19 -4
- package/lib/rules/no-empty-function.js +9 -16
- package/lib/rules/no-extra-parens.js +110 -121
- package/lib/rules/no-extra-semi.js +16 -3
- package/lib/rules/no-global-assign.js +1 -1
- package/lib/rules/no-implicit-coercion.js +21 -8
- package/lib/rules/no-invalid-regexp.js +2 -1
- package/lib/rules/no-multiple-empty-lines.js +2 -4
- package/lib/rules/no-native-reassign.js +1 -1
- package/lib/rules/no-negated-in-lhs.js +1 -1
- package/lib/rules/no-new-func.js +6 -8
- package/lib/rules/no-new.js +2 -6
- package/lib/rules/no-param-reassign.js +37 -7
- 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 +32 -21
- package/lib/rules/no-return-await.js +1 -1
- package/lib/rules/no-sequences.js +2 -2
- package/lib/rules/no-sync.js +8 -13
- package/lib/rules/no-unsafe-negation.js +1 -1
- 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-computed-key.js +12 -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-curly-spacing.js +2 -2
- package/lib/rules/object-shorthand.js +10 -3
- package/lib/rules/operator-assignment.js +20 -3
- package/lib/rules/padded-blocks.js +37 -31
- package/lib/rules/prefer-const.js +1 -1
- package/lib/rules/prefer-destructuring.js +1 -1
- package/lib/rules/quotes.js +1 -0
- package/lib/rules/semi-spacing.js +2 -15
- package/lib/rules/semi.js +17 -13
- package/lib/rules/sort-vars.js +3 -5
- package/lib/rules/space-before-function-paren.js +53 -77
- package/lib/rules/space-in-parens.js +4 -8
- 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/glob-util.js +1 -1
- package/lib/util/node-event-generator.js +274 -4
- package/lib/util/source-code-fixer.js +3 -9
- package/lib/util/source-code.js +99 -2
- package/lib/util/traverser.js +16 -25
- package/package.json +34 -34
@@ -343,12 +343,13 @@ RuleTester.prototype = {
|
|
343
343
|
* running the rule under test.
|
344
344
|
*/
|
345
345
|
eslint.reset();
|
346
|
+
|
346
347
|
eslint.on("Program", node => {
|
347
348
|
beforeAST = cloneDeeplyExcludesParent(node);
|
349
|
+
});
|
348
350
|
|
349
|
-
|
350
|
-
|
351
|
-
});
|
351
|
+
eslint.on("Program:exit", node => {
|
352
|
+
afterAST = node;
|
352
353
|
});
|
353
354
|
|
354
355
|
// Freezes rule-context properties.
|
@@ -385,7 +386,7 @@ RuleTester.prototype = {
|
|
385
386
|
return {
|
386
387
|
messages: eslint.verify(code, config, filename, true),
|
387
388
|
beforeAST,
|
388
|
-
afterAST
|
389
|
+
afterAST: cloneDeeplyExcludesParent(afterAST)
|
389
390
|
};
|
390
391
|
} finally {
|
391
392
|
rules.get = originalGet;
|
@@ -425,6 +426,28 @@ RuleTester.prototype = {
|
|
425
426
|
assertASTDidntChange(result.beforeAST, result.afterAST);
|
426
427
|
}
|
427
428
|
|
429
|
+
/**
|
430
|
+
* Asserts that the message matches its expected value. If the expected
|
431
|
+
* value is a regular expression, it is checked against the actual
|
432
|
+
* value.
|
433
|
+
* @param {string} actual Actual value
|
434
|
+
* @param {string|RegExp} expected Expected value
|
435
|
+
* @returns {void}
|
436
|
+
* @private
|
437
|
+
*/
|
438
|
+
function assertMessageMatches(actual, expected) {
|
439
|
+
if (expected instanceof RegExp) {
|
440
|
+
|
441
|
+
// assert.js doesn't have a built-in RegExp match function
|
442
|
+
assert.ok(
|
443
|
+
expected.test(actual),
|
444
|
+
`Expected '${actual}' to match ${expected}`
|
445
|
+
);
|
446
|
+
} else {
|
447
|
+
assert.equal(actual, expected);
|
448
|
+
}
|
449
|
+
}
|
450
|
+
|
428
451
|
/**
|
429
452
|
* Check if the template is invalid or not
|
430
453
|
* all invalid cases go through this.
|
@@ -454,10 +477,10 @@ RuleTester.prototype = {
|
|
454
477
|
assert.ok(!("fatal" in messages[i]), `A fatal parsing error occurred: ${messages[i].message}`);
|
455
478
|
assert.equal(messages[i].ruleId, ruleName, "Error rule name should be the same as the name of the rule being tested");
|
456
479
|
|
457
|
-
if (typeof item.errors[i] === "string") {
|
480
|
+
if (typeof item.errors[i] === "string" || item.errors[i] instanceof RegExp) {
|
458
481
|
|
459
482
|
// Just an error message.
|
460
|
-
|
483
|
+
assertMessageMatches(messages[i].message, item.errors[i]);
|
461
484
|
} else if (typeof item.errors[i] === "object") {
|
462
485
|
|
463
486
|
/*
|
@@ -466,7 +489,7 @@ RuleTester.prototype = {
|
|
466
489
|
* column.
|
467
490
|
*/
|
468
491
|
if (item.errors[i].message) {
|
469
|
-
|
492
|
+
assertMessageMatches(messages[i].message, item.errors[i].message);
|
470
493
|
}
|
471
494
|
|
472
495
|
if (item.errors[i].type) {
|
@@ -490,16 +513,24 @@ RuleTester.prototype = {
|
|
490
513
|
}
|
491
514
|
} else {
|
492
515
|
|
493
|
-
//
|
494
|
-
assert.fail(messages[i], null, "Error should be a string or
|
516
|
+
// Message was an unexpected type
|
517
|
+
assert.fail(messages[i], null, "Error should be a string, object, or RegExp.");
|
495
518
|
}
|
496
519
|
}
|
497
520
|
}
|
498
521
|
|
499
522
|
if (item.hasOwnProperty("output")) {
|
500
|
-
|
501
|
-
|
502
|
-
|
523
|
+
if (item.output === null) {
|
524
|
+
assert.strictEqual(
|
525
|
+
messages.filter(message => message.fix).length,
|
526
|
+
0,
|
527
|
+
"Expected no autofixes to be suggested"
|
528
|
+
);
|
529
|
+
} else {
|
530
|
+
const fixResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages);
|
531
|
+
|
532
|
+
assert.equal(fixResult.output, item.output, "Output is incorrect.");
|
533
|
+
}
|
503
534
|
}
|
504
535
|
|
505
536
|
assertASTDidntChange(result.beforeAST, result.afterAST);
|
@@ -512,7 +543,7 @@ RuleTester.prototype = {
|
|
512
543
|
RuleTester.describe(ruleName, () => {
|
513
544
|
RuleTester.describe("valid", () => {
|
514
545
|
test.valid.forEach(valid => {
|
515
|
-
RuleTester.it(valid.code
|
546
|
+
RuleTester.it(typeof valid === "object" ? valid.code : valid, () => {
|
516
547
|
eslint.defineRules(this.rules);
|
517
548
|
testValidTemplate(ruleName, valid);
|
518
549
|
});
|
@@ -0,0 +1,121 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Helper class to aid in constructing fix commands.
|
3
|
+
* @author Alan Pierce
|
4
|
+
*/
|
5
|
+
"use strict";
|
6
|
+
|
7
|
+
//------------------------------------------------------------------------------
|
8
|
+
// Requirements
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
|
11
|
+
const astUtils = require("../ast-utils");
|
12
|
+
|
13
|
+
//------------------------------------------------------------------------------
|
14
|
+
// Public Interface
|
15
|
+
//------------------------------------------------------------------------------
|
16
|
+
|
17
|
+
/**
|
18
|
+
* A helper class to combine fix options into a fix command. Currently, it
|
19
|
+
* exposes some "retain" methods that extend the range of the text being
|
20
|
+
* replaced so that other fixes won't touch that region in the same pass.
|
21
|
+
*/
|
22
|
+
class FixTracker {
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Create a new FixTracker.
|
26
|
+
*
|
27
|
+
* @param {ruleFixer} fixer A ruleFixer instance.
|
28
|
+
* @param {SourceCode} sourceCode A SourceCode object for the current code.
|
29
|
+
*/
|
30
|
+
constructor(fixer, sourceCode) {
|
31
|
+
this.fixer = fixer;
|
32
|
+
this.sourceCode = sourceCode;
|
33
|
+
this.retainedRange = null;
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Mark the given range as "retained", meaning that other fixes may not
|
38
|
+
* may not modify this region in the same pass.
|
39
|
+
*
|
40
|
+
* @param {int[]} range The range to retain.
|
41
|
+
* @returns {FixTracker} The same RuleFixer, for chained calls.
|
42
|
+
*/
|
43
|
+
retainRange(range) {
|
44
|
+
this.retainedRange = range;
|
45
|
+
return this;
|
46
|
+
}
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Given a node, find the function containing it (or the entire program) and
|
50
|
+
* mark it as retained, meaning that other fixes may not modify it in this
|
51
|
+
* pass. This is useful for avoiding conflicts in fixes that modify control
|
52
|
+
* flow.
|
53
|
+
*
|
54
|
+
* @param {ASTNode} node The node to use as a starting point.
|
55
|
+
* @returns {FixTracker} The same RuleFixer, for chained calls.
|
56
|
+
*/
|
57
|
+
retainEnclosingFunction(node) {
|
58
|
+
const functionNode = astUtils.getUpperFunction(node);
|
59
|
+
|
60
|
+
return this.retainRange(
|
61
|
+
functionNode ? functionNode.range : this.sourceCode.ast.range);
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Given a node or token, find the token before and afterward, and mark that
|
66
|
+
* range as retained, meaning that other fixes may not modify it in this
|
67
|
+
* pass. This is useful for avoiding conflicts in fixes that make a small
|
68
|
+
* change to the code where the AST should not be changed.
|
69
|
+
*
|
70
|
+
* @param {ASTNode|Token} nodeOrToken The node or token to use as a starting
|
71
|
+
* point. The token to the left and right are use in the range.
|
72
|
+
* @returns {FixTracker} The same RuleFixer, for chained calls.
|
73
|
+
*/
|
74
|
+
retainSurroundingTokens(nodeOrToken) {
|
75
|
+
const tokenBefore = this.sourceCode.getTokenBefore(nodeOrToken) || nodeOrToken;
|
76
|
+
const tokenAfter = this.sourceCode.getTokenAfter(nodeOrToken) || nodeOrToken;
|
77
|
+
|
78
|
+
return this.retainRange([tokenBefore.range[0], tokenAfter.range[1]]);
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Create a fix command that replaces the given range with the given text,
|
83
|
+
* accounting for any retained ranges.
|
84
|
+
*
|
85
|
+
* @param {int[]} range The range to remove in the fix.
|
86
|
+
* @param {string} text The text to insert in place of the range.
|
87
|
+
* @returns {Object} The fix command.
|
88
|
+
*/
|
89
|
+
replaceTextRange(range, text) {
|
90
|
+
let actualRange;
|
91
|
+
|
92
|
+
if (this.retainedRange) {
|
93
|
+
actualRange = [
|
94
|
+
Math.min(this.retainedRange[0], range[0]),
|
95
|
+
Math.max(this.retainedRange[1], range[1])
|
96
|
+
];
|
97
|
+
} else {
|
98
|
+
actualRange = range;
|
99
|
+
}
|
100
|
+
|
101
|
+
return this.fixer.replaceTextRange(
|
102
|
+
actualRange,
|
103
|
+
this.sourceCode.text.slice(actualRange[0], range[0]) +
|
104
|
+
text +
|
105
|
+
this.sourceCode.text.slice(range[1], actualRange[1])
|
106
|
+
);
|
107
|
+
}
|
108
|
+
|
109
|
+
/**
|
110
|
+
* Create a fix command that removes the given node or token, accounting for
|
111
|
+
* any retained ranges.
|
112
|
+
*
|
113
|
+
* @param {ASTNode|Token} nodeOrToken The node or token to remove.
|
114
|
+
* @returns {Object} The fix command.
|
115
|
+
*/
|
116
|
+
remove(nodeOrToken) {
|
117
|
+
return this.replaceTextRange(nodeOrToken.range, "");
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
module.exports = FixTracker;
|
package/lib/util/glob-util.js
CHANGED
@@ -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
|
}
|
@@ -108,13 +108,7 @@ SourceCodeFixer.applyFixes = function(sourceCode, messages) {
|
|
108
108
|
// Make output to this fix.
|
109
109
|
output += text.slice(Math.max(0, lastPos), Math.max(0, start));
|
110
110
|
output += fix.text;
|
111
|
-
|
112
|
-
/*
|
113
|
-
* If the start of the range is larger than the end for some reason, make sure
|
114
|
-
* the text between the end and the start doesn't get duplicated.
|
115
|
-
* https://github.com/eslint/eslint/issues/8116
|
116
|
-
*/
|
117
|
-
lastPos = Math.max(start, end);
|
111
|
+
lastPos = end;
|
118
112
|
}
|
119
113
|
output += text.slice(Math.max(0, lastPos));
|
120
114
|
|