forked from typescript-eslint/typescript-eslint
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eslint-plugin) Added await-promise rule
Adds the equivalent of TSLint's `await-promise` rule.
- Loading branch information
Josh Goldberg
committed
Feb 3, 2019
1 parent
3efb265
commit 951a4fc
Showing
5 changed files
with
390 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Promise<string>) { | ||
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<string>) { | ||
for await (const element of iterable) { | ||
} | ||
} | ||
|
||
async function overIterableIterator(iterable: AsyncIterableIterator<string>) { | ||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string>} 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)) | ||
); | ||
} |
Oops, something went wrong.