eslint 8.53.0 → 8.55.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/lib/cli-engine/lint-result-cache.js +18 -6
- package/lib/linter/linter.js +2 -2
- package/lib/rules/for-direction.js +23 -16
- package/lib/rules/no-array-constructor.js +85 -6
- package/lib/rules/no-console.js +74 -2
- package/lib/rules/no-object-constructor.js +7 -106
- package/lib/rules/no-restricted-imports.js +54 -31
- package/lib/rules/utils/ast-utils.js +111 -1
- package/package.json +22 -8
@@ -128,16 +128,28 @@ class LintResultCache {
|
|
128
128
|
return null;
|
129
129
|
}
|
130
130
|
|
131
|
+
const cachedResults = fileDescriptor.meta.results;
|
132
|
+
|
133
|
+
// Just in case, not sure if this can ever happen.
|
134
|
+
if (!cachedResults) {
|
135
|
+
return cachedResults;
|
136
|
+
}
|
137
|
+
|
138
|
+
/*
|
139
|
+
* Shallow clone the object to ensure that any properties added or modified afterwards
|
140
|
+
* will not be accidentally stored in the cache file when `reconcile()` is called.
|
141
|
+
* https://github.com/eslint/eslint/issues/13507
|
142
|
+
* All intentional changes to the cache file must be done through `setCachedLintResults()`.
|
143
|
+
*/
|
144
|
+
const results = { ...cachedResults };
|
145
|
+
|
131
146
|
// If source is present but null, need to reread the file from the filesystem.
|
132
|
-
if (
|
133
|
-
fileDescriptor.meta.results &&
|
134
|
-
fileDescriptor.meta.results.source === null
|
135
|
-
) {
|
147
|
+
if (results.source === null) {
|
136
148
|
debug(`Rereading cached result source from filesystem: ${filePath}`);
|
137
|
-
|
149
|
+
results.source = fs.readFileSync(filePath, "utf-8");
|
138
150
|
}
|
139
151
|
|
140
|
-
return
|
152
|
+
return results;
|
141
153
|
}
|
142
154
|
|
143
155
|
/**
|
package/lib/linter/linter.js
CHANGED
@@ -1422,7 +1422,7 @@ class Linter {
|
|
1422
1422
|
verify(textOrSourceCode, config, filenameOrOptions) {
|
1423
1423
|
debug("Verify");
|
1424
1424
|
|
1425
|
-
const { configType } = internalSlotsMap.get(this);
|
1425
|
+
const { configType, cwd } = internalSlotsMap.get(this);
|
1426
1426
|
|
1427
1427
|
const options = typeof filenameOrOptions === "string"
|
1428
1428
|
? { filename: filenameOrOptions }
|
@@ -1441,7 +1441,7 @@ class Linter {
|
|
1441
1441
|
let configArray = config;
|
1442
1442
|
|
1443
1443
|
if (!Array.isArray(config) || typeof config.getConfig !== "function") {
|
1444
|
-
configArray = new FlatConfigArray(config);
|
1444
|
+
configArray = new FlatConfigArray(config, { basePath: cwd });
|
1445
1445
|
configArray.normalizeSync();
|
1446
1446
|
}
|
1447
1447
|
|
@@ -101,30 +101,37 @@ module.exports = {
|
|
101
101
|
}
|
102
102
|
return 0;
|
103
103
|
}
|
104
|
+
|
104
105
|
return {
|
105
106
|
ForStatement(node) {
|
106
107
|
|
107
|
-
if (node.test && node.test.type === "BinaryExpression" && node.
|
108
|
-
const
|
109
|
-
|
110
|
-
|
108
|
+
if (node.test && node.test.type === "BinaryExpression" && node.update) {
|
109
|
+
for (const counterPosition of ["left", "right"]) {
|
110
|
+
if (node.test[counterPosition].type !== "Identifier") {
|
111
|
+
continue;
|
112
|
+
}
|
111
113
|
|
112
|
-
|
114
|
+
const counter = node.test[counterPosition].name;
|
115
|
+
const operator = node.test.operator;
|
116
|
+
const update = node.update;
|
113
117
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
118
|
+
let wrongDirection;
|
119
|
+
|
120
|
+
if (operator === "<" || operator === "<=") {
|
121
|
+
wrongDirection = counterPosition === "left" ? -1 : 1;
|
122
|
+
} else if (operator === ">" || operator === ">=") {
|
123
|
+
wrongDirection = counterPosition === "left" ? 1 : -1;
|
124
|
+
} else {
|
125
|
+
return;
|
126
|
+
}
|
121
127
|
|
122
|
-
|
123
|
-
|
128
|
+
if (update.type === "UpdateExpression") {
|
129
|
+
if (getUpdateDirection(update, counter) === wrongDirection) {
|
130
|
+
report(node);
|
131
|
+
}
|
132
|
+
} else if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) === wrongDirection) {
|
124
133
|
report(node);
|
125
134
|
}
|
126
|
-
} else if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) === wrongDirection) {
|
127
|
-
report(node);
|
128
135
|
}
|
129
136
|
}
|
130
137
|
}
|
@@ -5,6 +5,18 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const {
|
13
|
+
getVariableByName,
|
14
|
+
isClosingParenToken,
|
15
|
+
isOpeningParenToken,
|
16
|
+
isStartOfExpressionStatement,
|
17
|
+
needsPrecedingSemicolon
|
18
|
+
} = require("./utils/ast-utils");
|
19
|
+
|
8
20
|
//------------------------------------------------------------------------------
|
9
21
|
// Rule Definition
|
10
22
|
//------------------------------------------------------------------------------
|
@@ -20,15 +32,45 @@ module.exports = {
|
|
20
32
|
url: "https://eslint.org/docs/latest/rules/no-array-constructor"
|
21
33
|
},
|
22
34
|
|
35
|
+
hasSuggestions: true,
|
36
|
+
|
23
37
|
schema: [],
|
24
38
|
|
25
39
|
messages: {
|
26
|
-
preferLiteral: "The array literal notation [] is preferable."
|
40
|
+
preferLiteral: "The array literal notation [] is preferable.",
|
41
|
+
useLiteral: "Replace with an array literal.",
|
42
|
+
useLiteralAfterSemicolon: "Replace with an array literal, add preceding semicolon."
|
27
43
|
}
|
28
44
|
},
|
29
45
|
|
30
46
|
create(context) {
|
31
47
|
|
48
|
+
const sourceCode = context.sourceCode;
|
49
|
+
|
50
|
+
/**
|
51
|
+
* Gets the text between the calling parentheses of a CallExpression or NewExpression.
|
52
|
+
* @param {ASTNode} node A CallExpression or NewExpression node.
|
53
|
+
* @returns {string} The text between the calling parentheses, or an empty string if there are none.
|
54
|
+
*/
|
55
|
+
function getArgumentsText(node) {
|
56
|
+
const lastToken = sourceCode.getLastToken(node);
|
57
|
+
|
58
|
+
if (!isClosingParenToken(lastToken)) {
|
59
|
+
return "";
|
60
|
+
}
|
61
|
+
|
62
|
+
let firstToken = node.callee;
|
63
|
+
|
64
|
+
do {
|
65
|
+
firstToken = sourceCode.getTokenAfter(firstToken);
|
66
|
+
if (!firstToken || firstToken === lastToken) {
|
67
|
+
return "";
|
68
|
+
}
|
69
|
+
} while (!isOpeningParenToken(firstToken));
|
70
|
+
|
71
|
+
return sourceCode.text.slice(firstToken.range[1], lastToken.range[0]);
|
72
|
+
}
|
73
|
+
|
32
74
|
/**
|
33
75
|
* Disallow construction of dense arrays using the Array constructor
|
34
76
|
* @param {ASTNode} node node to evaluate
|
@@ -37,11 +79,48 @@ module.exports = {
|
|
37
79
|
*/
|
38
80
|
function check(node) {
|
39
81
|
if (
|
40
|
-
node.
|
41
|
-
node.callee.
|
42
|
-
node.
|
43
|
-
|
44
|
-
|
82
|
+
node.callee.type !== "Identifier" ||
|
83
|
+
node.callee.name !== "Array" ||
|
84
|
+
node.arguments.length === 1 &&
|
85
|
+
node.arguments[0].type !== "SpreadElement") {
|
86
|
+
return;
|
87
|
+
}
|
88
|
+
|
89
|
+
const variable = getVariableByName(sourceCode.getScope(node), "Array");
|
90
|
+
|
91
|
+
/*
|
92
|
+
* Check if `Array` is a predefined global variable: predefined globals have no declarations,
|
93
|
+
* meaning that the `identifiers` list of the variable object is empty.
|
94
|
+
*/
|
95
|
+
if (variable && variable.identifiers.length === 0) {
|
96
|
+
const argsText = getArgumentsText(node);
|
97
|
+
let fixText;
|
98
|
+
let messageId;
|
99
|
+
|
100
|
+
/*
|
101
|
+
* Check if the suggested change should include a preceding semicolon or not.
|
102
|
+
* Due to JavaScript's ASI rules, a missing semicolon may be inserted automatically
|
103
|
+
* before an expression like `Array()` or `new Array()`, but not when the expression
|
104
|
+
* is changed into an array literal like `[]`.
|
105
|
+
*/
|
106
|
+
if (isStartOfExpressionStatement(node) && needsPrecedingSemicolon(sourceCode, node)) {
|
107
|
+
fixText = `;[${argsText}]`;
|
108
|
+
messageId = "useLiteralAfterSemicolon";
|
109
|
+
} else {
|
110
|
+
fixText = `[${argsText}]`;
|
111
|
+
messageId = "useLiteral";
|
112
|
+
}
|
113
|
+
|
114
|
+
context.report({
|
115
|
+
node,
|
116
|
+
messageId: "preferLiteral",
|
117
|
+
suggest: [
|
118
|
+
{
|
119
|
+
messageId,
|
120
|
+
fix: fixer => fixer.replaceText(node, fixText)
|
121
|
+
}
|
122
|
+
]
|
123
|
+
});
|
45
124
|
}
|
46
125
|
}
|
47
126
|
|
package/lib/rules/no-console.js
CHANGED
@@ -43,8 +43,11 @@ module.exports = {
|
|
43
43
|
}
|
44
44
|
],
|
45
45
|
|
46
|
+
hasSuggestions: true,
|
47
|
+
|
46
48
|
messages: {
|
47
|
-
unexpected: "Unexpected console statement."
|
49
|
+
unexpected: "Unexpected console statement.",
|
50
|
+
removeConsole: "Remove the console.{{ propertyName }}()."
|
48
51
|
}
|
49
52
|
},
|
50
53
|
|
@@ -94,6 +97,64 @@ module.exports = {
|
|
94
97
|
);
|
95
98
|
}
|
96
99
|
|
100
|
+
/**
|
101
|
+
* Checks if removing the ExpressionStatement node will cause ASI to
|
102
|
+
* break.
|
103
|
+
* eg.
|
104
|
+
* foo()
|
105
|
+
* console.log();
|
106
|
+
* [1, 2, 3].forEach(a => doSomething(a))
|
107
|
+
*
|
108
|
+
* Removing the console.log(); statement should leave two statements, but
|
109
|
+
* here the two statements will become one because [ causes continuation after
|
110
|
+
* foo().
|
111
|
+
* @param {ASTNode} node The ExpressionStatement node to check.
|
112
|
+
* @returns {boolean} `true` if ASI will break after removing the ExpressionStatement
|
113
|
+
* node.
|
114
|
+
*/
|
115
|
+
function maybeAsiHazard(node) {
|
116
|
+
const SAFE_TOKENS_BEFORE = /^[:;{]$/u; // One of :;{
|
117
|
+
const UNSAFE_CHARS_AFTER = /^[-[(/+`]/u; // One of [(/+-`
|
118
|
+
|
119
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
120
|
+
const tokenAfter = sourceCode.getTokenAfter(node);
|
121
|
+
|
122
|
+
return (
|
123
|
+
Boolean(tokenAfter) &&
|
124
|
+
UNSAFE_CHARS_AFTER.test(tokenAfter.value) &&
|
125
|
+
tokenAfter.value !== "++" &&
|
126
|
+
tokenAfter.value !== "--" &&
|
127
|
+
Boolean(tokenBefore) &&
|
128
|
+
!SAFE_TOKENS_BEFORE.test(tokenBefore.value)
|
129
|
+
);
|
130
|
+
}
|
131
|
+
|
132
|
+
/**
|
133
|
+
* Checks if the MemberExpression node's parent.parent.parent is a
|
134
|
+
* Program, BlockStatement, StaticBlock, or SwitchCase node. This check
|
135
|
+
* is necessary to avoid providing a suggestion that might cause a syntax error.
|
136
|
+
*
|
137
|
+
* eg. if (a) console.log(b), removing console.log() here will lead to a
|
138
|
+
* syntax error.
|
139
|
+
* if (a) { console.log(b) }, removing console.log() here is acceptable.
|
140
|
+
*
|
141
|
+
* Additionally, it checks if the callee of the CallExpression node is
|
142
|
+
* the node itself.
|
143
|
+
*
|
144
|
+
* eg. foo(console.log), cannot provide a suggestion here.
|
145
|
+
* @param {ASTNode} node The MemberExpression node to check.
|
146
|
+
* @returns {boolean} `true` if a suggestion can be provided for a node.
|
147
|
+
*/
|
148
|
+
function canProvideSuggestions(node) {
|
149
|
+
return (
|
150
|
+
node.parent.type === "CallExpression" &&
|
151
|
+
node.parent.callee === node &&
|
152
|
+
node.parent.parent.type === "ExpressionStatement" &&
|
153
|
+
astUtils.STATEMENT_LIST_PARENTS.has(node.parent.parent.parent.type) &&
|
154
|
+
!maybeAsiHazard(node.parent.parent)
|
155
|
+
);
|
156
|
+
}
|
157
|
+
|
97
158
|
/**
|
98
159
|
* Reports the given reference as a violation.
|
99
160
|
* @param {eslint-scope.Reference} reference The reference to report.
|
@@ -102,10 +163,21 @@ module.exports = {
|
|
102
163
|
function report(reference) {
|
103
164
|
const node = reference.identifier.parent;
|
104
165
|
|
166
|
+
const propertyName = astUtils.getStaticPropertyName(node);
|
167
|
+
|
105
168
|
context.report({
|
106
169
|
node,
|
107
170
|
loc: node.loc,
|
108
|
-
messageId: "unexpected"
|
171
|
+
messageId: "unexpected",
|
172
|
+
suggest: canProvideSuggestions(node)
|
173
|
+
? [{
|
174
|
+
messageId: "removeConsole",
|
175
|
+
data: { propertyName },
|
176
|
+
fix(fixer) {
|
177
|
+
return fixer.remove(node.parent.parent);
|
178
|
+
}
|
179
|
+
}]
|
180
|
+
: []
|
109
181
|
});
|
110
182
|
}
|
111
183
|
|
@@ -9,67 +9,12 @@
|
|
9
9
|
// Requirements
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
|
-
const {
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]);
|
19
|
-
|
20
|
-
// Declaration types that must contain a string Literal node at the end.
|
21
|
-
const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]);
|
22
|
-
|
23
|
-
const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]);
|
24
|
-
|
25
|
-
// Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types.
|
26
|
-
const NODE_TYPES_BY_KEYWORD = {
|
27
|
-
__proto__: null,
|
28
|
-
break: "BreakStatement",
|
29
|
-
continue: "ContinueStatement",
|
30
|
-
debugger: "DebuggerStatement",
|
31
|
-
do: "DoWhileStatement",
|
32
|
-
else: "IfStatement",
|
33
|
-
return: "ReturnStatement",
|
34
|
-
yield: "YieldExpression"
|
35
|
-
};
|
36
|
-
|
37
|
-
/*
|
38
|
-
* Before an opening parenthesis, postfix `++` and `--` always trigger ASI;
|
39
|
-
* the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement.
|
40
|
-
*/
|
41
|
-
const PUNCTUATORS = new Set([":", ";", "{", "=>", "++", "--"]);
|
42
|
-
|
43
|
-
/*
|
44
|
-
* Statements that can contain an `ExpressionStatement` after a closing parenthesis.
|
45
|
-
* DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis.
|
46
|
-
*/
|
47
|
-
const STATEMENTS = new Set([
|
48
|
-
"DoWhileStatement",
|
49
|
-
"ForInStatement",
|
50
|
-
"ForOfStatement",
|
51
|
-
"ForStatement",
|
52
|
-
"IfStatement",
|
53
|
-
"WhileStatement",
|
54
|
-
"WithStatement"
|
55
|
-
]);
|
56
|
-
|
57
|
-
/**
|
58
|
-
* Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
|
59
|
-
* @param {ASTNode} node The node to check.
|
60
|
-
* @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node.
|
61
|
-
*/
|
62
|
-
function isStartOfExpressionStatement(node) {
|
63
|
-
const start = node.range[0];
|
64
|
-
let ancestor = node;
|
65
|
-
|
66
|
-
while ((ancestor = ancestor.parent) && ancestor.range[0] === start) {
|
67
|
-
if (ancestor.type === "ExpressionStatement") {
|
68
|
-
return true;
|
69
|
-
}
|
70
|
-
}
|
71
|
-
return false;
|
72
|
-
}
|
12
|
+
const {
|
13
|
+
getVariableByName,
|
14
|
+
isArrowToken,
|
15
|
+
isStartOfExpressionStatement,
|
16
|
+
needsPrecedingSemicolon
|
17
|
+
} = require("./utils/ast-utils");
|
73
18
|
|
74
19
|
//------------------------------------------------------------------------------
|
75
20
|
// Rule Definition
|
@@ -120,50 +65,6 @@ module.exports = {
|
|
120
65
|
return false;
|
121
66
|
}
|
122
67
|
|
123
|
-
/**
|
124
|
-
* Determines whether a parenthesized object literal that replaces a specified node needs to be preceded by a semicolon.
|
125
|
-
* @param {ASTNode} node The node to be replaced. This node should be at the start of an `ExpressionStatement` or at the start of the body of an `ArrowFunctionExpression`.
|
126
|
-
* @returns {boolean} Whether a semicolon is required before the parenthesized object literal.
|
127
|
-
*/
|
128
|
-
function needsSemicolon(node) {
|
129
|
-
const prevToken = sourceCode.getTokenBefore(node);
|
130
|
-
|
131
|
-
if (!prevToken || prevToken.type === "Punctuator" && PUNCTUATORS.has(prevToken.value)) {
|
132
|
-
return false;
|
133
|
-
}
|
134
|
-
|
135
|
-
const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
|
136
|
-
|
137
|
-
if (isClosingParenToken(prevToken)) {
|
138
|
-
return !STATEMENTS.has(prevNode.type);
|
139
|
-
}
|
140
|
-
|
141
|
-
if (isClosingBraceToken(prevToken)) {
|
142
|
-
return (
|
143
|
-
prevNode.type === "BlockStatement" && prevNode.parent.type === "FunctionExpression" ||
|
144
|
-
prevNode.type === "ClassBody" && prevNode.parent.type === "ClassExpression" ||
|
145
|
-
prevNode.type === "ObjectExpression"
|
146
|
-
);
|
147
|
-
}
|
148
|
-
|
149
|
-
if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) {
|
150
|
-
if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) {
|
151
|
-
return false;
|
152
|
-
}
|
153
|
-
|
154
|
-
const keyword = prevToken.value;
|
155
|
-
const nodeType = NODE_TYPES_BY_KEYWORD[keyword];
|
156
|
-
|
157
|
-
return prevNode.type !== nodeType;
|
158
|
-
}
|
159
|
-
|
160
|
-
if (prevToken.type === "String") {
|
161
|
-
return !DECLARATIONS.has(prevNode.parent.type);
|
162
|
-
}
|
163
|
-
|
164
|
-
return true;
|
165
|
-
}
|
166
|
-
|
167
68
|
/**
|
168
69
|
* Reports on nodes where the `Object` constructor is called without arguments.
|
169
70
|
* @param {ASTNode} node The node to evaluate.
|
@@ -183,7 +84,7 @@ module.exports = {
|
|
183
84
|
|
184
85
|
if (needsParentheses(node)) {
|
185
86
|
replacement = "({})";
|
186
|
-
if (
|
87
|
+
if (needsPrecedingSemicolon(sourceCode, node)) {
|
187
88
|
fixText = ";({})";
|
188
89
|
messageId = "useLiteralAfterSemicolon";
|
189
90
|
} else {
|
@@ -74,6 +74,9 @@ const arrayOfStringsOrObjectPatterns = {
|
|
74
74
|
minItems: 1,
|
75
75
|
uniqueItems: true
|
76
76
|
},
|
77
|
+
importNamePattern: {
|
78
|
+
type: "string"
|
79
|
+
},
|
77
80
|
message: {
|
78
81
|
type: "string",
|
79
82
|
minLength: 1
|
@@ -115,8 +118,12 @@ module.exports = {
|
|
115
118
|
patternAndImportNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}",
|
116
119
|
|
117
120
|
patternAndEverything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern.",
|
121
|
+
|
122
|
+
patternAndEverythingWithRegexImportName: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used.",
|
118
123
|
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
|
119
124
|
patternAndEverythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}",
|
125
|
+
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
|
126
|
+
patternAndEverythingWithRegexImportNameAndCustomMessage: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used. {{customMessage}}",
|
120
127
|
|
121
128
|
everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
|
122
129
|
// eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
|
@@ -175,10 +182,11 @@ module.exports = {
|
|
175
182
|
}
|
176
183
|
|
177
184
|
// relative paths are supported for this rule
|
178
|
-
const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive, importNames }) => ({
|
185
|
+
const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive, importNames, importNamePattern }) => ({
|
179
186
|
matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group),
|
180
187
|
customMessage: message,
|
181
|
-
importNames
|
188
|
+
importNames,
|
189
|
+
importNamePattern
|
182
190
|
}));
|
183
191
|
|
184
192
|
// if no imports are restricted we don't need to check
|
@@ -262,12 +270,13 @@ module.exports = {
|
|
262
270
|
|
263
271
|
const customMessage = group.customMessage;
|
264
272
|
const restrictedImportNames = group.importNames;
|
273
|
+
const restrictedImportNamePattern = group.importNamePattern ? new RegExp(group.importNamePattern, "u") : null;
|
265
274
|
|
266
275
|
/*
|
267
276
|
* If we are not restricting to any specific import names and just the pattern itself,
|
268
277
|
* report the error and move on
|
269
278
|
*/
|
270
|
-
if (!restrictedImportNames) {
|
279
|
+
if (!restrictedImportNames && !restrictedImportNamePattern) {
|
271
280
|
context.report({
|
272
281
|
node,
|
273
282
|
messageId: customMessage ? "patternWithCustomMessage" : "patterns",
|
@@ -279,40 +288,54 @@ module.exports = {
|
|
279
288
|
return;
|
280
289
|
}
|
281
290
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
291
|
+
importNames.forEach((specifiers, importName) => {
|
292
|
+
if (importName === "*") {
|
293
|
+
const [specifier] = specifiers;
|
294
|
+
|
295
|
+
if (restrictedImportNames) {
|
296
|
+
context.report({
|
297
|
+
node,
|
298
|
+
messageId: customMessage ? "patternAndEverythingWithCustomMessage" : "patternAndEverything",
|
299
|
+
loc: specifier.loc,
|
300
|
+
data: {
|
301
|
+
importSource,
|
302
|
+
importNames: restrictedImportNames,
|
303
|
+
customMessage
|
304
|
+
}
|
305
|
+
});
|
306
|
+
} else {
|
307
|
+
context.report({
|
308
|
+
node,
|
309
|
+
messageId: customMessage ? "patternAndEverythingWithRegexImportNameAndCustomMessage" : "patternAndEverythingWithRegexImportName",
|
310
|
+
loc: specifier.loc,
|
311
|
+
data: {
|
312
|
+
importSource,
|
313
|
+
importNames: restrictedImportNamePattern,
|
314
|
+
customMessage
|
315
|
+
}
|
316
|
+
});
|
293
317
|
}
|
294
|
-
});
|
295
|
-
}
|
296
318
|
|
297
|
-
restrictedImportNames.forEach(importName => {
|
298
|
-
if (!importNames.has(importName)) {
|
299
319
|
return;
|
300
320
|
}
|
301
321
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
322
|
+
if (
|
323
|
+
(restrictedImportNames && restrictedImportNames.includes(importName)) ||
|
324
|
+
(restrictedImportNamePattern && restrictedImportNamePattern.test(importName))
|
325
|
+
) {
|
326
|
+
specifiers.forEach(specifier => {
|
327
|
+
context.report({
|
328
|
+
node,
|
329
|
+
messageId: customMessage ? "patternAndImportNameWithCustomMessage" : "patternAndImportName",
|
330
|
+
loc: specifier.loc,
|
331
|
+
data: {
|
332
|
+
importSource,
|
333
|
+
customMessage,
|
334
|
+
importName
|
335
|
+
}
|
336
|
+
});
|
314
337
|
});
|
315
|
-
}
|
338
|
+
}
|
316
339
|
});
|
317
340
|
}
|
318
341
|
|
@@ -1015,6 +1015,114 @@ function isDirective(node) {
|
|
1015
1015
|
return node.type === "ExpressionStatement" && typeof node.directive === "string";
|
1016
1016
|
}
|
1017
1017
|
|
1018
|
+
/**
|
1019
|
+
* Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
|
1020
|
+
* @param {ASTNode} node The node to check.
|
1021
|
+
* @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node.
|
1022
|
+
*/
|
1023
|
+
function isStartOfExpressionStatement(node) {
|
1024
|
+
const start = node.range[0];
|
1025
|
+
let ancestor = node;
|
1026
|
+
|
1027
|
+
while ((ancestor = ancestor.parent) && ancestor.range[0] === start) {
|
1028
|
+
if (ancestor.type === "ExpressionStatement") {
|
1029
|
+
return true;
|
1030
|
+
}
|
1031
|
+
}
|
1032
|
+
return false;
|
1033
|
+
}
|
1034
|
+
|
1035
|
+
/**
|
1036
|
+
* Determines whether an opening parenthesis `(`, bracket `[` or backtick ``` ` ``` needs to be preceded by a semicolon.
|
1037
|
+
* This opening parenthesis or bracket should be at the start of an `ExpressionStatement` or at the start of the body of an `ArrowFunctionExpression`.
|
1038
|
+
* @type {(sourceCode: SourceCode, node: ASTNode) => boolean}
|
1039
|
+
* @param {SourceCode} sourceCode The source code object.
|
1040
|
+
* @param {ASTNode} node A node at the position where an opening parenthesis or bracket will be inserted.
|
1041
|
+
* @returns {boolean} Whether a semicolon is required before the opening parenthesis or braket.
|
1042
|
+
*/
|
1043
|
+
let needsPrecedingSemicolon;
|
1044
|
+
|
1045
|
+
{
|
1046
|
+
const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]);
|
1047
|
+
|
1048
|
+
// Declaration types that must contain a string Literal node at the end.
|
1049
|
+
const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]);
|
1050
|
+
|
1051
|
+
const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]);
|
1052
|
+
|
1053
|
+
// Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types.
|
1054
|
+
const NODE_TYPES_BY_KEYWORD = {
|
1055
|
+
__proto__: null,
|
1056
|
+
break: "BreakStatement",
|
1057
|
+
continue: "ContinueStatement",
|
1058
|
+
debugger: "DebuggerStatement",
|
1059
|
+
do: "DoWhileStatement",
|
1060
|
+
else: "IfStatement",
|
1061
|
+
return: "ReturnStatement",
|
1062
|
+
yield: "YieldExpression"
|
1063
|
+
};
|
1064
|
+
|
1065
|
+
/*
|
1066
|
+
* Before an opening parenthesis, postfix `++` and `--` always trigger ASI;
|
1067
|
+
* the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement.
|
1068
|
+
*/
|
1069
|
+
const PUNCTUATORS = new Set([":", ";", "{", "=>", "++", "--"]);
|
1070
|
+
|
1071
|
+
/*
|
1072
|
+
* Statements that can contain an `ExpressionStatement` after a closing parenthesis.
|
1073
|
+
* DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis.
|
1074
|
+
*/
|
1075
|
+
const STATEMENTS = new Set([
|
1076
|
+
"DoWhileStatement",
|
1077
|
+
"ForInStatement",
|
1078
|
+
"ForOfStatement",
|
1079
|
+
"ForStatement",
|
1080
|
+
"IfStatement",
|
1081
|
+
"WhileStatement",
|
1082
|
+
"WithStatement"
|
1083
|
+
]);
|
1084
|
+
|
1085
|
+
needsPrecedingSemicolon =
|
1086
|
+
function(sourceCode, node) {
|
1087
|
+
const prevToken = sourceCode.getTokenBefore(node);
|
1088
|
+
|
1089
|
+
if (!prevToken || prevToken.type === "Punctuator" && PUNCTUATORS.has(prevToken.value)) {
|
1090
|
+
return false;
|
1091
|
+
}
|
1092
|
+
|
1093
|
+
const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
|
1094
|
+
|
1095
|
+
if (isClosingParenToken(prevToken)) {
|
1096
|
+
return !STATEMENTS.has(prevNode.type);
|
1097
|
+
}
|
1098
|
+
|
1099
|
+
if (isClosingBraceToken(prevToken)) {
|
1100
|
+
return (
|
1101
|
+
prevNode.type === "BlockStatement" && prevNode.parent.type === "FunctionExpression" ||
|
1102
|
+
prevNode.type === "ClassBody" && prevNode.parent.type === "ClassExpression" ||
|
1103
|
+
prevNode.type === "ObjectExpression"
|
1104
|
+
);
|
1105
|
+
}
|
1106
|
+
|
1107
|
+
if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) {
|
1108
|
+
if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) {
|
1109
|
+
return false;
|
1110
|
+
}
|
1111
|
+
|
1112
|
+
const keyword = prevToken.value;
|
1113
|
+
const nodeType = NODE_TYPES_BY_KEYWORD[keyword];
|
1114
|
+
|
1115
|
+
return prevNode.type !== nodeType;
|
1116
|
+
}
|
1117
|
+
|
1118
|
+
if (prevToken.type === "String") {
|
1119
|
+
return !DECLARATIONS.has(prevNode.parent.type);
|
1120
|
+
}
|
1121
|
+
|
1122
|
+
return true;
|
1123
|
+
};
|
1124
|
+
}
|
1125
|
+
|
1018
1126
|
//------------------------------------------------------------------------------
|
1019
1127
|
// Public Interface
|
1020
1128
|
//------------------------------------------------------------------------------
|
@@ -2168,5 +2276,7 @@ module.exports = {
|
|
2168
2276
|
getModuleExportName,
|
2169
2277
|
isConstant,
|
2170
2278
|
isTopLevelExpressionStatement,
|
2171
|
-
isDirective
|
2279
|
+
isDirective,
|
2280
|
+
isStartOfExpressionStatement,
|
2281
|
+
needsPrecedingSemicolon
|
2172
2282
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "8.
|
3
|
+
"version": "8.55.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -19,6 +19,7 @@
|
|
19
19
|
"build:readme": "node tools/update-readme.js",
|
20
20
|
"lint": "node Makefile.js lint",
|
21
21
|
"lint:docs:js": "node Makefile.js lintDocsJS",
|
22
|
+
"lint:docs:rule-examples": "node Makefile.js checkRuleExamples",
|
22
23
|
"lint:fix": "node Makefile.js lint -- fix",
|
23
24
|
"lint:fix:docs:js": "node Makefile.js lintDocsJS -- fix",
|
24
25
|
"release:generate:alpha": "node Makefile.js generatePrerelease -- alpha",
|
@@ -41,6 +42,11 @@
|
|
41
42
|
"node tools/update-eslint-all.js",
|
42
43
|
"git add packages/js/src/configs/eslint-all.js"
|
43
44
|
],
|
45
|
+
"docs/src/rules/*.md": [
|
46
|
+
"node tools/check-rule-examples.js",
|
47
|
+
"node tools/fetch-docs-links.js",
|
48
|
+
"git add docs/src/_data/further_reading_links.json"
|
49
|
+
],
|
44
50
|
"docs/**/*.svg": "npx svgo -r --multipass"
|
45
51
|
},
|
46
52
|
"files": [
|
@@ -58,8 +64,8 @@
|
|
58
64
|
"dependencies": {
|
59
65
|
"@eslint-community/eslint-utils": "^4.2.0",
|
60
66
|
"@eslint-community/regexpp": "^4.6.1",
|
61
|
-
"@eslint/eslintrc": "^2.1.
|
62
|
-
"@eslint/js": "8.
|
67
|
+
"@eslint/eslintrc": "^2.1.4",
|
68
|
+
"@eslint/js": "8.55.0",
|
63
69
|
"@humanwhocodes/config-array": "^0.11.13",
|
64
70
|
"@humanwhocodes/module-importer": "^1.0.1",
|
65
71
|
"@nodelib/fs.walk": "^1.2.8",
|
@@ -128,10 +134,18 @@
|
|
128
134
|
"gray-matter": "^4.0.3",
|
129
135
|
"lint-staged": "^11.0.0",
|
130
136
|
"load-perf": "^0.2.0",
|
131
|
-
"
|
132
|
-
"
|
137
|
+
"markdown-it": "^12.2.0",
|
138
|
+
"markdown-it-container": "^3.0.0",
|
139
|
+
"markdownlint": "^0.31.1",
|
140
|
+
"markdownlint-cli": "^0.37.0",
|
133
141
|
"marked": "^4.0.8",
|
134
142
|
"memfs": "^3.0.1",
|
143
|
+
"metascraper": "^5.25.7",
|
144
|
+
"metascraper-description": "^5.25.7",
|
145
|
+
"metascraper-image": "^5.29.3",
|
146
|
+
"metascraper-logo": "^5.25.7",
|
147
|
+
"metascraper-logo-favicon": "^5.25.7",
|
148
|
+
"metascraper-title": "^5.25.7",
|
135
149
|
"mocha": "^8.3.2",
|
136
150
|
"mocha-junit-reporter": "^2.0.0",
|
137
151
|
"node-polyfill-webpack-plugin": "^1.0.3",
|
@@ -139,13 +153,13 @@
|
|
139
153
|
"pirates": "^4.0.5",
|
140
154
|
"progress": "^2.0.3",
|
141
155
|
"proxyquire": "^2.0.1",
|
142
|
-
"recast": "^0.
|
143
|
-
"regenerator-runtime": "^0.
|
156
|
+
"recast": "^0.23.0",
|
157
|
+
"regenerator-runtime": "^0.14.0",
|
144
158
|
"rollup-plugin-node-polyfills": "^0.2.1",
|
145
159
|
"semver": "^7.5.3",
|
146
160
|
"shelljs": "^0.8.2",
|
147
161
|
"sinon": "^11.0.0",
|
148
|
-
"vite-plugin-commonjs": "^0.
|
162
|
+
"vite-plugin-commonjs": "^0.10.0",
|
149
163
|
"webdriverio": "^8.14.6",
|
150
164
|
"webpack": "^5.23.0",
|
151
165
|
"webpack-cli": "^4.5.0",
|