eslint 9.3.0 → 9.4.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.js +2 -2
- package/lib/config/flat-config-array.js +1 -1
- package/lib/eslint/eslint-helpers.js +7 -7
- package/lib/eslint/eslint.js +1 -10
- package/lib/rule-tester/rule-tester.js +1 -1
- package/lib/rules/func-style.js +4 -4
- package/lib/rules/no-constructor-return.js +1 -1
- package/lib/rules/no-loop-func.js +161 -129
- package/package.json +3 -3
package/lib/cli.js
CHANGED
@@ -448,14 +448,14 @@ const cli = {
|
|
448
448
|
|
449
449
|
if (options.inspectConfig) {
|
450
450
|
|
451
|
-
log.info("You can also run this command directly using 'npx @eslint/config-inspector' in the same directory as your configuration file.");
|
451
|
+
log.info("You can also run this command directly using 'npx @eslint/config-inspector@latest' in the same directory as your configuration file.");
|
452
452
|
|
453
453
|
try {
|
454
454
|
const flatOptions = await translateOptions(options, "flat");
|
455
455
|
const spawn = require("cross-spawn");
|
456
456
|
const flags = await cli.calculateInspectConfigFlags(flatOptions.overrideConfigFile);
|
457
457
|
|
458
|
-
spawn.sync("npx", ["@eslint/config-inspector", ...flags], { encoding: "utf8", stdio: "inherit" });
|
458
|
+
spawn.sync("npx", ["@eslint/config-inspector@latest", ...flags], { encoding: "utf8", stdio: "inherit" });
|
459
459
|
} catch (error) {
|
460
460
|
log.error(error);
|
461
461
|
return 2;
|
@@ -9,7 +9,7 @@
|
|
9
9
|
// Requirements
|
10
10
|
//-----------------------------------------------------------------------------
|
11
11
|
|
12
|
-
const { ConfigArray, ConfigArraySymbol } = require("@
|
12
|
+
const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array");
|
13
13
|
const { flatConfigSchema } = require("./flat-config-schema");
|
14
14
|
const { RuleValidator } = require("./rule-validator");
|
15
15
|
const { defaultConfig } = require("./default-config");
|
@@ -335,15 +335,15 @@ async function globSearch({
|
|
335
335
|
|
336
336
|
/*
|
337
337
|
* We updated the unmatched patterns set only if the path
|
338
|
-
* matches and the file
|
339
|
-
*
|
338
|
+
* matches and the file has a config. If the file has no
|
339
|
+
* config, that means there wasn't a match for the
|
340
340
|
* pattern so it should not be removed.
|
341
341
|
*
|
342
|
-
* Performance note:
|
342
|
+
* Performance note: `getConfig()` aggressively caches
|
343
343
|
* results so there is no performance penalty for calling
|
344
|
-
* it
|
344
|
+
* it multiple times with the same argument.
|
345
345
|
*/
|
346
|
-
if (pathMatches &&
|
346
|
+
if (pathMatches && configs.getConfig(entry.path)) {
|
347
347
|
unmatchedPatterns.delete(matcher.pattern);
|
348
348
|
}
|
349
349
|
|
@@ -351,7 +351,7 @@ async function globSearch({
|
|
351
351
|
}, false)
|
352
352
|
: matchers.some(matcher => matcher.match(relativePath));
|
353
353
|
|
354
|
-
return matchesPattern &&
|
354
|
+
return matchesPattern && configs.getConfig(entry.path) !== void 0;
|
355
355
|
})
|
356
356
|
},
|
357
357
|
(error, entries) => {
|
@@ -545,7 +545,7 @@ async function findFiles({
|
|
545
545
|
if (stat.isFile()) {
|
546
546
|
results.push({
|
547
547
|
filePath,
|
548
|
-
ignored: configs.
|
548
|
+
ignored: !configs.getConfig(filePath)
|
549
549
|
});
|
550
550
|
}
|
551
551
|
|
package/lib/eslint/eslint.js
CHANGED
@@ -509,7 +509,7 @@ function verifyText({
|
|
509
509
|
* @returns {boolean} `true` if the linter should adopt the code block.
|
510
510
|
*/
|
511
511
|
filterCodeBlock(blockFilename) {
|
512
|
-
return configs.
|
512
|
+
return configs.getConfig(blockFilename) !== void 0;
|
513
513
|
}
|
514
514
|
}
|
515
515
|
);
|
@@ -881,15 +881,6 @@ class ESLint {
|
|
881
881
|
|
882
882
|
const config = configs.getConfig(filePath);
|
883
883
|
|
884
|
-
/*
|
885
|
-
* Sometimes a file found through a glob pattern will
|
886
|
-
* be ignored. In this case, `config` will be undefined
|
887
|
-
* and we just silently ignore the file.
|
888
|
-
*/
|
889
|
-
if (!config) {
|
890
|
-
return void 0;
|
891
|
-
}
|
892
|
-
|
893
884
|
// Skip if there is cached result.
|
894
885
|
if (lintResultCache) {
|
895
886
|
const cachedResult =
|
@@ -28,7 +28,7 @@ const ajv = require("../shared/ajv")({ strictDefaults: true });
|
|
28
28
|
|
29
29
|
const parserSymbol = Symbol.for("eslint.RuleTester.parser");
|
30
30
|
const { SourceCode } = require("../source-code");
|
31
|
-
const { ConfigArraySymbol } = require("@
|
31
|
+
const { ConfigArraySymbol } = require("@eslint/config-array");
|
32
32
|
const { isSerializable } = require("../shared/serialization");
|
33
33
|
|
34
34
|
//------------------------------------------------------------------------------
|
package/lib/rules/func-style.js
CHANGED
@@ -14,7 +14,7 @@ module.exports = {
|
|
14
14
|
type: "suggestion",
|
15
15
|
|
16
16
|
docs: {
|
17
|
-
description: "Enforce the consistent use of either `function` declarations or expressions",
|
17
|
+
description: "Enforce the consistent use of either `function` declarations or expressions assigned to variables",
|
18
18
|
recommended: false,
|
19
19
|
url: "https://eslint.org/docs/latest/rules/func-style"
|
20
20
|
},
|
@@ -100,7 +100,7 @@ module.exports = {
|
|
100
100
|
stack.pop();
|
101
101
|
},
|
102
102
|
|
103
|
-
ThisExpression() {
|
103
|
+
"ThisExpression, Super"() {
|
104
104
|
if (stack.length > 0) {
|
105
105
|
stack[stack.length - 1] = true;
|
106
106
|
}
|
@@ -113,9 +113,9 @@ module.exports = {
|
|
113
113
|
};
|
114
114
|
|
115
115
|
nodesToCheck["ArrowFunctionExpression:exit"] = function(node) {
|
116
|
-
const
|
116
|
+
const hasThisOrSuperExpr = stack.pop();
|
117
117
|
|
118
|
-
if (!
|
118
|
+
if (!hasThisOrSuperExpr && node.parent.type === "VariableDeclarator") {
|
119
119
|
if (
|
120
120
|
enforceDeclarations &&
|
121
121
|
(typeof exportFunctionStyle === "undefined" || node.parent.parent.parent.type !== "ExportNamedDeclaration")
|
@@ -9,140 +9,16 @@
|
|
9
9
|
// Helpers
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
|
-
/**
|
13
|
-
* Gets the containing loop node of a specified node.
|
14
|
-
*
|
15
|
-
* We don't need to check nested functions, so this ignores those.
|
16
|
-
* `Scope.through` contains references of nested functions.
|
17
|
-
* @param {ASTNode} node An AST node to get.
|
18
|
-
* @returns {ASTNode|null} The containing loop node of the specified node, or
|
19
|
-
* `null`.
|
20
|
-
*/
|
21
|
-
function getContainingLoopNode(node) {
|
22
|
-
for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) {
|
23
|
-
const parent = currentNode.parent;
|
24
|
-
|
25
|
-
switch (parent.type) {
|
26
|
-
case "WhileStatement":
|
27
|
-
case "DoWhileStatement":
|
28
|
-
return parent;
|
29
|
-
|
30
|
-
case "ForStatement":
|
31
|
-
|
32
|
-
// `init` is outside of the loop.
|
33
|
-
if (parent.init !== currentNode) {
|
34
|
-
return parent;
|
35
|
-
}
|
36
|
-
break;
|
37
|
-
|
38
|
-
case "ForInStatement":
|
39
|
-
case "ForOfStatement":
|
40
|
-
|
41
|
-
// `right` is outside of the loop.
|
42
|
-
if (parent.right !== currentNode) {
|
43
|
-
return parent;
|
44
|
-
}
|
45
|
-
break;
|
46
|
-
|
47
|
-
case "ArrowFunctionExpression":
|
48
|
-
case "FunctionExpression":
|
49
|
-
case "FunctionDeclaration":
|
50
|
-
|
51
|
-
// We don't need to check nested functions.
|
52
|
-
return null;
|
53
|
-
|
54
|
-
default:
|
55
|
-
break;
|
56
|
-
}
|
57
|
-
}
|
58
|
-
|
59
|
-
return null;
|
60
|
-
}
|
61
12
|
|
62
13
|
/**
|
63
|
-
*
|
64
|
-
*
|
65
|
-
* @
|
66
|
-
* @param {ASTNode|null} excludedNode A node that the result node should not
|
67
|
-
* include.
|
68
|
-
* @returns {ASTNode} The most outer loop node.
|
14
|
+
* Identifies is a node is a FunctionExpression which is part of an IIFE
|
15
|
+
* @param {ASTNode} node Node to test
|
16
|
+
* @returns {boolean} True if it's an IIFE
|
69
17
|
*/
|
70
|
-
function
|
71
|
-
|
72
|
-
let retv = node;
|
73
|
-
let containingLoopNode = node;
|
74
|
-
|
75
|
-
while (containingLoopNode && containingLoopNode.range[0] >= border) {
|
76
|
-
retv = containingLoopNode;
|
77
|
-
containingLoopNode = getContainingLoopNode(containingLoopNode);
|
78
|
-
}
|
79
|
-
|
80
|
-
return retv;
|
18
|
+
function isIIFE(node) {
|
19
|
+
return (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node;
|
81
20
|
}
|
82
21
|
|
83
|
-
/**
|
84
|
-
* Checks whether a given reference which refers to an upper scope's variable is
|
85
|
-
* safe or not.
|
86
|
-
* @param {ASTNode} loopNode A containing loop node.
|
87
|
-
* @param {eslint-scope.Reference} reference A reference to check.
|
88
|
-
* @returns {boolean} `true` if the reference is safe or not.
|
89
|
-
*/
|
90
|
-
function isSafe(loopNode, reference) {
|
91
|
-
const variable = reference.resolved;
|
92
|
-
const definition = variable && variable.defs[0];
|
93
|
-
const declaration = definition && definition.parent;
|
94
|
-
const kind = (declaration && declaration.type === "VariableDeclaration")
|
95
|
-
? declaration.kind
|
96
|
-
: "";
|
97
|
-
|
98
|
-
// Variables which are declared by `const` is safe.
|
99
|
-
if (kind === "const") {
|
100
|
-
return true;
|
101
|
-
}
|
102
|
-
|
103
|
-
/*
|
104
|
-
* Variables which are declared by `let` in the loop is safe.
|
105
|
-
* It's a different instance from the next loop step's.
|
106
|
-
*/
|
107
|
-
if (kind === "let" &&
|
108
|
-
declaration.range[0] > loopNode.range[0] &&
|
109
|
-
declaration.range[1] < loopNode.range[1]
|
110
|
-
) {
|
111
|
-
return true;
|
112
|
-
}
|
113
|
-
|
114
|
-
/*
|
115
|
-
* WriteReferences which exist after this border are unsafe because those
|
116
|
-
* can modify the variable.
|
117
|
-
*/
|
118
|
-
const border = getTopLoopNode(
|
119
|
-
loopNode,
|
120
|
-
(kind === "let") ? declaration : null
|
121
|
-
).range[0];
|
122
|
-
|
123
|
-
/**
|
124
|
-
* Checks whether a given reference is safe or not.
|
125
|
-
* The reference is every reference of the upper scope's variable we are
|
126
|
-
* looking now.
|
127
|
-
*
|
128
|
-
* It's safe if the reference matches one of the following condition.
|
129
|
-
* - is readonly.
|
130
|
-
* - doesn't exist inside a local function and after the border.
|
131
|
-
* @param {eslint-scope.Reference} upperRef A reference to check.
|
132
|
-
* @returns {boolean} `true` if the reference is safe.
|
133
|
-
*/
|
134
|
-
function isSafeReference(upperRef) {
|
135
|
-
const id = upperRef.identifier;
|
136
|
-
|
137
|
-
return (
|
138
|
-
!upperRef.isWrite() ||
|
139
|
-
variable.scope.variableScope === upperRef.from.variableScope &&
|
140
|
-
id.range[0] < border
|
141
|
-
);
|
142
|
-
}
|
143
|
-
|
144
|
-
return Boolean(variable) && variable.references.every(isSafeReference);
|
145
|
-
}
|
146
22
|
|
147
23
|
//------------------------------------------------------------------------------
|
148
24
|
// Rule Definition
|
@@ -168,8 +44,147 @@ module.exports = {
|
|
168
44
|
|
169
45
|
create(context) {
|
170
46
|
|
47
|
+
const SKIPPED_IIFE_NODES = new Set();
|
171
48
|
const sourceCode = context.sourceCode;
|
172
49
|
|
50
|
+
/**
|
51
|
+
* Gets the containing loop node of a specified node.
|
52
|
+
*
|
53
|
+
* We don't need to check nested functions, so this ignores those, with the exception of IIFE.
|
54
|
+
* `Scope.through` contains references of nested functions.
|
55
|
+
* @param {ASTNode} node An AST node to get.
|
56
|
+
* @returns {ASTNode|null} The containing loop node of the specified node, or
|
57
|
+
* `null`.
|
58
|
+
*/
|
59
|
+
function getContainingLoopNode(node) {
|
60
|
+
for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) {
|
61
|
+
const parent = currentNode.parent;
|
62
|
+
|
63
|
+
switch (parent.type) {
|
64
|
+
case "WhileStatement":
|
65
|
+
case "DoWhileStatement":
|
66
|
+
return parent;
|
67
|
+
|
68
|
+
case "ForStatement":
|
69
|
+
|
70
|
+
// `init` is outside of the loop.
|
71
|
+
if (parent.init !== currentNode) {
|
72
|
+
return parent;
|
73
|
+
}
|
74
|
+
break;
|
75
|
+
|
76
|
+
case "ForInStatement":
|
77
|
+
case "ForOfStatement":
|
78
|
+
|
79
|
+
// `right` is outside of the loop.
|
80
|
+
if (parent.right !== currentNode) {
|
81
|
+
return parent;
|
82
|
+
}
|
83
|
+
break;
|
84
|
+
|
85
|
+
case "ArrowFunctionExpression":
|
86
|
+
case "FunctionExpression":
|
87
|
+
case "FunctionDeclaration":
|
88
|
+
|
89
|
+
// We need to check nested functions only in case of IIFE.
|
90
|
+
if (SKIPPED_IIFE_NODES.has(parent)) {
|
91
|
+
break;
|
92
|
+
}
|
93
|
+
|
94
|
+
return null;
|
95
|
+
default:
|
96
|
+
break;
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
return null;
|
101
|
+
}
|
102
|
+
|
103
|
+
/**
|
104
|
+
* Gets the containing loop node of a given node.
|
105
|
+
* If the loop was nested, this returns the most outer loop.
|
106
|
+
* @param {ASTNode} node A node to get. This is a loop node.
|
107
|
+
* @param {ASTNode|null} excludedNode A node that the result node should not
|
108
|
+
* include.
|
109
|
+
* @returns {ASTNode} The most outer loop node.
|
110
|
+
*/
|
111
|
+
function getTopLoopNode(node, excludedNode) {
|
112
|
+
const border = excludedNode ? excludedNode.range[1] : 0;
|
113
|
+
let retv = node;
|
114
|
+
let containingLoopNode = node;
|
115
|
+
|
116
|
+
while (containingLoopNode && containingLoopNode.range[0] >= border) {
|
117
|
+
retv = containingLoopNode;
|
118
|
+
containingLoopNode = getContainingLoopNode(containingLoopNode);
|
119
|
+
}
|
120
|
+
|
121
|
+
return retv;
|
122
|
+
}
|
123
|
+
|
124
|
+
/**
|
125
|
+
* Checks whether a given reference which refers to an upper scope's variable is
|
126
|
+
* safe or not.
|
127
|
+
* @param {ASTNode} loopNode A containing loop node.
|
128
|
+
* @param {eslint-scope.Reference} reference A reference to check.
|
129
|
+
* @returns {boolean} `true` if the reference is safe or not.
|
130
|
+
*/
|
131
|
+
function isSafe(loopNode, reference) {
|
132
|
+
const variable = reference.resolved;
|
133
|
+
const definition = variable && variable.defs[0];
|
134
|
+
const declaration = definition && definition.parent;
|
135
|
+
const kind = (declaration && declaration.type === "VariableDeclaration")
|
136
|
+
? declaration.kind
|
137
|
+
: "";
|
138
|
+
|
139
|
+
// Variables which are declared by `const` is safe.
|
140
|
+
if (kind === "const") {
|
141
|
+
return true;
|
142
|
+
}
|
143
|
+
|
144
|
+
/*
|
145
|
+
* Variables which are declared by `let` in the loop is safe.
|
146
|
+
* It's a different instance from the next loop step's.
|
147
|
+
*/
|
148
|
+
if (kind === "let" &&
|
149
|
+
declaration.range[0] > loopNode.range[0] &&
|
150
|
+
declaration.range[1] < loopNode.range[1]
|
151
|
+
) {
|
152
|
+
return true;
|
153
|
+
}
|
154
|
+
|
155
|
+
/*
|
156
|
+
* WriteReferences which exist after this border are unsafe because those
|
157
|
+
* can modify the variable.
|
158
|
+
*/
|
159
|
+
const border = getTopLoopNode(
|
160
|
+
loopNode,
|
161
|
+
(kind === "let") ? declaration : null
|
162
|
+
).range[0];
|
163
|
+
|
164
|
+
/**
|
165
|
+
* Checks whether a given reference is safe or not.
|
166
|
+
* The reference is every reference of the upper scope's variable we are
|
167
|
+
* looking now.
|
168
|
+
*
|
169
|
+
* It's safe if the reference matches one of the following condition.
|
170
|
+
* - is readonly.
|
171
|
+
* - doesn't exist inside a local function and after the border.
|
172
|
+
* @param {eslint-scope.Reference} upperRef A reference to check.
|
173
|
+
* @returns {boolean} `true` if the reference is safe.
|
174
|
+
*/
|
175
|
+
function isSafeReference(upperRef) {
|
176
|
+
const id = upperRef.identifier;
|
177
|
+
|
178
|
+
return (
|
179
|
+
!upperRef.isWrite() ||
|
180
|
+
variable.scope.variableScope === upperRef.from.variableScope &&
|
181
|
+
id.range[0] < border
|
182
|
+
);
|
183
|
+
}
|
184
|
+
|
185
|
+
return Boolean(variable) && variable.references.every(isSafeReference);
|
186
|
+
}
|
187
|
+
|
173
188
|
/**
|
174
189
|
* Reports functions which match the following condition:
|
175
190
|
*
|
@@ -186,6 +201,23 @@ module.exports = {
|
|
186
201
|
}
|
187
202
|
|
188
203
|
const references = sourceCode.getScope(node).through;
|
204
|
+
|
205
|
+
// Check if the function is not asynchronous or a generator function
|
206
|
+
if (!(node.async || node.generator)) {
|
207
|
+
if (isIIFE(node)) {
|
208
|
+
|
209
|
+
const isFunctionExpression = node.type === "FunctionExpression";
|
210
|
+
|
211
|
+
// Check if the function is referenced elsewhere in the code
|
212
|
+
const isFunctionReferenced = isFunctionExpression && node.id ? references.some(r => r.identifier.name === node.id.name) : false;
|
213
|
+
|
214
|
+
if (!isFunctionReferenced) {
|
215
|
+
SKIPPED_IIFE_NODES.add(node);
|
216
|
+
return;
|
217
|
+
}
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
189
221
|
const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name);
|
190
222
|
|
191
223
|
if (unsafeRefs.length > 0) {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "9.
|
3
|
+
"version": "9.4.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -68,9 +68,9 @@
|
|
68
68
|
"dependencies": {
|
69
69
|
"@eslint-community/eslint-utils": "^4.2.0",
|
70
70
|
"@eslint-community/regexpp": "^4.6.1",
|
71
|
+
"@eslint/config-array": "^0.15.1",
|
71
72
|
"@eslint/eslintrc": "^3.1.0",
|
72
|
-
"@eslint/js": "9.
|
73
|
-
"@humanwhocodes/config-array": "^0.13.0",
|
73
|
+
"@eslint/js": "9.4.0",
|
74
74
|
"@humanwhocodes/module-importer": "^1.0.1",
|
75
75
|
"@humanwhocodes/retry": "^0.3.0",
|
76
76
|
"@nodelib/fs.walk": "^1.2.8",
|