eslint 7.24.0 → 7.28.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 +54 -0
- package/README.md +7 -7
- package/bin/eslint.js +2 -12
- package/lib/cli-engine/cli-engine.js +2 -7
- package/lib/cli-engine/file-enumerator.js +1 -1
- package/lib/cli-engine/formatters/html.js +193 -9
- package/lib/init/autoconfig.js +2 -2
- package/lib/init/config-file.js +1 -0
- package/lib/init/config-initializer.js +14 -1
- package/lib/init/npm-utils.js +1 -0
- package/lib/linter/apply-disable-directives.js +15 -3
- package/lib/linter/linter.js +18 -12
- package/lib/linter/node-event-generator.js +43 -6
- package/lib/rule-tester/rule-tester.js +14 -10
- package/lib/rules/arrow-body-style.js +21 -11
- package/lib/rules/comma-dangle.js +16 -7
- package/lib/rules/comma-spacing.js +1 -1
- package/lib/rules/complexity.js +2 -3
- package/lib/rules/consistent-return.js +2 -2
- package/lib/rules/eol-last.js +2 -7
- package/lib/rules/indent.js +8 -9
- package/lib/rules/max-lines-per-function.js +2 -3
- package/lib/rules/max-lines.js +32 -7
- package/lib/rules/max-params.js +2 -3
- package/lib/rules/max-statements.js +2 -3
- package/lib/rules/no-duplicate-imports.js +214 -66
- package/lib/rules/no-fallthrough.js +2 -8
- package/lib/rules/no-implicit-coercion.js +21 -2
- package/lib/rules/no-restricted-imports.js +61 -24
- package/lib/rules/no-unused-vars.js +51 -13
- package/lib/rules/no-useless-backreference.js +1 -2
- package/lib/rules/no-useless-computed-key.js +8 -2
- package/lib/rules/no-warning-comments.js +1 -1
- package/lib/rules/object-curly-newline.js +19 -4
- package/lib/rules/radix.js +19 -3
- package/lib/rules/require-atomic-updates.js +23 -20
- package/lib/rules/spaced-comment.js +2 -2
- package/lib/rules/utils/ast-utils.js +2 -2
- package/lib/shared/deprecation-warnings.js +12 -3
- package/lib/shared/string-utils.js +22 -0
- package/lib/source-code/source-code.js +6 -5
- package/lib/source-code/token-store/utils.js +4 -12
- package/messages/{all-files-ignored.txt → all-files-ignored.js} +10 -2
- package/messages/extend-config-missing.js +13 -0
- package/messages/failed-to-read-json.js +11 -0
- package/messages/file-not-found.js +10 -0
- package/messages/{no-config-found.txt → no-config-found.js} +9 -1
- package/messages/plugin-conflict.js +22 -0
- package/messages/plugin-invalid.js +16 -0
- package/messages/plugin-missing.js +19 -0
- package/messages/{print-config-with-directory-path.txt → print-config-with-directory-path.js} +6 -0
- package/messages/whitespace-found.js +11 -0
- package/package.json +10 -13
- package/lib/cli-engine/formatters/html-template-message.html +0 -8
- package/lib/cli-engine/formatters/html-template-page.html +0 -115
- package/lib/cli-engine/formatters/html-template-result.html +0 -6
- package/messages/extend-config-missing.txt +0 -5
- package/messages/failed-to-read-json.txt +0 -3
- package/messages/file-not-found.txt +0 -2
- package/messages/plugin-conflict.txt +0 -7
- package/messages/plugin-invalid.txt +0 -8
- package/messages/plugin-missing.txt +0 -11
- package/messages/whitespace-found.txt +0 -3
@@ -4,92 +4,225 @@
|
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
|
+
//------------------------------------------------------------------------------
|
8
|
+
// Helpers
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
|
11
|
+
const NAMED_TYPES = ["ImportSpecifier", "ExportSpecifier"];
|
12
|
+
const NAMESPACE_TYPES = [
|
13
|
+
"ImportNamespaceSpecifier",
|
14
|
+
"ExportNamespaceSpecifier"
|
15
|
+
];
|
16
|
+
|
7
17
|
//------------------------------------------------------------------------------
|
8
18
|
// Rule Definition
|
9
19
|
//------------------------------------------------------------------------------
|
10
20
|
|
11
21
|
/**
|
12
|
-
*
|
22
|
+
* Check if an import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier).
|
23
|
+
* @param {string} importExportType An import/export type to check.
|
24
|
+
* @param {string} type Can be "named" or "namespace"
|
25
|
+
* @returns {boolean} True if import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier) and false if it doesn't.
|
26
|
+
*/
|
27
|
+
function isImportExportSpecifier(importExportType, type) {
|
28
|
+
const arrayToCheck = type === "named" ? NAMED_TYPES : NAMESPACE_TYPES;
|
29
|
+
|
30
|
+
return arrayToCheck.includes(importExportType);
|
31
|
+
}
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Return the type of (import|export).
|
13
35
|
* @param {ASTNode} node A node to get.
|
14
|
-
* @returns {string}
|
36
|
+
* @returns {string} The type of the (import|export).
|
15
37
|
*/
|
16
|
-
function
|
17
|
-
if (node && node.
|
18
|
-
|
38
|
+
function getImportExportType(node) {
|
39
|
+
if (node.specifiers && node.specifiers.length > 0) {
|
40
|
+
const nodeSpecifiers = node.specifiers;
|
41
|
+
const index = nodeSpecifiers.findIndex(
|
42
|
+
({ type }) =>
|
43
|
+
isImportExportSpecifier(type, "named") ||
|
44
|
+
isImportExportSpecifier(type, "namespace")
|
45
|
+
);
|
46
|
+
const i = index > -1 ? index : 0;
|
47
|
+
|
48
|
+
return nodeSpecifiers[i].type;
|
19
49
|
}
|
50
|
+
if (node.type === "ExportAllDeclaration") {
|
51
|
+
if (node.exported) {
|
52
|
+
return "ExportNamespaceSpecifier";
|
53
|
+
}
|
54
|
+
return "ExportAll";
|
55
|
+
}
|
56
|
+
return "SideEffectImport";
|
57
|
+
}
|
20
58
|
|
21
|
-
|
59
|
+
/**
|
60
|
+
* Returns a boolean indicates if two (import|export) can be merged
|
61
|
+
* @param {ASTNode} node1 A node to check.
|
62
|
+
* @param {ASTNode} node2 A node to check.
|
63
|
+
* @returns {boolean} True if two (import|export) can be merged, false if they can't.
|
64
|
+
*/
|
65
|
+
function isImportExportCanBeMerged(node1, node2) {
|
66
|
+
const importExportType1 = getImportExportType(node1);
|
67
|
+
const importExportType2 = getImportExportType(node2);
|
68
|
+
|
69
|
+
if (
|
70
|
+
(importExportType1 === "ExportAll" &&
|
71
|
+
importExportType2 !== "ExportAll" &&
|
72
|
+
importExportType2 !== "SideEffectImport") ||
|
73
|
+
(importExportType1 !== "ExportAll" &&
|
74
|
+
importExportType1 !== "SideEffectImport" &&
|
75
|
+
importExportType2 === "ExportAll")
|
76
|
+
) {
|
77
|
+
return false;
|
78
|
+
}
|
79
|
+
if (
|
80
|
+
(isImportExportSpecifier(importExportType1, "namespace") &&
|
81
|
+
isImportExportSpecifier(importExportType2, "named")) ||
|
82
|
+
(isImportExportSpecifier(importExportType2, "namespace") &&
|
83
|
+
isImportExportSpecifier(importExportType1, "named"))
|
84
|
+
) {
|
85
|
+
return false;
|
86
|
+
}
|
87
|
+
return true;
|
22
88
|
}
|
23
89
|
|
24
90
|
/**
|
25
|
-
*
|
26
|
-
* @param {
|
27
|
-
* @param {ASTNode}
|
28
|
-
* @
|
29
|
-
* @param {string[]} array The array containing other imports or exports in the file.
|
30
|
-
* @param {string} messageId A messageId to be reported after the name of the module
|
31
|
-
*
|
32
|
-
* @returns {void} No return value
|
91
|
+
* Returns a boolean if we should report (import|export).
|
92
|
+
* @param {ASTNode} node A node to be reported or not.
|
93
|
+
* @param {[ASTNode]} previousNodes An array contains previous nodes of the module imported or exported.
|
94
|
+
* @returns {boolean} True if the (import|export) should be reported.
|
33
95
|
*/
|
34
|
-
function
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
});
|
96
|
+
function shouldReportImportExport(node, previousNodes) {
|
97
|
+
let i = 0;
|
98
|
+
|
99
|
+
while (i < previousNodes.length) {
|
100
|
+
if (isImportExportCanBeMerged(node, previousNodes[i])) {
|
101
|
+
return true;
|
102
|
+
}
|
103
|
+
i++;
|
43
104
|
}
|
105
|
+
return false;
|
44
106
|
}
|
45
107
|
|
46
108
|
/**
|
47
|
-
*
|
48
|
-
* @param {ASTNode}
|
109
|
+
* Returns array contains only nodes with declarations types equal to type.
|
110
|
+
* @param {[{node: ASTNode, declarationType: string}]} nodes An array contains objects, each object contains a node and a declaration type.
|
111
|
+
* @param {string} type Declaration type.
|
112
|
+
* @returns {[ASTNode]} An array contains only nodes with declarations types equal to type.
|
113
|
+
*/
|
114
|
+
function getNodesByDeclarationType(nodes, type) {
|
115
|
+
return nodes
|
116
|
+
.filter(({ declarationType }) => declarationType === type)
|
117
|
+
.map(({ node }) => node);
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Returns the name of the module imported or re-exported.
|
122
|
+
* @param {ASTNode} node A node to get.
|
123
|
+
* @returns {string} The name of the module, or empty string if no name.
|
49
124
|
*/
|
125
|
+
function getModule(node) {
|
126
|
+
if (node && node.source && node.source.value) {
|
127
|
+
return node.source.value.trim();
|
128
|
+
}
|
129
|
+
return "";
|
130
|
+
}
|
50
131
|
|
51
132
|
/**
|
52
|
-
*
|
133
|
+
* Checks if the (import|export) can be merged with at least one import or one export, and reports if so.
|
53
134
|
* @param {RuleContext} context The ESLint rule context object.
|
135
|
+
* @param {ASTNode} node A node to get.
|
136
|
+
* @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
|
137
|
+
* @param {string} declarationType A declaration type can be an import or export.
|
54
138
|
* @param {boolean} includeExports Whether or not to check for exports in addition to imports.
|
55
|
-
* @
|
56
|
-
* @param {string[]} exportsInFile The array containing other exports in the file.
|
57
|
-
*
|
58
|
-
* @returns {nodeCallback} A function passed to ESLint to handle the statement.
|
139
|
+
* @returns {void} No return value.
|
59
140
|
*/
|
60
|
-
function
|
61
|
-
|
62
|
-
|
141
|
+
function checkAndReport(
|
142
|
+
context,
|
143
|
+
node,
|
144
|
+
modules,
|
145
|
+
declarationType,
|
146
|
+
includeExports
|
147
|
+
) {
|
148
|
+
const module = getModule(node);
|
63
149
|
|
64
|
-
|
65
|
-
|
150
|
+
if (modules.has(module)) {
|
151
|
+
const previousNodes = modules.get(module);
|
152
|
+
const messagesIds = [];
|
153
|
+
const importNodes = getNodesByDeclarationType(previousNodes, "import");
|
154
|
+
let exportNodes;
|
66
155
|
|
156
|
+
if (includeExports) {
|
157
|
+
exportNodes = getNodesByDeclarationType(previousNodes, "export");
|
158
|
+
}
|
159
|
+
if (declarationType === "import") {
|
160
|
+
if (shouldReportImportExport(node, importNodes)) {
|
161
|
+
messagesIds.push("import");
|
162
|
+
}
|
67
163
|
if (includeExports) {
|
68
|
-
|
164
|
+
if (shouldReportImportExport(node, exportNodes)) {
|
165
|
+
messagesIds.push("importAs");
|
166
|
+
}
|
167
|
+
}
|
168
|
+
} else if (declarationType === "export") {
|
169
|
+
if (shouldReportImportExport(node, exportNodes)) {
|
170
|
+
messagesIds.push("export");
|
171
|
+
}
|
172
|
+
if (shouldReportImportExport(node, importNodes)) {
|
173
|
+
messagesIds.push("exportAs");
|
69
174
|
}
|
70
|
-
|
71
|
-
importsInFile.push(value);
|
72
175
|
}
|
73
|
-
|
176
|
+
messagesIds.forEach(messageId =>
|
177
|
+
context.report({
|
178
|
+
node,
|
179
|
+
messageId,
|
180
|
+
data: {
|
181
|
+
module
|
182
|
+
}
|
183
|
+
}));
|
184
|
+
}
|
74
185
|
}
|
75
186
|
|
76
187
|
/**
|
77
|
-
*
|
188
|
+
* @callback nodeCallback
|
189
|
+
* @param {ASTNode} node A node to handle.
|
190
|
+
*/
|
191
|
+
|
192
|
+
/**
|
193
|
+
* Returns a function handling the (imports|exports) of a given file
|
78
194
|
* @param {RuleContext} context The ESLint rule context object.
|
79
|
-
* @param {
|
80
|
-
* @param {string
|
81
|
-
*
|
195
|
+
* @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
|
196
|
+
* @param {string} declarationType A declaration type can be an import or export.
|
197
|
+
* @param {boolean} includeExports Whether or not to check for exports in addition to imports.
|
82
198
|
* @returns {nodeCallback} A function passed to ESLint to handle the statement.
|
83
199
|
*/
|
84
|
-
function
|
200
|
+
function handleImportsExports(
|
201
|
+
context,
|
202
|
+
modules,
|
203
|
+
declarationType,
|
204
|
+
includeExports
|
205
|
+
) {
|
85
206
|
return function(node) {
|
86
|
-
const
|
207
|
+
const module = getModule(node);
|
208
|
+
|
209
|
+
if (module) {
|
210
|
+
checkAndReport(
|
211
|
+
context,
|
212
|
+
node,
|
213
|
+
modules,
|
214
|
+
declarationType,
|
215
|
+
includeExports
|
216
|
+
);
|
217
|
+
const currentNode = { node, declarationType };
|
218
|
+
let nodes = [currentNode];
|
87
219
|
|
88
|
-
|
89
|
-
|
90
|
-
checkAndReport(context, node, value, importsInFile, "exportAs");
|
220
|
+
if (modules.has(module)) {
|
221
|
+
const previousNodes = modules.get(module);
|
91
222
|
|
92
|
-
|
223
|
+
nodes = [...previousNodes, currentNode];
|
224
|
+
}
|
225
|
+
modules.set(module, nodes);
|
93
226
|
}
|
94
227
|
};
|
95
228
|
}
|
@@ -105,16 +238,19 @@ module.exports = {
|
|
105
238
|
url: "https://eslint.org/docs/rules/no-duplicate-imports"
|
106
239
|
},
|
107
240
|
|
108
|
-
schema: [
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
241
|
+
schema: [
|
242
|
+
{
|
243
|
+
type: "object",
|
244
|
+
properties: {
|
245
|
+
includeExports: {
|
246
|
+
type: "boolean",
|
247
|
+
default: false
|
248
|
+
}
|
249
|
+
},
|
250
|
+
additionalProperties: false
|
251
|
+
}
|
252
|
+
],
|
253
|
+
|
118
254
|
messages: {
|
119
255
|
import: "'{{module}}' import is duplicated.",
|
120
256
|
importAs: "'{{module}}' import is duplicated as export.",
|
@@ -125,18 +261,30 @@ module.exports = {
|
|
125
261
|
|
126
262
|
create(context) {
|
127
263
|
const includeExports = (context.options[0] || {}).includeExports,
|
128
|
-
|
129
|
-
exportsInFile = [];
|
130
|
-
|
264
|
+
modules = new Map();
|
131
265
|
const handlers = {
|
132
|
-
ImportDeclaration:
|
266
|
+
ImportDeclaration: handleImportsExports(
|
267
|
+
context,
|
268
|
+
modules,
|
269
|
+
"import",
|
270
|
+
includeExports
|
271
|
+
)
|
133
272
|
};
|
134
273
|
|
135
274
|
if (includeExports) {
|
136
|
-
handlers.ExportNamedDeclaration =
|
137
|
-
|
275
|
+
handlers.ExportNamedDeclaration = handleImportsExports(
|
276
|
+
context,
|
277
|
+
modules,
|
278
|
+
"export",
|
279
|
+
includeExports
|
280
|
+
);
|
281
|
+
handlers.ExportAllDeclaration = handleImportsExports(
|
282
|
+
context,
|
283
|
+
modules,
|
284
|
+
"export",
|
285
|
+
includeExports
|
286
|
+
);
|
138
287
|
}
|
139
|
-
|
140
288
|
return handlers;
|
141
289
|
}
|
142
290
|
};
|
@@ -4,12 +4,6 @@
|
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
|
-
//------------------------------------------------------------------------------
|
8
|
-
// Requirements
|
9
|
-
//------------------------------------------------------------------------------
|
10
|
-
|
11
|
-
const lodash = require("lodash");
|
12
|
-
|
13
7
|
//------------------------------------------------------------------------------
|
14
8
|
// Helpers
|
15
9
|
//------------------------------------------------------------------------------
|
@@ -25,7 +19,7 @@ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
|
|
25
19
|
*/
|
26
20
|
function hasFallthroughComment(node, context, fallthroughCommentPattern) {
|
27
21
|
const sourceCode = context.getSourceCode();
|
28
|
-
const comment =
|
22
|
+
const comment = sourceCode.getCommentsBefore(node).pop();
|
29
23
|
|
30
24
|
return Boolean(comment && fallthroughCommentPattern.test(comment.value));
|
31
25
|
}
|
@@ -133,7 +127,7 @@ module.exports = {
|
|
133
127
|
*/
|
134
128
|
if (currentCodePath.currentSegments.some(isReachable) &&
|
135
129
|
(node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) &&
|
136
|
-
|
130
|
+
node.parent.cases[node.parent.cases.length - 1] !== node) {
|
137
131
|
fallthroughCase = node;
|
138
132
|
}
|
139
133
|
}
|
@@ -109,6 +109,20 @@ function getNonNumericOperand(node) {
|
|
109
109
|
return null;
|
110
110
|
}
|
111
111
|
|
112
|
+
/**
|
113
|
+
* Checks whether an expression evaluates to a string.
|
114
|
+
* @param {ASTNode} node node that represents the expression to check.
|
115
|
+
* @returns {boolean} Whether or not the expression evaluates to a string.
|
116
|
+
*/
|
117
|
+
function isStringType(node) {
|
118
|
+
return astUtils.isStringLiteral(node) ||
|
119
|
+
(
|
120
|
+
node.type === "CallExpression" &&
|
121
|
+
node.callee.type === "Identifier" &&
|
122
|
+
node.callee.name === "String"
|
123
|
+
);
|
124
|
+
}
|
125
|
+
|
112
126
|
/**
|
113
127
|
* Checks whether a node is an empty string literal or not.
|
114
128
|
* @param {ASTNode} node The node to check.
|
@@ -126,8 +140,8 @@ function isEmptyString(node) {
|
|
126
140
|
*/
|
127
141
|
function isConcatWithEmptyString(node) {
|
128
142
|
return node.operator === "+" && (
|
129
|
-
(isEmptyString(node.left) && !
|
130
|
-
(isEmptyString(node.right) && !
|
143
|
+
(isEmptyString(node.left) && !isStringType(node.right)) ||
|
144
|
+
(isEmptyString(node.right) && !isStringType(node.left))
|
131
145
|
);
|
132
146
|
}
|
133
147
|
|
@@ -332,6 +346,11 @@ module.exports = {
|
|
332
346
|
return;
|
333
347
|
}
|
334
348
|
|
349
|
+
// if the expression is already a string, then this isn't a coercion
|
350
|
+
if (isStringType(node.expressions[0])) {
|
351
|
+
return;
|
352
|
+
}
|
353
|
+
|
335
354
|
const code = sourceCode.getText(node.expressions[0]);
|
336
355
|
const recommendation = `String(${code})`;
|
337
356
|
|
@@ -10,12 +10,6 @@
|
|
10
10
|
|
11
11
|
const ignore = require("ignore");
|
12
12
|
|
13
|
-
const arrayOfStrings = {
|
14
|
-
type: "array",
|
15
|
-
items: { type: "string" },
|
16
|
-
uniqueItems: true
|
17
|
-
};
|
18
|
-
|
19
13
|
const arrayOfStringsOrObjects = {
|
20
14
|
type: "array",
|
21
15
|
items: {
|
@@ -44,6 +38,41 @@ const arrayOfStringsOrObjects = {
|
|
44
38
|
uniqueItems: true
|
45
39
|
};
|
46
40
|
|
41
|
+
const arrayOfStringsOrObjectPatterns = {
|
42
|
+
anyOf: [
|
43
|
+
{
|
44
|
+
type: "array",
|
45
|
+
items: {
|
46
|
+
type: "string"
|
47
|
+
},
|
48
|
+
uniqueItems: true
|
49
|
+
},
|
50
|
+
{
|
51
|
+
type: "array",
|
52
|
+
items: {
|
53
|
+
type: "object",
|
54
|
+
properties: {
|
55
|
+
group: {
|
56
|
+
type: "array",
|
57
|
+
items: {
|
58
|
+
type: "string"
|
59
|
+
},
|
60
|
+
minItems: 1,
|
61
|
+
uniqueItems: true
|
62
|
+
},
|
63
|
+
message: {
|
64
|
+
type: "string",
|
65
|
+
minLength: 1
|
66
|
+
}
|
67
|
+
},
|
68
|
+
additionalProperties: false,
|
69
|
+
required: ["group"]
|
70
|
+
},
|
71
|
+
uniqueItems: true
|
72
|
+
}
|
73
|
+
]
|
74
|
+
};
|
75
|
+
|
47
76
|
module.exports = {
|
48
77
|
meta: {
|
49
78
|
type: "suggestion",
|
@@ -61,6 +90,8 @@ module.exports = {
|
|
61
90
|
pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}",
|
62
91
|
|
63
92
|
patterns: "'{{importSource}}' import is restricted from being used by a pattern.",
|
93
|
+
// eslint-disable-next-line eslint-plugin/report-message-format
|
94
|
+
patternWithCustomMessage: "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}",
|
64
95
|
|
65
96
|
everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
|
66
97
|
// eslint-disable-next-line eslint-plugin/report-message-format
|
@@ -80,7 +111,7 @@ module.exports = {
|
|
80
111
|
type: "object",
|
81
112
|
properties: {
|
82
113
|
paths: arrayOfStringsOrObjects,
|
83
|
-
patterns:
|
114
|
+
patterns: arrayOfStringsOrObjectPatterns
|
84
115
|
},
|
85
116
|
additionalProperties: false
|
86
117
|
}],
|
@@ -98,13 +129,6 @@ module.exports = {
|
|
98
129
|
(Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
|
99
130
|
|
100
131
|
const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
|
101
|
-
const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
|
102
|
-
|
103
|
-
// if no imports are restricted we don"t need to check
|
104
|
-
if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
|
105
|
-
return {};
|
106
|
-
}
|
107
|
-
|
108
132
|
const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
|
109
133
|
if (typeof importSource === "string") {
|
110
134
|
memo[importSource] = { message: null };
|
@@ -117,7 +141,16 @@ module.exports = {
|
|
117
141
|
return memo;
|
118
142
|
}, {});
|
119
143
|
|
120
|
-
|
144
|
+
// Handle patterns too, either as strings or groups
|
145
|
+
const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
|
146
|
+
const restrictedPatternGroups = restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string"
|
147
|
+
? [{ matcher: ignore().add(restrictedPatterns) }]
|
148
|
+
: restrictedPatterns.map(({ group, message }) => ({ matcher: ignore().add(group), customMessage: message }));
|
149
|
+
|
150
|
+
// if no imports are restricted we don"t need to check
|
151
|
+
if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
|
152
|
+
return {};
|
153
|
+
}
|
121
154
|
|
122
155
|
/**
|
123
156
|
* Report a restricted path.
|
@@ -184,17 +217,19 @@ module.exports = {
|
|
184
217
|
/**
|
185
218
|
* Report a restricted path specifically for patterns.
|
186
219
|
* @param {node} node representing the restricted path reference
|
220
|
+
* @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
|
187
221
|
* @returns {void}
|
188
222
|
* @private
|
189
223
|
*/
|
190
|
-
function reportPathForPatterns(node) {
|
224
|
+
function reportPathForPatterns(node, group) {
|
191
225
|
const importSource = node.source.value.trim();
|
192
226
|
|
193
227
|
context.report({
|
194
228
|
node,
|
195
|
-
messageId: "patterns",
|
229
|
+
messageId: group.customMessage ? "patternWithCustomMessage" : "patterns",
|
196
230
|
data: {
|
197
|
-
importSource
|
231
|
+
importSource,
|
232
|
+
customMessage: group.customMessage
|
198
233
|
}
|
199
234
|
});
|
200
235
|
}
|
@@ -202,11 +237,12 @@ module.exports = {
|
|
202
237
|
/**
|
203
238
|
* Check if the given importSource is restricted by a pattern.
|
204
239
|
* @param {string} importSource path of the import
|
240
|
+
* @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
|
205
241
|
* @returns {boolean} whether the variable is a restricted pattern or not
|
206
242
|
* @private
|
207
243
|
*/
|
208
|
-
function isRestrictedPattern(importSource) {
|
209
|
-
return
|
244
|
+
function isRestrictedPattern(importSource, group) {
|
245
|
+
return group.matcher.ignores(importSource);
|
210
246
|
}
|
211
247
|
|
212
248
|
/**
|
@@ -249,10 +285,11 @@ module.exports = {
|
|
249
285
|
}
|
250
286
|
|
251
287
|
checkRestrictedPathAndReport(importSource, importNames, node);
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
288
|
+
restrictedPatternGroups.forEach(group => {
|
289
|
+
if (isRestrictedPattern(importSource, group)) {
|
290
|
+
reportPathForPatterns(node, group);
|
291
|
+
}
|
292
|
+
});
|
256
293
|
}
|
257
294
|
|
258
295
|
return {
|
@@ -410,6 +410,31 @@ module.exports = {
|
|
410
410
|
);
|
411
411
|
}
|
412
412
|
|
413
|
+
/**
|
414
|
+
* Checks whether a given node is unused expression or not.
|
415
|
+
* @param {ASTNode} node The node itself
|
416
|
+
* @returns {boolean} The node is an unused expression.
|
417
|
+
* @private
|
418
|
+
*/
|
419
|
+
function isUnusedExpression(node) {
|
420
|
+
const parent = node.parent;
|
421
|
+
|
422
|
+
if (parent.type === "ExpressionStatement") {
|
423
|
+
return true;
|
424
|
+
}
|
425
|
+
|
426
|
+
if (parent.type === "SequenceExpression") {
|
427
|
+
const isLastExpression = parent.expressions[parent.expressions.length - 1] === node;
|
428
|
+
|
429
|
+
if (!isLastExpression) {
|
430
|
+
return true;
|
431
|
+
}
|
432
|
+
return isUnusedExpression(parent);
|
433
|
+
}
|
434
|
+
|
435
|
+
return false;
|
436
|
+
}
|
437
|
+
|
413
438
|
/**
|
414
439
|
* Checks whether a given reference is a read to update itself or not.
|
415
440
|
* @param {eslint-scope.Reference} ref A reference to check.
|
@@ -420,23 +445,28 @@ module.exports = {
|
|
420
445
|
function isReadForItself(ref, rhsNode) {
|
421
446
|
const id = ref.identifier;
|
422
447
|
const parent = id.parent;
|
423
|
-
const grandparent = parent.parent;
|
424
448
|
|
425
449
|
return ref.isRead() && (
|
426
450
|
|
427
451
|
// self update. e.g. `a += 1`, `a++`
|
428
|
-
(
|
429
|
-
(
|
452
|
+
(
|
453
|
+
(
|
430
454
|
parent.type === "AssignmentExpression" &&
|
431
|
-
|
432
|
-
|
455
|
+
parent.left === id &&
|
456
|
+
isUnusedExpression(parent)
|
433
457
|
) ||
|
458
|
+
(
|
459
|
+
parent.type === "UpdateExpression" &&
|
460
|
+
isUnusedExpression(parent)
|
461
|
+
)
|
462
|
+
) ||
|
463
|
+
|
464
|
+
// in RHS of an assignment for itself. e.g. `a = a + 1`
|
434
465
|
(
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
!isInsideOfStorableFunction(id, rhsNode)))
|
466
|
+
rhsNode &&
|
467
|
+
isInside(id, rhsNode) &&
|
468
|
+
!isInsideOfStorableFunction(id, rhsNode)
|
469
|
+
)
|
440
470
|
);
|
441
471
|
}
|
442
472
|
|
@@ -624,10 +654,18 @@ module.exports = {
|
|
624
654
|
|
625
655
|
// Report the first declaration.
|
626
656
|
if (unusedVar.defs.length > 0) {
|
657
|
+
|
658
|
+
// report last write reference, https://github.com/eslint/eslint/issues/14324
|
659
|
+
const writeReferences = unusedVar.references.filter(ref => ref.isWrite() && ref.from.variableScope === unusedVar.scope.variableScope);
|
660
|
+
|
661
|
+
let referenceToReport;
|
662
|
+
|
663
|
+
if (writeReferences.length > 0) {
|
664
|
+
referenceToReport = writeReferences[writeReferences.length - 1];
|
665
|
+
}
|
666
|
+
|
627
667
|
context.report({
|
628
|
-
node:
|
629
|
-
unusedVar.references.length - 1
|
630
|
-
].identifier : unusedVar.identifiers[0],
|
668
|
+
node: referenceToReport ? referenceToReport.identifier : unusedVar.identifiers[0],
|
631
669
|
messageId: "unusedVar",
|
632
670
|
data: unusedVar.references.some(ref => ref.isWrite())
|
633
671
|
? getAssignedMessageData(unusedVar)
|