diff --git a/packages/eslint-plugin/docs/rules/no-invalid-void-type.md b/packages/eslint-plugin/docs/rules/no-invalid-void-type.md index f1607478a0d..567d9c0bb33 100644 --- a/packages/eslint-plugin/docs/rules/no-invalid-void-type.md +++ b/packages/eslint-plugin/docs/rules/no-invalid-void-type.md @@ -54,10 +54,12 @@ type stillVoid = void | never; ```ts interface Options { allowInGenericTypeArguments?: boolean | string[]; + allowAsThisParameter?: boolean; } const defaultOptions: Options = { allowInGenericTypeArguments: true, + allowAsThisParameter: false, }; ``` @@ -97,6 +99,23 @@ type AllowedVoid = Ex.Mx.Tx; type AllowedVoidUnion = void | Ex.Mx.Tx; ``` +#### `allowAsThisParameter` + +This option allows specifying a `this` parameter of a function to be `void` when set to `true`. +This pattern can be useful to explicitly label function types that do not use a `this` argument. [See the TypeScript docs for more information](https://www.typescriptlang.org/docs/handbook/functions.html#this-parameters-in-callbacks). + +This option is `false` by default. + +The following patterns are considered warnings with `{ allowAsThisParameter: false }` but valid with `{ allowAsThisParameter: true }`: + +```ts +function doThing(this: void) {} +class Example { + static helper(this: void) {} + callback(this: void) {} +} +``` + ## When Not To Use It If you don't care about if `void` is used with other types, diff --git a/packages/eslint-plugin/src/rules/no-invalid-void-type.ts b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts index b78107bac65..0dff489e585 100644 --- a/packages/eslint-plugin/src/rules/no-invalid-void-type.ts +++ b/packages/eslint-plugin/src/rules/no-invalid-void-type.ts @@ -5,13 +5,16 @@ import { import * as util from '../util'; interface Options { - allowInGenericTypeArguments: boolean | string[]; + allowInGenericTypeArguments?: boolean | string[]; + allowAsThisParameter?: boolean; } type MessageIds = | 'invalidVoidForGeneric' | 'invalidVoidNotReturnOrGeneric' - | 'invalidVoidNotReturn'; + | 'invalidVoidNotReturn' + | 'invalidVoidNotReturnOrThisParam' + | 'invalidVoidNotReturnOrThisParamOrGeneric'; export default util.createRule<[Options], MessageIds>({ name: 'no-invalid-void-type', @@ -29,6 +32,10 @@ export default util.createRule<[Options], MessageIds>({ invalidVoidNotReturnOrGeneric: 'void is only valid as a return type or generic type variable', invalidVoidNotReturn: 'void is only valid as a return type', + invalidVoidNotReturnOrThisParam: + 'void is only valid as return type or type of `this` parameter', + invalidVoidNotReturnOrThisParamOrGeneric: + 'void is only valid as a return type or generic type variable or the type of a `this` parameter', }, schema: [ { @@ -44,13 +51,18 @@ export default util.createRule<[Options], MessageIds>({ }, ], }, + allowAsThisParameter: { + type: 'boolean', + }, }, additionalProperties: false, }, ], }, - defaultOptions: [{ allowInGenericTypeArguments: true }], - create(context, [{ allowInGenericTypeArguments }]) { + defaultOptions: [ + { allowInGenericTypeArguments: true, allowAsThisParameter: false }, + ], + create(context, [{ allowInGenericTypeArguments, allowAsThisParameter }]) { const validParents: AST_NODE_TYPES[] = [ AST_NODE_TYPES.TSTypeAnnotation, // ]; @@ -110,7 +122,9 @@ export default util.createRule<[Options], MessageIds>({ if (!allowInGenericTypeArguments) { context.report({ - messageId: 'invalidVoidNotReturn', + messageId: allowAsThisParameter + ? 'invalidVoidNotReturnOrThisParam' + : 'invalidVoidNotReturn', node, }); } @@ -159,6 +173,16 @@ export default util.createRule<[Options], MessageIds>({ return; } + // this parameter is ok to be void. + if ( + allowAsThisParameter && + node.parent.type === AST_NODE_TYPES.TSTypeAnnotation && + node.parent.parent.type === AST_NODE_TYPES.Identifier && + node.parent.parent.name === 'this' + ) { + return; + } + // default cases if ( validParents.includes(node.parent.type) && @@ -168,9 +192,14 @@ export default util.createRule<[Options], MessageIds>({ } context.report({ - messageId: allowInGenericTypeArguments - ? 'invalidVoidNotReturnOrGeneric' - : 'invalidVoidNotReturn', + messageId: + allowInGenericTypeArguments && allowAsThisParameter + ? 'invalidVoidNotReturnOrThisParamOrGeneric' + : allowInGenericTypeArguments + ? 'invalidVoidNotReturnOrGeneric' + : allowAsThisParameter + ? 'invalidVoidNotReturnOrThisParam' + : 'invalidVoidNotReturn', node, }); }, diff --git a/packages/eslint-plugin/tests/rules/no-invalid-void-type.test.ts b/packages/eslint-plugin/tests/rules/no-invalid-void-type.test.ts index 31771c561c0..56df4f47ab6 100644 --- a/packages/eslint-plugin/tests/rules/no-invalid-void-type.test.ts +++ b/packages/eslint-plugin/tests/rules/no-invalid-void-type.test.ts @@ -538,3 +538,56 @@ async function foo(bar: () => void | Promise) { }, ], }); + +ruleTester.run('allowAsThisParameter: true', rule, { + valid: [ + { + code: 'function f(this: void) {}', + options: [{ allowAsThisParameter: true }], + }, + { + code: ` +class Test { + public static helper(this: void) {} + method(this: void) {} +} + `, + options: [{ allowAsThisParameter: true }], + }, + ], + invalid: [ + { + code: 'type alias = void;', + options: [ + { allowAsThisParameter: true, allowInGenericTypeArguments: true }, + ], + errors: [ + { + messageId: 'invalidVoidNotReturnOrThisParamOrGeneric', + }, + ], + }, + { + code: 'type alias = void;', + options: [ + { allowAsThisParameter: true, allowInGenericTypeArguments: false }, + ], + errors: [ + { + messageId: 'invalidVoidNotReturnOrThisParam', + }, + ], + }, + { + code: 'type alias = Array;', + options: [ + { allowAsThisParameter: true, allowInGenericTypeArguments: false }, + ], + errors: [ + { + messageId: 'invalidVoidNotReturnOrThisParam', + }, + ], + }, + ], +});