-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat(eslint-plugin): [await-thenable] check Promise.all #10282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -1,5 +1,7 @@ | ||||||||
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; | ||||||||
import type * as ts from 'typescript'; | ||||||||
|
||||||||
import { AST_NODE_TYPES } from '@typescript-eslint/utils'; | ||||||||
import * as tsutils from 'ts-api-utils'; | ||||||||
|
||||||||
import { | ||||||||
|
@@ -8,6 +10,7 @@ | |||||||
getParserServices, | ||||||||
isAwaitKeyword, | ||||||||
isTypeAnyType, | ||||||||
isTypeReferenceType, | ||||||||
isTypeUnknownType, | ||||||||
nullThrows, | ||||||||
NullThrowsReasons, | ||||||||
|
@@ -19,6 +22,7 @@ | |||||||
| 'awaitUsingOfNonAsyncDisposable' | ||||||||
| 'convertToOrdinaryFor' | ||||||||
| 'forAwaitOfNonAsyncIterable' | ||||||||
| 'notPromises' | ||||||||
| 'removeAwait'; | ||||||||
|
||||||||
export default createRule<[], MessageId>({ | ||||||||
|
@@ -38,6 +42,7 @@ | |||||||
convertToOrdinaryFor: 'Convert to an ordinary `for...of` loop.', | ||||||||
forAwaitOfNonAsyncIterable: | ||||||||
'Unexpected `for await...of` of a value that is not async iterable.', | ||||||||
notPromises: 'Unexpected non-promise input to Promise.{methodName}.', | ||||||||
removeAwait: 'Remove unnecessary `await`.', | ||||||||
}, | ||||||||
schema: [], | ||||||||
|
@@ -167,6 +172,53 @@ | |||||||
} | ||||||||
} | ||||||||
}, | ||||||||
|
||||||||
// Check for e.g. `Promise.all(nonPromises)` | ||||||||
CallExpression(node): void { | ||||||||
if ( | ||||||||
node.callee.type === AST_NODE_TYPES.MemberExpression && | ||||||||
node.callee.object.type === AST_NODE_TYPES.Identifier && | ||||||||
node.callee.object.name === 'Promise' && | ||||||||
node.callee.property.type === AST_NODE_TYPES.Identifier && | ||||||||
(node.callee.property.name === 'all' || | ||||||||
node.callee.property.name === 'allSettled' || | ||||||||
node.callee.property.name === 'race') | ||||||||
) { | ||||||||
// Get the type of the thing being used in the method call. | ||||||||
const argument = node.arguments[0]; | ||||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||||||||
Comment on lines
+188
to
+189
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Style] 💡 hint:
Suggested change
|
||||||||
if (argument === undefined) { | ||||||||
return; | ||||||||
} | ||||||||
|
||||||||
const tsNode = services.esTreeNodeToTSNodeMap.get(argument); | ||||||||
const type = checker.getTypeAtLocation(tsNode); | ||||||||
if (!isTypeReferenceType(type) || type.typeArguments === undefined) { | ||||||||
return; | ||||||||
} | ||||||||
|
||||||||
const typeArg = type.typeArguments[0]; | ||||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||||||||
if (typeArg === undefined) { | ||||||||
return; | ||||||||
} | ||||||||
|
||||||||
const typeName = getTypeNameSimple(type); | ||||||||
const typeArgName = getTypeNameSimple(typeArg); | ||||||||
|
||||||||
if (typeName === 'Array' && typeArgName !== 'Promise') { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||
context.report({ | ||||||||
loc: node.loc, | ||||||||
messageId: 'notPromises', | ||||||||
}); | ||||||||
} | ||||||||
} | ||||||||
}, | ||||||||
}; | ||||||||
}, | ||||||||
}); | ||||||||
|
||||||||
/** This is a simplified version of the `getTypeName` utility function. */ | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Question] Why use this the existing This might be a moot point if you end up using |
||||||||
function getTypeNameSimple(type: ts.Type): string | undefined { | ||||||||
return type.getSymbol()?.escapedName as string | undefined; | ||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -278,6 +278,22 @@ async function iterateUsing(arr: Array<AsyncDisposable>) { | |
} | ||
`, | ||
}, | ||
{ | ||
code: ` | ||
declare const promises: Array<Promise<void>>; | ||
await Promise.all(promises); | ||
await Promise.allSettled(promises); | ||
await Promise.race(promises); | ||
`, | ||
}, | ||
{ | ||
code: ` | ||
declare const promises: Iterable<Promise<void>>; | ||
await Promise.all(promises); | ||
await Promise.allSettled(promises); | ||
await Promise.race(promises); | ||
`, | ||
}, | ||
], | ||
|
||
invalid: [ | ||
|
@@ -639,5 +655,24 @@ async function foo() { | |
}, | ||
], | ||
}, | ||
{ | ||
code: ` | ||
declare const booleans: boolean[]; | ||
await Promise.all(booleans); | ||
await Promise.allSettled(booleans); | ||
await Promise.race(booleans); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://typescript-eslint.io/contributing/pull-requests#granular-unit-tests: one error per |
||
`, | ||
errors: [ | ||
{ | ||
messageId: 'notPromises', | ||
}, | ||
{ | ||
messageId: 'notPromises', | ||
}, | ||
{ | ||
messageId: 'notPromises', | ||
}, | ||
], | ||
}, | ||
], | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Bug] Good question.
isBuiltinSymbolLike
is our standard utility for this. You'll want to use that, and add tests to make sure things coincidentally namedArray
orPromise
don't erroneously trigger this rule.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this comment is on the wrong line, see https://github.com/typescript-eslint/typescript-eslint/pull/10282/files#r1836229674