Skip to content

Commit

Permalink
fix(eslint-plugin): [explicit-function-return-type] improved checking…
Browse files Browse the repository at this point in the history
… for allowHigherOrderFunctions option
  • Loading branch information
yeonjuan committed Feb 19, 2024
1 parent 18c3216 commit 0d5d17c
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 85 deletions.
97 changes: 64 additions & 33 deletions packages/eslint-plugin/src/rules/explicit-function-return-type.ts
Expand Up @@ -2,6 +2,7 @@ import type { TSESTree } from '@typescript-eslint/utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';

import { createRule } from '../util';
import type { FunctionInfo } from '../util/explicitReturnTypeUtils';
import {
ancestorHasReturnType,
checkFunctionReturnType,
Expand All @@ -22,6 +23,11 @@ type Options = [
];
type MessageIds = 'missingReturnType';

type FunctionNode =
| TSESTree.ArrowFunctionExpression
| TSESTree.FunctionDeclaration
| TSESTree.FunctionExpression;

export default createRule<Options, MessageIds>({
name: 'explicit-function-return-type',
meta: {
Expand Down Expand Up @@ -98,6 +104,19 @@ export default createRule<Options, MessageIds>({
},
],
create(context, [options]) {
const functionInfoStack: FunctionInfo<FunctionNode>[] = [];

function enterFunction(node: FunctionNode): void {
functionInfoStack.push({
node,
returns: [],
});
}

function popFunctionInfo(): FunctionInfo<FunctionNode> {
return functionInfoStack.pop()!;
}

function isAllowedFunction(
node:
| TSESTree.ArrowFunctionExpression
Expand Down Expand Up @@ -168,56 +187,68 @@ export default createRule<Options, MessageIds>({
return node.parent.type === AST_NODE_TYPES.CallExpression;
}

return {
'ArrowFunctionExpression, FunctionExpression'(
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
): void {
if (
options.allowConciseArrowFunctionExpressionsStartingWithVoid &&
node.type === AST_NODE_TYPES.ArrowFunctionExpression &&
node.expression &&
node.body.type === AST_NODE_TYPES.UnaryExpression &&
node.body.operator === 'void'
) {
return;
}
function exitFunctionExpression(
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
): void {
const info = popFunctionInfo();

if (isAllowedFunction(node)) {
return;
}
if (
options.allowConciseArrowFunctionExpressionsStartingWithVoid &&
node.type === AST_NODE_TYPES.ArrowFunctionExpression &&
node.expression &&
node.body.type === AST_NODE_TYPES.UnaryExpression &&
node.body.operator === 'void'
) {
return;
}

if (
options.allowTypedFunctionExpressions &&
(isValidFunctionExpressionReturnType(node, options) ||
ancestorHasReturnType(node))
) {
return;
}
if (isAllowedFunction(node)) {
return;
}

checkFunctionReturnType(node, options, context.sourceCode, loc =>
context.report({
node,
loc,
messageId: 'missingReturnType',
}),
);
},
FunctionDeclaration(node): void {
if (
options.allowTypedFunctionExpressions &&
(isValidFunctionExpressionReturnType(node, options) ||
ancestorHasReturnType(node))
) {
return;
}

checkFunctionReturnType(info, options, context.sourceCode, loc =>
context.report({
node,
loc,
messageId: 'missingReturnType',
}),
);
}

return {
'ArrowFunctionExpression, FunctionExpression': enterFunction,
'ArrowFunctionExpression:exit': exitFunctionExpression,
'FunctionExpression:exit': exitFunctionExpression,
FunctionDeclaration: enterFunction,
'FunctionDeclaration:exit'(node): void {
const info = popFunctionInfo();
if (isAllowedFunction(node)) {
return;
}
if (options.allowTypedFunctionExpressions && node.returnType) {
return;
}

checkFunctionReturnType(node, options, context.sourceCode, loc =>
checkFunctionReturnType(info, options, context.sourceCode, loc =>
context.report({
node,
loc,
messageId: 'missingReturnType',
}),
);
},
ReturnStatement(node): void {
const current = functionInfoStack[functionInfoStack.length - 1];
current.returns.push(node);
},
};
},
});
96 changes: 70 additions & 26 deletions packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts
Expand Up @@ -5,6 +5,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { createRule, isFunction } from '../util';
import type {
FunctionExpression,
FunctionInfo,
FunctionNode,
} from '../util/explicitReturnTypeUtils';
import {
Expand Down Expand Up @@ -101,8 +102,11 @@ export default createRule<Options, MessageIds>({
// tracks all of the functions we've already checked
const checkedFunctions = new Set<FunctionNode>();

// tracks functions that were found whilst traversing
const foundFunctions: FunctionNode[] = [];
const functionStack: FunctionNode[] = [];
const functionReturnsMap = new Map<
FunctionNode,
TSESTree.ReturnStatement[]
>();

// all nodes visited, avoids infinite recursion for cyclic references
// (such as class member referring to itself)
Expand All @@ -119,11 +123,17 @@ export default createRule<Options, MessageIds>({
any of them are part of a higher-order function
*/

function getReturnsInFunction(
node: FunctionNode,
): TSESTree.ReturnStatement[] {
return functionReturnsMap.get(node) ?? [];
}

return {
ExportDefaultDeclaration(node): void {
'ExportDefaultDeclaration:exit'(node): void {
checkNode(node.declaration);
},
'ExportNamedDeclaration:not([source])'(
'ExportNamedDeclaration:not([source]):exit'(
node: TSESTree.ExportNamedDeclaration,
): void {
if (node.declaration) {
Expand All @@ -134,21 +144,35 @@ export default createRule<Options, MessageIds>({
}
}
},
TSExportAssignment(node): void {
'TSExportAssignment:exit'(node): void {
checkNode(node.expression);
},
'ArrowFunctionExpression, FunctionDeclaration, FunctionExpression'(
node: FunctionNode,
): void {
foundFunctions.push(node);
functionStack.push(node);
functionReturnsMap.set(node, []);
},
'ArrowFunctionExpression:exit'(): void {
functionStack.pop();
},
'FunctionDeclaration:exit'(): void {
functionStack.pop();
},
'FunctionExpression:exit'(): void {
functionStack.pop();
},
'Program:exit'(): void {
for (const func of foundFunctions) {
if (isExportedHigherOrderFunction(func)) {
checkNode(func);
for (const [node, returns] of functionReturnsMap) {
if (isExportedHigherOrderFunction({ node, returns })) {
checkNode(node);
}
}
},
ReturnStatement(node): void {
const current = functionStack[functionStack.length - 1];
functionReturnsMap.get(current)?.push(node);
},
};

function checkParameters(
Expand Down Expand Up @@ -265,7 +289,9 @@ export default createRule<Options, MessageIds>({
return false;
}

function isExportedHigherOrderFunction(node: FunctionNode): boolean {
function isExportedHigherOrderFunction({
node,
}: FunctionInfo<FunctionNode>): boolean {
let current: TSESTree.Node | undefined = node.parent;
while (current) {
if (current.type === AST_NODE_TYPES.ReturnStatement) {
Expand All @@ -274,9 +300,12 @@ export default createRule<Options, MessageIds>({
continue;
}

if (!isFunction(current)) {
return false;
}
const returns = getReturnsInFunction(current);
if (
!isFunction(current) ||
!doesImmediatelyReturnFunctionExpression(current)
!doesImmediatelyReturnFunctionExpression({ node: current, returns })
) {
return false;
}
Expand Down Expand Up @@ -335,8 +364,10 @@ export default createRule<Options, MessageIds>({

switch (node.type) {
case AST_NODE_TYPES.ArrowFunctionExpression:
case AST_NODE_TYPES.FunctionExpression:
return checkFunctionExpression(node);
case AST_NODE_TYPES.FunctionExpression: {
const returns = getReturnsInFunction(node);
return checkFunctionExpression({ node, returns });
}

case AST_NODE_TYPES.ArrayExpression:
for (const element of node.elements) {
Expand All @@ -360,8 +391,10 @@ export default createRule<Options, MessageIds>({
}
return;

case AST_NODE_TYPES.FunctionDeclaration:
return checkFunction(node);
case AST_NODE_TYPES.FunctionDeclaration: {
const returns = getReturnsInFunction(node);
return checkFunction({ node, returns });
}

case AST_NODE_TYPES.MethodDefinition:
case AST_NODE_TYPES.TSAbstractMethodDefinition:
Expand Down Expand Up @@ -419,7 +452,10 @@ export default createRule<Options, MessageIds>({
checkParameters(node);
}

function checkFunctionExpression(node: FunctionExpression): void {
function checkFunctionExpression({
node,
returns,
}: FunctionInfo<FunctionExpression>): void {
if (checkedFunctions.has(node)) {
return;
}
Expand All @@ -434,7 +470,7 @@ export default createRule<Options, MessageIds>({
}

checkFunctionExpressionReturnType(
node,
{ node, returns },
options,
context.sourceCode,
loc => {
Expand All @@ -449,7 +485,10 @@ export default createRule<Options, MessageIds>({
checkParameters(node);
}

function checkFunction(node: TSESTree.FunctionDeclaration): void {
function checkFunction({
node,
returns,
}: FunctionInfo<TSESTree.FunctionDeclaration>): void {
if (checkedFunctions.has(node)) {
return;
}
Expand All @@ -459,13 +498,18 @@ export default createRule<Options, MessageIds>({
return;
}

checkFunctionReturnType(node, options, context.sourceCode, loc => {
context.report({
node,
loc,
messageId: 'missingReturnType',
});
});
checkFunctionReturnType(
{ node, returns },
options,
context.sourceCode,
loc => {
context.report({
node,
loc,
messageId: 'missingReturnType',
});
},
);

checkParameters(node);
}
Expand Down

0 comments on commit 0d5d17c

Please sign in to comment.