Skip to content
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

feat(eslint-plugin): Check 'rest' parameters in no-misused-promises #5731

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
// 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];
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
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
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
if (
decl &&
ts.isParameter(decl) &&
decl.dotDotDotToken &&
checker.isArrayType(type) &&
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
node.arguments
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
) {
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' },
],
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
},
JoshuaKGoldberg marked this conversation as resolved.
Show resolved Hide resolved
],
});