8000 fix(eslint-plugin): [explicit-function-return-type, explicit-module-b… · naruaway/typescript-eslint@612875b · GitHub 8000
[go: up one dir, main page]

Skip to content

Commit 612875b

Browse files
fix(eslint-plugin): [explicit-function-return-type, explicit-module-boundary-types] improved checking for allowHigherOrderFunctions option (typescript-eslint#8508)
* fix(eslint-plugin): [explicit-function-return-type] improved checking for allowHigherOrderFunctions option * refactor * fix lint errors * apply reviews * fix lint error * Update packages/eslint-plugin/src/rules/explicit-function-return-type.ts --------- Co-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com>
1 parent 14649d1 commit 612875b

File tree

5 files changed

+288
-90
lines changed

5 files changed

+288
-90
lines changed

packages/eslint-plugin/src/rules/explicit-function-return-type.ts

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { TSESTree } from '@typescript-eslint/utils';
22
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
33

4-
import { createRule } from '../util';
4+
import { createRule, nullThrows } from '../util';
5+
import type { FunctionInfo } from '../util/explicitReturnTypeUtils';
56
import {
67
ancestorHasReturnType,
78
checkFunctionReturnType,
@@ -22,6 +23,11 @@ type Options = [
2223
];
2324
type MessageIds = 'missingReturnType';
2425

26+
type FunctionNode =
27+
| TSESTree.ArrowFunctionExpression
28+
| TSESTree.FunctionDeclaration
29+
| TSESTree.FunctionExpression;
30+
2531
export default createRule<Options, MessageIds>({
2632
name: 'explicit-function-return-type',
2733
meta: {
@@ -98,6 +104,22 @@ export default createRule<Options, MessageIds>({
98104
},
99105
],
100106
create(context, [options]) {
107+
const functionInfoStack: FunctionInfo<FunctionNode>[] = [];
108+
109+
function enterFunction(node: FunctionNode): void {
110+
functionInfoStack.push({
111+
node,
112+
returns: [],
113+
});
114+
}
115+
116+
function popFunctionInfo(exitNodeType: string): FunctionInfo<FunctionNode> {
117+
return nullThrows(
118+
functionInfoStack.pop(),
119+
`Stack should exist on ${exitNodeType} exit`,
120+
);
121+
}
122+
101123
function isAllowedFunction(
102124
node:
103125
| TSESTree.ArrowFunctionExpression
@@ -168,56 +190,67 @@ export default createRule<Options, MessageIds>({
168190
return node.parent.type === AST_NODE_TYPES.CallExpression;
169191
}
170192

171-
return {
172-
'ArrowFunctionExpression, FunctionExpression'(
173-
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
174-
): void {
175-
if (
176-
options.allowConciseArrowFunctionExpressionsStartingWithVoid &&
177-
node.type === AST_NODE_TYPES.ArrowFunctionExpression &&
178-
node.expression &&
179-
node.body.type === AST_NODE_TYPES.UnaryExpression &&
180-
node.body.operator === 'void'
181-
) {
182-
return;
183-
}
193+
function exitFunctionExpression(
194+
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
195+
): void {
196+
const info = popFunctionInfo('function expression');
184197

185-
if (isAllowedFunction(node)) {
186-
return;
187-
}
198+
if (
199+
options.allowConciseArrowFunctionExpressionsStartingWithVoid &&
200+
node.type === AST_NODE_TYPES.ArrowFunctionExpression &&
201+
node.expression &&
202+
node.body.type === AST_NODE_TYPES.UnaryExpression &&
203+
node.body.operator === 'void'
204+
) {
205+
return;
206+
}
188207

189-
if (
190-
options.allowTypedFunctionExpressions &&
191-
(isValidFunctionExpressionReturnType(node, options) ||
192-
ancestorHasReturnType(node))
193-
) {
194-
return;
195-
}
208+
if (isAllowedFunction(node)) {
209+
return;
210+
}
196211

197-
checkFunctionReturnType(node, options, context.sourceCode, loc =>
198-
context.report({
199-
node,
200-
loc,
201-
messageId: 'missingReturnType',
202-
}),
203-
);
204-
},
205-
FunctionDeclaration(node): void {
212+
if (
213+
options.allowTypedFunctionExpressions &&
214+
(isValidFunctionExpressionReturnType(node, options) ||
215+
ancestorHasReturnType(node))
216+
) {
217+
return;
218+
}
219+
220+
checkFunctionReturnType(info, options, context.sourceCode, loc =>
221+
context.report({
222+
node,
223+
loc,
224+
messageId: 'missingReturnType',
225+
}),
226+
);
227+
}
228+
229+
return {
230+
'ArrowFunctionExpression, FunctionExpression, FunctionDeclaration':
231+
enterFunction,
232+
'ArrowFunctionExpression:exit': exitFunctionExpression,
233+
'FunctionExpression:exit': exitFunctionExpression,
234+
'FunctionDeclaration:exit'(node): void {
235+
const info = popFunctionInfo('function declaration');
206236
if (isAllowedFunction(node)) {
207237
return;
208238
}
209239
if (options.allowTypedFunctionExpressions && node.returnType) {
210240
return;
211241
}
212242

213-
checkFunctionReturnType(node, options, context.sourceCode, loc =>
243+
checkFunctionReturnType(info, options, context.sourceCode, loc =>
214244
context.report({
215245
node,
216246
loc,
217247
messageId: 'missingReturnType',
218248
}),
219249
);
220250
},
251+
ReturnStatement(node): void {
252+
functionInfoStack.at(-1)?.returns.push(node);
253+
},
221254
};
222255
},
223256
});

packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
55
import { createRule, isFunction } from '../util';
66
import type {
77
FunctionExpression,
8+
FunctionInfo,
89
FunctionNode,
910
} from '../util/explicitReturnTypeUtils';
1011
import {
@@ -101,13 +102,31 @@ export default createRule<Options, MessageIds>({
101102
// tracks all of the functions we've already checked
102103
const checkedFunctions = new Set<FunctionNode>();
103104

104-
// tracks functions that were found whilst traversing
105-
const foundFunctions: FunctionNode[] = [];
105+
const functionStack: FunctionNode[] = [];
106+
const functionReturnsMap = new Map<
107+
FunctionNode,
108+
TSESTree.ReturnStatement[]
109+
>();
106110

107111
// all nodes visited, avoids infinite recursion for cyclic references
108112
// (such as class member referring to itself)
109113
const alreadyVisited = new Set<TSESTree.Node>();
110114

115+
function getReturnsInFunction(
116+
node: FunctionNode,
117+
): TSESTree.ReturnStatement[] {
118+
return functionReturnsMap.get(node) ?? [];
119+
}
120+
121+
function enterFunction(node: FunctionNode): void {
122+
functionStack.push(node);
123+
functionReturnsMap.set(node, []);
124+
}
125+
126+
function exitFunction(): void {
127+
functionStack.pop();
128+
}
129+
111130
/*
112131
# How the rule works:
113132
@@ -120,10 +139,10 @@ export default createRule<Options, MessageIds>({
120139
*/
121140

122141
return {
123-
ExportDefaultDeclaration(node): void {
142+
'ExportDefaultDeclaration:exit'(node): void {
124143
checkNode(node.declaration);
125144
},
126-
'ExportNamedDeclaration:not([source])'(
145+
'ExportNamedDeclaration:not([source]):exit'(
127146
node: TSESTree.ExportNamedDeclaration,
128147
): void {
129148
if (node.declaration) {
@@ -134,21 +153,25 @@ export default createRule<Options, MessageIds>({
134153
}
135154
}
136155
},
137-
TSExportAssignment(node): void {
156+
'TSExportAssignment:exit'(node): void {
138157
checkNode(node.expression);
139158
},
140-
'ArrowFunctionExpression, FunctionDeclaration, FunctionExpression'(
141-
node: FunctionNode,
142-
): void {
143-
foundFunctions.push(node);
144-
},
159+
'ArrowFunctionExpression, FunctionDeclaration, FunctionExpression':
160+
enterFunction,
161+
'ArrowFunctionExpression:exit': exitFunction,
162+
'FunctionDeclaration:exit': exitFunction,
163+
'FunctionExpression:exit': exitFunction,
145164
'Program:exit'(): void {
146-
for (const func of foundFunctions) {
147-
if (isExportedHigherOrderFunction(func)) {
148-
checkNode(func);
165+
for (const [node, returns] of functionReturnsMap) {
166+
if (isExportedHigherOrderFunction({ node, returns })) {
167+
checkNode(node);
149168
}
150169
}
151170
},
171+
ReturnStatement(node): void {
172+
const current = functionStack[functionStack.length - 1];
173+
functionReturnsMap.get(current)?.push(node);
174+
},
152175
};
153176

154177
function checkParameters(
@@ -265,7 +288,9 @@ export default createRule<Options, MessageIds>({
265288
return false;
266289
}
267290

268-
function isExportedHigherOrderFunction(node: FunctionNode): boolean {
291+
function isExportedHigherOrderFunction({
292+
node,
293+
}: FunctionInfo<FunctionNode>): boolean {
269294
let current: TSESTree.Node | undefined = node.parent;
270295
while (current) {
271296
if (current.type === AST_NODE_TYPES.ReturnStatement) {
@@ -274,9 +299,12 @@ export default createRule<Options, MessageIds>({
274299
continue;
275300
}
276301

302+
if (!isFunction(current)) {
303+
return false;
304+
}
305+
const returns = getReturnsInFunction(current);
277306
if (
278-
!isFunction(current) ||
279-
!doesImmediatelyReturnFunctionExpression(current)
307+
!doesImmediatelyReturnFunctionExpression({ node: current, returns })
280308
) {
281309
return false;
282310
}
@@ -335,8 +363,10 @@ export default createRule<Options, MessageIds>({
335363

336364
switch (node.type) {
337365
case AST_NODE_TYPES.ArrowFunctionExpression:
338-
case AST_NODE_TYPES.FunctionExpression:
339-
return checkFunctionExpression(node);
366+
case AST_NODE_TYPES.FunctionExpression: {
367+
const returns = getReturnsInFunction(node);
368+
return checkFunctionExpression({ node, returns });
369+
}
340370

341371
case AST_NODE_TYPES.ArrayExpression:
342372
for (const element of node.elements) {
@@ -360,8 +390,10 @@ export default createRule<Options, MessageIds>({
360390
}
361391
return;
362392

363-
case AST_NODE_TYPES.FunctionDeclaration:
364-
return checkFunction(node);
393+
case AST_NODE_TYPES.FunctionDeclaration: {
394+
const returns = getReturnsInFunction(node);
395+
return checkFunction({ node, returns });
396+
}
365397

366398
case AST_NODE_TYPES.MethodDefinition:
367399
case AST_NODE_TYPES.TSAbstractMethodDefinition:
@@ -419,7 +451,10 @@ export default createRule<Options, MessageIds>({
419451
checkParameters(node);
420452
}
421453

422-
function checkFunctionExpression(node: FunctionExpression): void {
454+
function checkFunctionExpression({
455+
node,
456+
returns,
457+
}: FunctionInfo<FunctionExpression>): void {
423458
if (checkedFunctions.has(node)) {
424459
return;
425460
}
@@ -434,7 +469,7 @@ export default createRule<Options, MessageIds>({
434469
}
435470

436471
checkFunctionExpressionReturnType(
437-
node,
472+
{ node, returns },
438473
options,
439474
context.sourceCode,
440475
loc => {
@@ -449,7 +484,10 @@ export default createRule<Options, MessageIds>({
449484
checkParameters(node);
450485
}
451486

452-
function checkFunction(node: TSESTree.FunctionDeclaration): void {
487+
function checkFunction({
488+
node,
489+
returns,
490+
}: FunctionInfo<TSESTree.FunctionDeclaration>): void {
453491
if (checkedFunctions.has(node)) {
454492
return;
455493
}
@@ -459,13 +497,18 @@ export default createRule<Options, MessageIds>({
459497
return;
460498
}
461499

462-
checkFunctionReturnType(node, options, context.sourceCode, loc => {
463-
context.report({
464-
node,
465-
loc,
466-
messageId: 'missingReturnType',
467-
});
468-
});
500+
checkFunctionReturnType(
501+
{ node, returns },
502+
options,
503+
context.sourceCode,
504+
loc => {
505+
context.report({
506+
node,
507+
loc,
508+
messageId: 'missingReturnType',
509+
});
510+
},
511+
);
469512

470513
checkParameters(node);
471514
}

0 commit comments

Comments
 (0)
0