@typescript-eslint/eslint-plugin 8.70.1-alpha.17 → 8.70.1-alpha.19

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.
@@ -55,6 +55,15 @@ function isPossiblyNullish(type) {
55
55
  .unionConstituents(type)
56
56
  .some(t => isNullishType(t) || (0, util_1.isTypeFlagSet)(t, ts.TypeFlags.Void));
57
57
  }
58
+ /**
59
+ * Note that this differs from {@link isNullableType} in that it doesn't consider
60
+ * `any` or `unknown` to be nullable.
61
+ */
62
+ function isPossiblyNonNullish(type) {
63
+ return tsutils
64
+ .unionConstituents(type)
65
+ .some(t => !isNullishType(t) && !(0, util_1.isTypeFlagSet)(t, ts.TypeFlags.Void));
66
+ }
58
67
  function toStaticValue(type) {
59
68
  // type.isLiteral() only covers numbers/bigints and strings, hence the rest of the branches.
60
69
  if (tsutils.isBooleanLiteralType(type)) {
@@ -262,11 +271,48 @@ exports.default = (0, util_1.createRule)({
262
271
  const propertyType = objectType
263
272
  .getProperties()
264
273
  .find(prop => prop.name === propertyName);
265
- if (propertyType &&
266
- tsutils.isSymbolFlagSet(propertyType, ts.SymbolFlags.Optional)) {
267
- return true;
274
+ return (propertyType != null &&
275
+ tsutils.isSymbolFlagSet(propertyType, ts.SymbolFlags.Optional));
276
+ }
277
+ /**
278
+ * Obtains the property types of the computed member expression
279
+ * @param node The member expression
280
+ * @returns The property types, unless the node isn't computed.
281
+ */
282
+ function getComputedMemberPropertyTypes(node) {
283
+ if (!node.computed) {
284
+ return undefined;
285
+ }
286
+ const objectType = (0, util_1.getConstrainedTypeAtLocation)(services, node.object);
287
+ const propertyType = (0, util_1.getConstrainedTypeAtLocation)(services, node.property);
288
+ const propertyTypes = [];
289
+ for (const keyType of tsutils.unionConstituents(propertyType)) {
290
+ if (!keyType.isStringLiteral() && !keyType.isNumberLiteral()) {
291
+ return undefined;
292
+ }
293
+ const selectedType = (0, util_1.getTypeOfPropertyOfName)(checker, objectType, String(keyType.value));
294
+ if (!selectedType) {
295
+ return undefined;
296
+ }
297
+ propertyTypes.push(selectedType);
268
298
  }
269
- return false;
299
+ return propertyTypes;
300
+ }
301
+ /**
302
+ * Verify if the node may have non-nullish properties
303
+ * @param node The node to verify
304
+ * @returns If it may have non-nullish properties, unless the node isn't a
305
+ * MemberExpression
306
+ */
307
+ function hasPossiblyNonNullishComputedMemberProperty(node) {
308
+ if (node.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
309
+ return false;
310
+ }
311
+ const propertyTypes = getComputedMemberPropertyTypes(node);
312
+ if (propertyTypes == null) {
313
+ return false;
314
+ }
315
+ return propertyTypes.some(isPossiblyNonNullish);
270
316
  }
271
317
  /**
272
318
  * Checks if a conditional node is necessary:
@@ -344,7 +390,8 @@ exports.default = (0, util_1.createRule)({
344
390
  messageId = 'neverNullish';
345
391
  }
346
392
  }
347
- else if (isAlwaysNullish(type)) {
393
+ else if (isAlwaysNullish(type) &&
394
+ !hasPossiblyNonNullishComputedMemberProperty(node)) {
348
395
  messageId = 'alwaysNullish';
349
396
  }
350
397
  if (messageId) {
@@ -101,10 +101,6 @@ exports.default = (0, util_1.createRule)({
101
101
  });
102
102
  },
103
103
  "MethodDefinition[kind='constructor'] > FunctionExpression AssignmentExpression"(node) {
104
- const leftName = getPropertyName(node.left);
105
- if (!leftName) {
106
- return;
107
- }
108
104
  let functionNode = findParentFunction(node);
109
105
  if (functionNode && isArrowIIFE(functionNode)) {
110
106
  functionNode = findParentFunction(functionNode.parent);
@@ -113,6 +109,18 @@ exports.default = (0, util_1.createRule)({
113
109
  return;
114
110
  }
115
111
  const { assignedBeforeUnnecessary, unnecessaryAssignments } = (0, util_1.nullThrows)(reportInfoStack.at(reportInfoStack.length - 1), 'The top of stack should exist');
112
+ // A prior write to the parameter binding means a later `this.x = x`
113
+ // may copy a different value than the parameter property initialized.
114
+ if (node.left.type === utils_1.AST_NODE_TYPES.Identifier) {
115
+ if (isReferenceFromParameter(node.left)) {
116
+ assignedBeforeUnnecessary.add(node.left.name);
117
+ }
118
+ return;
119
+ }
120
+ const leftName = getPropertyName(node.left);
121
+ if (!leftName) {
122
+ return;
123
+ }
116
124
  if (!UNNECESSARY_OPERATORS.has(node.operator)) {
117
125
  assignedBeforeUnnecessary.add(leftName);
118
126
  return;
@@ -90,6 +90,19 @@ const SUPPORTED_GLOBAL_TYPES = [
90
90
  'Math',
91
91
  'JSON',
92
92
  ];
93
+ /**
94
+ * Methods on instances of built-in classes that are defined by the ECMAScript
95
+ * spec as *bound* to their instance (i.e. they are safe to call without their
96
+ * receiver), but whose TypeScript declarations don't model that binding - they
97
+ * are declared as plain methods that would require a `this` value.
98
+ *
99
+ * For example, `Intl.Collator.prototype.compare` is defined as a bound getter
100
+ * function per the ECMA-402 spec:
101
+ * https://tc39.es/ecma402/#sec-intl.collator.prototype.compare
102
+ * ...but `lib.es5.d.ts` declares it as a regular method, so this rule would
103
+ * otherwise incorrectly flag usages such as `array.sort(collator.compare)`.
104
+ */
105
+ const nativelyBoundInstanceMethods = new Set(['Collator.compare']);
93
106
  const isNotImported = (symbol, currentSourceFile) => {
94
107
  const { valueDeclaration } = symbol;
95
108
  if (!valueDeclaration) {
@@ -99,6 +112,18 @@ const isNotImported = (symbol, currentSourceFile) => {
99
112
  return (!!currentSourceFile &&
100
113
  currentSourceFile !== valueDeclaration.getSourceFile());
101
114
  };
115
+ /**
116
+ * Returns `true` if `propertyName` is a member of a built-in class instance
117
+ * that is defined by the spec as bound to that instance (see
118
+ * `nativelyBoundInstanceMethods` above).
119
+ */
120
+ function isSpecBoundBuiltinMethod(program, objectType, propertyName) {
121
+ const symbol = objectType.getSymbol();
122
+ if (!symbol || !(0, util_1.isSymbolFromDefaultLibrary)(program, symbol)) {
123
+ return false;
124
+ }
125
+ return nativelyBoundInstanceMethods.has(`${symbol.getName()}.${propertyName}`);
126
+ }
102
127
  const BASE_MESSAGE = [
103
128
  `A method that is not declared with \`this: void\` may cause unintentional scoping of \`this\` when separated from its object.`,
104
129
  `Consider using an arrow function or explicitly \`.bind()\`ing the method to avoid calling the method with an unintended \`this\` value. `,
@@ -196,6 +221,12 @@ exports.default = (0, util_1.createRule)({
196
221
  return true;
197
222
  }
198
223
  }
224
+ if (property.type === utils_1.AST_NODE_TYPES.Identifier) {
225
+ const objectType = services.getTypeAtLocation(object);
226
+ if (isSpecBoundBuiltinMethod(services.program, objectType, property.name)) {
227
+ return true;
228
+ }
229
+ }
199
230
  // if `${object.name}.${property.name}` doesn't match any of
200
231
  // the nativelyBoundMembers, then we fallback to type-level checks
201
232
  return ((0, util_1.isBuiltinSymbolLike)(services.program, services.getTypeAtLocation(object), SUPPORTED_GLOBAL_TYPES) &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typescript-eslint/eslint-plugin",
3
- "version": "8.70.1-alpha.17",
3
+ "version": "8.70.1-alpha.19",
4
4
  "description": "TypeScript plugin for ESLint",
5
5
  "files": [
6
6
  "dist",
@@ -47,10 +47,10 @@
47
47
  ],
48
48
  "dependencies": {
49
49
  "@eslint-community/regexpp": "^4.12.2",
50
- "@typescript-eslint/scope-manager": "8.70.1-alpha.17",
51
- "@typescript-eslint/type-utils": "8.70.1-alpha.17",
52
- "@typescript-eslint/utils": "8.70.1-alpha.17",
53
- "@typescript-eslint/visitor-keys": "8.70.1-alpha.17",
50
+ "@typescript-eslint/scope-manager": "8.70.1-alpha.19",
51
+ "@typescript-eslint/type-utils": "8.70.1-alpha.19",
52
+ "@typescript-eslint/utils": "8.70.1-alpha.19",
53
+ "@typescript-eslint/visitor-keys": "8.70.1-alpha.19",
54
54
  "ignore": "^7.0.5",
55
55
  "natural-compare": "^1.4.0",
56
56
  "ts-api-utils": "^2.5.0"
@@ -60,8 +60,8 @@
60
60
  "@types/mdast": "^4.0.4",
61
61
  "@types/natural-compare": "^1.4.3",
62
62
  "@types/react": "^18.3.21",
63
- "@typescript-eslint/rule-schema-to-typescript-types": "8.70.1-alpha.17",
64
- "@typescript-eslint/rule-tester": "8.70.1-alpha.17",
63
+ "@typescript-eslint/rule-schema-to-typescript-types": "8.70.1-alpha.19",
64
+ "@typescript-eslint/rule-tester": "8.70.1-alpha.19",
65
65
  "@typescript/native": "npm:typescript@^7.0.2",
66
66
  "@vitest/coverage-v8": "^4.0.18",
67
67
  "ajv": "^6.12.6",
@@ -81,7 +81,7 @@
81
81
  "vitest": "^4.0.18"
82
82
  },
83
83
  "peerDependencies": {
84
- "@typescript-eslint/parser": "^8.70.1-alpha.17",
84
+ "@typescript-eslint/parser": "^8.70.1-alpha.19",
85
85
  "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
86
86
  "typescript": ">=4.8.4 <6.1.0"
87
87
  },