From 951a4fc452984557660f8c63482727bf9e102e9a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 2 Feb 2019 21:02:52 -0500 Subject: [PATCH 01/13] feat(eslint-plugin) Added await-promise rule Adds the equivalent of TSLint's `await-promise` rule. --- packages/eslint-plugin/README.md | 1 + packages/eslint-plugin/ROADMAP.md | 29 +-- .../eslint-plugin/docs/rules/await-promise.md | 83 +++++++++ .../eslint-plugin/lib/rules/await-promise.js | 124 +++++++++++++ .../tests/lib/rules/await-promise.js | 167 ++++++++++++++++++ 5 files changed, 390 insertions(+), 14 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/await-promise.md create mode 100644 packages/eslint-plugin/lib/rules/await-promise.js create mode 100644 packages/eslint-plugin/tests/lib/rules/await-promise.js diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 1ec6df21604..6474ff6cf72 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -96,6 +96,7 @@ Install [`eslint-config-prettier`](https://github.com/prettier/eslint-config-pre | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -------- | | [`@typescript-eslint/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive (`adjacent-overload-signatures` from TSLint) | :heavy_check_mark: | | | [`@typescript-eslint/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays (`array-type` from TSLint) | :heavy_check_mark: | :wrench: | +| [`@typescript-eslint/await-promise`](./docs/rules/await-promise.md) | Disallow awaiting a value that is not a Promise (`await-promise` from TSLint) | :heavy_check_mark: | :wrench: | | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used (`ban-types` from TSLint) | :heavy_check_mark: | :wrench: | | [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | | [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names (`class-name` from TSLint) | :heavy_check_mark: | | diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index 378a3140ffb..49b7a296775 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -1,10 +1,10 @@ ο»Ώ# Roadmap -βœ… (28) = done -🌟 (79) = in ESLint core -πŸ”Œ (33) = in another plugin -πŸŒ“ (16) = implementations differ or ESLint version is missing functionality -πŸ›‘ (70) = unimplemented +βœ… (28) = done +🌟 (79) = in ESLint core +πŸ”Œ (33) = in another plugin +πŸŒ“ (17) = implementations differ or ESLint version is missing functionality +πŸ›‘ (69) = unimplemented ## TSLint rules @@ -39,7 +39,7 @@ | TSLint rule | | ESLint rule | | ------------------------------------ | :-: | --------------------------------------------------------------------- | -| [`await-promise`] | πŸ›‘ | N/A | +| [`await-promise`] | βœ… | [`@typescript-eslint/no-misused-new`] | | [`ban-comma-operator`] | 🌟 | [`no-sequences`][no-sequences] | | [`ban`] | 🌟 | [`no-restricted-properties`][no-restricted-properties] | | [`curly`] | 🌟 | [`curly`][curly] | @@ -96,7 +96,7 @@ | [`use-default-type-parameter`] | πŸ›‘ | N/A | | [`use-isnan`] | 🌟 | [`use-isnan`][use-isnan] | -[1] The ESLint rule also supports silencing with an extra set of parens (`if ((foo = bar)) {}`) +[1] The ESLint rule also supports silencing with an extra set of parens (`if ((foo = bar)) {}`) [2] Missing private class member support. [`@typescript-eslint/no-unused-vars`] adds support for some TS-specific features. ### Maintainability @@ -120,7 +120,7 @@ | [`prefer-readonly`] | πŸ›‘ | N/A | | [`trailing-comma`] | πŸŒ“ | [`comma-dangle`][comma-dangle] or [Prettier] | -[1] Only warns when importing deprecated symbols +[1] Only warns when importing deprecated symbols [2] Missing support for blank-line-delimited sections ### Style @@ -179,7 +179,7 @@ | [`variable-name`] | 🌟 | [2] | | [`whitespace`] | πŸ”Œ | Use [Prettier] | -[1] Recommended config: `["error", { blankLine: "always", prev: "*", next: "return" }]` +[1] Recommended config: `["error", { blankLine: "always", prev: "*", next: "return" }]` [2] [`camelcase`][camelcase], [`no-underscore-dangle`][no-underscore-dangle], [`id-blacklist`][id-blacklist], and/or [`id-match`][id-match] ## tslint-microsoft-contrib rules @@ -245,10 +245,10 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- | `use-named-parameter` | πŸ›‘ | N/A | | `use-simple-attributes` | πŸ›‘ | N/A | -[1] Enforces blank lines both at the beginning and end of a block -[2] Recommended config: `["error", "ForInStatement"]` -[3] Recommended config: `["error", "declaration", { "allowArrowFunctions": true }]` -[4] Recommended config: `["error", { "terms": ["BUG", "HACK", "FIXME", "LATER", "LATER2", "TODO"], "location": "anywhere" }]` +[1] Enforces blank lines both at the beginning and end of a block +[2] Recommended config: `["error", "ForInStatement"]` +[3] Recommended config: `["error", "declaration", { "allowArrowFunctions": true }]` +[4] Recommended config: `["error", { "terms": ["BUG", "HACK", "FIXME", "LATER", "LATER2", "TODO"], "location": "anywhere" }]` [5] Does not check class fields. [insecure-random]: https://github.com/desktop/desktop/blob/master/eslint-rules/insecure-random.js @@ -310,7 +310,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- | `react-a11y-titles` | πŸ›‘ | N/A | | `react-anchor-blank-noopener` | πŸ›‘ | N/A | -[1] TSLint rule is more strict +[1] TSLint rule is more strict [2] ESLint rule only reports for click handlers [prettier]: https://prettier.io @@ -558,6 +558,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/adjacent-overload-signatures`]: https://github.com/bradzacher/@typescript-eslint/eslint-plugin/blob/master/docs/rules/adjacent-overload-signatures.md +[`@typescript-eslint/await-promise`]: https://github.com/bradzacher/@typescript-eslint/eslint-plugin/blob/master/docs/rules/await-promise.md [`@typescript-eslint/ban-types`]: https://github.com/bradzacher/@typescript-eslint/eslint-plugin/blob/master/docs/rules/ban-types.md [`@typescript-eslint/explicit-member-accessibility`]: https://github.com/bradzacher/@typescript-eslint/eslint-plugin/blob/master/docs/rules/explicit-member-accessibility.md [`@typescript-eslint/member-ordering`]: https://github.com/bradzacher/@typescript-eslint/eslint-plugin/blob/master/docs/rules/member-ordering.md diff --git a/packages/eslint-plugin/docs/rules/await-promise.md b/packages/eslint-plugin/docs/rules/await-promise.md new file mode 100644 index 00000000000..3deb893e04c --- /dev/null +++ b/packages/eslint-plugin/docs/rules/await-promise.md @@ -0,0 +1,83 @@ +# Disallows awaiting a value that is not a Promise (await-promise) + +This rule disallows awaiting a value that is not a Promise. +While it is valid JavaScript to await a non-`Promise`-like value (it will resolve immediately), this pattern is often a programmer error, such as forgetting to add parenthesis to call a function that returns a Promise. + +## Rule Details + +Examples of **incorrect** code for this rule: + +```ts +await 'value'; + +const createValue = () => 'value'; +await createValue(); +``` + +```ts +// An array of Promises is not the same as an AsyncIterable +async function incorrect(arrayOfPromises: Array) { + for await (const element of arrayOfPromises) {} +} +``` + +Examples of **correct** code for this rule: + +```ts +await Promise.resolve('value'); + +const createValue = (async() = 'value'); +await createValue(); +``` + +```ts +async function overIterable(iterable: AsyncIterable) { + for await (const element of iterable) { + } +} + +async function overIterableIterator(iterable: AsyncIterableIterator) { + for await (const element of iterable) { + } +} +``` + +## Options + +The rule accepts an options object with the following property: + +- `allowedPromiseNames` any extra names of classes or interfaces to be considered "awaitable" in `await` statements. + +Classes named `Promise` may always be awaited. +`allowedPromiseNames` does not affect `for-await-of` statements. + +### allowedPromiseNames + +Examples of **incorrect** code for this rule with `{ allowedPromiseNames: ["Thenable"] }`: + +```ts +class Thenable { + /* ... */ +} + +await new Thenable(); +``` + +Examples of **incorrect** code for this rule with `{ allowedPromiseNames: ["Thenable"] }`: + +```ts +class OtherClass { + /* ... */ +} + +await new OtherClass(); +``` + +## When Not To Use It + +If you want to allow code to `await` non-Promise values. +This is generally not preferred, but can sometimes be useful for visual consistency. + +## Related to + +- TSLint: ['await-promise'](https://palantir.github.io/tslint/rules/await-promise) diff --git a/packages/eslint-plugin/lib/rules/await-promise.js b/packages/eslint-plugin/lib/rules/await-promise.js new file mode 100644 index 00000000000..08f46cd37e6 --- /dev/null +++ b/packages/eslint-plugin/lib/rules/await-promise.js @@ -0,0 +1,124 @@ +/** + * @fileoverview Disallows awaiting a value that is not a Promise + * @author Josh Goldberg + */ +'use strict'; +const tsutils = require('tsutils'); +const ts = require('typescript'); +const util = require('../util'); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const defaultOptions = [ + { + allowedPromiseNames: [] + } +]; + +/** + * @type {import("eslint").Rule.RuleModule} + */ +module.exports = { + meta: { + docs: { + description: 'Disallows awaiting a value that is not a Promise', + category: 'Functionality', + recommended: 'error', + extraDescription: [util.tslintRule('await-promise')], + url: util.metaDocsUrl('await-promise') + }, + fixable: null, + messages: { + await: 'Invalid `await` of a non-Promise value.', + forOf: 'Invalid `for-await-of` of a non-AsyncIterable value.' + }, + schema: [ + { + type: 'object', + properties: { + allowedPromiseNames: { + type: 'array', + items: { + type: 'string' + } + } + }, + additionalProperties: false + } + ], + type: 'problem' + }, + + create(context) { + const options = util.applyDefault(defaultOptions, context.options)[0]; + + const allowedAsyncIterableNames = new Set([ + 'AsyncIterable', + 'AsyncIterableIterator' + ]); + + const allowedPromiseNames = new Set([ + 'Promise', + ...options.allowedPromiseNames + ]); + + const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + + function validateNode(node, allowedSymbolNames, messageId) { + const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const type = checker.getTypeAtLocation(originalNode.expression); + + if (!containsType(type, allowedSymbolNames)) { + context.report({ + messageId, + node + }); + } + } + + return { + AwaitExpression(node) { + validateNode(node, allowedPromiseNames, 'await'); + }, + ForOfStatement(node) { + if (node.await) { + validateNode(node, allowedAsyncIterableNames, 'forOf'); + } + } + }; + } +}; + +/** + * @param {string} type Type being awaited upon. + * @param {Set} allowedNames Symbol names being checked for. + */ +function containsType(type, allowedNames) { + if (tsutils.isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { + return true; + } + + if (tsutils.isTypeReference(type)) { + type = type.target; + } + + if ( + typeof type.symbol !== 'undefined' && + allowedNames.has(type.symbol.name) + ) { + return true; + } + + if (tsutils.isUnionOrIntersectionType(type)) { + return type.types.some(t => containsType(t, allowedNames)); + } + + const bases = type.getBaseTypes(); + return ( + typeof bases !== 'undefined' && + bases.some(t => containsType(t, allowedNames)) + ); +} diff --git a/packages/eslint-plugin/tests/lib/rules/await-promise.js b/packages/eslint-plugin/tests/lib/rules/await-promise.js new file mode 100644 index 00000000000..7c70c92ed9c --- /dev/null +++ b/packages/eslint-plugin/tests/lib/rules/await-promise.js @@ -0,0 +1,167 @@ +/** + * @fileoverview Disallows awaiting a value that is not a Promise + * @author Josh Goldberg + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/await-promise'), + RuleTester = require('eslint').RuleTester, + path = require('path'); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const rootDir = path.join(process.cwd(), 'tests/fixtures/'); +const parserOptions = { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json' +}; +const ruleTester = new RuleTester({ + parserOptions, + parser: '@typescript-eslint/parser' +}); +const messages = { + await: 'Invalid `await` of a non-Promise value.', + forOf: 'Invalid `for-await-of` of a non-AsyncIterable value.' +}; + +ruleTester.run('await-promise', rule, { + valid: [ + ` +async function test() { + await Promise.resolve("value"); + await Promise.reject(new Error("message")); + + await (async () => true)(); + + function returnsPromise() { + return Promise.resolve("value"); + } + await returnsPromise(); + + async function returnsPromiseAsync() {} + await returnsPromiseAsync(); + + let anyValue: any; + await anyValue; + + let unknownValue: unknown; + await unknownValue; + + const numberPromise: Promise; + await numberPromise; + + class Foo extends Promise {} + const foo: Foo = Foo.resolve(2); + await foo; + + class Bar extends Foo {} + const bar: Bar = Bar.resolve(2); + await bar; + + 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; +} +`, + { + code: ` +class AllowedCustomClass { } + +async function test() { + await new AllowedCustomClass(); +} +`, + options: [ + { + allowedPromiseNames: ['AllowedCustomClass'] + } + ] + }, + ` +async function correct(foo: AsyncIterableIterator) { + for await (const element of foo) {} +} + +async function correct2() { + for await (const element of asyncGenerator()) {} +} + +async function correct(foo: AsyncIterable) { + for await (const element of foo) {} +} + +async function correct3(foo: AsyncIterableIterator | AsyncIterableIterator) { + for await (const element of foo) {} +} + ` + ], + + invalid: [ + { + code: ` +async function test() { + await 0; + await "value"; + + await (Math.random() > 0.5 ? "" : 0); + + class NonPromise extends Array {} + await new NonPromise(); +} +`, + errors: [ + { + line: 3, + message: messages.await, + type: 'AwaitExpression' + }, + { + line: 4, + message: messages.await, + type: 'AwaitExpression' + }, + { + line: 6, + message: messages.await, + type: 'AwaitExpression' + }, + { + line: 9, + message: messages.await, + type: 'AwaitExpression' + } + ] + }, + { + code: ` +class NotAllowedCustomClass { } + +function test() { + await new NotAllowedCustomClass(); +} +`, + errors: [ + { + line: 5, + message: messages.await, + type: 'AwaitExpression' + } + ], + options: [ + { + allowedPromiseNames: ['AllowedCustomClass'] + } + ] + } + ] +}); From c821cc197ebed32b7e9cedbb3c5bc42896ceb018 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Sun, 3 Feb 2019 08:34:12 -0500 Subject: [PATCH 02/13] Update packages/eslint-plugin/docs/rules/await-promise.md Co-Authored-By: JoshuaKGoldberg --- packages/eslint-plugin/docs/rules/await-promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/await-promise.md b/packages/eslint-plugin/docs/rules/await-promise.md index 3deb893e04c..9b3152ecbaf 100644 --- a/packages/eslint-plugin/docs/rules/await-promise.md +++ b/packages/eslint-plugin/docs/rules/await-promise.md @@ -16,7 +16,7 @@ await createValue(); ```ts // An array of Promises is not the same as an AsyncIterable -async function incorrect(arrayOfPromises: Array) { +async function incorrect(arrayOfPromises: Array>) { for await (const element of arrayOfPromises) {} } ``` From 62a6a718bc04ccdf341c393ec40bbbe31f1a9936 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 3 Feb 2019 09:51:45 -0500 Subject: [PATCH 03/13] Fixed formatting complaint, I think? --- packages/eslint-plugin/docs/rules/await-promise.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/await-promise.md b/packages/eslint-plugin/docs/rules/await-promise.md index 9b3152ecbaf..7980cf92c9f 100644 --- a/packages/eslint-plugin/docs/rules/await-promise.md +++ b/packages/eslint-plugin/docs/rules/await-promise.md @@ -17,7 +17,8 @@ await createValue(); ```ts // An array of Promises is not the same as an AsyncIterable async function incorrect(arrayOfPromises: Array>) { - for await (const element of arrayOfPromises) {} + for await (const element of arrayOfPromises) { + } } ``` From 7c386cca30cc6847677695433f26448b79268404 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 3 Feb 2019 16:02:33 -0500 Subject: [PATCH 04/13] Whoops, docs typo --- packages/eslint-plugin/docs/rules/await-promise.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/await-promise.md b/packages/eslint-plugin/docs/rules/await-promise.md index 7980cf92c9f..8408d7f1e85 100644 --- a/packages/eslint-plugin/docs/rules/await-promise.md +++ b/packages/eslint-plugin/docs/rules/await-promise.md @@ -64,7 +64,7 @@ class Thenable { await new Thenable(); ``` -Examples of **incorrect** code for this rule with `{ allowedPromiseNames: ["Thenable"] }`: +Examples of **correct** code for this rule with `{ allowedPromiseNames: ["Thenable"] }`: ```ts class OtherClass { From 53163137669360a63edde4efed5c11657baf7e49 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 3 Feb 2019 17:14:29 -0500 Subject: [PATCH 05/13] 30 done, not 29 --- packages/eslint-plugin/ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index 1b050e86fd3..73125ecbd6b 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -1,6 +1,6 @@ ο»Ώ# Roadmap -βœ… (29) = done
+βœ… (30) = done
🌟 (79) = in ESLint core
πŸ”Œ (33) = in another plugin
πŸŒ“ (16) = implementations differ or ESLint version is missing functionality
From b8bd73e5b0a49d36360922f9f00687b786a05d89 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 9 Feb 2019 11:32:42 -0500 Subject: [PATCH 06/13] Fixed ROADMAP.md links for await-promise --- packages/eslint-plugin/ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index a5c9ce6b577..71f8405131a 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -39,7 +39,7 @@ | TSLint rule | | ESLint rule | | ------------------------------------ | :-: | --------------------------------------------------------------------- | -| [`await-promise`] | βœ… | [`@typescript-eslint/no-misused-new`] | +| [`await-promise`] | βœ… | [`await-promise`][await-promise] | | [`ban-comma-operator`] | 🌟 | [`no-sequences`][no-sequences] | | [`ban`] | 🌟 | [`no-restricted-properties`][no-restricted-properties] | | [`curly`] | 🌟 | [`curly`][curly] | From 364633196312e9253dc05d3407fcc2f544417271 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sat, 9 Feb 2019 11:46:31 -0500 Subject: [PATCH 07/13] Removed async iterable checking --- .../eslint-plugin/docs/rules/await-promise.md | 31 ++--------- .../eslint-plugin/lib/rules/await-promise.js | 33 ++++-------- .../tests/lib/rules/await-promise.js | 51 +------------------ 3 files changed, 15 insertions(+), 100 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/await-promise.md b/packages/eslint-plugin/docs/rules/await-promise.md index 8408d7f1e85..24f7c928283 100644 --- a/packages/eslint-plugin/docs/rules/await-promise.md +++ b/packages/eslint-plugin/docs/rules/await-promise.md @@ -14,14 +14,6 @@ const createValue = () => 'value'; await createValue(); ``` -```ts -// An array of Promises is not the same as an AsyncIterable -async function incorrect(arrayOfPromises: Array>) { - for await (const element of arrayOfPromises) { - } -} -``` - Examples of **correct** code for this rule: ```ts @@ -31,47 +23,34 @@ const createValue = (async() = 'value'); await createValue(); ``` -```ts -async function overIterable(iterable: AsyncIterable) { - for await (const element of iterable) { - } -} - -async function overIterableIterator(iterable: AsyncIterableIterator) { - for await (const element of iterable) { - } -} -``` - ## Options The rule accepts an options object with the following property: - `allowedPromiseNames` any extra names of classes or interfaces to be considered "awaitable" in `await` statements. -Classes named `Promise` may always be awaited. -`allowedPromiseNames` does not affect `for-await-of` statements. +Classes and interfaces named `Promise` are always allowed in `await` statements. ### allowedPromiseNames Examples of **incorrect** code for this rule with `{ allowedPromiseNames: ["Thenable"] }`: ```ts -class Thenable { +class OtherClass { /* ... */ } -await new Thenable(); +await new OtherClass(); ``` Examples of **correct** code for this rule with `{ allowedPromiseNames: ["Thenable"] }`: ```ts -class OtherClass { +class Thenable { /* ... */ } -await new OtherClass(); +await new Thenable(); ``` ## When Not To Use It diff --git a/packages/eslint-plugin/lib/rules/await-promise.js b/packages/eslint-plugin/lib/rules/await-promise.js index e0e71aeb0f1..21e023f7528 100644 --- a/packages/eslint-plugin/lib/rules/await-promise.js +++ b/packages/eslint-plugin/lib/rules/await-promise.js @@ -30,8 +30,7 @@ module.exports = { }, fixable: null, messages: { - await: 'Invalid `await` of a non-Promise value.', - forOf: 'Invalid `for-await-of` of a non-AsyncIterable value.' + await: 'Invalid `await` of a non-Promise value.' }, schema: [ { @@ -53,11 +52,6 @@ module.exports = { create(context) { const options = util.applyDefault(defaultOptions, context.options)[0]; - const allowedAsyncIterableNames = new Set([ - 'AsyncIterable', - 'AsyncIterableIterator' - ]); - const allowedPromiseNames = new Set([ 'Promise', ...options.allowedPromiseNames @@ -66,25 +60,16 @@ module.exports = { const parserServices = util.getParserServices(context); const checker = parserServices.program.getTypeChecker(); - function validateNode(node, allowedSymbolNames, messageId) { - const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(originalNode.expression); - - if (!types.containsTypeByName(type, allowedSymbolNames)) { - context.report({ - messageId, - node - }); - } - } - return { AwaitExpression(node) { - validateNode(node, allowedPromiseNames, 'await'); - }, - ForOfStatement(node) { - if (node.await) { - validateNode(node, allowedAsyncIterableNames, 'forOf'); + const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const type = checker.getTypeAtLocation(originalNode.expression); + + if (!types.containsTypeByName(type, allowedPromiseNames)) { + context.report({ + messageId, + node + }); } } }; diff --git a/packages/eslint-plugin/tests/lib/rules/await-promise.js b/packages/eslint-plugin/tests/lib/rules/await-promise.js index b5f93e1e61b..5683c279551 100644 --- a/packages/eslint-plugin/tests/lib/rules/await-promise.js +++ b/packages/eslint-plugin/tests/lib/rules/await-promise.js @@ -82,24 +82,7 @@ async function test() { allowedPromiseNames: ['AllowedCustomClass'] } ] - }, - ` -async function correct(foo: AsyncIterableIterator) { - for await (const element of foo) {} -} - -async function correct2() { - for await (const element of asyncGenerator()) {} -} - -async function correct(foo: AsyncIterable) { - for await (const element of foo) {} -} - -async function correct3(foo: AsyncIterableIterator | AsyncIterableIterator) { - for await (const element of foo) {} -} - ` + } ], invalid: [ @@ -158,38 +141,6 @@ function test() { allowedPromiseNames: ['AllowedCustomClass'] } ] - }, - { - code: ` -async function incorrect(foo: Array>) { - for await (const element of foo) {} -} - -async function incorrect2(foo: IterableIterator>) { - for await (const element of foo) {} -} - -async function incorrect5(foo: Iterable) { - for await (const element of foo) {} -} -`, - errors: [ - { - line: 3, - messageId: 'forOf', - type: 'ForOfStatement' - }, - { - line: 7, - messageId: 'forOf', - type: 'ForOfStatement' - }, - { - line: 11, - messageId: 'forOf', - type: 'ForOfStatement' - } - ] } ] }); From d5726e64f0798bd8fcaadacdb5c79d62c4e74484 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Sun, 24 Feb 2019 15:51:46 -0500 Subject: [PATCH 08/13] Converted to TypeScript; used tsutils.isThenableType --- .../{await-promise.md => await-thenable.md} | 34 +-- .../eslint-plugin/lib/rules/await-promise.js | 77 ------ .../eslint-plugin/src/rules/await-thenable.ts | 47 ++++ .../tests/lib/rules/await-promise.js | 146 ------------ .../tests/rules/await-thenable.test.ts | 223 ++++++++++++++++++ 5 files changed, 272 insertions(+), 255 deletions(-) rename packages/eslint-plugin/docs/rules/{await-promise.md => await-thenable.md} (51%) delete mode 100644 packages/eslint-plugin/lib/rules/await-promise.js create mode 100644 packages/eslint-plugin/src/rules/await-thenable.ts delete mode 100644 packages/eslint-plugin/tests/lib/rules/await-promise.js create mode 100644 packages/eslint-plugin/tests/rules/await-thenable.test.ts diff --git a/packages/eslint-plugin/docs/rules/await-promise.md b/packages/eslint-plugin/docs/rules/await-thenable.md similarity index 51% rename from packages/eslint-plugin/docs/rules/await-promise.md rename to packages/eslint-plugin/docs/rules/await-thenable.md index 24f7c928283..bbfa5668c10 100644 --- a/packages/eslint-plugin/docs/rules/await-promise.md +++ b/packages/eslint-plugin/docs/rules/await-thenable.md @@ -1,6 +1,6 @@ -# Disallows awaiting a value that is not a Promise (await-promise) +# Disallows awaiting a value that is not a Promise (await-thenable) -This rule disallows awaiting a value that is not a Promise. +This rule disallows awaiting a value that is not a "Thenable" (commonly, a Promise). While it is valid JavaScript to await a non-`Promise`-like value (it will resolve immediately), this pattern is often a programmer error, such as forgetting to add parenthesis to call a function that returns a Promise. ## Rule Details @@ -23,36 +23,6 @@ const createValue = (async() = 'value'); await createValue(); ``` -## Options - -The rule accepts an options object with the following property: - -- `allowedPromiseNames` any extra names of classes or interfaces to be considered "awaitable" in `await` statements. - -Classes and interfaces named `Promise` are always allowed in `await` statements. - -### allowedPromiseNames - -Examples of **incorrect** code for this rule with `{ allowedPromiseNames: ["Thenable"] }`: - -```ts -class OtherClass { - /* ... */ -} - -await new OtherClass(); -``` - -Examples of **correct** code for this rule with `{ allowedPromiseNames: ["Thenable"] }`: - -```ts -class Thenable { - /* ... */ -} - -await new Thenable(); -``` - ## When Not To Use It If you want to allow code to `await` non-Promise values. diff --git a/packages/eslint-plugin/lib/rules/await-promise.js b/packages/eslint-plugin/lib/rules/await-promise.js deleted file mode 100644 index 21e023f7528..00000000000 --- a/packages/eslint-plugin/lib/rules/await-promise.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @fileoverview Disallows awaiting a value that is not a Promise - * @author Josh Goldberg - */ -'use strict'; -const util = require('../util'); -const types = require('../utils/types'); - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -const defaultOptions = [ - { - allowedPromiseNames: [] - } -]; - -/** - * @type {import("eslint").Rule.RuleModule} - */ -module.exports = { - meta: { - docs: { - description: 'Disallows awaiting a value that is not a Promise', - category: 'Functionality', - recommended: 'error', - extraDescription: [util.tslintRule('await-promise')], - url: util.metaDocsUrl('await-promise') - }, - fixable: null, - messages: { - await: 'Invalid `await` of a non-Promise value.' - }, - schema: [ - { - type: 'object', - properties: { - allowedPromiseNames: { - type: 'array', - items: { - type: 'string' - } - } - }, - additionalProperties: false - } - ], - type: 'problem' - }, - - create(context) { - const options = util.applyDefault(defaultOptions, context.options)[0]; - - const allowedPromiseNames = new Set([ - 'Promise', - ...options.allowedPromiseNames - ]); - - const parserServices = util.getParserServices(context); - const checker = parserServices.program.getTypeChecker(); - - return { - AwaitExpression(node) { - const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(originalNode.expression); - - if (!types.containsTypeByName(type, allowedPromiseNames)) { - context.report({ - messageId, - node - }); - } - } - }; - } -}; diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts new file mode 100644 index 00000000000..f0d748579d2 --- /dev/null +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -0,0 +1,47 @@ +import * as tsutils from 'tsutils'; +import * as ts from 'typescript'; + +import * as util from '../util'; + +export default util.createRule({ + name: 'await-thenable', + meta: { + docs: { + description: 'Disallows awaiting a value that is not a Thenable', + category: 'Best Practices', + recommended: 'error', + tslintName: 'await-thenable', + }, + messages: { + await: 'Invalid `await` of a non-Promise (non-"Thenable") value.', + }, + schema: [], + type: 'problem', + }, + defaultOptions: [], + + create(context) { + const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + + return { + AwaitExpression(node) { + const originalNode = parserServices.esTreeNodeToTSNodeMap.get( + node, + ) as ts.AwaitExpression; + const type = checker.getTypeAtLocation(originalNode.expression); + + if ( + !tsutils.isTypeFlagSet(type, ts.TypeFlags.Any) && + !tsutils.isTypeFlagSet(type, ts.TypeFlags.Unknown) && + !tsutils.isThenableType(checker, originalNode.expression, type) + ) { + context.report({ + messageId: 'await', + node, + }); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/lib/rules/await-promise.js b/packages/eslint-plugin/tests/lib/rules/await-promise.js deleted file mode 100644 index 5683c279551..00000000000 --- a/packages/eslint-plugin/tests/lib/rules/await-promise.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @fileoverview Disallows awaiting a value that is not a Promise - * @author Josh Goldberg - */ -'use strict'; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const rule = require('../../../lib/rules/await-promise'), - RuleTester = require('eslint').RuleTester, - path = require('path'); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -const rootDir = path.join(process.cwd(), 'tests/fixtures/'); -const parserOptions = { - ecmaVersion: 2018, - tsconfigRootDir: rootDir, - project: './tsconfig.json' -}; -const ruleTester = new RuleTester({ - parserOptions, - parser: '@typescript-eslint/parser' -}); - -ruleTester.run('await-promise', rule, { - valid: [ - ` -async function test() { - await Promise.resolve("value"); - await Promise.reject(new Error("message")); - - await (async () => true)(); - - function returnsPromise() { - return Promise.resolve("value"); - } - await returnsPromise(); - - async function returnsPromiseAsync() {} - await returnsPromiseAsync(); - - let anyValue: any; - await anyValue; - - let unknownValue: unknown; - await unknownValue; - - const numberPromise: Promise; - await numberPromise; - - class Foo extends Promise {} - const foo: Foo = Foo.resolve(2); - await foo; - - class Bar extends Foo {} - const bar: Bar = Bar.resolve(2); - await bar; - - 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; -} -`, - { - code: ` -class AllowedCustomClass { } - -async function test() { - await new AllowedCustomClass(); -} -`, - options: [ - { - allowedPromiseNames: ['AllowedCustomClass'] - } - ] - } - ], - - invalid: [ - { - code: ` -async function test() { - await 0; - await "value"; - - await (Math.random() > 0.5 ? "" : 0); - - class NonPromise extends Array {} - await new NonPromise(); -} -`, - errors: [ - { - line: 3, - messageId: 'await', - type: 'AwaitExpression' - }, - { - line: 4, - messageId: 'await', - type: 'AwaitExpression' - }, - { - line: 6, - messageId: 'await', - type: 'AwaitExpression' - }, - { - line: 9, - messageId: 'await', - type: 'AwaitExpression' - } - ] - }, - { - code: ` -class NotAllowedCustomClass { } - -function test() { - await new NotAllowedCustomClass(); -} -`, - errors: [ - { - line: 5, - messageId: 'await', - type: 'AwaitExpression' - } - ], - options: [ - { - allowedPromiseNames: ['AllowedCustomClass'] - } - ] - } - ] -}); diff --git a/packages/eslint-plugin/tests/rules/await-thenable.test.ts b/packages/eslint-plugin/tests/rules/await-thenable.test.ts new file mode 100644 index 00000000000..deca521aefa --- /dev/null +++ b/packages/eslint-plugin/tests/rules/await-thenable.test.ts @@ -0,0 +1,223 @@ +import rule from '../../src/rules/await-thenable'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); +const parserOptions = { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json', +}; + +const messageId = 'await'; + +const ruleTester = new RuleTester({ + parserOptions, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('await-promise', rule, { + valid: [ + ` +async function test() { + await Promise.resolve("value"); + await Promise.reject(new Error("message")); +} +`, + ` +async function test() { + await (async () => true)(); +} +`, + ` +async function test() { + function returnsPromise() { + return Promise.resolve("value"); + } + await returnsPromise(); +} +`, + ` +async function test() { + async function returnsPromiseAsync() {} + await returnsPromiseAsync(); +} +`, + ` +async function test() { + let anyValue: any; + await anyValue; +} +`, + ` +async function test() { + let unknownValue: unknown; + await unknownValue; +} +`, + ` +async function test() { + const numberPromise: Promise; + await numberPromise; +} +`, + ` +async function test() { + class Foo extends Promise {} + const foo: Foo = Foo.resolve(2); + await foo; + + class Bar extends Foo {} + const bar: Bar = Bar.resolve(2); + await bar; +} +`, + ` +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: () => {}) { } }; + const thenable = new Thenable(); + + await 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; +} +`, + ` +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/bluebird/index.d.ts +// Type definitions for bluebird 3.5 +// Project: https://github.com/petkaantonov/bluebird +// Definitions by: Leonard Hecker +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.8 + +/*! + * The code following this comment originates from: + * https://github.com/types/npm-bluebird + * + * Note for browser users: use bluebird-global typings instead of this one + * if you want to use Bluebird via the global Promise symbol. + * + * Licensed under: + * The MIT License (MIT) + * + * Copyright (c) 2016 unional + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +type Constructor = new (...args: any[]) => E; +type CatchFilter = ((error: E) => boolean) | (object & E); +type IterableItem = R extends Iterable ? U : never; +type IterableOrNever = Extract>; +type Resolvable = R | PromiseLike; +type IterateFunction = (item: T, index: number, arrayLength: number) => Resolvable; + +declare class Bluebird implements PromiseLike { + then(onFulfill?: (value: R) => Resolvable, onReject?: (error: any) => Resolvable): Bluebird; // For simpler signature help. + then( + onfulfilled?: ((value: R) => Resolvable) | null, + onrejected?: ((reason: any) => Resolvable) | null + ): Bluebird; +} + +declare const bluebird: Bluebird; + +async function test() { + await bluebird; +} +`, + ], + + invalid: [ + { + code: ` +async function test() { + await 0; + await "value"; + + await (Math.random() > 0.5 ? "" : 0); + + class NonPromise extends Array {} + await new NonPromise(); +} +`, + errors: [ + { + line: 3, + messageId, + }, + { + line: 4, + messageId, + }, + { + line: 6, + messageId, + }, + { + line: 9, + messageId, + }, + ], + }, + { + code: ` +async function test() { + class IncorrectThenable { then() { } }; + const thenable = new IncorrectThenable(); + + await thenable; +} + `, + errors: [ + { + line: 6, + messageId, + }, + ], + }, + ], +}); From 61cfe81419761c3b95ec3573c08e8ffa6f949fb2 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Wed, 3 Apr 2019 08:08:29 -0400 Subject: [PATCH 09/13] Update packages/eslint-plugin/README.md Co-Authored-By: JoshuaKGoldberg --- packages/eslint-plugin/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 637a7abc2cd..5883b13d6f3 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -112,7 +112,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -------- | | [`@typescript-eslint/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive (`adjacent-overload-signatures` from TSLint) | :heavy_check_mark: | | | [`@typescript-eslint/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays (`array-type` from TSLint) | :heavy_check_mark: | :wrench: | -| [`@typescript-eslint/await-promise`](./docs/rules/await-promise.md) | Disallow awaiting a value that is not a Promise (`await-promise` from TSLint) | :heavy_check_mark: | | +| [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallow awaiting a value that is not a Promise (`await-promise` from TSLint) | :heavy_check_mark: | | | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used (`ban-types` from TSLint) | :heavy_check_mark: | :wrench: | | [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans β€œ// @ts-ignore” comments from being used (`ban-ts-ignore` from TSLint) | :heavy_check_mark: | | | [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | From 30585b3473f9511d1ac6c4ecc1b88a4d02a44d43 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Wed, 3 Apr 2019 08:08:56 -0400 Subject: [PATCH 10/13] Update packages/eslint-plugin/docs/rules/await-thenable.md Co-Authored-By: JoshuaKGoldberg --- packages/eslint-plugin/docs/rules/await-thenable.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/await-thenable.md b/packages/eslint-plugin/docs/rules/await-thenable.md index bbfa5668c10..0bfde1f5842 100644 --- a/packages/eslint-plugin/docs/rules/await-thenable.md +++ b/packages/eslint-plugin/docs/rules/await-thenable.md @@ -19,7 +19,7 @@ Examples of **correct** code for this rule: ```ts await Promise.resolve('value'); -const createValue = (async() = 'value'); +const createValue = (async () => 'value'); await createValue(); ``` From 4731cd353f1847167316c2bcdae6ea3b3a6949b7 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Wed, 3 Apr 2019 08:09:10 -0400 Subject: [PATCH 11/13] Update packages/eslint-plugin/ROADMAP.md Co-Authored-By: JoshuaKGoldberg --- packages/eslint-plugin/ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index 37d288f4d98..d17a69580ac 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -574,7 +574,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/adjacent-overload-signatures`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md -[`@typescript-eslint/await-promise`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/await-promise.md +[`@typescript-eslint/await-thenable`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/await-thenable.md [`@typescript-eslint/ban-types`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md [`@typescript-eslint/ban-ts-ignore`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-ts-ignore.md [`@typescript-eslint/explicit-member-accessibility`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md From 89ab9ba60e6343f4f0702373b58fcc67eac3b13d Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Wed, 3 Apr 2019 08:09:23 -0400 Subject: [PATCH 12/13] Update packages/eslint-plugin/ROADMAP.md Co-Authored-By: JoshuaKGoldberg --- packages/eslint-plugin/ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index d17a69580ac..d9e7234f21a 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -40,7 +40,7 @@ | TSLint rule | | ESLint rule | | ------------------------------------ | :-: | --------------------------------------------------------------------- | -| [`await-promise`] | βœ… | [`await-promise`][await-promise] | +| [`await-promise`] | βœ… | [`@typescript-eslint/await-thenable`] | | [`ban-comma-operator`] | 🌟 | [`no-sequences`][no-sequences] | | [`ban`] | 🌟 | [`no-restricted-properties`][no-restricted-properties] | | [`curly`] | 🌟 | [`curly`][curly] | From addce1dc522f1aaf6a8b9b70df491336c3ab8af6 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 3 Apr 2019 09:25:12 -0400 Subject: [PATCH 13/13] Docs touchups as requested --- packages/eslint-plugin/docs/rules/await-thenable.md | 4 ++-- packages/eslint-plugin/src/rules/await-thenable.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/await-thenable.md b/packages/eslint-plugin/docs/rules/await-thenable.md index 0bfde1f5842..47780d1db42 100644 --- a/packages/eslint-plugin/docs/rules/await-thenable.md +++ b/packages/eslint-plugin/docs/rules/await-thenable.md @@ -1,6 +1,6 @@ # Disallows awaiting a value that is not a Promise (await-thenable) -This rule disallows awaiting a value that is not a "Thenable" (commonly, a Promise). +This rule disallows awaiting a value that is not a "Thenable" (an object which has `then` method, such as a Promise). While it is valid JavaScript to await a non-`Promise`-like value (it will resolve immediately), this pattern is often a programmer error, such as forgetting to add parenthesis to call a function that returns a Promise. ## Rule Details @@ -19,7 +19,7 @@ Examples of **correct** code for this rule: ```ts await Promise.resolve('value'); -const createValue = (async () => 'value'); +const createValue = async () => 'value'; await createValue(); ``` diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index f0d748579d2..a52469ef6e3 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -13,7 +13,7 @@ export default util.createRule({ tslintName: 'await-thenable', }, messages: { - await: 'Invalid `await` of a non-Promise (non-"Thenable") value.', + await: 'Unexpected `await` of a non-Promise (non-"Thenable") value.', }, schema: [], type: 'problem',