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 fe4dbf28662..49fdfc1d5a0 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -4,7 +4,6 @@ import { } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; import { - ancestorHasReturnType, checkFunctionExpressionReturnType, checkFunctionReturnType, doesImmediatelyReturnFunctionExpression, @@ -377,6 +376,51 @@ export default util.createRule({ } } + /** + * Check whether any ancestor of the provided function has a valid return type. + * This function assumes that the function either: + * - belongs to an exported function chain validated by isExportedHigherOrderFunction + * - is directly exported itself + */ + function ancestorHasReturnType(node: FunctionNode): boolean { + let ancestor = node.parent; + + // if the ancestor is not a return, then this function was not returned at all, so we can exit early + const isReturnStatement = + ancestor?.type === AST_NODE_TYPES.ReturnStatement; + const isBodylessArrow = + ancestor?.type === AST_NODE_TYPES.ArrowFunctionExpression && + ancestor.body.type !== AST_NODE_TYPES.BlockStatement; + if (!isReturnStatement && !isBodylessArrow) { + return false; + } + + while (ancestor) { + switch (ancestor.type) { + case AST_NODE_TYPES.ArrowFunctionExpression: + case AST_NODE_TYPES.FunctionExpression: + case AST_NODE_TYPES.FunctionDeclaration: + if (ancestor.returnType) { + return true; + } + // assume + break; + + // const x: Foo = () => {}; + // Assume that a typed variable types the function expression + case AST_NODE_TYPES.VariableDeclarator: + if (ancestor.id.typeAnnotation) { + return true; + } + break; + } + + ancestor = ancestor.parent; + } + + return false; + } + function checkEmptyBodyFunctionExpression( node: TSESTree.TSEmptyBodyFunctionExpression, ): void { @@ -402,7 +446,7 @@ export default util.createRule({ if ( isAllowedName(node.parent) || isTypedFunctionExpression(node, options) || - ancestorHasReturnType(node.parent) + ancestorHasReturnType(node) ) { return; } @@ -424,7 +468,7 @@ export default util.createRule({ } checkedFunctions.add(node); - if (isAllowedName(node.parent) || ancestorHasReturnType(node.parent)) { + if (isAllowedName(node.parent) || ancestorHasReturnType(node)) { return; } diff --git a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts index 6c9c76645ac..466e7d207eb 100644 --- a/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts +++ b/packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts @@ -347,37 +347,7 @@ function checkFunctionExpressionReturnType( checkFunctionReturnType(node, options, sourceCode, report); } -/** - * Check whether any ancestor of the provided node has a valid return type, with - * the given options. - */ -function ancestorHasReturnType(ancestor: TSESTree.Node | undefined): boolean { - // if the ancestor is not a return, then this function was not returned at all, so we can exit early - const isReturnStatne = ancestor?.type === AST_NODE_TYPES.ReturnStatement; - const isBodylessArrow = - ancestor?.type === AST_NODE_TYPES.ArrowFunctionExpression && - ancestor.body.type !== AST_NODE_TYPES.BlockStatement; - if (!isReturnStatne && !isBodylessArrow) { - return false; - } - - while (ancestor) { - switch (ancestor.type) { - case AST_NODE_TYPES.ArrowFunctionExpression: - case AST_NODE_TYPES.FunctionExpression: - case AST_NODE_TYPES.FunctionDeclaration: - return ancestor.returnType != null; - } - - ancestor = ancestor.parent; - } - - /* istanbul ignore next */ - return false; -} - export { - ancestorHasReturnType, checkFunctionExpressionReturnType, checkFunctionReturnType, doesImmediatelyReturnFunctionExpression, 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 44aa3c3d066..114b26aed0f 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 @@ -624,6 +624,28 @@ export function foo(arg = 1): void {} export const foo = (): ((n: number) => string) => n => String(n); `, }, + // https://github.com/typescript-eslint/typescript-eslint/issues/2173 + ` +export function foo(): (n: number) => (m: number) => string { + return function (n) { + return function (m) { + return String(n + m); + }; + }; +} + `, + ` +export const foo = (): ((n: number) => (m: number) => string) => n => m => + String(n + m); + `, + ` +export const bar: () => (n: number) => string = () => n => String(n); + `, + ` +type Buz = () => (n: number) => string; + +export const buz: Buz = () => n => String(n); + `, ], invalid: [ {