From cea51bf130d6d3c2935f5e2dcc468196f2ad9d00 Mon Sep 17 00:00:00 2001 From: Anix Date: Mon, 20 Apr 2020 11:13:18 +0530 Subject: [PATCH] feat(eslint-plugin): [no-floating-promise] add option to ignore IIFEs (#1799) --- .../docs/rules/no-floating-promises.md | 24 ++- .../src/rules/no-floating-promises.ts | 26 ++- .../tests/rules/no-floating-promises.test.ts | 169 ++++++++++++++++++ .../src/ts-estree/ts-estree.ts | 3 +- 4 files changed, 217 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.md b/packages/eslint-plugin/docs/rules/no-floating-promises.md index ffaa542f3f1..4ea2e6addc9 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.md +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.md @@ -51,6 +51,8 @@ The rule accepts an options object with the following properties: type Options = { // if true, checking void expressions will be skipped ignoreVoid?: boolean; + // if true, checking for async iife will be skipped + ignoreIIFE?: boolean; }; const defaults = { @@ -60,7 +62,8 @@ const defaults = { ### `ignoreVoid` -This allows to easily suppress false-positives with void operator. +This allows you to stop the rule reporting promises consumed with void operator. +This can be a good way to explicitly mark a promise as intentionally not awaited. Examples of **correct** code for this rule with `{ ignoreVoid: true }`: @@ -73,10 +76,25 @@ void returnsPromise(); void Promise.reject('value'); ``` +### `ignoreIIFE` + +This allows you to skip checking of async iife + +Examples of **correct** code for this rule with `{ ignoreIIFE: true }`: + +```ts +await(async function() { + await res(1); +})(); + +(async function() { + await res(1); +})(); +``` + ## When Not To Use It -If you do not use Promise-like values in your codebase or want to allow them to -remain unhandled. +If you do not use Promise-like values in your codebase, or want to allow them to remain unhandled. ## Related to diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index c3ee7ffccf3..09d70bac42c 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -1,12 +1,17 @@ import * as tsutils from 'tsutils'; import * as ts from 'typescript'; -import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { + TSESLint, + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ { ignoreVoid?: boolean; + ignoreIIFE?: boolean; }, ]; @@ -33,6 +38,7 @@ export default util.createRule({ type: 'object', properties: { ignoreVoid: { type: 'boolean' }, + ignoreIIFE: { type: 'boolean' }, }, additionalProperties: false, }, @@ -42,6 +48,7 @@ export default util.createRule({ defaultOptions: [ { ignoreVoid: false, + ignoreIIFE: false, }, ], @@ -54,6 +61,10 @@ export default util.createRule({ ExpressionStatement(node): void { const { expression } = parserServices.esTreeNodeToTSNodeMap.get(node); + if (options.ignoreIIFE && isAsyncIife(node)) { + return; + } + if (isUnhandledPromise(checker, expression)) { if (options.ignoreVoid) { context.report({ @@ -80,6 +91,19 @@ export default util.createRule({ }, }; + function isAsyncIife(node: TSESTree.ExpressionStatement): boolean { + if (node.expression.type !== AST_NODE_TYPES.CallExpression) { + return false; + } + + return ( + node.expression.type === AST_NODE_TYPES.CallExpression && + (node.expression.callee.type === + AST_NODE_TYPES.ArrowFunctionExpression || + node.expression.callee.type === AST_NODE_TYPES.FunctionExpression) + ); + } + function isUnhandledPromise( checker: ts.TypeChecker, node: ts.Node, diff --git a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts index c7e688c9852..f6601a0613a 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -335,6 +335,54 @@ async function test() { return returnsPromise(); } `, + // ignoreIIFE + { + code: ` + (async () => { + await something(); + })(); + `, + options: [{ ignoreIIFE: true }], + }, + { + code: ` + (async () => { + something(); + })(); + `, + options: [{ ignoreIIFE: true }], + }, + { + code: '(async function foo() {})();', + options: [{ ignoreIIFE: true }], + }, + { + code: ` + function foo() { + (async function bar() {})(); + } + `, + options: [{ ignoreIIFE: true }], + }, + { + code: ` + const foo = () => + new Promise(res => { + (async function() { + await res(1); + })(); + }); + `, + options: [{ ignoreIIFE: true }], + }, + { + code: ` + (async function() { + await res(1); + })(); + `, + options: [{ ignoreIIFE: true }], + }, ], invalid: [ @@ -726,5 +774,126 @@ async function test() { }, ], }, + { + code: ` + (async () => { + await something(); + })(); + `, + errors: [ + { + line: 2, + messageId: 'floating', + }, + ], + }, + { + code: ` + (async () => { + something(); + })(); + `, + errors: [ + { + line: 2, + messageId: 'floating', + }, + ], + }, + { + code: '(async function foo() {})();', + errors: [ + { + line: 1, + messageId: 'floating', + }, + ], + }, + { + code: ` + function foo() { + (async function bar() {})(); + } + `, + errors: [ + { + line: 3, + messageId: 'floating', + }, + ], + }, + { + code: ` + const foo = () => + new Promise(res => { + (async function() { + await res(1); + })(); + }); + `, + errors: [ + { + line: 4, + messageId: 'floating', + }, + ], + }, + { + code: ` + (async function() { + await res(1); + })(); + `, + errors: [ + { + line: 2, + messageId: 'floating', + }, + ], + }, + { + code: ` + (async function() { + Promise.resolve(); + })(); + `, + options: [{ ignoreIIFE: true }], + errors: [ + { + line: 3, + messageId: 'floating', + }, + ], + }, + { + code: ` + (async function() { + const promiseIntersection: Promise & number; + promiseIntersection; + promiseIntersection.then(() => {}); + promiseIntersection.catch(); + promiseIntersection.finally(); + })(); + `, + options: [{ ignoreIIFE: true }], + errors: [ + { + line: 4, + messageId: 'floating', + }, + { + line: 5, + messageId: 'floating', + }, + { + line: 6, + messageId: 'floating', + }, + { + line: 7, + messageId: 'floating', + }, + ], + }, ], }); diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index 5792ebc0205..1781ffd5fff 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -392,7 +392,8 @@ export type LeftHandSideExpression = | PrimaryExpression | TaggedTemplateExpression | TSNonNullExpression - | TSAsExpression; + | TSAsExpression + | ArrowFunctionExpression; export type Literal = | BooleanLiteral | NumberLiteral