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.
Files changed (41) hide show
  1. package/README.md +7 -2
  2. package/bin/eslint.js +7 -11
  3. package/conf/rule-type-list.json +2 -1
  4. package/lib/cli-engine/cli-engine.js +7 -7
  5. package/lib/cli.js +19 -16
  6. package/lib/config/config-loader.js +42 -39
  7. package/lib/config/config.js +362 -16
  8. package/lib/eslint/eslint-helpers.js +3 -1
  9. package/lib/eslint/eslint.js +31 -13
  10. package/lib/eslint/legacy-eslint.js +6 -6
  11. package/lib/languages/js/source-code/source-code.js +40 -6
  12. package/lib/linter/apply-disable-directives.js +1 -1
  13. package/lib/linter/file-context.js +11 -0
  14. package/lib/linter/linter.js +102 -140
  15. package/lib/linter/report-translator.js +2 -1
  16. package/lib/linter/{node-event-generator.js → source-code-traverser.js} +143 -87
  17. package/lib/options.js +7 -0
  18. package/lib/rule-tester/rule-tester.js +3 -3
  19. package/lib/rules/func-style.js +57 -7
  20. package/lib/rules/index.js +1 -0
  21. package/lib/rules/max-params.js +32 -7
  22. package/lib/rules/no-array-constructor.js +51 -1
  23. package/lib/rules/no-implicit-globals.js +31 -15
  24. package/lib/rules/no-magic-numbers.js +98 -5
  25. package/lib/rules/no-shadow.js +262 -6
  26. package/lib/rules/no-unassigned-vars.js +80 -0
  27. package/lib/rules/no-use-before-define.js +97 -1
  28. package/lib/rules/no-useless-escape.js +24 -2
  29. package/lib/rules/prefer-arrow-callback.js +9 -0
  30. package/lib/rules/prefer-named-capture-group.js +7 -1
  31. package/lib/services/processor-service.js +1 -1
  32. package/lib/services/suppressions-service.js +5 -3
  33. package/lib/services/warning-service.js +85 -0
  34. package/lib/shared/flags.js +1 -0
  35. package/lib/types/index.d.ts +132 -9
  36. package/lib/types/rules.d.ts +66 -3
  37. package/package.json +11 -11
  38. package/lib/config/flat-config-helpers.js +0 -128
  39. package/lib/config/rule-validator.js +0 -199
  40. package/lib/mcp/mcp-server.js +0 -66
  41. 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
- // Undeclared assigned variables.
148
- scope.implicit.variables.forEach(variable => {
149
- const scopeVariable = scope.set.get(variable.name);
150
- let messageId;
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
- if (scopeVariable) {
153
- // ESLint global variable
154
- if (scopeVariable.writeable) {
155
- return;
156
- }
157
- messageId = "assignmentToReadonlyGlobal";
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, messageId);
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 = -node.value;
227
- raw = `-${node.raw}`;
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))
@@ -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: { enum: ["all", "functions", "never"] },
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 [{ builtinGlobals, hoist, allow, ignoreOnInitialization }] =
79
- context.options;
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
- isAllowed(variable)
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
+ };