Skip to content

Commit

Permalink
feat(eslint-plugin): Check 'rest' parameters in no-misused-promises
Browse files Browse the repository at this point in the history
Fixes typescript-eslint#4015

This extends 'no-misued-promises' with support for checking
variadic arguments passed to a 'rest' parameter. If a function
is declared with an argument like '...handlers: Array<() => void>',
we now check if the type argument to `Array` is a void-returning function,
and if so, check if any of the variadic arguments return a Promise.
  • Loading branch information
Aaron1011 authored and Aaron Hill committed Oct 3, 2022
1 parent 556b71f commit 0bb293b
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 9 deletions.
45 changes: 36 additions & 9 deletions packages/eslint-plugin/src/rules/no-misused-promises.ts
Expand Up @@ -213,13 +213,13 @@ export default util.createRule<Options, MessageId>({
node: TSESTree.CallExpression | TSESTree.NewExpression,
): void {
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const voidParams = voidFunctionParams(checker, tsNode);
if (voidParams.size === 0) {
const voidArgs = voidFunctionArguments(checker, tsNode);
if (voidArgs.size === 0) {
return;
}

for (const [index, argument] of node.arguments.entries()) {
if (!voidParams.has(index)) {
if (!voidArgs.has(index)) {
continue;
}

Expand Down Expand Up @@ -486,10 +486,13 @@ function isFunctionParam(
return false;
}

// Get the positions of parameters which are void functions (and not also
// Get the positions of arguments which are void functions (and not also
// thenable functions). These are the candidates for the void-return check at
// the current call site.
function voidFunctionParams(
// If the function parameters end with a 'rest' parameter, then we consider
// the array type parameter (e.g. '...args:Array<SomeType>') when determining
// if trailing arguments are candidates.
function voidFunctionArguments(
checker: ts.TypeChecker,
node: ts.CallExpression | ts.NewExpression,
): Set<number> {
Expand All @@ -507,17 +510,41 @@ function voidFunctionParams(
: subType.getConstructSignatures();
for (const signature of signatures) {
for (const [index, parameter] of signature.parameters.entries()) {
const type = checker.getTypeOfSymbolAtLocation(
const decl = parameter.getDeclarations()?.[0];
let type = checker.getTypeOfSymbolAtLocation(
parameter,
node.expression,
);

const indices = [index];
// If this is a array 'rest' parameter, add all of the remaining parameter indices.
// Note - we currently do not support 'spread' arguments
if (
decl &&
ts.isParameter(decl) &&
decl.dotDotDotToken &&
checker.isArrayType(type) &&
node.arguments
) {
for (let i = index + 1; i < node.arguments.length; i++) {
indices.push(i);
}
// Unwrap 'Array<MaybeVoidFunction>' to 'MaybeVoidFunction',
// so that we'll handle it in the same way as a non-rest
// 'param: MaybeVoidFunction'
type = checker.getTypeArguments(type)[0];
}

if (isThenableReturningFunctionType(checker, node.expression, type)) {
thenableReturnIndices.add(index);
indices.forEach(index => thenableReturnIndices.add(index));
} else if (
!thenableReturnIndices.has(index) &&
isVoidReturningFunctionType(checker, node.expression, type)
) {
voidReturnIndices.add(index);
indices.forEach(index => {
if (!thenableReturnIndices.has(index)) {
voidReturnIndices.add(index);
}
});
}
}
}
Expand Down
24 changes: 24 additions & 0 deletions packages/eslint-plugin/tests/rules/no-misused-promises.test.ts
Expand Up @@ -970,5 +970,29 @@ console.log({ ...(condition ? Promise.resolve({ key: 42 }) : {}) });
{ line: 7, messageId: 'spread' },
],
},
{
code: `
type MyUnion = (() => void) | boolean;
function restPromises(first: Boolean, ...callbacks: Array<() => void>): void {}
function restUnion(first: string, ...callbacks: Array<MyUnion>): void {}
restPromises(
true,
() => Promise.resolve(true),
() => Promise.resolve(null),
() => true,
() => Promise.resolve('Hello'),
);
restUnion('Testing', false, () => Promise.resolve(true));
`,
errors: [
{ line: 9, messageId: 'voidReturnArgument' },
{ line: 10, messageId: 'voidReturnArgument' },
{ line: 12, messageId: 'voidReturnArgument' },
{ line: 15, messageId: 'voidReturnArgument' },
],
},
],
});

0 comments on commit 0bb293b

Please sign in to comment.