Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(eslint-plugin): [no-misused-promises] add granular options withi…
…n `checksVoidReturns` (#4623)
  • Loading branch information
JoshuaKGoldberg committed Mar 3, 2022
1 parent fa381f3 commit 1085177
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 16 deletions.
48 changes: 43 additions & 5 deletions packages/eslint-plugin/docs/rules/no-misused-promises.md
Expand Up @@ -19,10 +19,18 @@ Both are enabled by default
type Options = [
{
checksConditionals?: boolean;
checksVoidReturn?: boolean;
checksVoidReturn?: boolean | ChecksVoidReturnOptions;
},
];

interface ChecksVoidReturnOptions {
arguments?: boolean;
attributes?: boolean;
properties?: boolean;
returns?: boolean;
variables?: boolean;
}

const defaultOptions: Options = [
{
checksConditionals: true,
Expand All @@ -31,7 +39,26 @@ const defaultOptions: Options = [
];
```

If you don't want functions that return promises where a void return is
### `"checksConditionals"`

If you don't want to check conditionals, you can configure the rule with `"checksConditionals": false`:

```json
{
"@typescript-eslint/no-misused-promises": [
"error",
{
"checksConditionals": false
}
]
}
```

Doing so prevents the rule from looking at code like `if (somePromise)`.

### `"checksVoidReturn"`

Likewise, if you don't want functions that return promises where a void return is
expected to be checked, your configuration will look like this:

```json
Expand All @@ -45,15 +72,26 @@ expected to be checked, your configuration will look like this:
}
```

Likewise, if you don't want to check conditionals, you can configure the rule
like this:
You can disable selective parts of the `checksVoidReturn` option by providing an object that disables specific checks.
The following options are supported:

- `arguments`: Disables checking an asynchronous function passed as argument where the parameter type expects a function that returns `void`
- `attributes`: Disables checking an asynchronous function passed as a JSX attribute expected to be a function that returns `void`
- `properties`: Disables checking an asynchronous function passed as an object property expected to be a function that returns `void`
- `returns`: Disables checking an asynchronous function returned in a function whose return type is a function that returns `void`
- `variables`: Disables checking an asynchronous function used as a variable whose return type is a function that returns `void`

For example, if you don't mind that passing a `() => Promise<void>` to a `() => void` parameter or JSX attribute can lead to a floating unhandled Promise:

```json
{
"@typescript-eslint/no-misused-promises": [
"error",
{
"checksConditionals": false
"checksVoidReturn": {
"arguments": false,
"attributes": false
}
}
]
}
Expand Down
85 changes: 74 additions & 11 deletions packages/eslint-plugin/src/rules/no-misused-promises.ts
Expand Up @@ -7,10 +7,18 @@ import * as util from '../util';
type Options = [
{
checksConditionals?: boolean;
checksVoidReturn?: boolean;
checksVoidReturn?: boolean | ChecksVoidReturnOptions;
},
];

interface ChecksVoidReturnOptions {
arguments?: boolean;
attributes?: boolean;
properties?: boolean;
returns?: boolean;
variables?: boolean;
}

type MessageId =
| 'conditional'
| 'voidReturnArgument'
Expand All @@ -19,6 +27,34 @@ type MessageId =
| 'voidReturnReturnValue'
| 'voidReturnAttribute';

function parseChecksVoidReturn(
checksVoidReturn: boolean | ChecksVoidReturnOptions | undefined,
): ChecksVoidReturnOptions | false {
switch (checksVoidReturn) {
case false:
return false;

case true:
case undefined:
return {
arguments: true,
attributes: true,
properties: true,
returns: true,
variables: true,
};

default:
return {
arguments: checksVoidReturn.arguments ?? true,
attributes: checksVoidReturn.attributes ?? true,
properties: checksVoidReturn.properties ?? true,
returns: checksVoidReturn.returns ?? true,
variables: checksVoidReturn.variables ?? true,
};
}
}

export default util.createRule<Options, MessageId>({
name: 'no-misused-promises',
meta: {
Expand Down Expand Up @@ -48,7 +84,20 @@ export default util.createRule<Options, MessageId>({
type: 'boolean',
},
checksVoidReturn: {
type: 'boolean',
oneOf: [
{ type: 'boolean' },
{
additionalProperties: false,
properties: {
arguments: { type: 'boolean' },
attributes: { type: 'boolean' },
properties: { type: 'boolean' },
returns: { type: 'boolean' },
variables: { type: 'boolean' },
},
type: 'object',
},
],
},
},
},
Expand Down Expand Up @@ -80,15 +129,29 @@ export default util.createRule<Options, MessageId>({
WhileStatement: checkTestConditional,
};

const voidReturnChecks: TSESLint.RuleListener = {
CallExpression: checkArguments,
NewExpression: checkArguments,
AssignmentExpression: checkAssignment,
VariableDeclarator: checkVariableDeclaration,
Property: checkProperty,
ReturnStatement: checkReturnStatement,
JSXAttribute: checkJSXAttribute,
};
checksVoidReturn = parseChecksVoidReturn(checksVoidReturn);

const voidReturnChecks: TSESLint.RuleListener = checksVoidReturn
? {
...(checksVoidReturn.arguments && {
CallExpression: checkArguments,
NewExpression: checkArguments,
}),
...(checksVoidReturn.attributes && {
JSXAttribute: checkJSXAttribute,
}),
...(checksVoidReturn.properties && {
Property: checkProperty,
}),
...(checksVoidReturn.returns && {
ReturnStatement: checkReturnStatement,
}),
...(checksVoidReturn.variables && {
AssignmentExpression: checkAssignment,
VariableDeclarator: checkVariableDeclaration,
}),
}
: {};

function checkTestConditional(node: {
test: TSESTree.Expression | null;
Expand Down
61 changes: 61 additions & 0 deletions packages/eslint-plugin/tests/rules/no-misused-promises.test.ts
Expand Up @@ -586,6 +586,21 @@ f = async () => {
},
{
code: `
let f: () => void;
f = async () => {
return 3;
};
`,
errors: [
{
line: 3,
messageId: 'voidReturnVariable',
},
],
options: [{ checksVoidReturn: { variables: true } }],
},
{
code: `
const f: () => void = async () => {
return 0;
};
Expand Down Expand Up @@ -636,6 +651,21 @@ const obj: O = {
{
code: `
type O = { f: () => void };
const obj: O = {
f: async () => 'foo',
};
`,
errors: [
{
line: 4,
messageId: 'voidReturnProperty',
},
],
options: [{ checksVoidReturn: { properties: true } }],
},
{
code: `
type O = { f: () => void };
const f = async () => 0;
const obj: O = {
f,
Expand Down Expand Up @@ -708,6 +738,36 @@ function f(): () => void {
},
{
code: `
function f(): () => void {
return async () => 0;
}
`,
errors: [
{
line: 3,
messageId: 'voidReturnReturnValue',
},
],
options: [{ checksVoidReturn: { returns: true } }],
},
{
code: `
type O = {
func: () => void;
};
const Component = (obj: O) => null;
<Component func={async () => 0} />;
`,
filename: 'react.tsx',
errors: [
{
line: 6,
messageId: 'voidReturnAttribute',
},
],
},
{
code: `
type O = {
func: () => void;
};
Expand All @@ -721,6 +781,7 @@ const Component = (obj: O) => null;
messageId: 'voidReturnAttribute',
},
],
options: [{ checksVoidReturn: { attributes: true } }],
},
{
code: `
Expand Down

0 comments on commit 1085177

Please sign in to comment.