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.
Files changed (63) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +7 -7
  3. package/bin/eslint.js +2 -12
  4. package/lib/cli-engine/cli-engine.js +2 -7
  5. package/lib/cli-engine/file-enumerator.js +1 -1
  6. package/lib/cli-engine/formatters/html.js +193 -9
  7. package/lib/init/autoconfig.js +2 -2
  8. package/lib/init/config-file.js +1 -0
  9. package/lib/init/config-initializer.js +14 -1
  10. package/lib/init/npm-utils.js +1 -0
  11. package/lib/linter/apply-disable-directives.js +15 -3
  12. package/lib/linter/linter.js +18 -12
  13. package/lib/linter/node-event-generator.js +43 -6
  14. package/lib/rule-tester/rule-tester.js +14 -10
  15. package/lib/rules/arrow-body-style.js +21 -11
  16. package/lib/rules/comma-dangle.js +16 -7
  17. package/lib/rules/comma-spacing.js +1 -1
  18. package/lib/rules/complexity.js +2 -3
  19. package/lib/rules/consistent-return.js +2 -2
  20. package/lib/rules/eol-last.js +2 -7
  21. package/lib/rules/indent.js +8 -9
  22. package/lib/rules/max-lines-per-function.js +2 -3
  23. package/lib/rules/max-lines.js +32 -7
  24. package/lib/rules/max-params.js +2 -3
  25. package/lib/rules/max-statements.js +2 -3
  26. package/lib/rules/no-duplicate-imports.js +214 -66
  27. package/lib/rules/no-fallthrough.js +2 -8
  28. package/lib/rules/no-implicit-coercion.js +21 -2
  29. package/lib/rules/no-restricted-imports.js +61 -24
  30. package/lib/rules/no-unused-vars.js +51 -13
  31. package/lib/rules/no-useless-backreference.js +1 -2
  32. package/lib/rules/no-useless-computed-key.js +8 -2
  33. package/lib/rules/no-warning-comments.js +1 -1
  34. package/lib/rules/object-curly-newline.js +19 -4
  35. package/lib/rules/radix.js +19 -3
  36. package/lib/rules/require-atomic-updates.js +23 -20
  37. package/lib/rules/spaced-comment.js +2 -2
  38. package/lib/rules/utils/ast-utils.js +2 -2
  39. package/lib/shared/deprecation-warnings.js +12 -3
  40. package/lib/shared/string-utils.js +22 -0
  41. package/lib/source-code/source-code.js +6 -5
  42. package/lib/source-code/token-store/utils.js +4 -12
  43. package/messages/{all-files-ignored.txt → all-files-ignored.js} +10 -2
  44. package/messages/extend-config-missing.js +13 -0
  45. package/messages/failed-to-read-json.js +11 -0
  46. package/messages/file-not-found.js +10 -0
  47. package/messages/{no-config-found.txt → no-config-found.js} +9 -1
  48. package/messages/plugin-conflict.js +22 -0
  49. package/messages/plugin-invalid.js +16 -0
  50. package/messages/plugin-missing.js +19 -0
  51. package/messages/{print-config-with-directory-path.txt → print-config-with-directory-path.js} +6 -0
  52. package/messages/whitespace-found.js +11 -0
  53. package/package.json +10 -13
  54. package/lib/cli-engine/formatters/html-template-message.html +0 -8
  55. package/lib/cli-engine/formatters/html-template-page.html +0 -115
  56. package/lib/cli-engine/formatters/html-template-result.html +0 -6
  57. package/messages/extend-config-missing.txt +0 -5
  58. package/messages/failed-to-read-json.txt +0 -3
  59. package/messages/file-not-found.txt +0 -2
  60. package/messages/plugin-conflict.txt +0 -7
  61. package/messages/plugin-invalid.txt +0 -8
  62. package/messages/plugin-missing.txt +0 -11
  63. 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
- * Returns the name of the module imported or re-exported.
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} the name of the module, or empty string if no name.
36
+ * @returns {string} The type of the (import|export).
15
37
  */
16
- function getValue(node) {
17
- if (node && node.source && node.source.value) {
18
- return node.source.value.trim();
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
- return "";
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
- * Checks if the name of the import or export exists in the given array, and reports if so.
26
- * @param {RuleContext} context The ESLint rule context object.
27
- * @param {ASTNode} node A node to get.
28
- * @param {string} value The name of the imported or exported module.
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 checkAndReport(context, node, value, array, messageId) {
35
- if (array.indexOf(value) !== -1) {
36
- context.report({
37
- node,
38
- messageId,
39
- data: {
40
- module: value
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
- * @callback nodeCallback
48
- * @param {ASTNode} node A node to handle.
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
- * Returns a function handling the imports of a given file
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
- * @param {string[]} importsInFile The array containing other imports in the file.
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 handleImports(context, includeExports, importsInFile, exportsInFile) {
61
- return function(node) {
62
- const value = getValue(node);
141
+ function checkAndReport(
142
+ context,
143
+ node,
144
+ modules,
145
+ declarationType,
146
+ includeExports
147
+ ) {
148
+ const module = getModule(node);
63
149
 
64
- if (value) {
65
- checkAndReport(context, node, value, importsInFile, "import");
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
- checkAndReport(context, node, value, exportsInFile, "importAs");
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
- * Returns a function handling the exports of a given file
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 {string[]} importsInFile The array containing other imports in the file.
80
- * @param {string[]} exportsInFile The array containing other exports in the file.
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 handleExports(context, importsInFile, exportsInFile) {
200
+ function handleImportsExports(
201
+ context,
202
+ modules,
203
+ declarationType,
204
+ includeExports
205
+ ) {
85
206
  return function(node) {
86
- const value = getValue(node);
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
- if (value) {
89
- checkAndReport(context, node, value, exportsInFile, "export");
90
- checkAndReport(context, node, value, importsInFile, "exportAs");
220
+ if (modules.has(module)) {
221
+ const previousNodes = modules.get(module);
91
222
 
92
- exportsInFile.push(value);
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
- type: "object",
110
- properties: {
111
- includeExports: {
112
- type: "boolean",
113
- default: false
114
- }
115
- },
116
- additionalProperties: false
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
- importsInFile = [],
129
- exportsInFile = [];
130
-
264
+ modules = new Map();
131
265
  const handlers = {
132
- ImportDeclaration: handleImports(context, includeExports, importsInFile, exportsInFile)
266
+ ImportDeclaration: handleImportsExports(
267
+ context,
268
+ modules,
269
+ "import",
270
+ includeExports
271
+ )
133
272
  };
134
273
 
135
274
  if (includeExports) {
136
- handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile);
137
- handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile);
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 = lodash.last(sourceCode.getCommentsBefore(node));
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
- lodash.last(node.parent.cases) !== node) {
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) && !astUtils.isStringLiteral(node.right)) ||
130
- (isEmptyString(node.right) && !astUtils.isStringLiteral(node.left))
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: arrayOfStrings
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
- const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
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 restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
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
- if (isRestrictedPattern(importSource)) {
254
- reportPathForPatterns(node);
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
- (// in RHS of an assignment for itself. e.g. `a = a + 1`
429
- ((
452
+ (
453
+ (
430
454
  parent.type === "AssignmentExpression" &&
431
- grandparent.type === "ExpressionStatement" &&
432
- parent.left === id
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
- parent.type === "UpdateExpression" &&
436
- grandparent.type === "ExpressionStatement"
437
- ) || rhsNode &&
438
- isInside(id, rhsNode) &&
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: unusedVar.references.length ? unusedVar.references[
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)