eslint 7.26.0 → 7.30.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 (67) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +12 -7
  3. package/bin/eslint.js +2 -12
  4. package/lib/cli-engine/file-enumerator.js +1 -1
  5. package/lib/cli-engine/formatters/html.js +193 -9
  6. package/lib/config/default-config.js +52 -0
  7. package/lib/config/flat-config-array.js +125 -0
  8. package/lib/config/flat-config-schema.js +452 -0
  9. package/lib/config/rule-validator.js +169 -0
  10. package/lib/eslint/eslint.js +38 -2
  11. package/lib/init/autoconfig.js +2 -2
  12. package/lib/init/npm-utils.js +1 -2
  13. package/lib/linter/apply-disable-directives.js +15 -3
  14. package/lib/linter/linter.js +31 -21
  15. package/lib/linter/node-event-generator.js +43 -6
  16. package/lib/rule-tester/rule-tester.js +89 -23
  17. package/lib/rules/arrow-body-style.js +21 -11
  18. package/lib/rules/comma-dangle.js +16 -7
  19. package/lib/rules/comma-spacing.js +1 -1
  20. package/lib/rules/comma-style.js +1 -2
  21. package/lib/rules/complexity.js +2 -3
  22. package/lib/rules/consistent-return.js +2 -2
  23. package/lib/rules/dot-notation.js +3 -3
  24. package/lib/rules/eol-last.js +2 -7
  25. package/lib/rules/indent.js +10 -13
  26. package/lib/rules/max-lines-per-function.js +2 -3
  27. package/lib/rules/max-lines.js +32 -7
  28. package/lib/rules/max-params.js +2 -3
  29. package/lib/rules/max-statements.js +2 -3
  30. package/lib/rules/no-duplicate-imports.js +214 -66
  31. package/lib/rules/no-fallthrough.js +18 -13
  32. package/lib/rules/no-implicit-coercion.js +21 -2
  33. package/lib/rules/no-restricted-imports.js +61 -24
  34. package/lib/rules/no-unused-vars.js +40 -10
  35. package/lib/rules/no-useless-backreference.js +1 -2
  36. package/lib/rules/no-useless-computed-key.js +8 -2
  37. package/lib/rules/no-warning-comments.js +1 -1
  38. package/lib/rules/object-curly-newline.js +19 -4
  39. package/lib/rules/prefer-arrow-callback.js +4 -4
  40. package/lib/rules/spaced-comment.js +2 -2
  41. package/lib/rules/use-isnan.js +4 -1
  42. package/lib/rules/utils/ast-utils.js +2 -2
  43. package/lib/shared/deprecation-warnings.js +12 -3
  44. package/lib/shared/string-utils.js +22 -0
  45. package/lib/source-code/source-code.js +8 -7
  46. package/lib/source-code/token-store/utils.js +4 -12
  47. package/messages/{all-files-ignored.txt → all-files-ignored.js} +10 -2
  48. package/messages/extend-config-missing.js +13 -0
  49. package/messages/failed-to-read-json.js +11 -0
  50. package/messages/file-not-found.js +10 -0
  51. package/messages/{no-config-found.txt → no-config-found.js} +9 -1
  52. package/messages/plugin-conflict.js +22 -0
  53. package/messages/plugin-invalid.js +16 -0
  54. package/messages/plugin-missing.js +19 -0
  55. package/messages/{print-config-with-directory-path.txt → print-config-with-directory-path.js} +6 -0
  56. package/messages/whitespace-found.js +11 -0
  57. package/package.json +9 -7
  58. package/lib/cli-engine/formatters/html-template-message.html +0 -8
  59. package/lib/cli-engine/formatters/html-template-page.html +0 -115
  60. package/lib/cli-engine/formatters/html-template-result.html +0 -6
  61. package/messages/extend-config-missing.txt +0 -5
  62. package/messages/failed-to-read-json.txt +0 -3
  63. package/messages/file-not-found.txt +0 -2
  64. package/messages/plugin-conflict.txt +0 -7
  65. package/messages/plugin-invalid.txt +0 -8
  66. package/messages/plugin-missing.txt +0 -11
  67. package/messages/whitespace-found.txt +0 -3
@@ -10,9 +10,8 @@
10
10
  // Requirements
11
11
  //------------------------------------------------------------------------------
12
12
 
13
- const lodash = require("lodash");
14
-
15
13
  const astUtils = require("./utils/ast-utils");
14
+ const { upperCaseFirst } = require("../shared/string-utils");
16
15
 
17
16
  //------------------------------------------------------------------------------
18
17
  // Rule Definition
@@ -95,7 +94,7 @@ module.exports = {
95
94
  * @private
96
95
  */
97
96
  function endFunction(node) {
98
- const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
97
+ const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
99
98
  const complexity = fns.pop();
100
99
 
101
100
  if (complexity > THRESHOLD) {
@@ -8,8 +8,8 @@
8
8
  // Requirements
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const lodash = require("lodash");
12
11
  const astUtils = require("./utils/ast-utils");
12
+ const { upperCaseFirst } = require("../shared/string-utils");
13
13
 
14
14
  //------------------------------------------------------------------------------
15
15
  // Helpers
@@ -164,7 +164,7 @@ module.exports = {
164
164
  funcInfo.data = {
165
165
  name: funcInfo.node.type === "Program"
166
166
  ? "Program"
167
- : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
167
+ : upperCaseFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
168
168
  };
169
169
  } else if (funcInfo.hasReturnValue !== hasReturnValue) {
170
170
  context.report({
@@ -94,7 +94,7 @@ module.exports = {
94
94
 
95
95
  // Don't perform any fixes if there are comments inside the brackets.
96
96
  if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
97
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
97
+ return;
98
98
  }
99
99
 
100
100
  // Replace the brackets by an identifier.
@@ -154,12 +154,12 @@ module.exports = {
154
154
 
155
155
  // A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression.
156
156
  if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) {
157
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
157
+ return;
158
158
  }
159
159
 
160
160
  // Don't perform any fixes if there are comments between the dot and the property name.
161
161
  if (sourceCode.commentsExistBetween(dotToken, node.property)) {
162
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
162
+ return;
163
163
  }
164
164
 
165
165
  // Replace the identifier to brackets.
@@ -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
  // Rule Definition
15
9
  //------------------------------------------------------------------------------
@@ -48,8 +42,9 @@ module.exports = {
48
42
  Program: function checkBadEOF(node) {
49
43
  const sourceCode = context.getSourceCode(),
50
44
  src = sourceCode.getText(),
45
+ lastLine = sourceCode.lines[sourceCode.lines.length - 1],
51
46
  location = {
52
- column: lodash.last(sourceCode.lines).length,
47
+ column: lastLine.length,
53
48
  line: sourceCode.lines.length
54
49
  },
55
50
  LF = "\n",
@@ -12,10 +12,10 @@
12
12
  // Requirements
13
13
  //------------------------------------------------------------------------------
14
14
 
15
- const lodash = require("lodash");
16
- const astUtils = require("./utils/ast-utils");
17
15
  const createTree = require("functional-red-black-tree");
18
16
 
17
+ const astUtils = require("./utils/ast-utils");
18
+
19
19
  //------------------------------------------------------------------------------
20
20
  // Rule Definition
21
21
  //------------------------------------------------------------------------------
@@ -1068,7 +1068,7 @@ module.exports = {
1068
1068
  const baseOffsetListeners = {
1069
1069
  "ArrayExpression, ArrayPattern"(node) {
1070
1070
  const openingBracket = sourceCode.getFirstToken(node);
1071
- const closingBracket = sourceCode.getTokenAfter(lodash.findLast(node.elements) || openingBracket, astUtils.isClosingBracketToken);
1071
+ const closingBracket = sourceCode.getTokenAfter([...node.elements].reverse().find(_ => _) || openingBracket, astUtils.isClosingBracketToken);
1072
1072
 
1073
1073
  addElementListIndent(node.elements, openingBracket, closingBracket, options.ArrayExpression);
1074
1074
  },
@@ -1177,8 +1177,7 @@ module.exports = {
1177
1177
  offsets.setDesiredOffset(questionMarkToken, firstToken, 1);
1178
1178
  offsets.setDesiredOffset(colonToken, firstToken, 1);
1179
1179
 
1180
- offsets.setDesiredOffset(firstConsequentToken, firstToken,
1181
- firstConsequentToken.type === "Punctuator" &&
1180
+ offsets.setDesiredOffset(firstConsequentToken, firstToken, firstConsequentToken.type === "Punctuator" &&
1182
1181
  options.offsetTernaryExpressions ? 2 : 1);
1183
1182
 
1184
1183
  /*
@@ -1204,8 +1203,7 @@ module.exports = {
1204
1203
  * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up
1205
1204
  * having no expected indentation.
1206
1205
  */
1207
- offsets.setDesiredOffset(firstAlternateToken, firstToken,
1208
- firstAlternateToken.type === "Punctuator" &&
1206
+ offsets.setDesiredOffset(firstAlternateToken, firstToken, firstAlternateToken.type === "Punctuator" &&
1209
1207
  options.offsetTernaryExpressions ? 2 : 1);
1210
1208
  }
1211
1209
  }
@@ -1560,8 +1558,9 @@ module.exports = {
1560
1558
  * 2. Don't set any offsets against the first token of the node.
1561
1559
  * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets.
1562
1560
  */
1563
- const offsetListeners = lodash.mapValues(
1564
- baseOffsetListeners,
1561
+ const offsetListeners = {};
1562
+
1563
+ for (const [selector, listener] of Object.entries(baseOffsetListeners)) {
1565
1564
 
1566
1565
  /*
1567
1566
  * Offset listener calls are deferred until traversal is finished, and are called as
@@ -1579,10 +1578,8 @@ module.exports = {
1579
1578
  * To avoid this, the `Identifier` listener isn't called until traversal finishes and all
1580
1579
  * ignored nodes are known.
1581
1580
  */
1582
- listener =>
1583
- node =>
1584
- listenerCallQueue.push({ listener, node })
1585
- );
1581
+ offsetListeners[selector] = node => listenerCallQueue.push({ listener, node });
1582
+ }
1586
1583
 
1587
1584
  // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set.
1588
1585
  const ignoredNodes = new Set();
@@ -9,8 +9,7 @@
9
9
  //------------------------------------------------------------------------------
10
10
 
11
11
  const astUtils = require("./utils/ast-utils");
12
-
13
- const lodash = require("lodash");
12
+ const { upperCaseFirst } = require("../shared/string-utils");
14
13
 
15
14
  //------------------------------------------------------------------------------
16
15
  // Constants
@@ -191,7 +190,7 @@ module.exports = {
191
190
  }
192
191
 
193
192
  if (lineCount > maxLines) {
194
- const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(funcNode));
193
+ const name = upperCaseFirst(astUtils.getFunctionNameWithKind(funcNode));
195
194
 
196
195
  context.report({
197
196
  node,
@@ -8,9 +8,22 @@
8
8
  // Requirements
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const lodash = require("lodash");
12
11
  const astUtils = require("./utils/ast-utils");
13
12
 
13
+ //------------------------------------------------------------------------------
14
+ // Helpers
15
+ //------------------------------------------------------------------------------
16
+
17
+ /**
18
+ * Creates an array of numbers from `start` up to, but not including, `end`
19
+ * @param {number} start The start of the range
20
+ * @param {number} end The end of the range
21
+ * @returns {number[]} The range of numbers
22
+ */
23
+ function range(start, end) {
24
+ return [...Array(end - start).keys()].map(x => x + start);
25
+ }
26
+
14
27
  //------------------------------------------------------------------------------
15
28
  // Rule Definition
16
29
  //------------------------------------------------------------------------------
@@ -119,11 +132,25 @@ module.exports = {
119
132
  }
120
133
 
121
134
  if (start <= end) {
122
- return lodash.range(start, end + 1);
135
+ return range(start, end + 1);
123
136
  }
124
137
  return [];
125
138
  }
126
139
 
140
+ /**
141
+ * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
142
+ * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
143
+ * @param {any[]} array The array to process
144
+ * @param {Function} fn The function to use
145
+ * @returns {any[]} The result array
146
+ */
147
+ function flatMap(array, fn) {
148
+ const mapped = array.map(fn);
149
+ const flattened = [].concat(...mapped);
150
+
151
+ return flattened;
152
+ }
153
+
127
154
  return {
128
155
  "Program:exit"() {
129
156
  let lines = sourceCode.lines.map((text, i) => ({
@@ -135,7 +162,7 @@ module.exports = {
135
162
  * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end.
136
163
  * That isn't a real line, so we shouldn't count it.
137
164
  */
138
- if (lines.length > 1 && lodash.last(lines).text === "") {
165
+ if (lines.length > 1 && lines[lines.length - 1].text === "") {
139
166
  lines.pop();
140
167
  }
141
168
 
@@ -146,9 +173,7 @@ module.exports = {
146
173
  if (skipComments) {
147
174
  const comments = sourceCode.getAllComments();
148
175
 
149
- const commentLines = lodash.flatten(
150
- comments.map(comment => getLinesWithoutCode(comment))
151
- );
176
+ const commentLines = flatMap(comments, comment => getLinesWithoutCode(comment));
152
177
 
153
178
  lines = lines.filter(
154
179
  l => !commentLines.includes(l.lineNumber)
@@ -163,7 +188,7 @@ module.exports = {
163
188
  },
164
189
  end: {
165
190
  line: sourceCode.lines.length,
166
- column: lodash.last(sourceCode.lines).length
191
+ column: sourceCode.lines[sourceCode.lines.length - 1].length
167
192
  }
168
193
  };
169
194
 
@@ -9,9 +9,8 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const lodash = require("lodash");
13
-
14
12
  const astUtils = require("./utils/ast-utils");
13
+ const { upperCaseFirst } = require("../shared/string-utils");
15
14
 
16
15
  //------------------------------------------------------------------------------
17
16
  // Rule Definition
@@ -85,7 +84,7 @@ module.exports = {
85
84
  node,
86
85
  messageId: "exceed",
87
86
  data: {
88
- name: lodash.upperFirst(astUtils.getFunctionNameWithKind(node)),
87
+ name: upperCaseFirst(astUtils.getFunctionNameWithKind(node)),
89
88
  count: node.params.length,
90
89
  max: numParams
91
90
  }
@@ -9,9 +9,8 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const lodash = require("lodash");
13
-
14
12
  const astUtils = require("./utils/ast-utils");
13
+ const { upperCaseFirst } = require("../shared/string-utils");
15
14
 
16
15
  //------------------------------------------------------------------------------
17
16
  // Rule Definition
@@ -97,7 +96,7 @@ module.exports = {
97
96
  */
98
97
  function reportIfTooManyStatements(node, count, max) {
99
98
  if (count > max) {
100
- const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
99
+ const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
101
100
 
102
101
  context.report({
103
102
  node,
@@ -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
  };