Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 50a493e

Browse files
SvishJamesHenry
authored andcommitted
feat(eslint-plugin): [explicit-function-return-type] allowHigherOrderFunctions (typescript-eslint#193) (typescript-eslint#538)
1 parent f354a3d commit 50a493e

File tree

3 files changed

+275
-3
lines changed

3 files changed

+275
-3
lines changed

packages/eslint-plugin/docs/rules/explicit-function-return-type.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,16 @@ The rule accepts an options object with the following properties:
6565
type Options = {
6666
// if true, only functions which are part of a declaration will be checked
6767
allowExpressions?: boolean;
68-
// if true, type annotations are also allowed on the variable of a function expression rather than on the function directly.
68+
// if true, type annotations are also allowed on the variable of a function expression rather than on the function directly
6969
allowTypedFunctionExpressions?: boolean;
70+
// if true, functions immediately returning another function expression will not be checked
71+
allowHigherOrderFunctions?: boolean;
7072
};
7173

7274
const defaults = {
7375
allowExpressions: false,
7476
allowTypedFunctionExpressions: false,
77+
allowHigherOrderFunctions: false,
7578
};
7679
```
7780

@@ -121,7 +124,7 @@ let funcExpr: FuncType = function() {
121124
};
122125

123126
let asTyped = (() => '') as () => string;
124-
let caasTyped = <() => string>(() => '');
127+
let castTyped = <() => string>(() => '');
125128

126129
interface ObjectType {
127130
foo(): number;
@@ -137,6 +140,32 @@ let objectPropCast = <ObjectType>{
137140
};
138141
```
139142

143+
### allowHigherOrderFunctions
144+
145+
Examples of **incorrect** code for this rule with `{ allowHigherOrderFunctions: true }`:
146+
147+
```ts
148+
var arrowFn = (x: number) => (y: number) => x + y;
149+
150+
function fn(x: number) {
151+
return function(y: number) {
152+
return x + y;
153+
};
154+
}
155+
```
156+
157+
Examples of **correct** code for this rule with `{ allowHigherOrderFunctions: true }`:
158+
159+
```ts
160+
var arrowFn = (x: number) => (y: number): number => x + y;
161+
162+
function fn(x: number) {
163+
return function(y: number): number {
164+
return x + y;
165+
};
166+
}
167+
```
168+
140169
## When Not To Use It
141170

142171
If you don't wish to prevent calling code from using function return values in unexpected ways, then

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

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type Options = [
88
{
99
allowExpressions?: boolean;
1010
allowTypedFunctionExpressions?: boolean;
11+
allowHigherOrderFunctions?: boolean;
1112
}
1213
];
1314
type MessageIds = 'missingReturnType';
@@ -35,6 +36,9 @@ export default util.createRule<Options, MessageIds>({
3536
allowTypedFunctionExpressions: {
3637
type: 'boolean',
3738
},
39+
allowHigherOrderFunctions: {
40+
type: 'boolean',
41+
},
3842
},
3943
additionalProperties: false,
4044
},
@@ -44,6 +48,7 @@ export default util.createRule<Options, MessageIds>({
4448
{
4549
allowExpressions: false,
4650
allowTypedFunctionExpressions: false,
51+
allowHigherOrderFunctions: false,
4752
},
4853
],
4954
create(context, [options]) {
@@ -138,6 +143,50 @@ export default util.createRule<Options, MessageIds>({
138143
);
139144
}
140145

146+
/**
147+
* Checks if a function belongs to:
148+
* `() => () => ...`
149+
* `() => function () { ... }`
150+
* `() => { return () => ... }`
151+
* `() => { return function () { ... } }`
152+
* `function fn() { return () => ... }`
153+
* `function fn() { return function() { ... } }`
154+
*/
155+
function doesImmediatelyReturnFunctionExpression({
156+
body,
157+
}:
158+
| TSESTree.ArrowFunctionExpression
159+
| TSESTree.FunctionDeclaration
160+
| TSESTree.FunctionExpression): boolean {
161+
// Should always have a body; really checking just in case
162+
/* istanbul ignore if */ if (!body) {
163+
return false;
164+
}
165+
166+
// Check if body is a block with a single statement
167+
if (
168+
body.type === AST_NODE_TYPES.BlockStatement &&
169+
body.body.length === 1
170+
) {
171+
const [statement] = body.body;
172+
173+
// Check if that statement is a return statement with an argument
174+
if (
175+
statement.type === AST_NODE_TYPES.ReturnStatement &&
176+
!!statement.argument
177+
) {
178+
// If so, check that returned argument as body
179+
body = statement.argument;
180+
}
181+
}
182+
183+
// Check if the body being returned is a function expression
184+
return (
185+
body.type === AST_NODE_TYPES.ArrowFunctionExpression ||
186+
body.type === AST_NODE_TYPES.FunctionExpression
187+
);
188+
}
189+
141190
/**
142191
* Checks if a function declaration/expression has a return type.
143192
*/
@@ -147,6 +196,13 @@ export default util.createRule<Options, MessageIds>({
147196
| TSESTree.FunctionDeclaration
148197
| TSESTree.FunctionExpression,
149198
): void {
199+
if (
200+
options.allowHigherOrderFunctions &&
201+
doesImmediatelyReturnFunctionExpression(node)
202+
) {
203+
return;
204+
}
205+
150206
if (
151207
node.returnType ||
152208
isConstructor(node.parent) ||
@@ -169,7 +225,8 @@ export default util.createRule<Options, MessageIds>({
169225
function checkFunctionExpressionReturnType(
170226
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
171227
): void {
172-
if (node.parent) {
228+
// Should always have a parent; checking just in case
229+
/* istanbul ignore else */ if (node.parent) {
173230
if (options.allowTypedFunctionExpressions) {
174231
if (
175232
isTypeCast(node.parent) ||

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

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,71 @@ const myObj = {
180180
};
181181
`,
182182
},
183+
{
184+
filename: 'test.ts',
185+
code: `
186+
() => (): void => {};
187+
`,
188+
options: [{ allowHigherOrderFunctions: true }],
189+
},
190+
{
191+
filename: 'test.ts',
192+
code: `
193+
() => function (): void {};
194+
`,
195+
options: [{ allowHigherOrderFunctions: true }],
196+
},
197+
{
198+
filename: 'test.ts',
199+
code: `
200+
() => { return (): void => {} };
201+
`,
202+
options: [{ allowHigherOrderFunctions: true }],
203+
},
204+
{
205+
filename: 'test.ts',
206+
code: `
207+
() => { return function (): void {} };
208+
`,
209+
options: [{ allowHigherOrderFunctions: true }],
210+
},
211+
{
212+
filename: 'test.ts',
213+
code: `
214+
function fn() { return (): void => {} };
215+
`,
216+
options: [{ allowHigherOrderFunctions: true }],
217+
},
218+
{
219+
filename: 'test.ts',
220+
code: `
221+
function fn() { return function (): void {} };
222+
`,
223+
options: [{ allowHigherOrderFunctions: true }],
224+
},
225+
{
226+
filename: 'test.ts',
227+
code: `
228+
function FunctionDeclaration() {
229+
return function FunctionExpression_Within_FunctionDeclaration() {
230+
return function FunctionExpression_Within_FunctionExpression() {
231+
return () => { // ArrowFunctionExpression_Within_FunctionExpression
232+
return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression
233+
(): number => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody
234+
}
235+
}
236+
}
237+
}
238+
`,
239+
options: [{ allowHigherOrderFunctions: true }],
240+
},
241+
{
242+
filename: 'test.ts',
243+
code: `
244+
() => () => { return (): void => { return; } };
245+
`,
246+
options: [{ allowHigherOrderFunctions: true }],
247+
},
183248
],
184249
invalid: [
185250
{
@@ -364,5 +429,126 @@ const x: Foo = {
364429
},
365430
],
366431
},
432+
{
433+
filename: 'test.ts',
434+
code: `
435+
() => () => {};
436+
`,
437+
options: [{ allowHigherOrderFunctions: true }],
438+
errors: [
439+
{
440+
messageId: 'missingReturnType',
441+
line: 2,
442+
column: 7,
443+
},
444+
],
445+
},
446+
{
447+
filename: 'test.ts',
448+
code: `
449+
() => function () {};
450+
`,
451+
options: [{ allowHigherOrderFunctions: true }],
452+
errors: [
453+
{
454+
messageId: 'missingReturnType',
455+
line: 2,
456+
column: 7,
457+
},
458+
],
459+
},
460+
{
461+
filename: 'test.ts',
462+
code: `
463+
() => { return () => {} };
464+
`,
465+
options: [{ allowHigherOrderFunctions: true }],
466+
errors: [
467+
{
468+
messageId: 'missingReturnType',
469+
line: 2,
470+
column: 16,
471+
},
472+
],
473+
},
474+
{
475+
filename: 'test.ts',
476+
code: `
477+
() => { return function () {} };
478+
`,
479+
options: [{ allowHigherOrderFunctions: true }],
480+
errors: [
481+
{
482+
messageId: 'missingReturnType',
483+
line: 2,
484+
column: 16,
485+
},
486+
],
487+
},
488+
{
489+
filename: 'test.ts',
490+
code: `
491+
function fn() { return () => {} };
492+
`,
493+
options: [{ allowHigherOrderFunctions: true }],
494+
errors: [
495+
{
496+
messageId: 'missingReturnType',
497+
line: 2,
498+
column: 24,
499+
},
500+
],
501+
},
502+
{
503+
filename: 'test.ts',
504+
code: `
505+
function fn() { return function () {} };
506+
`,
507+
options: [{ allowHigherOrderFunctions: true }],
508+
errors: [
509+
{
510+
messageId: 'missingReturnType',
511+
line: 2,
512+
column: 24,
513+
},
514+
],
515+
},
516+
{
517+
filename: 'test.ts',
518+
code: `
519+
function FunctionDeclaration() {
520+
return function FunctionExpression_Within_FunctionDeclaration() {
521+
return function FunctionExpression_Within_FunctionExpression() {
522+
return () => { // ArrowFunctionExpression_Within_FunctionExpression
523+
return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression
524+
() => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody
525+
}
526+
}
527+
}
528+
}
529+
`,
530+
options: [{ allowHigherOrderFunctions: true }],
531+
errors: [
532+
{
533+
messageId: 'missingReturnType',
534+
line: 7,
535+
column: 11,
536+
},
537+
],
538+
},
539+
{
540+
filename: 'test.ts',
541+
code: `
542+
() => () => { return () => { return; } };
543+
`,
544+
options: [{ allowHigherOrderFunctions: true }],
545+
errors: [
546+
{
547+
messageId: 'missingReturnType',
548+
line: 2,
549+
column: 22,
550+
},
551+
],
552+
},
367553
],
368554
});

0 commit comments

Comments
 (0)