Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(eslint-plugin): [explicit-module-boundary-types] handle nested fu…
…nctions and functions expressions in a typed variable declaration (#2176)
  • Loading branch information
bradzacher committed Jun 4, 2020
1 parent 58db655 commit 6ff450d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 33 deletions.
50 changes: 47 additions & 3 deletions packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts
Expand Up @@ -4,7 +4,6 @@ import {
} from '@typescript-eslint/experimental-utils';
import * as util from '../util';
import {
ancestorHasReturnType,
checkFunctionExpressionReturnType,
checkFunctionReturnType,
doesImmediatelyReturnFunctionExpression,
Expand Down Expand Up @@ -377,6 +376,51 @@ export default util.createRule<Options, MessageIds>({
}
}

/**
* 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 {
Expand All @@ -402,7 +446,7 @@ export default util.createRule<Options, MessageIds>({
if (
isAllowedName(node.parent) ||
isTypedFunctionExpression(node, options) ||
ancestorHasReturnType(node.parent)
ancestorHasReturnType(node)
) {
return;
}
Expand All @@ -424,7 +468,7 @@ export default util.createRule<Options, MessageIds>({
}
checkedFunctions.add(node);

if (isAllowedName(node.parent) || ancestorHasReturnType(node.parent)) {
if (isAllowedName(node.parent) || ancestorHasReturnType(node)) {
return;
}

Expand Down
30 changes: 0 additions & 30 deletions packages/eslint-plugin/src/util/explicitReturnTypeUtils.ts
Expand Up @@ -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,
Expand Down
Expand Up @@ -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: [
{
Expand Down

0 comments on commit 6ff450d

Please sign in to comment.