@typescript-eslint/eslint-plugin 8.52.1-alpha.1 → 8.52.1-alpha.11

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.
@@ -1,4 +1,4 @@
1
- export type MessageIds = 'unsafeCall' | 'unsafeCallThis' | 'unsafeNew' | 'unsafeTemplateTag';
1
+ export type MessageIds = 'errorCall' | 'errorCallThis' | 'errorNew' | 'errorTemplateTag' | 'unsafeCall' | 'unsafeCallThis' | 'unsafeNew' | 'unsafeTemplateTag';
2
2
  declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, [], import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
3
3
  name: string;
4
4
  };
@@ -45,13 +45,17 @@ exports.default = (0, util_1.createRule)({
45
45
  requiresTypeChecking: true,
46
46
  },
47
47
  messages: {
48
- unsafeCall: 'Unsafe call of a(n) {{type}} typed value.',
48
+ errorCall: 'Unsafe call of a type that could not be resolved.',
49
+ errorCallThis: 'Unsafe call of a `this` type that could not be resolved.',
50
+ errorNew: 'Unsafe construction of a type that could not be resolved.',
51
+ errorTemplateTag: 'Unsafe use of a template tag whose type could not be resolved.',
52
+ unsafeCall: 'Unsafe call of {{type}} typed value.',
49
53
  unsafeCallThis: [
50
- 'Unsafe call of a(n) {{type}} typed value. `this` is typed as {{type}}.',
54
+ 'Unsafe call of {{type}} typed value. `this` is typed as {{type}}.',
51
55
  'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.',
52
56
  ].join('\n'),
53
- unsafeNew: 'Unsafe construction of a(n) {{type}} typed value.',
54
- unsafeTemplateTag: 'Unsafe use of a(n) {{type}} typed template tag.',
57
+ unsafeNew: 'Unsafe construction of {{type}} typed value.',
58
+ unsafeTemplateTag: 'Unsafe use of {{type}} typed template tag.',
55
59
  },
56
60
  schema: [],
57
61
  },
@@ -60,7 +64,7 @@ exports.default = (0, util_1.createRule)({
60
64
  const services = (0, util_1.getParserServices)(context);
61
65
  const compilerOptions = services.program.getCompilerOptions();
62
66
  const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled(compilerOptions, 'noImplicitThis');
63
- function checkCall(node, reportingNode, messageId) {
67
+ function checkCall(node, reportingNode, unsafeMessageId, errorMessageId) {
64
68
  const type = (0, util_1.getConstrainedTypeAtLocation)(services, node);
65
69
  if ((0, util_1.isTypeAnyType)(type)) {
66
70
  if (!isNoImplicitThis) {
@@ -68,15 +72,16 @@ exports.default = (0, util_1.createRule)({
68
72
  const thisExpression = (0, util_1.getThisExpression)(node);
69
73
  if (thisExpression &&
70
74
  (0, util_1.isTypeAnyType)((0, util_1.getConstrainedTypeAtLocation)(services, thisExpression))) {
71
- messageId = 'unsafeCallThis';
75
+ unsafeMessageId = 'unsafeCallThis';
76
+ errorMessageId = 'errorCallThis';
72
77
  }
73
78
  }
74
79
  const isErrorType = tsutils.isIntrinsicErrorType(type);
75
80
  context.report({
76
81
  node: reportingNode,
77
- messageId,
82
+ messageId: isErrorType ? errorMessageId : unsafeMessageId,
78
83
  data: {
79
- type: isErrorType ? '`error` type' : '`any`',
84
+ type: 'an `any`',
80
85
  },
81
86
  });
82
87
  return;
@@ -98,7 +103,7 @@ exports.default = (0, util_1.createRule)({
98
103
  return;
99
104
  }
100
105
  const callSignatures = type.getCallSignatures();
101
- if (messageId === 'unsafeNew') {
106
+ if (unsafeMessageId === 'unsafeNew') {
102
107
  if (callSignatures.some(signature => !tsutils.isIntrinsicVoidType(signature.getReturnType()))) {
103
108
  return;
104
109
  }
@@ -108,9 +113,9 @@ exports.default = (0, util_1.createRule)({
108
113
  }
109
114
  context.report({
110
115
  node: reportingNode,
111
- messageId,
116
+ messageId: unsafeMessageId,
112
117
  data: {
113
- type: '`Function`',
118
+ type: 'a `Function`',
114
119
  },
115
120
  });
116
121
  return;
@@ -118,13 +123,13 @@ exports.default = (0, util_1.createRule)({
118
123
  }
119
124
  return {
120
125
  'CallExpression > *.callee'(node) {
121
- checkCall(node, node, 'unsafeCall');
126
+ checkCall(node, node, 'unsafeCall', 'errorCall');
122
127
  },
123
128
  NewExpression(node) {
124
- checkCall(node.callee, node, 'unsafeNew');
129
+ checkCall(node.callee, node, 'unsafeNew', 'errorNew');
125
130
  },
126
131
  'TaggedTemplateExpression > *.tag'(node) {
127
- checkCall(node, node, 'unsafeTemplateTag');
132
+ checkCall(node, node, 'unsafeTemplateTag', 'errorTemplateTag');
128
133
  },
129
134
  };
130
135
  },
@@ -3,7 +3,7 @@ export type Options = [
3
3
  allowOptionalChaining?: boolean;
4
4
  }
5
5
  ];
6
- export type MessageIds = 'unsafeComputedMemberAccess' | 'unsafeMemberExpression' | 'unsafeThisMemberExpression';
6
+ export type MessageIds = 'errorComputedMemberAccess' | 'errorMemberExpression' | 'errorThisMemberExpression' | 'unsafeComputedMemberAccess' | 'unsafeMemberExpression' | 'unsafeThisMemberExpression';
7
7
  declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, Options, import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
8
8
  name: string;
9
9
  };
@@ -42,10 +42,6 @@ var State;
42
42
  State[State["Safe"] = 2] = "Safe";
43
43
  State[State["Chained"] = 3] = "Chained";
44
44
  })(State || (State = {}));
45
- function createDataType(type) {
46
- const isErrorType = tsutils.isIntrinsicErrorType(type);
47
- return isErrorType ? '`error` typed' : '`any`';
48
- }
49
45
  exports.default = (0, util_1.createRule)({
50
46
  name: 'no-unsafe-member-access',
51
47
  meta: {
@@ -56,8 +52,14 @@ exports.default = (0, util_1.createRule)({
56
52
  requiresTypeChecking: true,
57
53
  },
58
54
  messages: {
59
- unsafeComputedMemberAccess: 'Computed name {{property}} resolves to an {{type}} value.',
60
- unsafeMemberExpression: 'Unsafe member access {{property}} on an {{type}} value.',
55
+ errorComputedMemberAccess: 'The type of computed name {{property}} cannot be resolved.',
56
+ errorMemberExpression: 'Unsafe member access {{property}} on a type that cannot be resolved.',
57
+ errorThisMemberExpression: [
58
+ 'Unsafe member access {{property}}. The type of `this` cannot be resolved.',
59
+ 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.',
60
+ ].join('\n'),
61
+ unsafeComputedMemberAccess: 'Computed name {{property}} resolves to an `any` value.',
62
+ unsafeMemberExpression: 'Unsafe member access {{property}} on an `any` value.',
61
63
  unsafeThisMemberExpression: [
62
64
  'Unsafe member access {{property}} on an `any` value. `this` is typed as `any`.',
63
65
  'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.',
@@ -116,20 +118,28 @@ exports.default = (0, util_1.createRule)({
116
118
  stateCache.set(node, state);
117
119
  if (state === State.Unsafe) {
118
120
  const propertyName = context.sourceCode.getText(node.property);
119
- let messageId = 'unsafeMemberExpression';
121
+ let messageId;
120
122
  if (!isNoImplicitThis) {
121
123
  // `this.foo` or `this.foo[bar]`
122
124
  const thisExpression = (0, util_1.getThisExpression)(node);
123
- if (thisExpression &&
124
- (0, util_1.isTypeAnyType)((0, util_1.getConstrainedTypeAtLocation)(services, thisExpression))) {
125
- messageId = 'unsafeThisMemberExpression';
125
+ if (thisExpression) {
126
+ const thisType = (0, util_1.getConstrainedTypeAtLocation)(services, thisExpression);
127
+ if ((0, util_1.isTypeAnyType)(thisType)) {
128
+ messageId = tsutils.isIntrinsicErrorType(thisType)
129
+ ? 'errorThisMemberExpression'
130
+ : 'unsafeThisMemberExpression';
131
+ }
126
132
  }
127
133
  }
134
+ if (!messageId) {
135
+ messageId = tsutils.isIntrinsicErrorType(type)
136
+ ? 'errorMemberExpression'
137
+ : 'unsafeMemberExpression';
138
+ }
128
139
  context.report({
129
140
  node: node.property,
130
141
  messageId,
131
142
  data: {
132
- type: createDataType(type),
133
143
  property: node.computed ? `[${propertyName}]` : `.${propertyName}`,
134
144
  },
135
145
  });
@@ -159,9 +169,10 @@ exports.default = (0, util_1.createRule)({
159
169
  const propertyName = context.sourceCode.getText(node);
160
170
  context.report({
161
171
  node,
162
- messageId: 'unsafeComputedMemberAccess',
172
+ messageId: tsutils.isIntrinsicErrorType(type)
173
+ ? 'errorComputedMemberAccess'
174
+ : 'unsafeComputedMemberAccess',
163
175
  data: {
164
- type: createDataType(type),
165
176
  property: `[${propertyName}]`,
166
177
  },
167
178
  });
@@ -1,5 +1,5 @@
1
1
  import { TSESLint } from '@typescript-eslint/utils';
2
- export type MessageIds = 'unusedVar' | 'usedIgnoredVar' | 'usedOnlyAsType';
2
+ export type MessageIds = 'removeUnusedImportDeclaration' | 'removeUnusedVar' | 'unusedVar' | 'usedIgnoredVar' | 'usedOnlyAsType';
3
3
  export type Options = [
4
4
  'all' | 'local' | {
5
5
  args?: 'after-used' | 'all' | 'none';
@@ -7,6 +7,9 @@ export type Options = [
7
7
  caughtErrors?: 'all' | 'none';
8
8
  caughtErrorsIgnorePattern?: string;
9
9
  destructuredArrayIgnorePattern?: string;
10
+ enableAutofixRemoval?: {
11
+ imports?: boolean;
12
+ };
10
13
  ignoreClassWithStaticInitBlock?: boolean;
11
14
  ignoreRestSiblings?: boolean;
12
15
  ignoreUsingDeclarations?: boolean;
@@ -4,6 +4,45 @@ const scope_manager_1 = require("@typescript-eslint/scope-manager");
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const util_1 = require("../util");
6
6
  const referenceContainsTypeQuery_1 = require("../util/referenceContainsTypeQuery");
7
+ // this is a superset of DefinitionType which defines sub-types for better granularity
8
+ var VariableType;
9
+ (function (VariableType) {
10
+ // New sub-types
11
+ VariableType[VariableType["ArrayDestructure"] = 0] = "ArrayDestructure";
12
+ // DefinitionType
13
+ VariableType[VariableType["CatchClause"] = 1] = "CatchClause";
14
+ VariableType[VariableType["ClassName"] = 2] = "ClassName";
15
+ VariableType[VariableType["FunctionName"] = 3] = "FunctionName";
16
+ VariableType[VariableType["ImportBinding"] = 4] = "ImportBinding";
17
+ VariableType[VariableType["ImplicitGlobalVariable"] = 5] = "ImplicitGlobalVariable";
18
+ VariableType[VariableType["Parameter"] = 6] = "Parameter";
19
+ VariableType[VariableType["TSEnumMember"] = 7] = "TSEnumMember";
20
+ VariableType[VariableType["TSEnumName"] = 8] = "TSEnumName";
21
+ VariableType[VariableType["TSModuleName"] = 9] = "TSModuleName";
22
+ VariableType[VariableType["Type"] = 10] = "Type";
23
+ VariableType[VariableType["Variable"] = 11] = "Variable";
24
+ })(VariableType || (VariableType = {}));
25
+ const isCommaToken = {
26
+ predicate: (token) => token.type === utils_1.AST_TOKEN_TYPES.Punctuator && token.value === ',',
27
+ tokenChar: ',',
28
+ };
29
+ const isLeftCurlyToken = {
30
+ predicate: (token) => token.type === utils_1.AST_TOKEN_TYPES.Punctuator && token.value === '{',
31
+ tokenChar: '{',
32
+ };
33
+ const isRightCurlyToken = {
34
+ predicate: (token) => token.type === utils_1.AST_TOKEN_TYPES.Punctuator && token.value === '}',
35
+ tokenChar: '}',
36
+ };
37
+ function assertToken({ predicate, tokenChar }, token) {
38
+ if (token == null) {
39
+ throw new Error(`Expected a valid "${tokenChar}" token, but found no token`);
40
+ }
41
+ if (!predicate(token)) {
42
+ throw new Error(`Expected a valid "${tokenChar}" token, but got "${token.value}" instead`);
43
+ }
44
+ return token;
45
+ }
7
46
  exports.default = (0, util_1.createRule)({
8
47
  name: 'no-unused-vars',
9
48
  meta: {
@@ -13,7 +52,11 @@ exports.default = (0, util_1.createRule)({
13
52
  extendsBaseRule: true,
14
53
  recommended: 'recommended',
15
54
  },
55
+ fixable: 'code',
56
+ hasSuggestions: true,
16
57
  messages: {
58
+ removeUnusedImportDeclaration: 'Remove unused import declaration.',
59
+ removeUnusedVar: 'Remove unused variable "{{varName}}".',
17
60
  unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.",
18
61
  usedIgnoredVar: "'{{varName}}' is marked as ignored but is used{{additional}}.",
19
62
  usedOnlyAsType: "'{{varName}}' is {{action}} but only used as a type{{additional}}.",
@@ -52,6 +95,17 @@ exports.default = (0, util_1.createRule)({
52
95
  type: 'string',
53
96
  description: 'Regular expressions of destructured array variable names to not check for usage.',
54
97
  },
98
+ enableAutofixRemoval: {
99
+ type: 'object',
100
+ additionalProperties: false,
101
+ description: 'Configurable automatic fixes for different types of unused variables.',
102
+ properties: {
103
+ imports: {
104
+ type: 'boolean',
105
+ description: 'Whether to enable automatic removal of unused imports.',
106
+ },
107
+ },
108
+ },
55
109
  ignoreClassWithStaticInitBlock: {
56
110
  type: 'boolean',
57
111
  description: 'Whether to ignore classes with at least one static initialization block.',
@@ -86,10 +140,130 @@ exports.default = (0, util_1.createRule)({
86
140
  defaultOptions: [{}],
87
141
  create(context, [firstOption]) {
88
142
  const MODULE_DECL_CACHE = new Map();
143
+ const reportedUnusedVariables = new Set();
144
+ function areAllSpecifiersUnused(decl) {
145
+ return context.sourceCode.getDeclaredVariables(decl).every(variable => {
146
+ return reportedUnusedVariables.has(variable);
147
+ });
148
+ }
149
+ const report = (unusedVar, opts) => {
150
+ reportedUnusedVariables.add(unusedVar);
151
+ const writeReferences = unusedVar.references.filter(ref => ref.isWrite() &&
152
+ ref.from.variableScope === unusedVar.scope.variableScope);
153
+ const id = writeReferences.length
154
+ ? writeReferences[writeReferences.length - 1].identifier
155
+ : unusedVar.identifiers[0];
156
+ const { start } = id.loc;
157
+ const idLength = id.name.length;
158
+ const loc = {
159
+ start,
160
+ end: {
161
+ column: start.column + idLength,
162
+ line: start.line,
163
+ },
164
+ };
165
+ const fixer = (() => {
166
+ const { messageId, fix, useAutofix } = (() => {
167
+ if (unusedVar.defs.length !== 1) {
168
+ // If there's multiple definitions then we'd have to clean them all
169
+ // up! That's complicated and messy so for now let's just ignore it.
170
+ return {};
171
+ }
172
+ const { type, def } = defToVariableType(unusedVar.defs[0]);
173
+ switch (type) {
174
+ case VariableType.ArrayDestructure:
175
+ // TODO(bradzacher) -- this would be really easy to implement and
176
+ // is side-effect free!
177
+ return {};
178
+ case VariableType.CatchClause:
179
+ // TODO(bradzacher) -- this would be really easy to implement and
180
+ // is side-effect free!
181
+ return {};
182
+ case VariableType.ClassName:
183
+ // This would be easy to implement -- but classes can have
184
+ // side-effects in static initializers / static blocks. So it's
185
+ // dangerous to ever auto-fix remove them.
186
+ //
187
+ // Perhaps as an always-suggestion fixer...?
188
+ return {};
189
+ case VariableType.FunctionName:
190
+ // TODO(bradzacher) -- this would be really easy to implement and
191
+ // is side-effect free!
192
+ return {};
193
+ case VariableType.ImportBinding:
194
+ return {
195
+ ...getImportFixer(def),
196
+ useAutofix: options.enableAutofixRemoval.imports,
197
+ };
198
+ case VariableType.ImplicitGlobalVariable:
199
+ // We don't report these via this code path, so no fixer is possible
200
+ return {};
201
+ case VariableType.Parameter:
202
+ // This is easy to implement -- but we cannot implement it cos it
203
+ // changes the signature of the function which in turn might
204
+ // introduce type errors in consumers.
205
+ //
206
+ // Also parameters can have default values which might have
207
+ // side-effects.
208
+ //
209
+ // Perhaps as an always-suggestion fixer...?
210
+ return {};
211
+ case VariableType.TSEnumMember:
212
+ // We don't report unused enum members so no fixer is ever possible
213
+ return {};
214
+ case VariableType.TSEnumName:
215
+ // TODO(bradzacher) -- this would be really easy to implement and
216
+ // is side-effect free!
217
+ return {};
218
+ case VariableType.TSModuleName:
219
+ // This is easy to implement -- but TS namespaces are eagerly
220
+ // initialized -- meaning that they might have side-effects in
221
+ // the body. So it's dangerous to ever auto-fix remove them.
222
+ //
223
+ // Perhaps as an always-suggestion fixer...?
224
+ return {};
225
+ case VariableType.Type:
226
+ // TODO(bradzacher) -- this would be really easy to implement and
227
+ // is side-effect free!
228
+ return {};
229
+ case VariableType.Variable:
230
+ // TODO(bradzacher) -- this would be really easy to implement
231
+ return {};
232
+ }
233
+ })();
234
+ if (!fix) {
235
+ return {};
236
+ }
237
+ if (useAutofix) {
238
+ return { fix };
239
+ }
240
+ const data = {
241
+ varName: unusedVar.name,
242
+ };
243
+ return {
244
+ suggest: [
245
+ {
246
+ messageId: messageId ?? 'removeUnusedVar',
247
+ data,
248
+ fix,
249
+ },
250
+ ],
251
+ };
252
+ })();
253
+ context.report({
254
+ ...opts,
255
+ ...fixer,
256
+ loc,
257
+ node: id,
258
+ });
259
+ };
89
260
  const options = (() => {
90
261
  const options = {
91
262
  args: 'after-used',
92
263
  caughtErrors: 'all',
264
+ enableAutofixRemoval: {
265
+ imports: false,
266
+ },
93
267
  ignoreClassWithStaticInitBlock: false,
94
268
  ignoreRestSiblings: false,
95
269
  ignoreUsingDeclarations: false,
@@ -126,9 +300,171 @@ exports.default = (0, util_1.createRule)({
126
300
  if (firstOption.destructuredArrayIgnorePattern) {
127
301
  options.destructuredArrayIgnorePattern = new RegExp(firstOption.destructuredArrayIgnorePattern, 'u');
128
302
  }
303
+ if (firstOption.enableAutofixRemoval) {
304
+ // eslint-disable-next-line unicorn/no-lonely-if -- will add more cases later
305
+ if (firstOption.enableAutofixRemoval.imports != null) {
306
+ options.enableAutofixRemoval.imports =
307
+ firstOption.enableAutofixRemoval.imports;
308
+ }
309
+ }
129
310
  }
130
311
  return options;
131
312
  })();
313
+ function getImportFixer(def) {
314
+ switch (def.node.type) {
315
+ case utils_1.AST_NODE_TYPES.TSImportEqualsDeclaration:
316
+ // import equals declarations can only have one binding -- so we can
317
+ // just remove entire import declaration
318
+ return {
319
+ messageId: 'removeUnusedImportDeclaration',
320
+ fix: fixer => fixer.remove(def.node),
321
+ };
322
+ case utils_1.AST_NODE_TYPES.ImportDefaultSpecifier: {
323
+ const importDecl = def.node.parent;
324
+ if (importDecl.specifiers.length === 1 ||
325
+ areAllSpecifiersUnused(importDecl)) {
326
+ // all specifiers are unused -- so we can just remove entire import
327
+ // declaration
328
+ return {
329
+ messageId: 'removeUnusedImportDeclaration',
330
+ fix: fixer => fixer.remove(importDecl),
331
+ };
332
+ }
333
+ // in this branch we know the following things:
334
+ // 1) there is at least one specifier that is used
335
+ // 2) the default specifier is unused
336
+ //
337
+ // by process of elimination we can deduce that there is at least one
338
+ // named specifier that is used
339
+ //
340
+ // i.e. the code must be import Unused, { Used, ... } from 'module';
341
+ //
342
+ // there's one or more unused named specifiers, so we must remove the
343
+ // default specifier in isolation including the trailing comma.
344
+ //
345
+ // import Unused, { Used, ... } from 'module';
346
+ // ^^^^^^^ remove this
347
+ //
348
+ // NOTE: we could also remove the spaces between the comma and the
349
+ // opening curly brace -- but this does risk removing comments. To be
350
+ // safe we'll be conservative for now
351
+ //
352
+ // TODO(bradzacher) -- consider removing the extra space whilst also
353
+ // preserving comments.
354
+ return {
355
+ messageId: 'removeUnusedVar',
356
+ fix: fixer => {
357
+ const comma = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(def.node), util_1.NullThrowsReasons.MissingToken(',', 'import specifier'));
358
+ assertToken(isCommaToken, comma);
359
+ return fixer.removeRange([
360
+ Math.min(def.node.range[0], comma.range[0]),
361
+ Math.max(def.node.range[1], comma.range[1]),
362
+ ]);
363
+ },
364
+ };
365
+ }
366
+ case utils_1.AST_NODE_TYPES.ImportSpecifier: {
367
+ // guaranteed to NOT be in an export statement as we're inspecting an
368
+ // import
369
+ const importDecl = def.node.parent;
370
+ if (importDecl.specifiers.length === 1 ||
371
+ areAllSpecifiersUnused(importDecl)) {
372
+ // all specifiers are unused -- so we can just remove entire import
373
+ // declaration
374
+ return {
375
+ messageId: 'removeUnusedImportDeclaration',
376
+ fix: fixer => fixer.remove(importDecl),
377
+ };
378
+ }
379
+ return {
380
+ messageId: 'removeUnusedVar',
381
+ fix: fixer => {
382
+ const usedNamedSpecifiers = context.sourceCode
383
+ .getDeclaredVariables(importDecl)
384
+ .map(variable => {
385
+ if (reportedUnusedVariables.has(variable)) {
386
+ return null;
387
+ }
388
+ const specifier = variable.defs[0].node;
389
+ if (specifier.type !== utils_1.AST_NODE_TYPES.ImportSpecifier) {
390
+ return null;
391
+ }
392
+ return specifier;
393
+ })
394
+ .filter(v => v != null);
395
+ if (usedNamedSpecifiers.length === 0) {
396
+ // in this branch we know the following things:
397
+ // 1) there is at least one specifier that is used
398
+ // 2) all named specifiers are unused
399
+ //
400
+ // by process of elimination we can deduce that there is a
401
+ // default specifier and it is the only used specifier
402
+ //
403
+ // i.e. the code must be import Used, { Unused, ... } from
404
+ // 'module';
405
+ //
406
+ // So we can just remove the entire curly content and the comma
407
+ // before, eg import Used, { Unused, ... } from 'module';
408
+ // ^^^^^^^^^^^^^^^^^ remove this
409
+ const leftCurly = assertToken(isLeftCurlyToken, context.sourceCode.getFirstToken(importDecl, isLeftCurlyToken.predicate));
410
+ const leftToken = assertToken(isCommaToken, context.sourceCode.getTokenBefore(leftCurly));
411
+ const rightToken = assertToken(isRightCurlyToken, context.sourceCode.getFirstToken(importDecl, isRightCurlyToken.predicate));
412
+ return fixer.removeRange([
413
+ leftToken.range[0],
414
+ rightToken.range[1],
415
+ ]);
416
+ }
417
+ // in this branch we know there is at least one used named
418
+ // specifier which means we have to remove each unused specifier
419
+ // in isolation.
420
+ //
421
+ // there's 3 possible cases to care about: import { Unused,
422
+ // Used... } from 'module'; import { ...Used, Unused } from
423
+ // 'module'; import { ...Used, Unused, } from 'module';
424
+ //
425
+ // Note that because of the above usedNamedSpecifiers check we
426
+ // know that we don't have one of these cases: import { Unused }
427
+ // from 'module'; import { Unused, Unused... } from 'module';
428
+ // import { ...Unused, Unused, } from 'module';
429
+ //
430
+ // The result is that we know that there _must_ be a comma that
431
+ // needs cleaning up
432
+ //
433
+ // try to remove the leading comma first as it leads to a nicer
434
+ // fix output in most cases
435
+ //
436
+ // leading preferred: import { Used, Unused, Used } from 'module';
437
+ // ^^^^^^^^ remove import { Used, Used } from 'module';
438
+ //
439
+ // trailing preferred: import { Used, Unused, Used } from
440
+ // 'module'; ^^^^^^^ remove import { Used, Used } from
441
+ // 'module'; ^^ ugly double space
442
+ //
443
+ // But we need to still fallback to the trailing comma for cases
444
+ // where the unused specifier is the first in the import eg:
445
+ // import { Unused, Used } from 'module';
446
+ const maybeComma = context.sourceCode.getTokenBefore(def.node);
447
+ const comma = maybeComma && isCommaToken.predicate(maybeComma)
448
+ ? maybeComma
449
+ : assertToken(isCommaToken, context.sourceCode.getTokenAfter(def.node));
450
+ return fixer.removeRange([
451
+ Math.min(def.node.range[0], comma.range[0]),
452
+ Math.max(def.node.range[1], comma.range[1]),
453
+ ]);
454
+ },
455
+ };
456
+ }
457
+ case utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier: {
458
+ // namespace specifiers cannot be used with any other specifier -- so
459
+ // we can just remove entire import declaration
460
+ const importDecl = def.node.parent;
461
+ return {
462
+ messageId: 'removeUnusedImportDeclaration',
463
+ fix: fixer => fixer.remove(importDecl),
464
+ };
465
+ }
466
+ }
467
+ }
132
468
  /**
133
469
  * Determines what variable type a def is.
134
470
  * @param def the declaration to check
@@ -146,15 +482,31 @@ exports.default = (0, util_1.createRule)({
146
482
  */
147
483
  if (options.destructuredArrayIgnorePattern &&
148
484
  def.name.parent.type === utils_1.AST_NODE_TYPES.ArrayPattern) {
149
- return 'array-destructure';
485
+ return { type: VariableType.ArrayDestructure, def };
150
486
  }
151
487
  switch (def.type) {
152
488
  case scope_manager_1.DefinitionType.CatchClause:
153
- return 'catch-clause';
489
+ return { type: VariableType.CatchClause, def };
490
+ case scope_manager_1.DefinitionType.ClassName:
491
+ return { type: VariableType.ClassName, def };
492
+ case scope_manager_1.DefinitionType.FunctionName:
493
+ return { type: VariableType.FunctionName, def };
494
+ case scope_manager_1.DefinitionType.ImplicitGlobalVariable:
495
+ return { type: VariableType.ImplicitGlobalVariable, def };
496
+ case scope_manager_1.DefinitionType.ImportBinding:
497
+ return { type: VariableType.ImportBinding, def };
154
498
  case scope_manager_1.DefinitionType.Parameter:
155
- return 'parameter';
156
- default:
157
- return 'variable';
499
+ return { type: VariableType.Parameter, def };
500
+ case scope_manager_1.DefinitionType.TSEnumName:
501
+ return { type: VariableType.TSEnumName, def };
502
+ case scope_manager_1.DefinitionType.TSEnumMember:
503
+ return { type: VariableType.TSEnumMember, def };
504
+ case scope_manager_1.DefinitionType.TSModuleName:
505
+ return { type: VariableType.TSModuleName, def };
506
+ case scope_manager_1.DefinitionType.Type:
507
+ return { type: VariableType.Type, def };
508
+ case scope_manager_1.DefinitionType.Variable:
509
+ return { type: VariableType.Variable, def };
158
510
  }
159
511
  }
160
512
  /**
@@ -166,22 +518,22 @@ exports.default = (0, util_1.createRule)({
166
518
  */
167
519
  function getVariableDescription(variableType) {
168
520
  switch (variableType) {
169
- case 'array-destructure':
521
+ case VariableType.ArrayDestructure:
170
522
  return {
171
523
  pattern: options.destructuredArrayIgnorePattern?.toString(),
172
524
  variableDescription: 'elements of array destructuring',
173
525
  };
174
- case 'catch-clause':
526
+ case VariableType.CatchClause:
175
527
  return {
176
528
  pattern: options.caughtErrorsIgnorePattern?.toString(),
177
529
  variableDescription: 'caught errors',
178
530
  };
179
- case 'parameter':
531
+ case VariableType.Parameter:
180
532
  return {
181
533
  pattern: options.argsIgnorePattern?.toString(),
182
534
  variableDescription: 'args',
183
535
  };
184
- case 'variable':
536
+ default:
185
537
  return {
186
538
  pattern: options.varsIgnorePattern?.toString(),
187
539
  variableDescription: 'vars',
@@ -198,7 +550,7 @@ exports.default = (0, util_1.createRule)({
198
550
  const def = unusedVar.defs.at(0);
199
551
  let additionalMessageData = '';
200
552
  if (def) {
201
- const { pattern, variableDescription } = getVariableDescription(defToVariableType(def));
553
+ const { pattern, variableDescription } = getVariableDescription(defToVariableType(def).type);
202
554
  if (pattern && variableDescription) {
203
555
  additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
204
556
  }
@@ -219,7 +571,7 @@ exports.default = (0, util_1.createRule)({
219
571
  const def = unusedVar.defs.at(0);
220
572
  let additionalMessageData = '';
221
573
  if (def) {
222
- const { pattern, variableDescription } = getVariableDescription(defToVariableType(def));
574
+ const { pattern, variableDescription } = getVariableDescription(defToVariableType(def).type);
223
575
  if (pattern && variableDescription) {
224
576
  additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`;
225
577
  }
@@ -318,10 +670,9 @@ exports.default = (0, util_1.createRule)({
318
670
  def.name.type === utils_1.AST_NODE_TYPES.Identifier &&
319
671
  options.destructuredArrayIgnorePattern?.test(def.name.name)) {
320
672
  if (options.reportUsedIgnorePattern && used) {
321
- context.report({
322
- node: def.name,
673
+ report(variable, {
323
674
  messageId: 'usedIgnoredVar',
324
- data: getUsedIgnoredMessageData(variable, 'array-destructure'),
675
+ data: getUsedIgnoredMessageData(variable, VariableType.ArrayDestructure),
325
676
  });
326
677
  }
327
678
  continue;
@@ -341,10 +692,9 @@ exports.default = (0, util_1.createRule)({
341
692
  if (def.name.type === utils_1.AST_NODE_TYPES.Identifier &&
342
693
  options.caughtErrorsIgnorePattern?.test(def.name.name)) {
343
694
  if (options.reportUsedIgnorePattern && used) {
344
- context.report({
345
- node: def.name,
695
+ report(variable, {
346
696
  messageId: 'usedIgnoredVar',
347
- data: getUsedIgnoredMessageData(variable, 'catch-clause'),
697
+ data: getUsedIgnoredMessageData(variable, VariableType.CatchClause),
348
698
  });
349
699
  }
350
700
  continue;
@@ -359,10 +709,9 @@ exports.default = (0, util_1.createRule)({
359
709
  if (def.name.type === utils_1.AST_NODE_TYPES.Identifier &&
360
710
  options.argsIgnorePattern?.test(def.name.name)) {
361
711
  if (options.reportUsedIgnorePattern && used) {
362
- context.report({
363
- node: def.name,
712
+ report(variable, {
364
713
  messageId: 'usedIgnoredVar',
365
- data: getUsedIgnoredMessageData(variable, 'parameter'),
714
+ data: getUsedIgnoredMessageData(variable, VariableType.Parameter),
366
715
  });
367
716
  }
368
717
  continue;
@@ -382,10 +731,9 @@ exports.default = (0, util_1.createRule)({
382
731
  /* enum members are always marked as 'used' by `collectVariables`, but in reality they may be used or
383
732
  unused. either way, don't complain about their naming. */
384
733
  def.type !== utils_1.TSESLint.Scope.DefinitionType.TSEnumMember) {
385
- context.report({
386
- node: def.name,
734
+ report(variable, {
387
735
  messageId: 'usedIgnoredVar',
388
- data: getUsedIgnoredMessageData(variable, 'variable'),
736
+ data: getUsedIgnoredMessageData(variable, VariableType.Variable),
389
737
  });
390
738
  }
391
739
  continue;
@@ -455,28 +803,13 @@ exports.default = (0, util_1.createRule)({
455
803
  // Report the first declaration.
456
804
  if (unusedVar.defs.length > 0) {
457
805
  const usedOnlyAsType = unusedVar.references.some(ref => (0, referenceContainsTypeQuery_1.referenceContainsTypeQuery)(ref.identifier));
806
+ const messageId = usedOnlyAsType ? 'usedOnlyAsType' : 'unusedVar';
458
807
  const isImportUsedOnlyAsType = usedOnlyAsType &&
459
808
  unusedVar.defs.some(def => def.type === scope_manager_1.DefinitionType.ImportBinding);
460
809
  if (isImportUsedOnlyAsType) {
461
810
  continue;
462
811
  }
463
- const writeReferences = unusedVar.references.filter(ref => ref.isWrite() &&
464
- ref.from.variableScope === unusedVar.scope.variableScope);
465
- const id = writeReferences.length
466
- ? writeReferences[writeReferences.length - 1].identifier
467
- : unusedVar.identifiers[0];
468
- const messageId = usedOnlyAsType ? 'usedOnlyAsType' : 'unusedVar';
469
- const { start } = id.loc;
470
- const idLength = id.name.length;
471
- const loc = {
472
- start,
473
- end: {
474
- column: start.column + idLength,
475
- line: start.line,
476
- },
477
- };
478
- context.report({
479
- loc,
812
+ report(unusedVar, {
480
813
  messageId,
481
814
  data: unusedVar.references.some(ref => ref.isWrite())
482
815
  ? getAssignedMessageData(unusedVar)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typescript-eslint/eslint-plugin",
3
- "version": "8.52.1-alpha.1",
3
+ "version": "8.52.1-alpha.11",
4
4
  "description": "TypeScript plugin for ESLint",
5
5
  "files": [
6
6
  "dist",
@@ -59,10 +59,10 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@eslint-community/regexpp": "^4.12.2",
62
- "@typescript-eslint/scope-manager": "8.52.1-alpha.1",
63
- "@typescript-eslint/type-utils": "8.52.1-alpha.1",
64
- "@typescript-eslint/utils": "8.52.1-alpha.1",
65
- "@typescript-eslint/visitor-keys": "8.52.1-alpha.1",
62
+ "@typescript-eslint/scope-manager": "8.52.1-alpha.11",
63
+ "@typescript-eslint/type-utils": "8.52.1-alpha.11",
64
+ "@typescript-eslint/utils": "8.52.1-alpha.11",
65
+ "@typescript-eslint/visitor-keys": "8.52.1-alpha.11",
66
66
  "ignore": "^7.0.5",
67
67
  "natural-compare": "^1.4.0",
68
68
  "ts-api-utils": "^2.4.0"
@@ -70,8 +70,8 @@
70
70
  "devDependencies": {
71
71
  "@types/mdast": "^4.0.4",
72
72
  "@types/natural-compare": "*",
73
- "@typescript-eslint/rule-schema-to-typescript-types": "8.52.1-alpha.1",
74
- "@typescript-eslint/rule-tester": "8.52.1-alpha.1",
73
+ "@typescript-eslint/rule-schema-to-typescript-types": "8.52.1-alpha.11",
74
+ "@typescript-eslint/rule-tester": "8.52.1-alpha.11",
75
75
  "@vitest/coverage-v8": "^3.2.4",
76
76
  "ajv": "^6.12.6",
77
77
  "eslint": "*",
@@ -90,7 +90,7 @@
90
90
  "vitest": "^3.2.4"
91
91
  },
92
92
  "peerDependencies": {
93
- "@typescript-eslint/parser": "^8.52.1-alpha.1",
93
+ "@typescript-eslint/parser": "^8.52.1-alpha.11",
94
94
  "eslint": "^8.57.0 || ^9.0.0",
95
95
  "typescript": ">=4.8.4 <6.0.0"
96
96
  },