eslint 9.26.0 → 9.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/bin/eslint.js +7 -11
- package/conf/rule-type-list.json +2 -1
- package/lib/cli-engine/cli-engine.js +7 -7
- package/lib/cli.js +19 -16
- package/lib/config/config-loader.js +42 -39
- package/lib/config/config.js +362 -16
- package/lib/eslint/eslint-helpers.js +3 -1
- package/lib/eslint/eslint.js +31 -13
- package/lib/eslint/legacy-eslint.js +6 -6
- package/lib/languages/js/source-code/source-code.js +40 -6
- package/lib/linter/apply-disable-directives.js +1 -1
- package/lib/linter/file-context.js +11 -0
- package/lib/linter/linter.js +102 -140
- package/lib/linter/report-translator.js +2 -1
- package/lib/linter/{node-event-generator.js → source-code-traverser.js} +143 -87
- package/lib/options.js +7 -0
- package/lib/rule-tester/rule-tester.js +3 -3
- package/lib/rules/func-style.js +57 -7
- package/lib/rules/index.js +1 -0
- package/lib/rules/max-params.js +32 -7
- package/lib/rules/no-array-constructor.js +51 -1
- package/lib/rules/no-implicit-globals.js +31 -15
- package/lib/rules/no-magic-numbers.js +98 -5
- package/lib/rules/no-shadow.js +262 -6
- package/lib/rules/no-unassigned-vars.js +80 -0
- package/lib/rules/no-use-before-define.js +97 -1
- package/lib/rules/no-useless-escape.js +24 -2
- package/lib/rules/prefer-arrow-callback.js +9 -0
- package/lib/rules/prefer-named-capture-group.js +7 -1
- package/lib/services/processor-service.js +1 -1
- package/lib/services/suppressions-service.js +5 -3
- package/lib/services/warning-service.js +85 -0
- package/lib/shared/flags.js +1 -0
- package/lib/types/index.d.ts +132 -9
- package/lib/types/rules.d.ts +66 -3
- package/package.json +11 -11
- package/lib/config/flat-config-helpers.js +0 -128
- package/lib/config/rule-validator.js +0 -199
- package/lib/mcp/mcp-server.js +0 -66
- package/lib/shared/types.js +0 -229
@@ -5,6 +5,12 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
const ASSIGNMENT_NODES = new Set([
|
9
|
+
"AssignmentExpression",
|
10
|
+
"ForInStatement",
|
11
|
+
"ForOfStatement",
|
12
|
+
]);
|
13
|
+
|
8
14
|
//------------------------------------------------------------------------------
|
9
15
|
// Rule Definition
|
10
16
|
//------------------------------------------------------------------------------
|
@@ -142,27 +148,37 @@ module.exports = {
|
|
142
148
|
}
|
143
149
|
}
|
144
150
|
});
|
145
|
-
});
|
146
151
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
152
|
+
if (
|
153
|
+
isReadonlyEslintGlobalVariable &&
|
154
|
+
variable.defs.length === 0
|
155
|
+
) {
|
156
|
+
variable.references.forEach(reference => {
|
157
|
+
if (reference.isWrite() && !reference.isRead()) {
|
158
|
+
let assignmentParent =
|
159
|
+
reference.identifier.parent;
|
160
|
+
|
161
|
+
while (
|
162
|
+
assignmentParent &&
|
163
|
+
!ASSIGNMENT_NODES.has(assignmentParent.type)
|
164
|
+
) {
|
165
|
+
assignmentParent = assignmentParent.parent;
|
166
|
+
}
|
151
167
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
} else {
|
159
|
-
// Reference to an unknown variable, possible global leak.
|
160
|
-
messageId = "globalVariableLeak";
|
168
|
+
report(
|
169
|
+
assignmentParent ?? reference.identifier,
|
170
|
+
"assignmentToReadonlyGlobal",
|
171
|
+
);
|
172
|
+
}
|
173
|
+
});
|
161
174
|
}
|
175
|
+
});
|
162
176
|
|
177
|
+
// Undeclared assigned variables.
|
178
|
+
scope.implicit.variables.forEach(variable => {
|
163
179
|
// def.node is an AssignmentExpression, ForInStatement or ForOfStatement.
|
164
180
|
variable.defs.forEach(def => {
|
165
|
-
report(def.node,
|
181
|
+
report(def.node, "globalVariableLeak");
|
166
182
|
});
|
167
183
|
});
|
168
184
|
},
|
@@ -26,10 +26,73 @@ function normalizeIgnoreValue(x) {
|
|
26
26
|
return x;
|
27
27
|
}
|
28
28
|
|
29
|
+
/**
|
30
|
+
* Checks if the node parent is a TypeScript enum member
|
31
|
+
* @param {ASTNode} node The node to be validated
|
32
|
+
* @returns {boolean} True if the node parent is a TypeScript enum member
|
33
|
+
*/
|
34
|
+
function isParentTSEnumDeclaration(node) {
|
35
|
+
return node.parent.type === "TSEnumMember";
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Checks if the node is a valid TypeScript numeric literal type.
|
40
|
+
* @param {ASTNode} node The node to be validated
|
41
|
+
* @returns {boolean} True if the node is a TypeScript numeric literal type
|
42
|
+
*/
|
43
|
+
function isTSNumericLiteralType(node) {
|
44
|
+
let ancestor = node.parent;
|
45
|
+
|
46
|
+
// Go up while we're part of a type union
|
47
|
+
while (ancestor.parent.type === "TSUnionType") {
|
48
|
+
ancestor = ancestor.parent;
|
49
|
+
}
|
50
|
+
|
51
|
+
// Check if the final ancestor is in a type alias declaration
|
52
|
+
return ancestor.parent.type === "TSTypeAliasDeclaration";
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Checks if the node parent is a readonly class property
|
57
|
+
* @param {ASTNode} node The node to be validated
|
58
|
+
* @returns {boolean} True if the node parent is a readonly class property
|
59
|
+
*/
|
60
|
+
function isParentTSReadonlyPropertyDefinition(node) {
|
61
|
+
if (node.parent?.type === "PropertyDefinition" && node.parent.readonly) {
|
62
|
+
return true;
|
63
|
+
}
|
64
|
+
|
65
|
+
return false;
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* Checks if the node is part of a type indexed access (eg. Foo[4])
|
70
|
+
* @param {ASTNode} node The node to be validated
|
71
|
+
* @returns {boolean} True if the node is part of an indexed access
|
72
|
+
*/
|
73
|
+
function isAncestorTSIndexedAccessType(node) {
|
74
|
+
let ancestor = node.parent;
|
75
|
+
|
76
|
+
/*
|
77
|
+
* Go up another level while we're part of a type union (eg. 1 | 2) or
|
78
|
+
* intersection (eg. 1 & 2)
|
79
|
+
*/
|
80
|
+
while (
|
81
|
+
ancestor.parent.type === "TSUnionType" ||
|
82
|
+
ancestor.parent.type === "TSIntersectionType"
|
83
|
+
) {
|
84
|
+
ancestor = ancestor.parent;
|
85
|
+
}
|
86
|
+
|
87
|
+
return ancestor.parent.type === "TSIndexedAccessType";
|
88
|
+
}
|
89
|
+
|
29
90
|
/** @type {import('../types').Rule.RuleModule} */
|
30
91
|
module.exports = {
|
31
92
|
meta: {
|
32
93
|
type: "suggestion",
|
94
|
+
dialects: ["typescript", "javascript"],
|
95
|
+
language: "javascript",
|
33
96
|
|
34
97
|
docs: {
|
35
98
|
description: "Disallow magic numbers",
|
@@ -75,6 +138,22 @@ module.exports = {
|
|
75
138
|
type: "boolean",
|
76
139
|
default: false,
|
77
140
|
},
|
141
|
+
ignoreEnums: {
|
142
|
+
type: "boolean",
|
143
|
+
default: false,
|
144
|
+
},
|
145
|
+
ignoreNumericLiteralTypes: {
|
146
|
+
type: "boolean",
|
147
|
+
default: false,
|
148
|
+
},
|
149
|
+
ignoreReadonlyClassProperties: {
|
150
|
+
type: "boolean",
|
151
|
+
default: false,
|
152
|
+
},
|
153
|
+
ignoreTypeIndexes: {
|
154
|
+
type: "boolean",
|
155
|
+
default: false,
|
156
|
+
},
|
78
157
|
},
|
79
158
|
additionalProperties: false,
|
80
159
|
},
|
@@ -94,7 +173,12 @@ module.exports = {
|
|
94
173
|
ignoreArrayIndexes = !!config.ignoreArrayIndexes,
|
95
174
|
ignoreDefaultValues = !!config.ignoreDefaultValues,
|
96
175
|
ignoreClassFieldInitialValues =
|
97
|
-
!!config.ignoreClassFieldInitialValues
|
176
|
+
!!config.ignoreClassFieldInitialValues,
|
177
|
+
ignoreEnums = !!config.ignoreEnums,
|
178
|
+
ignoreNumericLiteralTypes = !!config.ignoreNumericLiteralTypes,
|
179
|
+
ignoreReadonlyClassProperties =
|
180
|
+
!!config.ignoreReadonlyClassProperties,
|
181
|
+
ignoreTypeIndexes = !!config.ignoreTypeIndexes;
|
98
182
|
|
99
183
|
const okTypes = detectObjects
|
100
184
|
? []
|
@@ -217,14 +301,15 @@ module.exports = {
|
|
217
301
|
let value;
|
218
302
|
let raw;
|
219
303
|
|
220
|
-
// Treat unary minus as a part of the number
|
304
|
+
// Treat unary minus/plus as a part of the number
|
221
305
|
if (
|
222
306
|
node.parent.type === "UnaryExpression" &&
|
223
|
-
node.parent.operator
|
307
|
+
["-", "+"].includes(node.parent.operator)
|
224
308
|
) {
|
225
309
|
fullNumberNode = node.parent;
|
226
|
-
value =
|
227
|
-
|
310
|
+
value =
|
311
|
+
node.parent.operator === "-" ? -node.value : node.value;
|
312
|
+
raw = `${node.parent.operator}${node.raw}`;
|
228
313
|
} else {
|
229
314
|
fullNumberNode = node;
|
230
315
|
value = node.value;
|
@@ -239,6 +324,14 @@ module.exports = {
|
|
239
324
|
(ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||
|
240
325
|
(ignoreClassFieldInitialValues &&
|
241
326
|
isClassFieldInitialValue(fullNumberNode)) ||
|
327
|
+
(ignoreEnums &&
|
328
|
+
isParentTSEnumDeclaration(fullNumberNode)) ||
|
329
|
+
(ignoreNumericLiteralTypes &&
|
330
|
+
isTSNumericLiteralType(fullNumberNode)) ||
|
331
|
+
(ignoreTypeIndexes &&
|
332
|
+
isAncestorTSIndexedAccessType(fullNumberNode)) ||
|
333
|
+
(ignoreReadonlyClassProperties &&
|
334
|
+
isParentTSReadonlyPropertyDefinition(fullNumberNode)) ||
|
242
335
|
isParseIntRadix(fullNumberNode) ||
|
243
336
|
isJSXNumber(fullNumberNode) ||
|
244
337
|
(ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))
|
package/lib/rules/no-shadow.js
CHANGED
@@ -24,6 +24,23 @@ const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u;
|
|
24
24
|
const SENTINEL_TYPE =
|
25
25
|
/^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u;
|
26
26
|
|
27
|
+
// TS-specific node types
|
28
|
+
const TYPES_HOISTED_NODES = new Set([
|
29
|
+
"TSInterfaceDeclaration",
|
30
|
+
"TSTypeAliasDeclaration",
|
31
|
+
]);
|
32
|
+
|
33
|
+
// TS-specific function variable def types
|
34
|
+
const ALLOWED_FUNCTION_VARIABLE_DEF_TYPES = new Set([
|
35
|
+
"TSCallSignatureDeclaration",
|
36
|
+
"TSFunctionType",
|
37
|
+
"TSMethodSignature",
|
38
|
+
"TSEmptyBodyFunctionExpression",
|
39
|
+
"TSDeclareFunction",
|
40
|
+
"TSConstructSignatureDeclaration",
|
41
|
+
"TSConstructorType",
|
42
|
+
]);
|
43
|
+
|
27
44
|
//------------------------------------------------------------------------------
|
28
45
|
// Rule Definition
|
29
46
|
//------------------------------------------------------------------------------
|
@@ -32,6 +49,8 @@ const SENTINEL_TYPE =
|
|
32
49
|
module.exports = {
|
33
50
|
meta: {
|
34
51
|
type: "suggestion",
|
52
|
+
dialects: ["typescript", "javascript"],
|
53
|
+
language: "javascript",
|
35
54
|
|
36
55
|
defaultOptions: [
|
37
56
|
{
|
@@ -39,6 +58,8 @@ module.exports = {
|
|
39
58
|
builtinGlobals: false,
|
40
59
|
hoist: "functions",
|
41
60
|
ignoreOnInitialization: false,
|
61
|
+
ignoreTypeValueShadow: true,
|
62
|
+
ignoreFunctionTypeParameterNameValueShadow: true,
|
42
63
|
},
|
43
64
|
],
|
44
65
|
|
@@ -54,7 +75,15 @@ module.exports = {
|
|
54
75
|
type: "object",
|
55
76
|
properties: {
|
56
77
|
builtinGlobals: { type: "boolean" },
|
57
|
-
hoist: {
|
78
|
+
hoist: {
|
79
|
+
enum: [
|
80
|
+
"all",
|
81
|
+
"functions",
|
82
|
+
"never",
|
83
|
+
"types",
|
84
|
+
"functions-and-types",
|
85
|
+
],
|
86
|
+
},
|
58
87
|
allow: {
|
59
88
|
type: "array",
|
60
89
|
items: {
|
@@ -62,6 +91,10 @@ module.exports = {
|
|
62
91
|
},
|
63
92
|
},
|
64
93
|
ignoreOnInitialization: { type: "boolean" },
|
94
|
+
ignoreTypeValueShadow: { type: "boolean" },
|
95
|
+
ignoreFunctionTypeParameterNameValueShadow: {
|
96
|
+
type: "boolean",
|
97
|
+
},
|
65
98
|
},
|
66
99
|
additionalProperties: false,
|
67
100
|
},
|
@@ -75,10 +108,112 @@ module.exports = {
|
|
75
108
|
},
|
76
109
|
|
77
110
|
create(context) {
|
78
|
-
const [
|
79
|
-
|
111
|
+
const [
|
112
|
+
{
|
113
|
+
builtinGlobals,
|
114
|
+
hoist,
|
115
|
+
allow,
|
116
|
+
ignoreOnInitialization,
|
117
|
+
ignoreTypeValueShadow,
|
118
|
+
ignoreFunctionTypeParameterNameValueShadow,
|
119
|
+
},
|
120
|
+
] = context.options;
|
80
121
|
const sourceCode = context.sourceCode;
|
81
122
|
|
123
|
+
/**
|
124
|
+
* Check if a scope is a TypeScript module augmenting the global namespace.
|
125
|
+
* @param {Scope} scope The scope to check
|
126
|
+
* @returns {boolean} Whether the scope is a global augmentation
|
127
|
+
*/
|
128
|
+
function isGlobalAugmentation(scope) {
|
129
|
+
return (
|
130
|
+
scope.block.kind === "global" ||
|
131
|
+
(!!scope.upper && isGlobalAugmentation(scope.upper))
|
132
|
+
);
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Check if variable is a `this` parameter.
|
137
|
+
* @param {Object} variable The variable to check
|
138
|
+
* @returns {boolean} Whether the variable is a this parameter
|
139
|
+
*/
|
140
|
+
function isThisParam(variable) {
|
141
|
+
return variable.name === "this";
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Checks if type and value shadows each other
|
146
|
+
* @param {Object} variable The variable to check
|
147
|
+
* @param {Object} shadowedVariable The shadowed variable
|
148
|
+
* @returns {boolean} Whether it's a type/value shadow case to ignore
|
149
|
+
*/
|
150
|
+
function isTypeValueShadow(variable, shadowedVariable) {
|
151
|
+
if (ignoreTypeValueShadow !== true) {
|
152
|
+
return false;
|
153
|
+
}
|
154
|
+
|
155
|
+
if (!("isValueVariable" in variable)) {
|
156
|
+
return false;
|
157
|
+
}
|
158
|
+
|
159
|
+
const firstDefinition = shadowedVariable.defs[0];
|
160
|
+
|
161
|
+
// Check if shadowedVariable is a type import
|
162
|
+
const isTypeImport =
|
163
|
+
firstDefinition &&
|
164
|
+
firstDefinition.parent?.type === "ImportDeclaration" &&
|
165
|
+
(firstDefinition.parent.importKind === "type" ||
|
166
|
+
firstDefinition.parent.specifiers.some(
|
167
|
+
s => s.importKind === "type",
|
168
|
+
));
|
169
|
+
|
170
|
+
const isShadowedValue =
|
171
|
+
!firstDefinition ||
|
172
|
+
(isTypeImport ? false : shadowedVariable.isValueVariable);
|
173
|
+
|
174
|
+
return variable.isValueVariable !== isShadowedValue;
|
175
|
+
}
|
176
|
+
|
177
|
+
/**
|
178
|
+
* Checks if it's a function type parameter shadow
|
179
|
+
* @param {Object} variable The variable to check
|
180
|
+
* @returns {boolean} Whether it's a function type parameter shadow case to ignore
|
181
|
+
*/
|
182
|
+
function isFunctionTypeParameterNameValueShadow(variable) {
|
183
|
+
if (ignoreFunctionTypeParameterNameValueShadow !== true) {
|
184
|
+
return false;
|
185
|
+
}
|
186
|
+
|
187
|
+
return variable.defs.some(def =>
|
188
|
+
ALLOWED_FUNCTION_VARIABLE_DEF_TYPES.has(def.node.type),
|
189
|
+
);
|
190
|
+
}
|
191
|
+
|
192
|
+
/**
|
193
|
+
* Checks if the variable is a generic of a static method
|
194
|
+
* @param {Object} variable The variable to check
|
195
|
+
* @returns {boolean} Whether the variable is a generic of a static method
|
196
|
+
*/
|
197
|
+
function isTypeParameterOfStaticMethod(variable) {
|
198
|
+
const typeParameter = variable.identifiers[0].parent;
|
199
|
+
const typeParameterDecl = typeParameter.parent;
|
200
|
+
if (typeParameterDecl.type !== "TSTypeParameterDeclaration") {
|
201
|
+
return false;
|
202
|
+
}
|
203
|
+
const functionExpr = typeParameterDecl.parent;
|
204
|
+
const methodDefinition = functionExpr.parent;
|
205
|
+
return methodDefinition.static;
|
206
|
+
}
|
207
|
+
|
208
|
+
/**
|
209
|
+
* Checks for static method generic shadowing class generic
|
210
|
+
* @param {Object} variable The variable to check
|
211
|
+
* @returns {boolean} Whether it's a static method generic shadowing class generic
|
212
|
+
*/
|
213
|
+
function isGenericOfAStaticMethodShadow(variable) {
|
214
|
+
return isTypeParameterOfStaticMethod(variable);
|
215
|
+
}
|
216
|
+
|
82
217
|
/**
|
83
218
|
* Checks whether or not a given location is inside of the range of a given node.
|
84
219
|
* @param {ASTNode} node An node to check.
|
@@ -114,7 +249,7 @@ module.exports = {
|
|
114
249
|
function getOuterScope(scope) {
|
115
250
|
const upper = scope.upper;
|
116
251
|
|
117
|
-
if (upper.type === "function-expression-name") {
|
252
|
+
if (upper && upper.type === "function-expression-name") {
|
118
253
|
return upper.upper;
|
119
254
|
}
|
120
255
|
return upper;
|
@@ -284,6 +419,21 @@ module.exports = {
|
|
284
419
|
const inner = getNameRange(variable);
|
285
420
|
const outer = getNameRange(scopeVar);
|
286
421
|
|
422
|
+
if (!outer || inner[1] >= outer[0]) {
|
423
|
+
return false;
|
424
|
+
}
|
425
|
+
|
426
|
+
if (hoist === "types") {
|
427
|
+
return !TYPES_HOISTED_NODES.has(outerDef.node.type);
|
428
|
+
}
|
429
|
+
|
430
|
+
if (hoist === "functions-and-types") {
|
431
|
+
return (
|
432
|
+
outerDef.node.type !== "FunctionDeclaration" &&
|
433
|
+
!TYPES_HOISTED_NODES.has(outerDef.node.type)
|
434
|
+
);
|
435
|
+
}
|
436
|
+
|
287
437
|
return (
|
288
438
|
inner &&
|
289
439
|
outer &&
|
@@ -295,12 +445,111 @@ module.exports = {
|
|
295
445
|
);
|
296
446
|
}
|
297
447
|
|
448
|
+
/**
|
449
|
+
* Checks if the initialization of a variable has the declare modifier in a
|
450
|
+
* definition file.
|
451
|
+
* @param {Object} variable The variable to check
|
452
|
+
* @returns {boolean} Whether the variable is declared in a definition file
|
453
|
+
*/
|
454
|
+
function isDeclareInDTSFile(variable) {
|
455
|
+
const fileName = context.filename;
|
456
|
+
if (
|
457
|
+
!fileName.endsWith(".d.ts") &&
|
458
|
+
!fileName.endsWith(".d.cts") &&
|
459
|
+
!fileName.endsWith(".d.mts")
|
460
|
+
) {
|
461
|
+
return false;
|
462
|
+
}
|
463
|
+
return variable.defs.some(
|
464
|
+
def =>
|
465
|
+
(def.type === "Variable" && def.parent.declare) ||
|
466
|
+
(def.type === "ClassName" && def.node.declare) ||
|
467
|
+
(def.type === "TSEnumName" && def.node.declare) ||
|
468
|
+
(def.type === "TSModuleName" && def.node.declare),
|
469
|
+
);
|
470
|
+
}
|
471
|
+
|
472
|
+
/**
|
473
|
+
* Checks if a variable is a duplicate of an enum name in the enum scope
|
474
|
+
* @param {Object} variable The variable to check
|
475
|
+
* @returns {boolean} Whether it's a duplicate enum name variable
|
476
|
+
*/
|
477
|
+
function isDuplicatedEnumNameVariable(variable) {
|
478
|
+
const block = variable.scope.block;
|
479
|
+
|
480
|
+
return (
|
481
|
+
block.type === "TSEnumDeclaration" &&
|
482
|
+
block.id === variable.identifiers[0]
|
483
|
+
);
|
484
|
+
}
|
485
|
+
|
486
|
+
/**
|
487
|
+
* Check if this is an external module declaration merging with a type import
|
488
|
+
* @param {Scope} scope Current scope
|
489
|
+
* @param {Object} variable Current variable
|
490
|
+
* @param {Object} shadowedVariable Shadowed variable
|
491
|
+
* @returns {boolean} Whether it's an external declaration merging
|
492
|
+
*/
|
493
|
+
function isExternalDeclarationMerging(
|
494
|
+
scope,
|
495
|
+
variable,
|
496
|
+
shadowedVariable,
|
497
|
+
) {
|
498
|
+
const firstDefinition = shadowedVariable.defs[0];
|
499
|
+
|
500
|
+
if (!firstDefinition || !firstDefinition.parent) {
|
501
|
+
return false;
|
502
|
+
}
|
503
|
+
|
504
|
+
// Check if the shadowed variable is a type import
|
505
|
+
const isTypeImport =
|
506
|
+
firstDefinition.parent.type === "ImportDeclaration" &&
|
507
|
+
(firstDefinition.parent.importKind === "type" ||
|
508
|
+
firstDefinition.parent.specifiers?.some(
|
509
|
+
s =>
|
510
|
+
s.type === "ImportSpecifier" &&
|
511
|
+
s.importKind === "type" &&
|
512
|
+
s.local.name === shadowedVariable.name,
|
513
|
+
));
|
514
|
+
|
515
|
+
if (!isTypeImport) {
|
516
|
+
return false;
|
517
|
+
}
|
518
|
+
|
519
|
+
// Check if the current variable is within a module declaration
|
520
|
+
const moduleDecl = findSelfOrAncestor(
|
521
|
+
variable.identifiers[0]?.parent,
|
522
|
+
node => node.type === "TSModuleDeclaration",
|
523
|
+
);
|
524
|
+
|
525
|
+
if (!moduleDecl) {
|
526
|
+
return false;
|
527
|
+
}
|
528
|
+
|
529
|
+
/*
|
530
|
+
* Module declaration merging should only happen within the same module
|
531
|
+
* Check if the module name matches the import source
|
532
|
+
*/
|
533
|
+
const importSource = firstDefinition.parent.source.value;
|
534
|
+
const moduleName =
|
535
|
+
moduleDecl.id.type === "Literal"
|
536
|
+
? moduleDecl.id.value
|
537
|
+
: moduleDecl.id.name;
|
538
|
+
|
539
|
+
return importSource === moduleName;
|
540
|
+
}
|
541
|
+
|
298
542
|
/**
|
299
543
|
* Checks the current context for shadowed variables.
|
300
544
|
* @param {Scope} scope Fixme
|
301
545
|
* @returns {void}
|
302
546
|
*/
|
303
547
|
function checkForShadows(scope) {
|
548
|
+
// ignore global augmentation
|
549
|
+
if (isGlobalAugmentation(scope)) {
|
550
|
+
return;
|
551
|
+
}
|
552
|
+
|
304
553
|
const variables = scope.variables;
|
305
554
|
|
306
555
|
for (let i = 0; i < variables.length; ++i) {
|
@@ -310,7 +559,10 @@ module.exports = {
|
|
310
559
|
if (
|
311
560
|
variable.identifiers.length === 0 ||
|
312
561
|
isDuplicatedClassNameVariable(variable) ||
|
313
|
-
|
562
|
+
isDuplicatedEnumNameVariable(variable) ||
|
563
|
+
isAllowed(variable) ||
|
564
|
+
isDeclareInDTSFile(variable) ||
|
565
|
+
isThisParam(variable)
|
314
566
|
) {
|
315
567
|
continue;
|
316
568
|
}
|
@@ -330,7 +582,11 @@ module.exports = {
|
|
330
582
|
ignoreOnInitialization &&
|
331
583
|
isInitPatternNode(variable, shadowed)
|
332
584
|
) &&
|
333
|
-
!(hoist !== "all" && isInTdz(variable, shadowed))
|
585
|
+
!(hoist !== "all" && isInTdz(variable, shadowed)) &&
|
586
|
+
!isTypeValueShadow(variable, shadowed) &&
|
587
|
+
!isFunctionTypeParameterNameValueShadow(variable) &&
|
588
|
+
!isGenericOfAStaticMethodShadow(variable, shadowed) &&
|
589
|
+
!isExternalDeclarationMerging(scope, variable, shadowed)
|
334
590
|
) {
|
335
591
|
const location = getDeclaredLocation(shadowed);
|
336
592
|
const messageId = location.global
|
@@ -0,0 +1,80 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Rule to flag variables that are never assigned
|
3
|
+
* @author Jacob Bandes-Storch <https://github.com/jtbandes>
|
4
|
+
*/
|
5
|
+
"use strict";
|
6
|
+
|
7
|
+
//------------------------------------------------------------------------------
|
8
|
+
// Rule Definition
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
|
11
|
+
/** @type {import('../types').Rule.RuleModule} */
|
12
|
+
module.exports = {
|
13
|
+
meta: {
|
14
|
+
type: "problem",
|
15
|
+
dialects: ["typescript", "javascript"],
|
16
|
+
language: "javascript",
|
17
|
+
|
18
|
+
docs: {
|
19
|
+
description:
|
20
|
+
"Disallow `let` or `var` variables that are read but never assigned",
|
21
|
+
recommended: false,
|
22
|
+
url: "https://eslint.org/docs/latest/rules/no-unassigned-vars",
|
23
|
+
},
|
24
|
+
|
25
|
+
schema: [],
|
26
|
+
messages: {
|
27
|
+
unassigned:
|
28
|
+
"'{{name}}' is always 'undefined' because it's never assigned.",
|
29
|
+
},
|
30
|
+
},
|
31
|
+
|
32
|
+
create(context) {
|
33
|
+
const sourceCode = context.sourceCode;
|
34
|
+
let insideDeclareModule = false;
|
35
|
+
|
36
|
+
return {
|
37
|
+
"TSModuleDeclaration[declare=true]"() {
|
38
|
+
insideDeclareModule = true;
|
39
|
+
},
|
40
|
+
"TSModuleDeclaration[declare=true]:exit"() {
|
41
|
+
insideDeclareModule = false;
|
42
|
+
},
|
43
|
+
VariableDeclarator(node) {
|
44
|
+
/** @type {import('estree').VariableDeclaration} */
|
45
|
+
const declaration = node.parent;
|
46
|
+
const shouldSkip =
|
47
|
+
node.init ||
|
48
|
+
node.id.type !== "Identifier" ||
|
49
|
+
declaration.kind === "const" ||
|
50
|
+
declaration.declare ||
|
51
|
+
insideDeclareModule;
|
52
|
+
if (shouldSkip) {
|
53
|
+
return;
|
54
|
+
}
|
55
|
+
const [variable] = sourceCode.getDeclaredVariables(node);
|
56
|
+
if (!variable) {
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
let hasRead = false;
|
60
|
+
for (const reference of variable.references) {
|
61
|
+
if (reference.isWrite()) {
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
if (reference.isRead()) {
|
65
|
+
hasRead = true;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
if (!hasRead) {
|
69
|
+
// Variables that are never read should be flagged by no-unused-vars instead
|
70
|
+
return;
|
71
|
+
}
|
72
|
+
context.report({
|
73
|
+
node,
|
74
|
+
messageId: "unassigned",
|
75
|
+
data: { name: node.id.name },
|
76
|
+
});
|
77
|
+
},
|
78
|
+
};
|
79
|
+
},
|
80
|
+
};
|