eslint 9.31.0 → 9.33.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.
@@ -148,6 +148,7 @@ module.exports = {
148
148
 
149
149
  defaultOptions: [
150
150
  {
151
+ enforceForTSTypes: false,
151
152
  enforceForClassMembers: true,
152
153
  getWithoutSet: false,
153
154
  setWithoutGet: true,
@@ -174,6 +175,9 @@ module.exports = {
174
175
  enforceForClassMembers: {
175
176
  type: "boolean",
176
177
  },
178
+ enforceForTSTypes: {
179
+ type: "boolean",
180
+ },
177
181
  },
178
182
  additionalProperties: false,
179
183
  },
@@ -190,6 +194,8 @@ module.exports = {
190
194
  "Setter is not present for {{ name }}.",
191
195
  missingGetterInClass: "Getter is not present for class {{ name }}.",
192
196
  missingSetterInClass: "Setter is not present for class {{ name }}.",
197
+ missingGetterInType: "Getter is not present for type {{ name }}.",
198
+ missingSetterInType: "Setter is not present for type {{ name }}.",
193
199
  },
194
200
  },
195
201
  create(context) {
@@ -198,6 +204,7 @@ module.exports = {
198
204
  getWithoutSet: checkGetWithoutSet,
199
205
  setWithoutGet: checkSetWithoutGet,
200
206
  enforceForClassMembers,
207
+ enforceForTSTypes,
201
208
  },
202
209
  ] = context.options;
203
210
  const sourceCode = context.sourceCode;
@@ -228,6 +235,15 @@ module.exports = {
228
235
  name: astUtils.getFunctionNameWithKind(node.value),
229
236
  },
230
237
  });
238
+ } else if (node.type === "TSMethodSignature") {
239
+ context.report({
240
+ node,
241
+ messageId: `${messageKind}InType`,
242
+ loc: astUtils.getFunctionHeadLoc(node, sourceCode),
243
+ data: {
244
+ name: astUtils.getFunctionNameWithKind(node),
245
+ },
246
+ });
231
247
  } else {
232
248
  context.report({
233
249
  node,
@@ -371,6 +387,22 @@ module.exports = {
371
387
  checkList(methodDefinitions.filter(m => !m.static));
372
388
  }
373
389
 
390
+ /**
391
+ * Checks the given type.
392
+ * @param {ASTNode} node `TSTypeLiteral` or `TSInterfaceBody` node to check.
393
+ * @returns {void}
394
+ * @private
395
+ */
396
+ function checkType(node) {
397
+ const members =
398
+ node.type === "TSTypeLiteral" ? node.members : node.body;
399
+ const methodDefinitions = members.filter(
400
+ m => m.type === "TSMethodSignature",
401
+ );
402
+
403
+ checkList(methodDefinitions);
404
+ }
405
+
374
406
  const listeners = {};
375
407
 
376
408
  if (checkSetWithoutGet || checkGetWithoutSet) {
@@ -378,6 +410,9 @@ module.exports = {
378
410
  if (enforceForClassMembers) {
379
411
  listeners.ClassBody = checkClassBody;
380
412
  }
413
+ if (enforceForTSTypes) {
414
+ listeners["TSTypeLiteral, TSInterfaceBody"] = checkType;
415
+ }
381
416
  }
382
417
 
383
418
  return listeners;
@@ -87,6 +87,8 @@ function isAccessorKind(node) {
87
87
  return node.kind === "get" || node.kind === "set";
88
88
  }
89
89
 
90
+ const DEFAULT_ORDER = "anyOrder";
91
+
90
92
  //------------------------------------------------------------------------------
91
93
  // Rule Definition
92
94
  //------------------------------------------------------------------------------
@@ -96,7 +98,7 @@ module.exports = {
96
98
  meta: {
97
99
  type: "suggestion",
98
100
 
99
- defaultOptions: ["anyOrder"],
101
+ defaultOptions: [DEFAULT_ORDER],
100
102
 
101
103
  docs: {
102
104
  description:
@@ -106,8 +108,15 @@ module.exports = {
106
108
  },
107
109
 
108
110
  schema: [
111
+ { enum: ["anyOrder", "getBeforeSet", "setBeforeGet"] },
109
112
  {
110
- enum: ["anyOrder", "getBeforeSet", "setBeforeGet"],
113
+ type: "object",
114
+ properties: {
115
+ enforceForTSTypes: {
116
+ type: "boolean",
117
+ },
118
+ },
119
+ additionalProperties: false,
111
120
  },
112
121
  ],
113
122
 
@@ -120,7 +129,9 @@ module.exports = {
120
129
  },
121
130
 
122
131
  create(context) {
123
- const [order] = context.options;
132
+ const order = context.options[0] ?? DEFAULT_ORDER;
133
+ const enforceForTSTypes =
134
+ context.options[1]?.enforceForTSTypes ?? false;
124
135
  const sourceCode = context.sourceCode;
125
136
 
126
137
  /**
@@ -135,13 +146,22 @@ module.exports = {
135
146
  context.report({
136
147
  node: latterNode,
137
148
  messageId,
138
- loc: astUtils.getFunctionHeadLoc(latterNode.value, sourceCode),
149
+ loc: astUtils.getFunctionHeadLoc(
150
+ latterNode.type !== "TSMethodSignature"
151
+ ? latterNode.value
152
+ : latterNode,
153
+ sourceCode,
154
+ ),
139
155
  data: {
140
156
  formerName: astUtils.getFunctionNameWithKind(
141
- formerNode.value,
157
+ formerNode.type !== "TSMethodSignature"
158
+ ? formerNode.value
159
+ : formerNode,
142
160
  ),
143
161
  latterName: astUtils.getFunctionNameWithKind(
144
- latterNode.value,
162
+ latterNode.type !== "TSMethodSignature"
163
+ ? latterNode.value
164
+ : latterNode,
145
165
  ),
146
166
  },
147
167
  });
@@ -232,6 +252,16 @@ module.exports = {
232
252
  n => n.type === "MethodDefinition" && n.static,
233
253
  );
234
254
  },
255
+ "TSTypeLiteral, TSInterfaceBody"(node) {
256
+ if (enforceForTSTypes) {
257
+ checkList(
258
+ node.type === "TSTypeLiteral"
259
+ ? node.members
260
+ : node.body,
261
+ n => n.type === "TSMethodSignature",
262
+ );
263
+ }
264
+ },
235
265
  };
236
266
  },
237
267
  };
@@ -41,7 +41,10 @@ function isLooped(node, parent) {
41
41
 
42
42
  case "ForOfStatement":
43
43
  case "ForInStatement":
44
- return node === parent.body;
44
+ return (
45
+ node === parent.body ||
46
+ (node === parent.left && node.kind === "await using")
47
+ );
45
48
 
46
49
  case "WhileStatement":
47
50
  case "DoWhileStatement":
@@ -76,6 +79,13 @@ module.exports = {
76
79
  * @returns {void}
77
80
  */
78
81
  function validate(awaitNode) {
82
+ if (
83
+ awaitNode.type === "VariableDeclaration" &&
84
+ awaitNode.kind !== "await using"
85
+ ) {
86
+ return;
87
+ }
88
+
79
89
  if (awaitNode.type === "ForOfStatement" && !awaitNode.await) {
80
90
  return;
81
91
  }
@@ -99,6 +109,7 @@ module.exports = {
99
109
  return {
100
110
  AwaitExpression: validate,
101
111
  ForOfStatement: validate,
112
+ VariableDeclaration: validate,
102
113
  };
103
114
  },
104
115
  };
@@ -32,6 +32,7 @@ module.exports = {
32
32
  messages: {
33
33
  impliedEval:
34
34
  "Implied eval. Consider passing a function instead of a string.",
35
+ execScript: "Implied eval. Do not use execScript().",
35
36
  },
36
37
  },
37
38
 
@@ -85,9 +86,14 @@ module.exports = {
85
86
  isStaticString || isEvaluatedString(firstArgument);
86
87
 
87
88
  if (isString) {
89
+ const calleeName =
90
+ node.callee.type === "Identifier"
91
+ ? node.callee.name
92
+ : astUtils.getStaticPropertyName(node.callee);
93
+ const isExecScript = calleeName === "execScript";
88
94
  context.report({
89
95
  node,
90
- messageId: "impliedEval",
96
+ messageId: isExecScript ? "execScript" : "impliedEval",
91
97
  });
92
98
  }
93
99
  }
@@ -139,7 +145,11 @@ module.exports = {
139
145
  return {
140
146
  CallExpression(node) {
141
147
  if (
142
- astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)
148
+ astUtils.isSpecificId(
149
+ node.callee,
150
+ EVAL_LIKE_FUNC_PATTERN,
151
+ ) &&
152
+ sourceCode.isGlobalReference(node.callee)
143
153
  ) {
144
154
  reportImpliedEvalCallExpression(node);
145
155
  }
@@ -4,6 +4,12 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const astUtils = require("./utils/ast-utils");
12
+
7
13
  //------------------------------------------------------------------------------
8
14
  // Helpers
9
15
  //------------------------------------------------------------------------------
@@ -16,10 +22,34 @@ const TYPE_NODES = new Set([
16
22
  "TSQualifiedName",
17
23
  ]);
18
24
 
25
+ const GLOBAL_OBJECTS = new Set(["globalThis", "self", "window"]);
26
+
19
27
  //------------------------------------------------------------------------------
20
28
  // Rule Definition
21
29
  //------------------------------------------------------------------------------
22
30
 
31
+ const arrayOfGlobals = {
32
+ type: "array",
33
+ items: {
34
+ oneOf: [
35
+ {
36
+ type: "string",
37
+ },
38
+ {
39
+ type: "object",
40
+ properties: {
41
+ name: { type: "string" },
42
+ message: { type: "string" },
43
+ },
44
+ required: ["name"],
45
+ additionalProperties: false,
46
+ },
47
+ ],
48
+ },
49
+ uniqueItems: true,
50
+ minItems: 0,
51
+ };
52
+
23
53
  /** @type {import('../types').Rule.RuleModule} */
24
54
  module.exports = {
25
55
  meta: {
@@ -34,25 +64,33 @@ module.exports = {
34
64
  },
35
65
 
36
66
  schema: {
37
- type: "array",
38
- items: {
39
- oneOf: [
40
- {
41
- type: "string",
42
- },
43
- {
44
- type: "object",
45
- properties: {
46
- name: { type: "string" },
47
- message: { type: "string" },
67
+ anyOf: [
68
+ arrayOfGlobals,
69
+ {
70
+ type: "array",
71
+ items: [
72
+ {
73
+ type: "object",
74
+ properties: {
75
+ globals: arrayOfGlobals,
76
+ checkGlobalObject: {
77
+ type: "boolean",
78
+ },
79
+ globalObjects: {
80
+ type: "array",
81
+ items: {
82
+ type: "string",
83
+ },
84
+ uniqueItems: true,
85
+ },
86
+ },
87
+ required: ["globals"],
88
+ additionalProperties: false,
48
89
  },
49
- required: ["name"],
50
- additionalProperties: false,
51
- },
52
- ],
53
- },
54
- uniqueItems: true,
55
- minItems: 0,
90
+ ],
91
+ additionalItems: false,
92
+ },
93
+ ],
56
94
  },
57
95
 
58
96
  messages: {
@@ -63,14 +101,33 @@ module.exports = {
63
101
  },
64
102
 
65
103
  create(context) {
66
- const sourceCode = context.sourceCode;
104
+ const { sourceCode, options } = context;
105
+
106
+ const isGlobalsObject =
107
+ typeof options[0] === "object" &&
108
+ Object.hasOwn(options[0], "globals");
109
+
110
+ const restrictedGlobals = isGlobalsObject
111
+ ? options[0].globals
112
+ : options;
113
+ const checkGlobalObject = isGlobalsObject
114
+ ? options[0].checkGlobalObject
115
+ : false;
116
+ const userGlobalObjects = isGlobalsObject
117
+ ? options[0].globalObjects || []
118
+ : [];
119
+
120
+ const globalObjects = new Set([
121
+ ...GLOBAL_OBJECTS,
122
+ ...userGlobalObjects,
123
+ ]);
67
124
 
68
125
  // If no globals are restricted, we don't need to do anything
69
- if (context.options.length === 0) {
126
+ if (restrictedGlobals.length === 0) {
70
127
  return {};
71
128
  }
72
129
 
73
- const restrictedGlobalMessages = context.options.reduce(
130
+ const restrictedGlobalMessages = restrictedGlobals.reduce(
74
131
  (memo, option) => {
75
132
  if (typeof option === "string") {
76
133
  memo[option] = null;
@@ -151,6 +208,59 @@ module.exports = {
151
208
  }
152
209
  });
153
210
  },
211
+
212
+ "Program:exit"(node) {
213
+ if (!checkGlobalObject) {
214
+ return;
215
+ }
216
+
217
+ const globalScope = sourceCode.getScope(node);
218
+ globalObjects.forEach(globalObjectName => {
219
+ const variable = astUtils.getVariableByName(
220
+ globalScope,
221
+ globalObjectName,
222
+ );
223
+
224
+ if (!variable) {
225
+ return;
226
+ }
227
+
228
+ variable.references.forEach(reference => {
229
+ const identifier = reference.identifier;
230
+ let parent = identifier.parent;
231
+
232
+ // To detect code like `window.window.Promise`.
233
+ while (
234
+ astUtils.isSpecificMemberAccess(
235
+ parent,
236
+ null,
237
+ globalObjectName,
238
+ )
239
+ ) {
240
+ parent = parent.parent;
241
+ }
242
+
243
+ const propertyName =
244
+ astUtils.getStaticPropertyName(parent);
245
+ if (propertyName && isRestricted(propertyName)) {
246
+ const customMessage =
247
+ restrictedGlobalMessages[propertyName];
248
+ const messageId = customMessage
249
+ ? "customMessage"
250
+ : "defaultMessage";
251
+
252
+ context.report({
253
+ node: parent.property,
254
+ messageId,
255
+ data: {
256
+ name: propertyName,
257
+ customMessage,
258
+ },
259
+ });
260
+ }
261
+ });
262
+ });
263
+ },
154
264
  };
155
265
  },
156
266
  };
@@ -88,6 +88,9 @@ module.exports = {
88
88
  ignoreClassWithStaticInitBlock: {
89
89
  type: "boolean",
90
90
  },
91
+ ignoreUsingDeclarations: {
92
+ type: "boolean",
93
+ },
91
94
  reportUsedIgnorePattern: {
92
95
  type: "boolean",
93
96
  },
@@ -119,6 +122,7 @@ module.exports = {
119
122
  ignoreRestSiblings: false,
120
123
  caughtErrors: "all",
121
124
  ignoreClassWithStaticInitBlock: false,
125
+ ignoreUsingDeclarations: false,
122
126
  reportUsedIgnorePattern: false,
123
127
  };
124
128
 
@@ -137,6 +141,9 @@ module.exports = {
137
141
  config.ignoreClassWithStaticInitBlock =
138
142
  firstOption.ignoreClassWithStaticInitBlock ||
139
143
  config.ignoreClassWithStaticInitBlock;
144
+ config.ignoreUsingDeclarations =
145
+ firstOption.ignoreUsingDeclarations ||
146
+ config.ignoreUsingDeclarations;
140
147
  config.reportUsedIgnorePattern =
141
148
  firstOption.reportUsedIgnorePattern ||
142
149
  config.reportUsedIgnorePattern;
@@ -357,6 +364,22 @@ module.exports = {
357
364
  return false;
358
365
  }
359
366
 
367
+ /**
368
+ * Determines if a given variable uses the explicit resource management protocol.
369
+ * @param {Variable} variable eslint-scope variable object.
370
+ * @returns {boolean} True if the variable is declared with "using" or "await using"
371
+ * @private
372
+ */
373
+ function usesExplicitResourceManagement(variable) {
374
+ const [definition] = variable.defs;
375
+
376
+ return (
377
+ definition?.type === "Variable" &&
378
+ (definition.parent.kind === "using" ||
379
+ definition.parent.kind === "await using")
380
+ );
381
+ }
382
+
360
383
  /**
361
384
  * Checks whether a node is a sibling of the rest property or not.
362
385
  * @param {ASTNode} node a node to check
@@ -922,6 +945,10 @@ module.exports = {
922
945
  if (
923
946
  !isUsedVariable(variable) &&
924
947
  !isExported(variable) &&
948
+ !(
949
+ config.ignoreUsingDeclarations &&
950
+ usesExplicitResourceManagement(variable)
951
+ ) &&
925
952
  !hasRestSpreadSibling(variable)
926
953
  ) {
927
954
  unusedVars.push(variable);