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.
- package/CHANGELOG.md +58 -0
- package/README.md +12 -7
- package/bin/eslint.js +2 -12
- package/lib/cli-engine/file-enumerator.js +1 -1
- package/lib/cli-engine/formatters/html.js +193 -9
- package/lib/config/default-config.js +52 -0
- package/lib/config/flat-config-array.js +125 -0
- package/lib/config/flat-config-schema.js +452 -0
- package/lib/config/rule-validator.js +169 -0
- package/lib/eslint/eslint.js +38 -2
- package/lib/init/autoconfig.js +2 -2
- package/lib/init/npm-utils.js +1 -2
- package/lib/linter/apply-disable-directives.js +15 -3
- package/lib/linter/linter.js +31 -21
- package/lib/linter/node-event-generator.js +43 -6
- package/lib/rule-tester/rule-tester.js +89 -23
- 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/comma-style.js +1 -2
- package/lib/rules/complexity.js +2 -3
- package/lib/rules/consistent-return.js +2 -2
- package/lib/rules/dot-notation.js +3 -3
- package/lib/rules/eol-last.js +2 -7
- package/lib/rules/indent.js +10 -13
- 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 +18 -13
- 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 +40 -10
- 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/prefer-arrow-callback.js +4 -4
- package/lib/rules/spaced-comment.js +2 -2
- package/lib/rules/use-isnan.js +4 -1
- 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 +8 -7
- 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 +9 -7
- 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
package/lib/rules/complexity.js
CHANGED
@@ -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 =
|
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
|
-
:
|
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;
|
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;
|
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;
|
162
|
+
return;
|
163
163
|
}
|
164
164
|
|
165
165
|
// Replace the identifier to brackets.
|
package/lib/rules/eol-last.js
CHANGED
@@ -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:
|
47
|
+
column: lastLine.length,
|
53
48
|
line: sourceCode.lines.length
|
54
49
|
},
|
55
50
|
LF = "\n",
|
package/lib/rules/indent.js
CHANGED
@@ -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(
|
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 =
|
1564
|
-
|
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
|
-
|
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 =
|
193
|
+
const name = upperCaseFirst(astUtils.getFunctionNameWithKind(funcNode));
|
195
194
|
|
196
195
|
context.report({
|
197
196
|
node,
|
package/lib/rules/max-lines.js
CHANGED
@@ -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
|
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 &&
|
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 =
|
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:
|
191
|
+
column: sourceCode.lines[sourceCode.lines.length - 1].length
|
167
192
|
}
|
168
193
|
};
|
169
194
|
|
package/lib/rules/max-params.js
CHANGED
@@ -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:
|
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 =
|
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
|
-
*
|
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
|
};
|