Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): [explicit-function-return-type] add allowIIFEs option #6237

Merged
merged 16 commits into from Feb 12, 2023
20 changes: 20 additions & 0 deletions packages/eslint-plugin/docs/rules/explicit-function-return-type.md
Expand Up @@ -257,6 +257,26 @@ You may pass function/method names you would like this rule to ignore, like so:
}
```

### `allowIIFE`

Examples of code for this rule with `{ allowIIFE: true }`:

#### ❌ Incorrect

```ts
var func = () => 'foo';
```

#### ✅ Correct

```ts
var foo = (() => 'foo')();

var bar = (function () {
return 'bar';
})();
```

## When Not To Use It

If you don't wish to prevent calling code from using function return values in unexpected ways, then
Expand Down
21 changes: 21 additions & 0 deletions packages/eslint-plugin/src/rules/explicit-function-return-type.ts
Expand Up @@ -17,6 +17,7 @@ type Options = [
allowConciseArrowFunctionExpressionsStartingWithVoid?: boolean;
allowFunctionsWithoutTypeParameters?: boolean;
allowedNames?: string[];
allowIIFEs?: boolean;
},
];
type MessageIds = 'missingReturnType';
Expand Down Expand Up @@ -75,6 +76,11 @@ export default util.createRule<Options, MessageIds>({
},
type: 'array',
},
allowIIFEs: {
description:
'Whether to ignore immediately invoked function expressions (IIFEs).',
type: 'boolean',
},
},
additionalProperties: false,
},
Expand All @@ -88,6 +94,7 @@ export default util.createRule<Options, MessageIds>({
allowDirectConstAssertionInArrowFunctions: true,
allowConciseArrowFunctionExpressionsStartingWithVoid: false,
allowedNames: [],
allowIIFEs: false,
},
],
create(context, [options]) {
Expand All @@ -102,6 +109,10 @@ export default util.createRule<Options, MessageIds>({
return true;
}

if (options.allowIIFEs && isIIFE(node)) {
return true;
}

if (!options.allowedNames?.length) {
return false;
}
Expand Down Expand Up @@ -149,6 +160,16 @@ export default util.createRule<Options, MessageIds>({
}
return false;
}

function isIIFE(
node:
| TSESTree.ArrowFunctionExpression
| TSESTree.FunctionExpression
| TSESTree.FunctionDeclaration,
): boolean {
return node.parent!.type === AST_NODE_TYPES.CallExpression;
}

return {
'ArrowFunctionExpression, FunctionExpression'(
node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression,
Expand Down
Expand Up @@ -595,6 +595,132 @@ const x: Bar<Foo> = arg1 => arg2 => arg1 + arg2;
},
],
},
{
filename: 'test.ts',
code: `
let foo = function (): number {
return 1;
};
`,
options: [
{
allowIIFEs: true,
},
],
},
{
filename: 'test.ts',
code: `
const foo = (function () {
return 1;
})();
`,
options: [
{
allowIIFEs: true,
},
],
},
{
filename: 'test.ts',
code: `
const foo = (() => {
return 1;
})();
`,
options: [
{
allowIIFEs: true,
},
],
},
{
filename: 'test.ts',
code: `
const foo = ((arg: number): number => {
return arg;
})(0);
`,
options: [
{
allowIIFEs: true,
},
],
},
{
filename: 'test.ts',
code: `
const foo = (() => (() => 'foo')())();
`,
options: [
{
allowIIFEs: true,
},
],
},
{
filename: 'test.ts',
code: `
let foo = (() => (): string => {
return 'foo';
})()();
`,
options: [
{
allowIIFEs: true,
},
],
},
{
filename: 'test.ts',
code: `
let foo = (() => (): string => {
return 'foo';
})();
`,
options: [
{
allowIIFEs: true,
allowHigherOrderFunctions: false,
},
],
},
{
filename: 'test.ts',
code: `
let foo = (() => (): string => {
return 'foo';
})()();
`,
options: [
{
allowIIFEs: true,
allowHigherOrderFunctions: true,
},
],
},
{
filename: 'test.ts',
code: `
let foo = (() => (): void => {})()();
`,
options: [
{
allowIIFEs: true,
},
],
},
{
filename: 'test.ts',
code: `
let foo = (() => (() => {})())();
`,
options: [
{
allowIIFEs: true,
},
],
},
],
invalid: [
{
Expand Down Expand Up @@ -1477,5 +1603,93 @@ class Foo {
},
],
},
{
filename: 'test.ts',
code: `
const foo = (function () {
return 'foo';
})();
`,
options: [
{
allowIIFEs: false,
},
],
errors: [
{
messageId: 'missingReturnType',
line: 2,
endLine: 2,
column: 14,
endColumn: 25,
},
],
},
{
filename: 'test.ts',
code: `
const foo = (function () {
return () => {
return 1;
};
})();
`,
options: [
{
allowIIFEs: true,
},
],
errors: [
{
messageId: 'missingReturnType',
line: 3,
endLine: 3,
column: 10,
endColumn: 15,
},
],
},
{
filename: 'test.ts',
code: `
let foo = function () {
return 'foo';
};
`,
options: [
{
allowIIFEs: true,
},
],
errors: [
{
messageId: 'missingReturnType',
line: 2,
endLine: 2,
column: 11,
endColumn: 22,
},
],
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a bit more test coverage:

  • IIFEs with return types
  • Multiple levels of IIFEs (e.g. (() => () => {})()())
  • Arguments & parameters

{
filename: 'test.ts',
code: `
let foo = (() => () => {})()();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoshuaKGoldberg
How should we handle multiple levels of IIFEs? For now, it allows only if the function is called directly.

const foo = (() => () => "foo")()(); // error

const bar = (() => (() => "foo")())(); // pass

Should both cases be passed when the allowIIFEs option is true?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yeonjuan The first one IMO is not an "IIFE" by definition—(() => () => "foo")() is an expression that happens to evaluate to a function, not a function expression itself. It's fine if we don't handle it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would agree with that assessment.
The inner function is not an IIFE, just the outer function.

`,
options: [
{
allowIIFEs: true,
},
],
errors: [
{
messageId: 'missingReturnType',
line: 2,
endLine: 2,
column: 18,
endColumn: 23,
},
],
},
],
});