eslint 4.7.1 → 4.10.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 +113 -0
- package/README.md +34 -19
- package/conf/default-cli-options.js +2 -1
- package/conf/eslint-recommended.js +2 -0
- package/lib/ast-utils.js +2 -1
- package/lib/cli-engine.js +26 -5
- package/lib/cli.js +17 -9
- package/lib/code-path-analysis/code-path-segment.js +39 -39
- package/lib/code-path-analysis/code-path-state.js +3 -0
- package/lib/formatters/html-template-message.html +1 -1
- package/lib/formatters/html-template-page.html +3 -1
- package/lib/formatters/html.js +2 -1
- package/lib/ignored-paths.js +1 -1
- package/lib/linter.js +43 -71
- package/lib/logging.js +2 -2
- package/lib/options.js +12 -0
- package/lib/rules/array-bracket-newline.js +19 -5
- package/lib/rules/block-spacing.js +1 -1
- package/lib/rules/callback-return.js +2 -1
- package/lib/rules/capitalized-comments.js +2 -1
- package/lib/rules/comma-style.js +3 -1
- package/lib/rules/dot-notation.js +56 -35
- package/lib/rules/generator-star-spacing.js +3 -3
- package/lib/rules/indent-legacy.js +5 -2
- package/lib/rules/indent.js +25 -19
- package/lib/rules/lines-around-comment.js +33 -4
- package/lib/rules/lines-between-class-members.js +91 -0
- package/lib/rules/max-len.js +2 -3
- package/lib/rules/multiline-comment-style.js +294 -0
- package/lib/rules/new-cap.js +2 -1
- package/lib/rules/newline-before-return.js +4 -2
- package/lib/rules/no-alert.js +7 -15
- package/lib/rules/no-catch-shadow.js +1 -1
- package/lib/rules/no-constant-condition.js +2 -2
- package/lib/rules/no-control-regex.js +2 -1
- package/lib/rules/no-else-return.js +43 -8
- package/lib/rules/no-extra-parens.js +6 -3
- package/lib/rules/no-lonely-if.js +2 -1
- package/lib/rules/no-loop-func.js +2 -3
- package/lib/rules/no-mixed-requires.js +8 -4
- package/lib/rules/no-restricted-imports.js +86 -17
- package/lib/rules/no-restricted-modules.js +84 -15
- package/lib/rules/no-trailing-spaces.js +1 -1
- package/lib/rules/no-unneeded-ternary.js +3 -1
- package/lib/rules/no-unused-labels.js +2 -1
- package/lib/rules/no-useless-computed-key.js +2 -1
- package/lib/rules/no-useless-escape.js +8 -1
- package/lib/rules/no-var.js +11 -0
- package/lib/rules/object-shorthand.js +6 -2
- package/lib/rules/operator-linebreak.js +3 -1
- package/lib/rules/padding-line-between-statements.js +2 -2
- package/lib/rules/require-jsdoc.js +11 -18
- package/lib/rules/sort-imports.js +6 -3
- package/lib/rules/space-unary-ops.js +6 -8
- package/lib/rules/valid-jsdoc.js +39 -33
- package/lib/testers/rule-tester.js +20 -6
- package/lib/util/apply-disable-directives.js +56 -27
- package/lib/util/node-event-generator.js +6 -20
- package/lib/util/safe-emitter.js +54 -0
- package/lib/util/source-code.js +73 -67
- package/messages/no-config-found.txt +1 -1
- package/package.json +3 -4
- package/lib/internal-rules/.eslintrc.yml +0 -3
- package/lib/internal-rules/internal-consistent-docs-description.js +0 -130
- package/lib/internal-rules/internal-no-invalid-meta.js +0 -188
package/lib/rules/no-var.js
CHANGED
@@ -15,6 +15,15 @@ const astUtils = require("../ast-utils");
|
|
15
15
|
// Helpers
|
16
16
|
//------------------------------------------------------------------------------
|
17
17
|
|
18
|
+
/**
|
19
|
+
* Check whether a given variable is a global variable or not.
|
20
|
+
* @param {eslint-scope.Variable} variable The variable to check.
|
21
|
+
* @returns {boolean} `true` if the variable is a global variable.
|
22
|
+
*/
|
23
|
+
function isGlobal(variable) {
|
24
|
+
return Boolean(variable.scope) && variable.scope.type === "global";
|
25
|
+
}
|
26
|
+
|
18
27
|
/**
|
19
28
|
* Finds the nearest function scope or global scope walking up the scope
|
20
29
|
* hierarchy.
|
@@ -203,6 +212,7 @@ module.exports = {
|
|
203
212
|
* Checks whether it can fix a given variable declaration or not.
|
204
213
|
* It cannot fix if the following cases:
|
205
214
|
*
|
215
|
+
* - A variable is a global variable.
|
206
216
|
* - A variable is declared on a SwitchCase node.
|
207
217
|
* - A variable is redeclared.
|
208
218
|
* - A variable is used from outside the scope.
|
@@ -256,6 +266,7 @@ module.exports = {
|
|
256
266
|
|
257
267
|
if (node.parent.type === "SwitchCase" ||
|
258
268
|
node.declarations.some(hasSelfReferenceInTDZ) ||
|
269
|
+
variables.some(isGlobal) ||
|
259
270
|
variables.some(isRedeclared) ||
|
260
271
|
variables.some(isUsedFromOutsideOf(scopeNode))
|
261
272
|
) {
|
@@ -215,8 +215,12 @@ module.exports = {
|
|
215
215
|
* @returns {Object} A fix for this node
|
216
216
|
*/
|
217
217
|
function makeFunctionShorthand(fixer, node) {
|
218
|
-
const firstKeyToken = node.computed
|
219
|
-
|
218
|
+
const firstKeyToken = node.computed
|
219
|
+
? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken)
|
220
|
+
: sourceCode.getFirstToken(node.key);
|
221
|
+
const lastKeyToken = node.computed
|
222
|
+
? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken)
|
223
|
+
: sourceCode.getLastToken(node.key);
|
220
224
|
const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]);
|
221
225
|
let keyPrefix = "";
|
222
226
|
|
@@ -87,7 +87,9 @@ module.exports = {
|
|
87
87
|
if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
|
88
88
|
|
89
89
|
// If there is a comment before and after the operator, don't do a fix.
|
90
|
-
if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore &&
|
90
|
+
if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore &&
|
91
|
+
sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
|
92
|
+
|
91
93
|
return null;
|
92
94
|
}
|
93
95
|
|
@@ -205,14 +205,14 @@ function verifyForAny() {
|
|
205
205
|
* blank lines automatically.
|
206
206
|
*
|
207
207
|
* @param {RuleContext} context The rule context to report.
|
208
|
-
* @param {ASTNode}
|
208
|
+
* @param {ASTNode} _ Unused. The previous node to check.
|
209
209
|
* @param {ASTNode} nextNode The next node to check.
|
210
210
|
* @param {Array<Token[]>} paddingLines The array of token pairs that blank
|
211
211
|
* lines exist between the pair.
|
212
212
|
* @returns {void}
|
213
213
|
* @private
|
214
214
|
*/
|
215
|
-
function verifyForNever(context,
|
215
|
+
function verifyForNever(context, _, nextNode, paddingLines) {
|
216
216
|
if (paddingLines.length === 0) {
|
217
217
|
return;
|
218
218
|
}
|
@@ -30,6 +30,9 @@ module.exports = {
|
|
30
30
|
},
|
31
31
|
ArrowFunctionExpression: {
|
32
32
|
type: "boolean"
|
33
|
+
},
|
34
|
+
FunctionExpression: {
|
35
|
+
type: "boolean"
|
33
36
|
}
|
34
37
|
},
|
35
38
|
additionalProperties: false
|
@@ -45,7 +48,9 @@ module.exports = {
|
|
45
48
|
const DEFAULT_OPTIONS = {
|
46
49
|
FunctionDeclaration: true,
|
47
50
|
MethodDefinition: false,
|
48
|
-
ClassDeclaration: false
|
51
|
+
ClassDeclaration: false,
|
52
|
+
ArrowFunctionExpression: false,
|
53
|
+
FunctionExpression: false
|
49
54
|
};
|
50
55
|
const options = Object.assign(DEFAULT_OPTIONS, context.options[0] && context.options[0].require || {});
|
51
56
|
|
@@ -58,21 +63,6 @@ module.exports = {
|
|
58
63
|
context.report({ node, message: "Missing JSDoc comment." });
|
59
64
|
}
|
60
65
|
|
61
|
-
/**
|
62
|
-
* Check if the jsdoc comment is present for class methods
|
63
|
-
* @param {ASTNode} node node to examine
|
64
|
-
* @returns {void}
|
65
|
-
*/
|
66
|
-
function checkClassMethodJsDoc(node) {
|
67
|
-
if (node.parent.type === "MethodDefinition") {
|
68
|
-
const jsdocComment = source.getJSDocComment(node);
|
69
|
-
|
70
|
-
if (!jsdocComment) {
|
71
|
-
report(node);
|
72
|
-
}
|
73
|
-
}
|
74
|
-
}
|
75
|
-
|
76
66
|
/**
|
77
67
|
* Check if the jsdoc comment is present or not.
|
78
68
|
* @param {ASTNode} node node to examine
|
@@ -93,8 +83,11 @@ module.exports = {
|
|
93
83
|
}
|
94
84
|
},
|
95
85
|
FunctionExpression(node) {
|
96
|
-
if (
|
97
|
-
|
86
|
+
if (
|
87
|
+
(options.MethodDefinition && node.parent.type === "MethodDefinition") ||
|
88
|
+
(options.FunctionExpression && (node.parent.type === "VariableDeclarator" || (node.parent.type === "Property" && node === node.parent.value)))
|
89
|
+
) {
|
90
|
+
checkJsDoc(node);
|
98
91
|
}
|
99
92
|
},
|
100
93
|
ClassDeclaration(node) {
|
@@ -67,9 +67,11 @@ module.exports = {
|
|
67
67
|
function usedMemberSyntax(node) {
|
68
68
|
if (node.specifiers.length === 0) {
|
69
69
|
return "none";
|
70
|
-
}
|
70
|
+
}
|
71
|
+
if (node.specifiers[0].type === "ImportNamespaceSpecifier") {
|
71
72
|
return "all";
|
72
|
-
}
|
73
|
+
}
|
74
|
+
if (node.specifiers.length === 1) {
|
73
75
|
return "single";
|
74
76
|
}
|
75
77
|
return "multiple";
|
@@ -149,7 +151,8 @@ module.exports = {
|
|
149
151
|
message: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.",
|
150
152
|
data: { memberName: importSpecifiers[firstUnsortedIndex].local.name },
|
151
153
|
fix(fixer) {
|
152
|
-
if (importSpecifiers.some(specifier =>
|
154
|
+
if (importSpecifiers.some(specifier =>
|
155
|
+
sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) {
|
153
156
|
|
154
157
|
// If there are comments in the ImportSpecifier list, don't rearrange the specifiers.
|
155
158
|
return null;
|
@@ -76,21 +76,19 @@ module.exports = {
|
|
76
76
|
|
77
77
|
/**
|
78
78
|
* Checks if an override exists for a given operator.
|
79
|
-
* @param {ASTnode} node AST node
|
80
79
|
* @param {string} operator Operator
|
81
80
|
* @returns {boolean} Whether or not an override has been provided for the operator
|
82
81
|
*/
|
83
|
-
function overrideExistsForOperator(
|
82
|
+
function overrideExistsForOperator(operator) {
|
84
83
|
return options.overrides && options.overrides.hasOwnProperty(operator);
|
85
84
|
}
|
86
85
|
|
87
86
|
/**
|
88
87
|
* Gets the value that the override was set to for this operator
|
89
|
-
* @param {ASTnode} node AST node
|
90
88
|
* @param {string} operator Operator
|
91
89
|
* @returns {boolean} Whether or not an override enforces a space with this operator
|
92
90
|
*/
|
93
|
-
function overrideEnforcesSpaces(
|
91
|
+
function overrideEnforcesSpaces(operator) {
|
94
92
|
return options.overrides[operator];
|
95
93
|
}
|
96
94
|
|
@@ -153,8 +151,8 @@ module.exports = {
|
|
153
151
|
function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) {
|
154
152
|
word = word || firstToken.value;
|
155
153
|
|
156
|
-
if (overrideExistsForOperator(
|
157
|
-
if (overrideEnforcesSpaces(
|
154
|
+
if (overrideExistsForOperator(word)) {
|
155
|
+
if (overrideEnforcesSpaces(word)) {
|
158
156
|
verifyWordHasSpaces(node, firstToken, secondToken, word);
|
159
157
|
} else {
|
160
158
|
verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
|
@@ -292,8 +290,8 @@ module.exports = {
|
|
292
290
|
|
293
291
|
const operator = node.prefix ? tokens[0].value : tokens[1].value;
|
294
292
|
|
295
|
-
if (overrideExistsForOperator(
|
296
|
-
if (overrideEnforcesSpaces(
|
293
|
+
if (overrideExistsForOperator(operator)) {
|
294
|
+
if (overrideEnforcesSpaces(operator)) {
|
297
295
|
verifyNonWordsHaveSpaces(node, firstToken, secondToken);
|
298
296
|
} else {
|
299
297
|
verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
|
package/lib/rules/valid-jsdoc.js
CHANGED
@@ -226,8 +226,10 @@ module.exports = {
|
|
226
226
|
function checkJSDoc(node) {
|
227
227
|
const jsdocNode = sourceCode.getJSDocComment(node),
|
228
228
|
functionData = fns.pop(),
|
229
|
-
params = Object.create(null)
|
229
|
+
params = Object.create(null),
|
230
|
+
paramsTags = [];
|
230
231
|
let hasReturns = false,
|
232
|
+
returnsTag,
|
231
233
|
hasConstructor = false,
|
232
234
|
isInterface = false,
|
233
235
|
isOverride = false,
|
@@ -261,43 +263,13 @@ module.exports = {
|
|
261
263
|
case "param":
|
262
264
|
case "arg":
|
263
265
|
case "argument":
|
264
|
-
|
265
|
-
context.report({ node: jsdocNode, message: "Missing JSDoc parameter type for '{{name}}'.", data: { name: tag.name } });
|
266
|
-
}
|
267
|
-
|
268
|
-
if (!tag.description && requireParamDescription) {
|
269
|
-
context.report({ node: jsdocNode, message: "Missing JSDoc parameter description for '{{name}}'.", data: { name: tag.name } });
|
270
|
-
}
|
271
|
-
|
272
|
-
if (params[tag.name]) {
|
273
|
-
context.report({ node: jsdocNode, message: "Duplicate JSDoc parameter '{{name}}'.", data: { name: tag.name } });
|
274
|
-
} else if (tag.name.indexOf(".") === -1) {
|
275
|
-
params[tag.name] = 1;
|
276
|
-
}
|
266
|
+
paramsTags.push(tag);
|
277
267
|
break;
|
278
268
|
|
279
269
|
case "return":
|
280
270
|
case "returns":
|
281
271
|
hasReturns = true;
|
282
|
-
|
283
|
-
if (!requireReturn && !functionData.returnPresent && (tag.type === null || !isValidReturnType(tag)) && !isAbstract) {
|
284
|
-
context.report({
|
285
|
-
node: jsdocNode,
|
286
|
-
message: "Unexpected @{{title}} tag; function has no return statement.",
|
287
|
-
data: {
|
288
|
-
title: tag.title
|
289
|
-
}
|
290
|
-
});
|
291
|
-
} else {
|
292
|
-
if (requireReturnType && !tag.type) {
|
293
|
-
context.report({ node: jsdocNode, message: "Missing JSDoc return type." });
|
294
|
-
}
|
295
|
-
|
296
|
-
if (!isValidReturnType(tag) && !tag.description && requireReturnDescription) {
|
297
|
-
context.report({ node: jsdocNode, message: "Missing JSDoc return description." });
|
298
|
-
}
|
299
|
-
}
|
300
|
-
|
272
|
+
returnsTag = tag;
|
301
273
|
break;
|
302
274
|
|
303
275
|
case "constructor":
|
@@ -333,6 +305,40 @@ module.exports = {
|
|
333
305
|
}
|
334
306
|
});
|
335
307
|
|
308
|
+
paramsTags.forEach(param => {
|
309
|
+
if (!param.type) {
|
310
|
+
context.report({ node: jsdocNode, message: "Missing JSDoc parameter type for '{{name}}'.", data: { name: param.name } });
|
311
|
+
}
|
312
|
+
if (!param.description && requireParamDescription) {
|
313
|
+
context.report({ node: jsdocNode, message: "Missing JSDoc parameter description for '{{name}}'.", data: { name: param.name } });
|
314
|
+
}
|
315
|
+
if (params[param.name]) {
|
316
|
+
context.report({ node: jsdocNode, message: "Duplicate JSDoc parameter '{{name}}'.", data: { name: param.name } });
|
317
|
+
} else if (param.name.indexOf(".") === -1) {
|
318
|
+
params[param.name] = 1;
|
319
|
+
}
|
320
|
+
});
|
321
|
+
|
322
|
+
if (hasReturns) {
|
323
|
+
if (!requireReturn && !functionData.returnPresent && (returnsTag.type === null || !isValidReturnType(returnsTag)) && !isAbstract) {
|
324
|
+
context.report({
|
325
|
+
node: jsdocNode,
|
326
|
+
message: "Unexpected @{{title}} tag; function has no return statement.",
|
327
|
+
data: {
|
328
|
+
title: returnsTag.title
|
329
|
+
}
|
330
|
+
});
|
331
|
+
} else {
|
332
|
+
if (requireReturnType && !returnsTag.type) {
|
333
|
+
context.report({ node: jsdocNode, message: "Missing JSDoc return type." });
|
334
|
+
}
|
335
|
+
|
336
|
+
if (!isValidReturnType(returnsTag) && !returnsTag.description && requireReturnDescription) {
|
337
|
+
context.report({ node: jsdocNode, message: "Missing JSDoc return description." });
|
338
|
+
}
|
339
|
+
}
|
340
|
+
}
|
341
|
+
|
336
342
|
// check for functions missing @returns
|
337
343
|
if (!isOverride && !hasReturns && !hasConstructor && !isInterface &&
|
338
344
|
node.parent.kind !== "get" && node.parent.kind !== "constructor" &&
|
@@ -178,7 +178,7 @@ class RuleTester {
|
|
178
178
|
*/
|
179
179
|
static setDefaultConfig(config) {
|
180
180
|
if (typeof config !== "object") {
|
181
|
-
throw new
|
181
|
+
throw new TypeError("RuleTester.setDefaultConfig: config must be an object");
|
182
182
|
}
|
183
183
|
defaultConfig = config;
|
184
184
|
|
@@ -254,7 +254,7 @@ class RuleTester {
|
|
254
254
|
linter = this.linter;
|
255
255
|
|
256
256
|
if (lodash.isNil(test) || typeof test !== "object") {
|
257
|
-
throw new
|
257
|
+
throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
|
258
258
|
}
|
259
259
|
|
260
260
|
requiredScenarios.forEach(scenarioType => {
|
@@ -369,6 +369,7 @@ class RuleTester {
|
|
369
369
|
if (!lodash.isEqual(beforeAST, afterAST)) {
|
370
370
|
|
371
371
|
// Not using directly to avoid performance problem in node 6.1.0. See #6111
|
372
|
+
// eslint-disable-next-line no-restricted-properties
|
372
373
|
assert.deepEqual(beforeAST, afterAST, "Rule should not modify AST.");
|
373
374
|
}
|
374
375
|
}
|
@@ -384,7 +385,7 @@ class RuleTester {
|
|
384
385
|
const result = runRuleForItem(item);
|
385
386
|
const messages = result.messages;
|
386
387
|
|
387
|
-
assert.
|
388
|
+
assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s",
|
388
389
|
messages.length, util.inspect(messages)));
|
389
390
|
|
390
391
|
assertASTDidntChange(result.beforeAST, result.afterAST);
|
@@ -408,7 +409,7 @@ class RuleTester {
|
|
408
409
|
`Expected '${actual}' to match ${expected}`
|
409
410
|
);
|
410
411
|
} else {
|
411
|
-
assert.
|
412
|
+
assert.strictEqual(actual, expected);
|
412
413
|
}
|
413
414
|
}
|
414
415
|
|
@@ -428,10 +429,10 @@ class RuleTester {
|
|
428
429
|
|
429
430
|
|
430
431
|
if (typeof item.errors === "number") {
|
431
|
-
assert.
|
432
|
+
assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
|
432
433
|
item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages)));
|
433
434
|
} else {
|
434
|
-
assert.
|
435
|
+
assert.strictEqual(
|
435
436
|
messages.length, item.errors.length,
|
436
437
|
util.format(
|
437
438
|
"Should have %d error%s but had %d: %s",
|
@@ -460,23 +461,35 @@ class RuleTester {
|
|
460
461
|
assertMessageMatches(messages[i].message, item.errors[i].message);
|
461
462
|
}
|
462
463
|
|
464
|
+
// The following checks use loose equality assertions for backwards compatibility.
|
465
|
+
|
463
466
|
if (item.errors[i].type) {
|
467
|
+
|
468
|
+
// eslint-disable-next-line no-restricted-properties
|
464
469
|
assert.equal(messages[i].nodeType, item.errors[i].type, `Error type should be ${item.errors[i].type}, found ${messages[i].nodeType}`);
|
465
470
|
}
|
466
471
|
|
467
472
|
if (item.errors[i].hasOwnProperty("line")) {
|
473
|
+
|
474
|
+
// eslint-disable-next-line no-restricted-properties
|
468
475
|
assert.equal(messages[i].line, item.errors[i].line, `Error line should be ${item.errors[i].line}`);
|
469
476
|
}
|
470
477
|
|
471
478
|
if (item.errors[i].hasOwnProperty("column")) {
|
479
|
+
|
480
|
+
// eslint-disable-next-line no-restricted-properties
|
472
481
|
assert.equal(messages[i].column, item.errors[i].column, `Error column should be ${item.errors[i].column}`);
|
473
482
|
}
|
474
483
|
|
475
484
|
if (item.errors[i].hasOwnProperty("endLine")) {
|
485
|
+
|
486
|
+
// eslint-disable-next-line no-restricted-properties
|
476
487
|
assert.equal(messages[i].endLine, item.errors[i].endLine, `Error endLine should be ${item.errors[i].endLine}`);
|
477
488
|
}
|
478
489
|
|
479
490
|
if (item.errors[i].hasOwnProperty("endColumn")) {
|
491
|
+
|
492
|
+
// eslint-disable-next-line no-restricted-properties
|
480
493
|
assert.equal(messages[i].endColumn, item.errors[i].endColumn, `Error endColumn should be ${item.errors[i].endColumn}`);
|
481
494
|
}
|
482
495
|
} else {
|
@@ -497,6 +510,7 @@ class RuleTester {
|
|
497
510
|
} else {
|
498
511
|
const fixResult = SourceCodeFixer.applyFixes(item.code, messages);
|
499
512
|
|
513
|
+
// eslint-disable-next-line no-restricted-properties
|
500
514
|
assert.equal(fixResult.output, item.output, "Output is incorrect.");
|
501
515
|
}
|
502
516
|
}
|
@@ -19,20 +19,24 @@ function compareLocations(itemA, itemB) {
|
|
19
19
|
}
|
20
20
|
|
21
21
|
/**
|
22
|
-
* This is the same as the exported function, except that it
|
23
|
-
*
|
24
|
-
*
|
22
|
+
* This is the same as the exported function, except that it
|
23
|
+
* doesn't handle disable-line and disable-next-line directives, and it always reports unused
|
24
|
+
* disable directives.
|
25
|
+
* @param {Object} options options for applying directives. This is the same as the options
|
26
|
+
* for the exported function, except that `reportUnusedDisableDirectives` is not supported
|
27
|
+
* (this function always reports unused disable directives).
|
28
|
+
* @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list
|
29
|
+
* of filtered problems and unused eslint-disable directives
|
25
30
|
*/
|
26
31
|
function applyDirectives(options) {
|
27
32
|
const problems = [];
|
28
33
|
let nextDirectiveIndex = 0;
|
29
|
-
let
|
34
|
+
let currentGlobalDisableDirective = null;
|
35
|
+
const disabledRuleMap = new Map();
|
30
36
|
|
31
|
-
//
|
32
|
-
const disabledRules = new Set();
|
33
|
-
|
34
|
-
// enabledRules is only used when there is an active global /* eslint-disable */ comment.
|
37
|
+
// enabledRules is only used when there is a current global disable directive.
|
35
38
|
const enabledRules = new Set();
|
39
|
+
const usedDisableDirectives = new Set();
|
36
40
|
|
37
41
|
for (const problem of options.problems) {
|
38
42
|
while (
|
@@ -44,23 +48,26 @@ function applyDirectives(options) {
|
|
44
48
|
switch (directive.type) {
|
45
49
|
case "disable":
|
46
50
|
if (directive.ruleId === null) {
|
47
|
-
|
51
|
+
currentGlobalDisableDirective = directive;
|
52
|
+
disabledRuleMap.clear();
|
48
53
|
enabledRules.clear();
|
49
|
-
} else if (
|
54
|
+
} else if (currentGlobalDisableDirective) {
|
50
55
|
enabledRules.delete(directive.ruleId);
|
56
|
+
disabledRuleMap.set(directive.ruleId, directive);
|
51
57
|
} else {
|
52
|
-
|
58
|
+
disabledRuleMap.set(directive.ruleId, directive);
|
53
59
|
}
|
54
60
|
break;
|
55
61
|
|
56
62
|
case "enable":
|
57
63
|
if (directive.ruleId === null) {
|
58
|
-
|
59
|
-
|
60
|
-
} else if (
|
64
|
+
currentGlobalDisableDirective = null;
|
65
|
+
disabledRuleMap.clear();
|
66
|
+
} else if (currentGlobalDisableDirective) {
|
61
67
|
enabledRules.add(directive.ruleId);
|
68
|
+
disabledRuleMap.delete(directive.ruleId);
|
62
69
|
} else {
|
63
|
-
|
70
|
+
disabledRuleMap.delete(directive.ruleId);
|
64
71
|
}
|
65
72
|
break;
|
66
73
|
|
@@ -68,15 +75,30 @@ function applyDirectives(options) {
|
|
68
75
|
}
|
69
76
|
}
|
70
77
|
|
71
|
-
if (
|
72
|
-
|
73
|
-
|
74
|
-
|
78
|
+
if (disabledRuleMap.has(problem.ruleId)) {
|
79
|
+
usedDisableDirectives.add(disabledRuleMap.get(problem.ruleId));
|
80
|
+
} else if (currentGlobalDisableDirective && !enabledRules.has(problem.ruleId)) {
|
81
|
+
usedDisableDirectives.add(currentGlobalDisableDirective);
|
82
|
+
} else {
|
75
83
|
problems.push(problem);
|
76
84
|
}
|
77
85
|
}
|
78
86
|
|
79
|
-
|
87
|
+
const unusedDisableDirectives = options.directives
|
88
|
+
.filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive))
|
89
|
+
.map(directive => ({
|
90
|
+
ruleId: null,
|
91
|
+
message: directive.ruleId
|
92
|
+
? `Unused eslint-disable directive (no problems were reported from '${directive.ruleId}').`
|
93
|
+
: "Unused eslint-disable directive (no problems were reported).",
|
94
|
+
line: directive.unprocessedDirective.line,
|
95
|
+
column: directive.unprocessedDirective.column,
|
96
|
+
severity: 2,
|
97
|
+
source: null,
|
98
|
+
nodeType: null
|
99
|
+
}));
|
100
|
+
|
101
|
+
return { problems, unusedDisableDirectives };
|
80
102
|
}
|
81
103
|
|
82
104
|
/**
|
@@ -93,12 +115,14 @@ function applyDirectives(options) {
|
|
93
115
|
* comment for two different rules is represented as two directives).
|
94
116
|
* @param {{ruleId: (string|null), line: number, column: number}[]} options.problems
|
95
117
|
* A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
|
118
|
+
* @param {boolean} options.reportUnusedDisableDirectives If `true`, adds additional problems for unused directives
|
96
119
|
* @returns {{ruleId: (string|null), line: number, column: number}[]}
|
97
120
|
* A list of reported problems that were not disabled by the directive comments.
|
98
121
|
*/
|
99
122
|
module.exports = options => {
|
100
123
|
const blockDirectives = options.directives
|
101
124
|
.filter(directive => directive.type === "disable" || directive.type === "enable")
|
125
|
+
.map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
|
102
126
|
.sort(compareLocations);
|
103
127
|
|
104
128
|
const lineDirectives = lodash.flatMap(options.directives, directive => {
|
@@ -109,14 +133,14 @@ module.exports = options => {
|
|
109
133
|
|
110
134
|
case "disable-line":
|
111
135
|
return [
|
112
|
-
{ type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId },
|
113
|
-
{ type: "enable", line: directive.line + 1, column: 0, ruleId: directive.ruleId }
|
136
|
+
{ type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
|
137
|
+
{ type: "enable", line: directive.line + 1, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
|
114
138
|
];
|
115
139
|
|
116
140
|
case "disable-next-line":
|
117
141
|
return [
|
118
|
-
{ type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId },
|
119
|
-
{ type: "enable", line: directive.line + 2, column: 0, ruleId: directive.ruleId }
|
142
|
+
{ type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
|
143
|
+
{ type: "enable", line: directive.line + 2, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
|
120
144
|
];
|
121
145
|
|
122
146
|
default:
|
@@ -124,8 +148,13 @@ module.exports = options => {
|
|
124
148
|
}
|
125
149
|
}).sort(compareLocations);
|
126
150
|
|
127
|
-
const
|
128
|
-
const
|
151
|
+
const blockDirectivesResult = applyDirectives({ problems: options.problems, directives: blockDirectives });
|
152
|
+
const lineDirectivesResult = applyDirectives({ problems: blockDirectivesResult.problems, directives: lineDirectives });
|
129
153
|
|
130
|
-
return
|
154
|
+
return options.reportUnusedDisableDirectives
|
155
|
+
? lineDirectivesResult.problems
|
156
|
+
.concat(blockDirectivesResult.unusedDisableDirectives)
|
157
|
+
.concat(lineDirectivesResult.unusedDisableDirectives)
|
158
|
+
.sort(compareLocations)
|
159
|
+
: lineDirectivesResult.problems;
|
131
160
|
};
|
@@ -160,7 +160,7 @@ function tryParseSelector(rawSelector) {
|
|
160
160
|
return esquery.parse(rawSelector.replace(/:exit$/, ""));
|
161
161
|
} catch (err) {
|
162
162
|
if (typeof err.offset === "number") {
|
163
|
-
throw new
|
163
|
+
throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.offset}: ${err.message}`);
|
164
164
|
}
|
165
165
|
throw err;
|
166
166
|
}
|
@@ -194,7 +194,7 @@ const parseSelector = lodash.memoize(rawSelector => {
|
|
194
194
|
*
|
195
195
|
* ```ts
|
196
196
|
* interface EventGenerator {
|
197
|
-
* emitter:
|
197
|
+
* emitter: SafeEmitter;
|
198
198
|
* enterNode(node: ASTNode): void;
|
199
199
|
* leaveNode(node: ASTNode): void;
|
200
200
|
* }
|
@@ -203,8 +203,10 @@ const parseSelector = lodash.memoize(rawSelector => {
|
|
203
203
|
class NodeEventGenerator {
|
204
204
|
|
205
205
|
/**
|
206
|
-
* @param {
|
206
|
+
* @param {SafeEmitter} emitter
|
207
|
+
* An SafeEmitter which is the destination of events. This emitter must already
|
207
208
|
* have registered listeners for all of the events that it needs to listen for.
|
209
|
+
* (See lib/util/safe-emitter.js for more details on `SafeEmitter`.)
|
208
210
|
* @returns {NodeEventGenerator} new instance
|
209
211
|
*/
|
210
212
|
constructor(emitter) {
|
@@ -215,23 +217,7 @@ class NodeEventGenerator {
|
|
215
217
|
this.anyTypeEnterSelectors = [];
|
216
218
|
this.anyTypeExitSelectors = [];
|
217
219
|
|
218
|
-
|
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 => {
|
220
|
+
emitter.eventNames().forEach(rawSelector => {
|
235
221
|
const selector = parseSelector(rawSelector);
|
236
222
|
|
237
223
|
if (selector.listenerTypes) {
|