diff --git a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.mdx b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.mdx index 339150327d99..c4a21e23547c 100644 --- a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.mdx +++ b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.mdx @@ -260,6 +260,20 @@ export const foo: FooType = bar => {}; +### `allowOverloadFunctions` + +{/* insert option description */} + +Examples of correct code when `allowOverloadFunctions` is set to `true`: + +```ts option='{ "allowOverloadFunctions": true }' showPlaygroundButton +export function test(a: string): string; +export function test(a: number): number; +export function test(a: unknown) { + return a; +} +``` + ## When Not To Use It If your project is not used by downstream consumers that are sensitive to API types, you can disable this rule. diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index c8a627e7aca5..8ec466524988 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -9,7 +9,12 @@ import type { FunctionNode, } from '../util/explicitReturnTypeUtils'; -import { createRule, isFunction, isStaticMemberAccessOfValue } from '../util'; +import { + createRule, + hasOverloadSignatures, + isFunction, + isStaticMemberAccessOfValue, +} from '../util'; import { ancestorHasReturnType, checkFunctionExpressionReturnType, @@ -25,6 +30,7 @@ export type Options = [ allowedNames?: string[]; allowHigherOrderFunctions?: boolean; allowTypedFunctionExpressions?: boolean; + allowOverloadFunctions?: boolean; }, ]; export type MessageIds = @@ -82,6 +88,11 @@ export default createRule({ 'You must still type the parameters of the function.', ].join('\n'), }, + allowOverloadFunctions: { + type: 'boolean', + description: + 'Whether to ignore return type annotations on functions with overload signatures.', + }, allowTypedFunctionExpressions: { type: 'boolean', description: @@ -97,6 +108,7 @@ export default createRule({ allowDirectConstAssertionInArrowFunctions: true, allowedNames: [], allowHigherOrderFunctions: true, + allowOverloadFunctions: false, allowTypedFunctionExpressions: true, }, ], @@ -456,6 +468,14 @@ export default createRule({ return; } + if ( + options.allowOverloadFunctions && + node.parent.type === AST_NODE_TYPES.MethodDefinition && + hasOverloadSignatures(node.parent, context) + ) { + return; + } + checkFunctionExpressionReturnType( { node, returns }, options, @@ -485,6 +505,13 @@ export default createRule({ return; } + if ( + options.allowOverloadFunctions && + hasOverloadSignatures(node, context) + ) { + return; + } + checkFunctionReturnType( { node, returns }, options, diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/explicit-module-boundary-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/explicit-module-boundary-types.shot index 8fcda4fd876a..7aba83e40296 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/explicit-module-boundary-types.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/explicit-module-boundary-types.shot @@ -192,3 +192,14 @@ type FooType = (bar: string) => void; export const foo: FooType = bar => {}; " `; + +exports[`Validating rule docs explicit-module-boundary-types.mdx code examples ESLint output 11`] = ` +"Options: { "allowOverloadFunctions": true } + +export function test(a: string): string; +export function test(a: number): number; +export function test(a: unknown) { + return a; +} +" +`; diff --git a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts index 1bd477f206ce..1897957aa5e3 100644 --- a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts @@ -821,6 +821,64 @@ export const a: Foo = { f: (x: boolean) => x, }; `, + { + code: ` +export function test(a: string): string; +export function test(a: number): number; +export function test(a: unknown) { + return a; +} + `, + options: [ + { + allowOverloadFunctions: true, + }, + ], + }, + { + code: ` +export default function test(a: string): string; +export default function test(a: number): number; +export default function test(a: unknown) { + return a; +} + `, + options: [ + { + allowOverloadFunctions: true, + }, + ], + }, + { + code: ` +export default function (a: string): string; +export default function (a: number): number; +export default function (a: unknown) { + return a; +} + `, + options: [ + { + allowOverloadFunctions: true, + }, + ], + }, + { + code: ` +export class Test { + test(a: string): string; + test(a: number): number; + test(a: unknown) { + return a; + } +} + `, + options: [ + { + allowOverloadFunctions: true, + }, + ], + }, ], invalid: [ { @@ -2077,5 +2135,75 @@ export const foo = { }, ], }, + { + code: ` +export function test(a: string): string; +export function test(a: number): number; +export function test(a: unknown) { + return a; +} + `, + errors: [ + { + column: 8, + endColumn: 21, + line: 4, + messageId: 'missingReturnType', + }, + ], + }, + { + code: ` +export default function test(a: string): string; +export default function test(a: number): number; +export default function test(a: unknown) { + return a; +} + `, + errors: [ + { + column: 16, + endColumn: 29, + line: 4, + messageId: 'missingReturnType', + }, + ], + }, + { + code: ` +export default function (a: string): string; +export default function (a: number): number; +export default function (a: unknown) { + return a; +} + `, + errors: [ + { + column: 16, + endColumn: 25, + line: 4, + messageId: 'missingReturnType', + }, + ], + }, + { + code: ` +export class Test { + test(a: string): string; + test(a: number): number; + test(a: unknown) { + return a; + } +} + `, + errors: [ + { + column: 3, + endColumn: 7, + line: 5, + messageId: 'missingReturnType', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/explicit-module-boundary-types.shot b/packages/eslint-plugin/tests/schema-snapshots/explicit-module-boundary-types.shot index aa3494b2c238..ca7169354bdf 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/explicit-module-boundary-types.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/explicit-module-boundary-types.shot @@ -27,6 +27,10 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "description": "Whether to ignore return type annotations on functions immediately returning another function expression.\\nYou must still type the parameters of the function.", "type": "boolean" }, + "allowOverloadFunctions": { + "description": "Whether to ignore return type annotations on functions with overload signatures.", + "type": "boolean" + }, "allowTypedFunctionExpressions": { "description": "Whether to ignore type annotations on the variable of a function expression.", "type": "boolean" @@ -53,6 +57,8 @@ type Options = [ * You must still type the parameters of the function. */ allowHigherOrderFunctions?: boolean; + /** Whether to ignore return type annotations on functions with overload signatures. */ + allowOverloadFunctions?: boolean; /** Whether to ignore type annotations on the variable of a function expression. */ allowTypedFunctionExpressions?: boolean; /** An array of function/method names that will not have their arguments or return values checked. */