@typescript-eslint/eslint-plugin 8.52.1-alpha.9 → 8.53.1

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 @@ declare const _default: {
148
148
  'no-return-await': "off";
149
149
  '@typescript-eslint/return-await': "error";
150
150
  '@typescript-eslint/strict-boolean-expressions': "error";
151
+ '@typescript-eslint/strict-void-return': "error";
151
152
  '@typescript-eslint/switch-exhaustiveness-check': "error";
152
153
  '@typescript-eslint/triple-slash-reference': "error";
153
154
  '@typescript-eslint/unbound-method': "error";
@@ -155,6 +155,7 @@ module.exports = {
155
155
  'no-return-await': 'off',
156
156
  '@typescript-eslint/return-await': 'error',
157
157
  '@typescript-eslint/strict-boolean-expressions': 'error',
158
+ '@typescript-eslint/strict-void-return': 'error',
158
159
  '@typescript-eslint/switch-exhaustiveness-check': 'error',
159
160
  '@typescript-eslint/triple-slash-reference': 'error',
160
161
  '@typescript-eslint/unbound-method': 'error',
@@ -62,6 +62,7 @@ declare const _default: {
62
62
  '@typescript-eslint/restrict-template-expressions': "off";
63
63
  '@typescript-eslint/return-await': "off";
64
64
  '@typescript-eslint/strict-boolean-expressions': "off";
65
+ '@typescript-eslint/strict-void-return': "off";
65
66
  '@typescript-eslint/switch-exhaustiveness-check': "off";
66
67
  '@typescript-eslint/unbound-method': "off";
67
68
  '@typescript-eslint/use-unknown-in-catch-callback-variable': "off";
@@ -65,6 +65,7 @@ module.exports = {
65
65
  '@typescript-eslint/restrict-template-expressions': 'off',
66
66
  '@typescript-eslint/return-await': 'off',
67
67
  '@typescript-eslint/strict-boolean-expressions': 'off',
68
+ '@typescript-eslint/strict-void-return': 'off',
68
69
  '@typescript-eslint/switch-exhaustiveness-check': 'off',
69
70
  '@typescript-eslint/unbound-method': 'off',
70
71
  '@typescript-eslint/use-unknown-in-catch-callback-variable': 'off',
@@ -168,6 +168,7 @@ exports.default = (plugin, parser) => [
168
168
  'no-return-await': 'off',
169
169
  '@typescript-eslint/return-await': 'error',
170
170
  '@typescript-eslint/strict-boolean-expressions': 'error',
171
+ '@typescript-eslint/strict-void-return': 'error',
171
172
  '@typescript-eslint/switch-exhaustiveness-check': 'error',
172
173
  '@typescript-eslint/triple-slash-reference': 'error',
173
174
  '@typescript-eslint/unbound-method': 'error',
@@ -70,6 +70,7 @@ exports.default = (_plugin, _parser) => ({
70
70
  '@typescript-eslint/restrict-template-expressions': 'off',
71
71
  '@typescript-eslint/return-await': 'off',
72
72
  '@typescript-eslint/strict-boolean-expressions': 'off',
73
+ '@typescript-eslint/strict-void-return': 'off',
73
74
  '@typescript-eslint/switch-exhaustiveness-check': 'off',
74
75
  '@typescript-eslint/unbound-method': 'off',
75
76
  '@typescript-eslint/use-unknown-in-catch-callback-variable': 'off',
package/dist/index.d.ts CHANGED
@@ -150,6 +150,7 @@ declare const _default: {
150
150
  'no-return-await': "off";
151
151
  '@typescript-eslint/return-await': "error";
152
152
  '@typescript-eslint/strict-boolean-expressions': "error";
153
+ '@typescript-eslint/strict-void-return': "error";
153
154
  '@typescript-eslint/switch-exhaustiveness-check': "error";
154
155
  '@typescript-eslint/triple-slash-reference': "error";
155
156
  '@typescript-eslint/unbound-method': "error";
@@ -228,6 +229,7 @@ declare const _default: {
228
229
  '@typescript-eslint/restrict-template-expressions': "off";
229
230
  '@typescript-eslint/return-await': "off";
230
231
  '@typescript-eslint/strict-boolean-expressions': "off";
232
+ '@typescript-eslint/strict-void-return': "off";
231
233
  '@typescript-eslint/switch-exhaustiveness-check': "off";
232
234
  '@typescript-eslint/unbound-method': "off";
233
235
  '@typescript-eslint/use-unknown-in-catch-callback-variable': "off";
@@ -1096,6 +1098,11 @@ declare const _default: {
1096
1098
  'strict-boolean-expressions': import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/strict-boolean-expressions").MessageId, import("./rules/strict-boolean-expressions").Options, import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
1097
1099
  name: string;
1098
1100
  };
1101
+ 'strict-void-return': import("@typescript-eslint/utils/ts-eslint").RuleModule<"asyncFunc" | "nonVoidFunc" | "nonVoidReturn", [{
1102
+ allowReturnAny?: boolean;
1103
+ }], import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
1104
+ name: string;
1105
+ };
1099
1106
  'switch-exhaustiveness-check': import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/switch-exhaustiveness-check").MessageIds, import("./rules/switch-exhaustiveness-check").Options, import("../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
1100
1107
  name: string;
1101
1108
  };
@@ -172,6 +172,7 @@ declare const _default: {
172
172
  'no-return-await': "off";
173
173
  '@typescript-eslint/return-await': "error";
174
174
  '@typescript-eslint/strict-boolean-expressions': "error";
175
+ '@typescript-eslint/strict-void-return': "error";
175
176
  '@typescript-eslint/switch-exhaustiveness-check': "error";
176
177
  '@typescript-eslint/triple-slash-reference': "error";
177
178
  '@typescript-eslint/unbound-method': "error";
@@ -250,6 +251,7 @@ declare const _default: {
250
251
  '@typescript-eslint/restrict-template-expressions': "off";
251
252
  '@typescript-eslint/return-await': "off";
252
253
  '@typescript-eslint/strict-boolean-expressions': "off";
254
+ '@typescript-eslint/strict-void-return': "off";
253
255
  '@typescript-eslint/switch-exhaustiveness-check': "off";
254
256
  '@typescript-eslint/unbound-method': "off";
255
257
  '@typescript-eslint/use-unknown-in-catch-callback-variable': "off";
@@ -1119,6 +1121,11 @@ declare const _default: {
1119
1121
  'strict-boolean-expressions': TSESLint.RuleModule<import("./rules/strict-boolean-expressions").MessageId, import("./rules/strict-boolean-expressions").Options, import("../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
1120
1122
  name: string;
1121
1123
  };
1124
+ 'strict-void-return': TSESLint.RuleModule<"asyncFunc" | "nonVoidFunc" | "nonVoidReturn", [{
1125
+ allowReturnAny?: boolean;
1126
+ }], import("../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
1127
+ name: string;
1128
+ };
1122
1129
  'switch-exhaustiveness-check': TSESLint.RuleModule<import("./rules/switch-exhaustiveness-check").MessageIds, import("./rules/switch-exhaustiveness-check").Options, import("../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
1123
1130
  name: string;
1124
1131
  };
@@ -114,7 +114,8 @@ exports.default = (0, util_1.createRule)({
114
114
  .map(p => context.sourceCode.getText(p))
115
115
  .join(', ')}>`;
116
116
  }
117
- checkMembers(node.body.body, node, node.id, `type ${node.id.name}${genericTypes} = `, ';', !node.extends.length);
117
+ checkMembers(node.body.body, node, node.id, `type ${node.id.name}${genericTypes} = `, ';', !node.extends.length &&
118
+ node.parent.type !== utils_1.AST_NODE_TYPES.ExportDefaultDeclaration);
118
119
  },
119
120
  TSMappedType(node) {
120
121
  const key = node.key;
@@ -419,6 +419,11 @@ declare const rules: {
419
419
  'strict-boolean-expressions': import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./strict-boolean-expressions").MessageId, import("./strict-boolean-expressions").Options, import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
420
420
  name: string;
421
421
  };
422
+ 'strict-void-return': import("@typescript-eslint/utils/ts-eslint").RuleModule<"asyncFunc" | "nonVoidFunc" | "nonVoidReturn", [{
423
+ allowReturnAny?: boolean;
424
+ }], import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
425
+ name: string;
426
+ };
422
427
  'switch-exhaustiveness-check': import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./switch-exhaustiveness-check").MessageIds, import("./switch-exhaustiveness-check").Options, import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
423
428
  name: string;
424
429
  };
@@ -129,6 +129,7 @@ const restrict_template_expressions_1 = __importDefault(require("./restrict-temp
129
129
  const return_await_1 = __importDefault(require("./return-await"));
130
130
  const sort_type_constituents_1 = __importDefault(require("./sort-type-constituents"));
131
131
  const strict_boolean_expressions_1 = __importDefault(require("./strict-boolean-expressions"));
132
+ const strict_void_return_1 = __importDefault(require("./strict-void-return"));
132
133
  const switch_exhaustiveness_check_1 = __importDefault(require("./switch-exhaustiveness-check"));
133
134
  const triple_slash_reference_1 = __importDefault(require("./triple-slash-reference"));
134
135
  const typedef_1 = __importDefault(require("./typedef"));
@@ -263,6 +264,7 @@ const rules = {
263
264
  'return-await': return_await_1.default,
264
265
  'sort-type-constituents': sort_type_constituents_1.default,
265
266
  'strict-boolean-expressions': strict_boolean_expressions_1.default,
267
+ 'strict-void-return': strict_void_return_1.default,
266
268
  'switch-exhaustiveness-check': switch_exhaustiveness_check_1.default,
267
269
  'triple-slash-reference': triple_slash_reference_1.default,
268
270
  typedef: typedef_1.default,
@@ -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)
@@ -111,6 +111,11 @@ exports.default = (0, util_1.createRule)({
111
111
  const params = signatures[0].getParameters();
112
112
  if (paramIndex < params.length) {
113
113
  const paramSymbol = params[paramIndex];
114
+ if (paramSymbol.valueDeclaration &&
115
+ ts.isParameter(paramSymbol.valueDeclaration) &&
116
+ paramSymbol.valueDeclaration.dotDotDotToken != null) {
117
+ return;
118
+ }
114
119
  if ((paramSymbol.flags & ts.SymbolFlags.Optional) === 0) {
115
120
  const paramType = checker.getTypeOfSymbol(paramSymbol);
116
121
  if (!canBeUndefined(paramType)) {
@@ -0,0 +1,10 @@
1
+ type Options = [
2
+ {
3
+ allowReturnAny?: boolean;
4
+ }
5
+ ];
6
+ type MessageId = `asyncFunc` | `nonVoidFunc` | `nonVoidReturn`;
7
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageId, Options, import("../../rules").ESLintPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
8
+ name: string;
9
+ };
10
+ export default _default;
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const utils_1 = require("@typescript-eslint/utils");
37
+ const tsutils = __importStar(require("ts-api-utils"));
38
+ const ts = __importStar(require("typescript"));
39
+ const util = __importStar(require("../util"));
40
+ exports.default = util.createRule({
41
+ name: 'strict-void-return',
42
+ meta: {
43
+ type: 'problem',
44
+ docs: {
45
+ description: 'Disallow passing a value-returning function in a position accepting a void function',
46
+ requiresTypeChecking: true,
47
+ },
48
+ messages: {
49
+ asyncFunc: 'Async function used in a context where a void function is expected.',
50
+ nonVoidFunc: 'Value-returning function used in a context where a void function is expected.',
51
+ nonVoidReturn: 'Value returned in a context where a void return is expected.',
52
+ },
53
+ schema: [
54
+ {
55
+ type: 'object',
56
+ additionalProperties: false,
57
+ properties: {
58
+ allowReturnAny: {
59
+ type: 'boolean',
60
+ description: 'Whether to allow functions returning `any` to be used in place expecting a `void` function.',
61
+ },
62
+ },
63
+ },
64
+ ],
65
+ },
66
+ defaultOptions: [
67
+ {
68
+ allowReturnAny: false,
69
+ },
70
+ ],
71
+ create(context, [options]) {
72
+ const sourceCode = context.sourceCode;
73
+ const parserServices = util.getParserServices(context);
74
+ const checker = parserServices.program.getTypeChecker();
75
+ return {
76
+ ArrayExpression: (node) => {
77
+ for (const elemNode of node.elements) {
78
+ if (elemNode != null &&
79
+ elemNode.type !== utils_1.AST_NODE_TYPES.SpreadElement) {
80
+ checkExpressionNode(elemNode);
81
+ }
82
+ }
83
+ },
84
+ ArrowFunctionExpression: (node) => {
85
+ if (node.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
86
+ checkExpressionNode(node.body);
87
+ }
88
+ },
89
+ AssignmentExpression: (node) => {
90
+ checkExpressionNode(node.right); // should ignore operators like `+=` or `-=` automatically
91
+ },
92
+ 'CallExpression, NewExpression': checkFunctionCallNode,
93
+ JSXAttribute: (node) => {
94
+ if (node.value?.type === utils_1.AST_NODE_TYPES.JSXExpressionContainer &&
95
+ node.value.expression.type !== utils_1.AST_NODE_TYPES.JSXEmptyExpression) {
96
+ checkExpressionNode(node.value.expression);
97
+ }
98
+ },
99
+ MethodDefinition: checkClassMethodNode,
100
+ ObjectExpression: (node) => {
101
+ for (const propNode of node.properties) {
102
+ if (propNode.type !== utils_1.AST_NODE_TYPES.SpreadElement) {
103
+ checkObjectPropertyNode(propNode);
104
+ }
105
+ }
106
+ },
107
+ PropertyDefinition: checkClassPropertyNode,
108
+ ReturnStatement: (node) => {
109
+ if (node.argument != null) {
110
+ checkExpressionNode(node.argument);
111
+ }
112
+ },
113
+ VariableDeclarator: (node) => {
114
+ if (node.init != null) {
115
+ checkExpressionNode(node.init);
116
+ }
117
+ },
118
+ };
119
+ function isVoidReturningFunctionType(type) {
120
+ const returnTypes = tsutils
121
+ .getCallSignaturesOfType(type)
122
+ .flatMap(signature => tsutils.unionConstituents(signature.getReturnType()));
123
+ return (returnTypes.length > 0 &&
124
+ returnTypes.every(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.Void)));
125
+ }
126
+ /**
127
+ * Finds errors in any expression node.
128
+ *
129
+ * Compares the type of the node against the contextual (expected) type.
130
+ *
131
+ * @returns `true` if the expected type was void function.
132
+ */
133
+ function checkExpressionNode(node) {
134
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
135
+ const expectedType = checker.getContextualType(tsNode);
136
+ if (expectedType != null && isVoidReturningFunctionType(expectedType)) {
137
+ reportIfNonVoidFunction(node);
138
+ return true;
139
+ }
140
+ return false;
141
+ }
142
+ /**
143
+ * Finds errors in function calls.
144
+ *
145
+ * When checking arguments, we also manually figure out the argument types
146
+ * by iterating over all the function signatures.
147
+ * Thanks to this, we can find arguments like `(() => void) | (() => any)`
148
+ * and treat them as void too.
149
+ * This is done to also support checking functions like `addEventListener`
150
+ * which have overloads where one callback returns any.
151
+ *
152
+ * Implementation mostly based on no-misused-promises,
153
+ * which does this to find `(() => void) | (() => NotThenable)`
154
+ * and report them too.
155
+ */
156
+ function checkFunctionCallNode(callNode) {
157
+ const callTsNode = parserServices.esTreeNodeToTSNodeMap.get(callNode);
158
+ const funcType = checker.getTypeAtLocation(callTsNode.expression);
159
+ const funcSignatures = tsutils
160
+ .unionConstituents(funcType)
161
+ .flatMap(type => ts.isCallExpression(callTsNode)
162
+ ? type.getCallSignatures()
163
+ : type.getConstructSignatures());
164
+ for (const [argIdx, argNode] of callNode.arguments.entries()) {
165
+ if (argNode.type === utils_1.AST_NODE_TYPES.SpreadElement) {
166
+ continue;
167
+ }
168
+ // Check against the contextual type first
169
+ if (checkExpressionNode(argNode)) {
170
+ continue;
171
+ }
172
+ // Check against the types from all of the call signatures
173
+ const argExpectedReturnTypes = funcSignatures
174
+ .map(s => s.parameters[argIdx])
175
+ .filter(Boolean)
176
+ .map(param => checker.getTypeOfSymbolAtLocation(param, callTsNode.expression))
177
+ .flatMap(paramType => tsutils.unionConstituents(paramType))
178
+ .flatMap(paramType => paramType.getCallSignatures())
179
+ .map(paramSignature => paramSignature.getReturnType());
180
+ if (
181
+ // At least one return type is void
182
+ argExpectedReturnTypes.some(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.Void)) &&
183
+ // The rest are nullish or any
184
+ argExpectedReturnTypes.every(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike |
185
+ ts.TypeFlags.Undefined |
186
+ ts.TypeFlags.Null |
187
+ ts.TypeFlags.Any |
188
+ ts.TypeFlags.Never))) {
189
+ // We treat this argument as void even though it might be technically any.
190
+ reportIfNonVoidFunction(argNode);
191
+ }
192
+ }
193
+ }
194
+ /**
195
+ * Finds errors in an object property.
196
+ *
197
+ * Object properties require different logic
198
+ * when the property is a method shorthand.
199
+ */
200
+ function checkObjectPropertyNode(propNode) {
201
+ const valueNode = propNode.value;
202
+ const propTsNode = parserServices.esTreeNodeToTSNodeMap.get(propNode);
203
+ if (propTsNode.kind === ts.SyntaxKind.MethodDeclaration) {
204
+ // Object property is a method shorthand.
205
+ if (propTsNode.name.kind === ts.SyntaxKind.ComputedPropertyName) {
206
+ // Don't check object methods with computed name.
207
+ return;
208
+ }
209
+ const objTsNode = propTsNode.parent;
210
+ const objType = checker.getContextualType(objTsNode);
211
+ if (objType == null) {
212
+ // Expected object type is unknown.
213
+ return;
214
+ }
215
+ const propSymbol = checker.getPropertyOfType(objType, propTsNode.name.text);
216
+ if (propSymbol == null) {
217
+ // Expected object type is known, but it doesn't have this method.
218
+ return;
219
+ }
220
+ const propExpectedType = checker.getTypeOfSymbolAtLocation(propSymbol, propTsNode);
221
+ if (isVoidReturningFunctionType(propExpectedType)) {
222
+ reportIfNonVoidFunction(valueNode);
223
+ }
224
+ return;
225
+ }
226
+ // Object property is a regular property.
227
+ checkExpressionNode(valueNode);
228
+ }
229
+ /**
230
+ * Finds errors in a class property.
231
+ *
232
+ * In addition to the regular check against the contextual type,
233
+ * we also check against the base class property (when the class extends another class)
234
+ * and the implemented interfaces (when the class implements an interface).
235
+ */
236
+ function checkClassPropertyNode(propNode) {
237
+ if (propNode.value == null) {
238
+ return;
239
+ }
240
+ // Check in comparison to the base types.
241
+ for (const { baseMemberType } of util.getBaseTypesOfClassMember(parserServices, propNode)) {
242
+ if (isVoidReturningFunctionType(baseMemberType)) {
243
+ reportIfNonVoidFunction(propNode.value);
244
+ return; // Report at most one error.
245
+ }
246
+ }
247
+ // Check in comparison to the contextual type.
248
+ checkExpressionNode(propNode.value);
249
+ }
250
+ /**
251
+ * Finds errors in a class method.
252
+ *
253
+ * We check against the base class method (when the class extends another class)
254
+ * and the implemented interfaces (when the class implements an interface).
255
+ */
256
+ function checkClassMethodNode(methodNode) {
257
+ if (methodNode.value.type === utils_1.AST_NODE_TYPES.TSEmptyBodyFunctionExpression) {
258
+ return;
259
+ }
260
+ // Check in comparison to the base types.
261
+ for (const { baseMemberType } of util.getBaseTypesOfClassMember(parserServices, methodNode)) {
262
+ if (isVoidReturningFunctionType(baseMemberType)) {
263
+ reportIfNonVoidFunction(methodNode.value);
264
+ return; // Report at most one error.
265
+ }
266
+ }
267
+ }
268
+ /**
269
+ * Reports an error if the provided node is not allowed in a void function context.
270
+ */
271
+ function reportIfNonVoidFunction(funcNode) {
272
+ const allowedReturnType = ts.TypeFlags.Void |
273
+ ts.TypeFlags.Never |
274
+ ts.TypeFlags.Undefined |
275
+ (options.allowReturnAny ? ts.TypeFlags.Any : 0);
276
+ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(funcNode);
277
+ const actualType = checker.getApparentType(checker.getTypeAtLocation(tsNode));
278
+ if (tsutils
279
+ .getCallSignaturesOfType(actualType)
280
+ .map(signature => signature.getReturnType())
281
+ .flatMap(returnType => tsutils.unionConstituents(returnType))
282
+ .every(type => tsutils.isTypeFlagSet(type, allowedReturnType))) {
283
+ // The function is already void.
284
+ return;
285
+ }
286
+ if (funcNode.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
287
+ funcNode.type !== utils_1.AST_NODE_TYPES.FunctionExpression) {
288
+ // The provided function is not a function literal.
289
+ // Report a generic error.
290
+ return context.report({
291
+ node: funcNode,
292
+ messageId: `nonVoidFunc`,
293
+ });
294
+ }
295
+ // The provided function is a function literal.
296
+ if (funcNode.generator) {
297
+ // The provided function is a generator function.
298
+ // Generator functions are not allowed.
299
+ return context.report({
300
+ loc: util.getFunctionHeadLoc(funcNode, sourceCode),
301
+ messageId: `nonVoidFunc`,
302
+ });
303
+ }
304
+ if (funcNode.async) {
305
+ // The provided function is an async function.
306
+ // Async functions aren't allowed.
307
+ return context.report({
308
+ loc: util.getFunctionHeadLoc(funcNode, sourceCode),
309
+ messageId: `asyncFunc`,
310
+ });
311
+ }
312
+ if (funcNode.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
313
+ // The provided function is an arrow function shorthand without braces.
314
+ return context.report({
315
+ node: funcNode.body,
316
+ messageId: `nonVoidReturn`,
317
+ });
318
+ }
319
+ // The function is a regular or arrow function with a block body.
320
+ // Check return type annotation.
321
+ if (funcNode.returnType != null) {
322
+ // The provided function has an explicit return type annotation.
323
+ const typeAnnotationNode = funcNode.returnType.typeAnnotation;
324
+ if (typeAnnotationNode.type !== utils_1.AST_NODE_TYPES.TSVoidKeyword) {
325
+ // The explicit return type is not `void`.
326
+ return context.report({
327
+ node: typeAnnotationNode,
328
+ messageId: `nonVoidFunc`,
329
+ });
330
+ }
331
+ }
332
+ // Iterate over all function's return statements.
333
+ for (const statement of util.walkStatements(funcNode.body.body)) {
334
+ if (statement.type !== utils_1.AST_NODE_TYPES.ReturnStatement ||
335
+ statement.argument == null) {
336
+ // We only care about return statements with a value.
337
+ continue;
338
+ }
339
+ const returnType = checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(statement.argument));
340
+ if (tsutils.isTypeFlagSet(returnType, allowedReturnType)) {
341
+ // Only visit return statements with invalid type.
342
+ continue;
343
+ }
344
+ // This return statement causes the non-void return type.
345
+ const returnKeyword = util.nullThrows(sourceCode.getFirstToken(statement, {
346
+ filter: token => token.value === 'return',
347
+ }), util.NullThrowsReasons.MissingToken('return keyword', statement.type));
348
+ context.report({
349
+ node: returnKeyword,
350
+ messageId: `nonVoidReturn`,
351
+ });
352
+ }
353
+ // No invalid returns found. The function is allowed.
354
+ }
355
+ },
356
+ });
@@ -0,0 +1,11 @@
1
+ import type { TSESTree, ParserServicesWithTypeInformation } from '@typescript-eslint/utils';
2
+ import type * as ts from 'typescript';
3
+ /**
4
+ * Given a member of a class which extends another class or implements an interface,
5
+ * yields the corresponding member type for each of the base class/interfaces.
6
+ */
7
+ export declare function getBaseTypesOfClassMember(services: ParserServicesWithTypeInformation, memberNode: TSESTree.MethodDefinition | TSESTree.PropertyDefinition): Generator<{
8
+ baseType: ts.Type;
9
+ baseMemberType: ts.Type;
10
+ heritageToken: ts.SyntaxKind.ExtendsKeyword | ts.SyntaxKind.ImplementsKeyword;
11
+ }>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getBaseTypesOfClassMember = getBaseTypesOfClassMember;
4
+ /**
5
+ * Given a member of a class which extends another class or implements an interface,
6
+ * yields the corresponding member type for each of the base class/interfaces.
7
+ */
8
+ function* getBaseTypesOfClassMember(services, memberNode) {
9
+ const memberTsNode = services.esTreeNodeToTSNodeMap.get(memberNode);
10
+ if (memberTsNode.name == null) {
11
+ return;
12
+ }
13
+ const checker = services.program.getTypeChecker();
14
+ const memberSymbol = checker.getSymbolAtLocation(memberTsNode.name);
15
+ if (memberSymbol == null) {
16
+ return;
17
+ }
18
+ const classNode = memberTsNode.parent;
19
+ for (const clauseNode of classNode.heritageClauses ?? []) {
20
+ for (const baseTypeNode of clauseNode.types) {
21
+ const baseType = checker.getTypeAtLocation(baseTypeNode);
22
+ const baseMemberSymbol = checker.getPropertyOfType(baseType, memberSymbol.name);
23
+ if (baseMemberSymbol == null) {
24
+ continue;
25
+ }
26
+ const baseMemberType = checker.getTypeOfSymbolAtLocation(baseMemberSymbol, memberTsNode);
27
+ const heritageToken = clauseNode.token;
28
+ yield { baseMemberType, baseType, heritageToken };
29
+ }
30
+ }
31
+ }
@@ -3,6 +3,7 @@ export * from './astUtils';
3
3
  export * from './baseTypeUtils';
4
4
  export * from './collectUnusedVariables';
5
5
  export * from './createRule';
6
+ export * from './getBaseTypesOfClassMember';
6
7
  export * from './getFixOrSuggest';
7
8
  export * from './getFunctionHeadLoc';
8
9
  export * from './getOperatorPrecedence';
@@ -29,6 +30,7 @@ export * from './getValueOfLiteralType';
29
30
  export * from './isHigherPrecedenceThanAwait';
30
31
  export * from './skipChainExpression';
31
32
  export * from './truthinessUtils';
33
+ export * from './walkStatements';
32
34
  export * from '@typescript-eslint/type-utils';
33
35
  export declare const applyDefault: typeof ESLintUtils.applyDefault, deepMerge: typeof ESLintUtils.deepMerge, getParserServices: typeof ESLintUtils.getParserServices, isObjectNotArray: typeof ESLintUtils.isObjectNotArray, nullThrows: typeof ESLintUtils.nullThrows, NullThrowsReasons: {
34
36
  readonly MissingParent: "Expected node to have a parent.";
@@ -20,6 +20,7 @@ __exportStar(require("./astUtils"), exports);
20
20
  __exportStar(require("./baseTypeUtils"), exports);
21
21
  __exportStar(require("./collectUnusedVariables"), exports);
22
22
  __exportStar(require("./createRule"), exports);
23
+ __exportStar(require("./getBaseTypesOfClassMember"), exports);
23
24
  __exportStar(require("./getFixOrSuggest"), exports);
24
25
  __exportStar(require("./getFunctionHeadLoc"), exports);
25
26
  __exportStar(require("./getOperatorPrecedence"), exports);
@@ -46,6 +47,7 @@ __exportStar(require("./getValueOfLiteralType"), exports);
46
47
  __exportStar(require("./isHigherPrecedenceThanAwait"), exports);
47
48
  __exportStar(require("./skipChainExpression"), exports);
48
49
  __exportStar(require("./truthinessUtils"), exports);
50
+ __exportStar(require("./walkStatements"), exports);
49
51
  // this is done for convenience - saves migrating all of the old rules
50
52
  __exportStar(require("@typescript-eslint/type-utils"), exports);
51
53
  exports.applyDefault = utils_1.ESLintUtils.applyDefault, exports.deepMerge = utils_1.ESLintUtils.deepMerge, exports.getParserServices = utils_1.ESLintUtils.getParserServices, exports.isObjectNotArray = utils_1.ESLintUtils.isObjectNotArray, exports.nullThrows = utils_1.ESLintUtils.nullThrows, exports.NullThrowsReasons = utils_1.ESLintUtils.NullThrowsReasons;
@@ -0,0 +1,7 @@
1
+ import type { TSESTree } from '@typescript-eslint/utils';
2
+ /**
3
+ * Yields all statement nodes in a block, including nested blocks.
4
+ *
5
+ * You can use it to find all return statements in a function body.
6
+ */
7
+ export declare function walkStatements(body: readonly TSESTree.Statement[]): Generator<TSESTree.Statement>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.walkStatements = walkStatements;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ /**
6
+ * Yields all statement nodes in a block, including nested blocks.
7
+ *
8
+ * You can use it to find all return statements in a function body.
9
+ */
10
+ function* walkStatements(body) {
11
+ for (const statement of body) {
12
+ switch (statement.type) {
13
+ case utils_1.AST_NODE_TYPES.BlockStatement: {
14
+ yield* walkStatements(statement.body);
15
+ continue;
16
+ }
17
+ case utils_1.AST_NODE_TYPES.SwitchStatement: {
18
+ for (const switchCase of statement.cases) {
19
+ yield* walkStatements(switchCase.consequent);
20
+ }
21
+ continue;
22
+ }
23
+ case utils_1.AST_NODE_TYPES.IfStatement: {
24
+ yield* walkStatements([statement.consequent]);
25
+ if (statement.alternate) {
26
+ yield* walkStatements([statement.alternate]);
27
+ }
28
+ continue;
29
+ }
30
+ case utils_1.AST_NODE_TYPES.WhileStatement:
31
+ case utils_1.AST_NODE_TYPES.DoWhileStatement:
32
+ case utils_1.AST_NODE_TYPES.ForStatement:
33
+ case utils_1.AST_NODE_TYPES.ForInStatement:
34
+ case utils_1.AST_NODE_TYPES.ForOfStatement:
35
+ case utils_1.AST_NODE_TYPES.WithStatement:
36
+ case utils_1.AST_NODE_TYPES.LabeledStatement: {
37
+ yield* walkStatements([statement.body]);
38
+ continue;
39
+ }
40
+ case utils_1.AST_NODE_TYPES.TryStatement: {
41
+ yield* walkStatements([statement.block]);
42
+ if (statement.handler) {
43
+ yield* walkStatements([statement.handler.body]);
44
+ }
45
+ if (statement.finalizer) {
46
+ yield* walkStatements([statement.finalizer]);
47
+ }
48
+ continue;
49
+ }
50
+ default: {
51
+ yield statement;
52
+ continue;
53
+ }
54
+ }
55
+ }
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typescript-eslint/eslint-plugin",
3
- "version": "8.52.1-alpha.9",
3
+ "version": "8.53.1",
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.9",
63
- "@typescript-eslint/type-utils": "8.52.1-alpha.9",
64
- "@typescript-eslint/utils": "8.52.1-alpha.9",
65
- "@typescript-eslint/visitor-keys": "8.52.1-alpha.9",
62
+ "@typescript-eslint/scope-manager": "8.53.1",
63
+ "@typescript-eslint/type-utils": "8.53.1",
64
+ "@typescript-eslint/utils": "8.53.1",
65
+ "@typescript-eslint/visitor-keys": "8.53.1",
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.9",
74
- "@typescript-eslint/rule-tester": "8.52.1-alpha.9",
73
+ "@typescript-eslint/rule-schema-to-typescript-types": "8.53.1",
74
+ "@typescript-eslint/rule-tester": "8.53.1",
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.9",
93
+ "@typescript-eslint/parser": "^8.53.1",
94
94
  "eslint": "^8.57.0 || ^9.0.0",
95
95
  "typescript": ">=4.8.4 <6.0.0"
96
96
  },