From 6bb84bdb29ad63b49a99a3e314d5d5b76d85abe0 Mon Sep 17 00:00:00 2001 From: Jeff Principe Date: Sat, 4 May 2019 22:47:33 -0700 Subject: [PATCH 1/5] feat(eslint-plugin): add new rule no-floating-promises Adds the equivalent of TSLint's `no-floating-promises` rule. Fixes #464 --- packages/eslint-plugin/README.md | 1 + packages/eslint-plugin/ROADMAP.md | 5 +- .../docs/rules/no-floating-promises.md | 46 ++ .../src/rules/no-floating-promises.ts | 173 ++++++ .../tests/rules/no-floating-promises.test.ts | 549 ++++++++++++++++++ 5 files changed, 772 insertions(+), 2 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/no-floating-promises.md create mode 100644 packages/eslint-plugin/src/rules/no-floating-promises.ts create mode 100644 packages/eslint-plugin/tests/rules/no-floating-promises.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 044ff064372..cd4ca274d7f 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -132,6 +132,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type (`no-any` from TSLint) | :heavy_check_mark: | | | | [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | | | [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces (`no-unnecessary-class` from TSLint) | | | | +| [`@typescript-eslint/no-floating-promises`](./docs/rules/no-floating-promises.md) | Requires Promise-like values to be handled appropriately. | | | :thought_balloon: | | [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop (`no-for-in-array` from TSLint) | | | :thought_balloon: | | [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint) | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint) | :heavy_check_mark: | | | diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index b2567afe070..f0171f73b09 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -1,4 +1,4 @@ -# Roadmap +# Roadmap ✅ = done
🌟 = in ESLint core
@@ -60,7 +60,7 @@ | [`no-dynamic-delete`] | 🛑 | N/A | | [`no-empty`] | 🌟 | [`no-empty`][no-empty] | | [`no-eval`] | 🌟 | [`no-eval`][no-eval] | -| [`no-floating-promises`] | 🛑 | N/A ([relevant plugin][plugin:promise]) | +| [`no-floating-promises`] | ✅ | [`@typescript-eslint/no-floating-promises`] | | [`no-for-in-array`] | ✅ | [`@typescript-eslint/no-for-in-array`] | | [`no-implicit-dependencies`] | 🔌 | [`import/no-extraneous-dependencies`] | | [`no-inferred-empty-object-type`] | 🛑 | N/A | @@ -612,6 +612,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/no-for-in-array`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md [`@typescript-eslint/no-unnecessary-qualifier`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md [`@typescript-eslint/semi`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/semi.md +[`@typescript-eslint/no-floating-promises`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-floating-promises.md diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.md b/packages/eslint-plugin/docs/rules/no-floating-promises.md new file mode 100644 index 00000000000..75bc49efa66 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.md @@ -0,0 +1,46 @@ +# Requires Promise-like values to be handled appropriately (no-floating-promises) + +This rule forbids usage of Promise-like values in statements without handling +their errors appropriately. Unhandled promises can cause several issues, such +as improperly sequenced operations, ignored Promise rejections and more. Valid +ways of handling a Promise-valued statement include `await`ing, returning, and +either calling `.then()` with two arguments or `.catch()` with one argument. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +const promise = new Promise((resolve, reject) => resolve('value')); +promise; + +async function returnsPromise() { + return 'value'; +} +returnsPromise().then(() => {}); + +Promise.reject('value').catch(); +``` + +Examples of **correct** code for this rule: + +```ts +const promise = new Promise((resolve, reject) => resolve('value')); +await promise; + +async function returnsPromise() { + return 'value'; +} +returnsPromise().then(() => {}, () => {}); + +Promise.reject('value').catch(() => {}); +``` + +## When Not To Use It + +If you do not use Promise-like values in your codebase or want to allow them to +remain unhandled. + +## Related to + +- Tslint: ['no-floating-promises'](https://palantir.github.io/tslint/rules/no-floating-promises/) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts new file mode 100644 index 00000000000..2ab6a631d6f --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -0,0 +1,173 @@ +import * as tsutils from 'tsutils'; +import * as ts from 'typescript'; + +import * as util from '../util'; + +export default util.createRule({ + name: 'no-floating-promises', + meta: { + docs: { + description: + 'Requires promises to be awaited or have a rejection handler.', + category: 'Best Practices', + recommended: false, + tslintName: 'no-floating-promises', + }, + messages: { + floating: 'Promises must be handled appropriately', + }, + schema: [], + type: 'problem', + }, + defaultOptions: [], + + create(context) { + const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + + return { + ExpressionStatement(node) { + const { expression } = parserServices.esTreeNodeToTSNodeMap.get( + node, + ) as ts.ExpressionStatement; + + if (isUnhandledPromise(checker, expression)) { + context.report({ + messageId: 'floating', + node, + }); + } + }, + }; + }, +}); + +function isUnhandledPromise(checker: ts.TypeChecker, node: ts.Node): boolean { + // First, check expressions whose resulting types may not be promise-like + if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.CommaToken + ) { + // Any child in a comma expression could return a potentially unhandled + // promise, so we check them all regardless of whether the final returned + // value is promise-like. + return ( + isUnhandledPromise(checker, node.left) || + isUnhandledPromise(checker, node.right) + ); + } else if (ts.isVoidExpression(node)) { + // Similarly, a `void` expression always returns undefined, so we need to + // see what's inside it without checking the type of the overall expression. + return isUnhandledPromise(checker, node.expression); + } + + // Check the type. At this point it can't be unhandled if it isn't a promise + if (!isPromiseLike(checker, node)) { + return false; + } + + if (ts.isCallExpression(node)) { + // If the outer expression is a call, it must be either a `.then()` or + // `.catch()` that handles the promise. + return ( + !isPromiseCatchCallWithHandler(node) && + !isPromiseThenCallWithRejectionHandler(node) + ); + } else if (ts.isConditionalExpression(node)) { + // We must be getting the promise-like value from one of the branches of the + // ternary. Check them directly. + return ( + isUnhandledPromise(checker, node.whenFalse) || + isUnhandledPromise(checker, node.whenTrue) + ); + } else if ( + ts.isPropertyAccessExpression(node) || + ts.isIdentifier(node) || + ts.isNewExpression(node) + ) { + // If it is just a property access chain or a `new` call (e.g. `foo.bar` or + // `new Promise()`), the promise is not handled because it doesn't have the + // necessary then/catch call at the end of the chain. + return true; + } + + // We conservatively return false for all other types of expressions because + // we don't want to accidentally fail if the promise is handled internally but + // we just can't tell. + return false; +} + +// Modified from tsutils.isThenable() to only consider thenables which can be +// rejected/caught via a second parameter. Original source (MIT licensed): +// +// https://github.com/ajafff/tsutils/blob/49d0d31050b44b81e918eae4fbaf1dfe7b7286af/util/type.ts#L95-L125 +function isPromiseLike(checker: ts.TypeChecker, node: ts.Node): boolean { + const type = checker.getTypeAtLocation(node); + for (const ty of tsutils.unionTypeParts(checker.getApparentType(type))) { + const then = ty.getProperty('then'); + if (then === undefined) { + continue; + } + + const thenType = checker.getTypeOfSymbolAtLocation(then, node); + if ( + hasMatchingSignature( + thenType, + signature => + signature.parameters.length >= 2 && + isFunctionParam(checker, signature.parameters[0], node) && + isFunctionParam(checker, signature.parameters[1], node), + ) + ) { + return true; + } + } + return false; +} + +function hasMatchingSignature( + type: ts.Type, + matcher: (signature: ts.Signature) => boolean, +): boolean { + for (const t of tsutils.unionTypeParts(type)) { + if (t.getCallSignatures().some(matcher)) { + return true; + } + } + + return false; +} + +function isFunctionParam( + checker: ts.TypeChecker, + param: ts.Symbol, + node: ts.Node, +): boolean { + let type: ts.Type | undefined = checker.getApparentType( + checker.getTypeOfSymbolAtLocation(param, node), + ); + for (const t of tsutils.unionTypeParts(type)) { + if (t.getCallSignatures().length !== 0) { + return true; + } + } + return false; +} + +function isPromiseCatchCallWithHandler(expression: ts.CallExpression): boolean { + return ( + tsutils.isPropertyAccessExpression(expression.expression) && + expression.expression.name.text === 'catch' && + expression.arguments.length >= 1 + ); +} + +function isPromiseThenCallWithRejectionHandler( + expression: ts.CallExpression, +): boolean { + return ( + tsutils.isPropertyAccessExpression(expression.expression) && + expression.expression.name.text === 'then' && + expression.arguments.length >= 2 + ); +} diff --git a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts new file mode 100644 index 00000000000..abe2bdf0d7e --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -0,0 +1,549 @@ +import rule from '../../src/rules/no-floating-promises'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); +const parserOptions = { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json', +}; + +const messageId = 'floating'; + +const ruleTester = new RuleTester({ + parserOptions, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-floating-promises', rule, { + valid: [ + ` +async function test() { + await Promise.resolve("value"); + Promise.resolve("value").then(() => {}, () => {}); + Promise.resolve("value").then(() => {}).catch(() => {}); + Promise.resolve("value").catch(() => {}); + return Promise.resolve("value"); +} +`, + ` +async function test() { + await Promise.reject(new Error("message")); + Promise.reject(new Error("message")).then(() => {}, () => {}); + Promise.reject(new Error("message")).then(() => {}).catch(() => {}); + Promise.reject(new Error("message")).catch(() => {}); + return Promise.reject(new Error("message")); +} +`, + ` +async function test() { + await (async () => true)(); + (async () => true)().then(() => {}, () => {}); + (async () => true)().then(() => {}).catch(() => {}); + (async () => true)().catch(() => {}); + return (async () => true)(); +} +`, + ` +async function test() { + async function returnsPromise() {} + await returnsPromise(); + returnsPromise().then(() => {}, () => {}); + returnsPromise().then(() => {}).catch(() => {}); + returnsPromise().catch(() => {}); + return returnsPromise(); +} +`, + ` +async function test() { + const x = Promise.resolve(); + const y = x.then(() => {}); + y.catch(() => {}); +} +`, + ` +async function test() { + Math.random() > 0.5 ? Promise.resolve().catch(() => {}) : null; +} +`, + ` +async function test() { + Promise.resolve().catch(() => {}), 123; + 123, Promise.resolve().then(() => {}, () => {}); + 123, Promise.resolve().then(() => {}, () => {}), 123; +} +`, + ` +async function test() { + void Promise.resolve().catch(() => {}); +} +`, + ` +async function test() { + Promise.resolve().catch(() => {}) || Promise.resolve().then(() => {}, () => {}); +} +`, + ` +async function test() { + const promiseValue: Promise; + + await promiseValue; + promiseValue.then(() => {}, () => {}); + promiseValue.then(() => {}).catch(() => {}); + promiseValue.catch(() => {}); + return promiseValue; +} +`, + ` +async function test() { + const promiseUnion: Promise | number; + + await promiseUnion; + promiseUnion.then(() => {}, () => {}); + promiseUnion.then(() => {}).catch(() => {}); + promiseUnion.catch(() => {}); + return promiseUnion; +} +`, + ` +async function test() { + const promiseIntersection: Promise & number; + + await promiseIntersection; + promiseIntersection.then(() => {}, () => {}); + promiseIntersection.then(() => {}).catch(() => {}); + promiseIntersection.catch(() => {}); + return promiseIntersection; +} +`, + ` +async function test() { + class CanThen extends Promise {} + const canThen: CanThen = Foo.resolve(2); + + await canThen; + canThen.then(() => {}, () => {}); + canThen.then(() => {}).catch(() => {}); + canThen.catch(() => {}); + return canThen; +} +`, + ` +async function test() { + await (Math.random() > 0.5 ? numberPromise : 0); + await (Math.random() > 0.5 ? foo : 0); + await (Math.random() > 0.5 ? bar : 0); + + const intersectionPromise: Promise & number; + await intersectionPromise; +} +`, + ` +async function test() { + class Thenable { + then(callback: () => {}): Thenable { return new Thenable(); } + }; + const thenable = new Thenable(); + + await thenable; + thenable; + thenable.then(() => {}); + return thenable; +} +`, + ` +async function test() { + class NonFunctionParamThenable { + then(param: string, param2: number): NonFunctionParamThenable { + return new NonFunctionParamThenable(); + } + }; + const thenable = new NonFunctionParamThenable(); + + await thenable; + thenable; + thenable.then('abc', 'def'); + return thenable; +} +`, + ` +async function test() { + class NonFunctionThenable { then: number }; + const thenable = new NonFunctionThenable(); + + thenable; + thenable.then; + return thenable; +} +`, + ` +async function test() { + class CatchableThenable { + then(callback: () => {}, callback: () => {}): CatchableThenable { + return new CatchableThenable(); + } + }; + const thenable = new CatchableThenable(); + + await thenable + return thenable; +} +`, + ` +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/promise-polyfill/index.d.ts +// Type definitions for promise-polyfill 6.0 +// Project: https://github.com/taylorhakes/promise-polyfill +// Definitions by: Steve Jenkins +// Daniel Cassidy +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +interface PromisePolyfillConstructor extends PromiseConstructor { + _immediateFn?: (handler: (() => void) | string) => void; +} + +declare const PromisePolyfill: PromisePolyfillConstructor; + +async function test() { + const promise = new PromisePolyfill(() => {}); + + await promise; + promise.then(() => {}, () => {}); + promise.then(() => {}).catch(() => {}); + promise.catch(() => {}); + return promise; +} +`, + ], + + invalid: [ + { + code: ` +async function test() { + Promise.resolve("value"); + Promise.resolve("value").then(() => {}); + Promise.resolve("value").catch(); +} +`, + errors: [ + { + line: 3, + messageId, + }, + { + line: 4, + messageId, + }, + { + line: 5, + messageId, + }, + ], + }, + { + code: ` +async function test() { + Promise.reject(new Error("message")); + Promise.reject(new Error("message")).then(() => {}); + Promise.reject(new Error("message")).catch(); +} +`, + errors: [ + { + line: 3, + messageId, + }, + { + line: 4, + messageId, + }, + { + line: 5, + messageId, + }, + ], + }, + { + code: ` +async function test() { + (async () => true)(); + (async () => true)().then(() => {}); + (async () => true)().catch(); +} +`, + errors: [ + { + line: 3, + messageId, + }, + { + line: 4, + messageId, + }, + { + line: 5, + messageId, + }, + ], + }, + { + code: ` +async function test() { + async function returnsPromise() {} + + returnsPromise(); + returnsPromise().then(() => {}); + returnsPromise().catch(); +} +`, + errors: [ + { + line: 5, + messageId, + }, + { + line: 6, + messageId, + }, + { + line: 7, + messageId, + }, + ], + }, + { + code: ` +async function test() { + Math.random() > 0.5 ? Promise.resolve() : null; + Math.random() > 0.5 ? null : Promise.resolve(); +} +`, + errors: [ + { + line: 3, + messageId, + }, + { + line: 4, + messageId, + }, + ], + }, + { + code: ` +async function test() { + Promise.resolve(), 123 + 123, Promise.resolve() + 123, Promise.resolve(), 123 +} +`, + errors: [ + { + line: 3, + messageId, + }, + { + line: 4, + messageId, + }, + { + line: 5, + messageId, + }, + ], + }, + { + code: ` +async function test() { + void Promise.resolve(); +} +`, + errors: [ + { + line: 3, + messageId, + }, + ], + }, + { + code: ` +async function test() { + const obj = { foo: Promise.resolve() }; + obj.foo; +} +`, + errors: [ + { + line: 4, + messageId, + }, + ], + }, + { + code: ` +async function test() { + new Promise(resolve => resolve()); +} +`, + errors: [ + { + line: 3, + messageId, + }, + ], + }, + { + code: ` +async function test() { + const promiseValue: Promise; + + promiseValue; + promiseValue.then(() => {}); + promiseValue.catch(); +} +`, + errors: [ + { + line: 5, + messageId, + }, + { + line: 6, + messageId, + }, + { + line: 7, + messageId, + }, + ], + }, + { + code: ` +async function test() { + const promiseUnion: Promise | number; + + promiseUnion; +} +`, + errors: [ + { + line: 5, + messageId, + }, + ], + }, + { + code: ` +async function test() { + const promiseIntersection: Promise & number; + + promiseIntersection; + promiseIntersection.then(() => {}) + promiseIntersection.catch(); +} +`, + errors: [ + { + line: 5, + messageId, + }, + { + line: 6, + messageId, + }, + { + line: 7, + messageId, + }, + ], + }, + { + code: ` +async function test() { + class CanThen extends Promise {} + const canThen: CanThen = Foo.resolve(2); + + canThen; + canThen.then(() => {}); + canThen.catch(); +} +`, + errors: [ + { + line: 6, + messageId, + }, + { + line: 7, + messageId, + }, + { + line: 8, + messageId, + }, + ], + }, + { + code: ` +async function test() { + class CatchableThenable { + then(callback: () => {}, callback: () => {}): CatchableThenable { + return new CatchableThenable(); + } + }; + const thenable = new CatchableThenable(); + + thenable; + thenable.then(() => {}); +} +`, + errors: [ + { + line: 10, + messageId, + }, + { + line: 11, + messageId, + }, + ], + }, + { + code: ` +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/promise-polyfill/index.d.ts +// Type definitions for promise-polyfill 6.0 +// Project: https://github.com/taylorhakes/promise-polyfill +// Definitions by: Steve Jenkins +// Daniel Cassidy +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +interface PromisePolyfillConstructor extends PromiseConstructor { + _immediateFn?: (handler: (() => void) | string) => void; +} + +declare const PromisePolyfill: PromisePolyfillConstructor; + +async function test() { + const promise = new PromisePolyfill(() => {}); + + promise; + promise.then(() => {}); + promise.catch(); +} +`, + errors: [ + { + line: 18, + messageId, + }, + { + line: 19, + messageId, + }, + { + line: 20, + messageId, + }, + ], + }, + ], +}); From 66c847e9b85b10a73421733d298a5f1ca4ad2aac Mon Sep 17 00:00:00 2001 From: Jeff Principe Date: Sun, 9 Jun 2019 12:39:09 -0700 Subject: [PATCH 2/5] chore: updates to fix build/test --- packages/eslint-plugin/src/rules/index.ts | 2 ++ packages/eslint-plugin/src/rules/no-floating-promises.ts | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index d62f58d6af3..c92f6a36d8b 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -20,6 +20,7 @@ import noEmptyInterface from './no-empty-interface'; import noExplicitAny from './no-explicit-any'; import noExtraParens from './no-extra-parens'; import noExtraneousClass from './no-extraneous-class'; +import noFloatingPromises from './no-floating-promises'; import noForInArray from './no-for-in-array'; import noInferrableTypes from './no-inferrable-types'; import noMagicNumbers from './no-magic-numbers'; @@ -76,6 +77,7 @@ export default { 'no-explicit-any': noExplicitAny, 'no-extra-parens': noExtraParens, 'no-extraneous-class': noExtraneousClass, + 'no-floating-promises': noFloatingPromises, 'no-for-in-array': noForInArray, 'no-inferrable-types': noInferrableTypes, 'no-magic-numbers': noMagicNumbers, diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 2ab6a631d6f..737c68531d0 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -11,7 +11,6 @@ export default util.createRule({ 'Requires promises to be awaited or have a rejection handler.', category: 'Best Practices', recommended: false, - tslintName: 'no-floating-promises', }, messages: { floating: 'Promises must be handled appropriately', From fbdbff349e268aaaf31b6e4a886c384ba79b4d4b Mon Sep 17 00:00:00 2001 From: Jeff Principe Date: Sun, 9 Jun 2019 12:49:50 -0700 Subject: [PATCH 3/5] chore: fix typecheck --- .../tests/rules/no-floating-promises.test.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) 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 abe2bdf0d7e..63a360715d9 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -2,16 +2,14 @@ import rule from '../../src/rules/no-floating-promises'; import { RuleTester, getFixturesRootDir } from '../RuleTester'; const rootDir = getFixturesRootDir(); -const parserOptions = { - ecmaVersion: 2018, - tsconfigRootDir: rootDir, - project: './tsconfig.json', -}; - const messageId = 'floating'; const ruleTester = new RuleTester({ - parserOptions, + parserOptions: { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, parser: '@typescript-eslint/parser', }); From f7601d56e5479a85f3572efe099a7bbecf31a5bd Mon Sep 17 00:00:00 2001 From: Jeff Principe Date: Sun, 9 Jun 2019 13:02:15 -0700 Subject: [PATCH 4/5] chore: fix lint error --- packages/eslint-plugin/src/rules/no-floating-promises.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 737c68531d0..968bcb88102 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -142,7 +142,7 @@ function isFunctionParam( param: ts.Symbol, node: ts.Node, ): boolean { - let type: ts.Type | undefined = checker.getApparentType( + const type: ts.Type | undefined = checker.getApparentType( checker.getTypeOfSymbolAtLocation(param, node), ); for (const t of tsutils.unionTypeParts(type)) { From def11d3794d723d2c51cb15ef95606f95ac3171d Mon Sep 17 00:00:00 2001 From: Jeff Principe Date: Sun, 9 Jun 2019 13:13:30 -0700 Subject: [PATCH 5/5] chore: fix docs mismatch --- packages/eslint-plugin/src/rules/no-floating-promises.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 968bcb88102..1c8cf412c07 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -7,8 +7,7 @@ export default util.createRule({ name: 'no-floating-promises', meta: { docs: { - description: - 'Requires promises to be awaited or have a rejection handler.', + description: 'Requires Promise-like values to be handled appropriately.', category: 'Best Practices', recommended: false, },