Skip to content

Commit

Permalink
feat(eslint-plugin): [no-misused-promises] add opt-opt options within…
Browse files Browse the repository at this point in the history
… checksVoidReturns
  • Loading branch information
JoshuaKGoldberg committed Mar 2, 2022
1 parent 699ef48 commit 7f8a1c2
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 16 deletions.
34 changes: 29 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,21 @@ const defaultOptions: Options = [
];
```

If you don't want functions that return promises where a void return is
If you don't want to check conditionals, you can configure the rule
like this:

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

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 +67,17 @@ 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:

```json
{
"@typescript-eslint/no-misused-promises": [
"error",
{
"checksConditionals": false
"checksVoidReturn": {
"arguments": 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
120 changes: 120 additions & 0 deletions packages/eslint-plugin/tests/rules/no-misused-promises.test.ts
Expand Up @@ -252,6 +252,51 @@ const Component: any = () => null;
`,
filename: 'react.tsx',
},
{
code: `
type O = {
func: () => void;
};
const Component = (obj: O) => null;
<Component func={async () => 0} />;
`,
filename: 'react.tsx',
options: [{ checksVoidReturn: { attributes: false } }],
},
{
code: `
[Promise.resolve(), Promise.reject()].forEach(async val => {
await val;
});
`,
options: [{ checksVoidReturn: { arguments: false } }],
},
{
code: `
type O = { f: () => void };
const obj: O = {
f: async () => 'foo',
};
`,
options: [{ checksVoidReturn: { properties: false } }],
},
{
code: `
function f(): () => void {
return async () => 0;
}
`,
options: [{ checksVoidReturn: { returns: false } }],
},
{
code: `
let f: () => void;
f = async () => {
return 3;
};
`,
options: [{ checksVoidReturn: { variables: false } }],
},
],

invalid: [
Expand Down Expand Up @@ -357,6 +402,20 @@ if (!Promise.resolve()) {
},
{
code: `
[Promise.resolve(), Promise.reject()].forEach(async val => {
await val;
});
`,
errors: [
{
line: 2,
messageId: 'voidReturnArgument',
},
],
options: [{ checksVoidReturn: { arguments: true } }],
},
{
code: `
new Promise(async (resolve, reject) => {
await Promise.resolve();
resolve();
Expand Down Expand Up @@ -534,6 +593,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 @@ -584,6 +658,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 @@ -656,6 +745,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 @@ -669,6 +788,7 @@ const Component = (obj: O) => null;
messageId: 'voidReturnAttribute',
},
],
options: [{ checksVoidReturn: { attributes: true } }],
},
{
code: `
Expand Down

0 comments on commit 7f8a1c2

Please sign in to comment.