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(lint): implement noFloatingPromises rule for reguler async function #4911

Merged
merged 6 commits into from
Jan 23, 2025

Conversation

kaykdm
Copy link
Contributor

@kaykdm kaykdm commented Jan 19, 2025

Summary

related: #3187 and #4956
This pull request introduces the initial implementation of the no-floating-promises rule.

The rule reports Promise-valued statements that are not treated in one of the following ways:

  • Calling .then() with two arguments
  • Calling .catch() with one argument
  • Awaiting the Promise
  • Returning
  • Using the void operator

Note

Current Scope:

  • This PR currently covers simple async functions.
  • Currently, reassigning functions is not supported

Example of invalid code:

async function returnsPromise(): Promise<string> {
  return 'value';
}
returnsPromise().then(() => {});

Example of valid code:

async function returnsPromise(): Promise<string> {
  return 'value';
}

await returnsPromise();
void returnsPromise();

// Calling .then() with two arguments
returnsPromise().then(
  () => {},
  () => {}
);

Remaining TODOs for Future PRs

  • Support arrow functions
  • Support promises like Promise.all, etc.
  • Support class methods
  • Support object methods

@github-actions github-actions bot added A-CLI Area: CLI A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels Jan 19, 2025
Copy link

codspeed-hq bot commented Jan 19, 2025

CodSpeed Performance Report

Merging #4911 will not alter performance

Comparing kaykdm:no-floating-promises (3768f87) with next (069cbb4)

Summary

✅ 95 untouched benchmarks

@dyc3 dyc3 changed the title feat(lint): implment no-floating-promises rule for reguler async func… feat(lint): implment no-floating-promises rule for reguler async function Jan 19, 2025
return false;
};

let AnyJsBindingDeclaration::JsFunctionDeclaration(func_decl) = any_js_binding_decl
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • For now, only checking a function declaration
  • Not considering reassigned variables like this
async function returnsPromise(): Promise<string> {
  return 'value';
}

let reassigned = returnsPromise;
reassigned();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have AnyFunctionLike that you can use for detecting other kinds of function/method declarations. I understand without further type-level inference there will be few situations where you can use this for now, so feel free to leave methods for a future improvement, although I think at least all the AnyJsFunction cases can be handled already.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for letting me know!
I’ll revisit this when I work on implementing support for other functions or methods

@kaykdm kaykdm marked this pull request as ready for review January 20, 2025 09:45
Copy link
Contributor

@arendjr arendjr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Just left a few comments.

ctx.metadata().applicability(),
markup! { "Add await operator." }.to_owned(),
mutation,
))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this may potentially create invalid syntax if it's done inside a non-async function. Can we check for that?

Copy link
Contributor Author

@kaykdm kaykdm Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arendjr thank you for the review!
That's a valid point. Applying the change inside a non-async function could indeed lead to invalid syntax.
Is there an easy way to handle this in Biome?

For typeScript-eslint, it offers 2 fixes:

  • Add a void operator to safely ignore the result.
  • Add an await operator to make it asynchronous.

see: playground example

Providing two fixes seems like a reasonable and user-friendly approach.
Is it possible to provide multiple fixes using the action() function in Biome?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think lint rules can only offer a single fix mechanism, but we have assists which might be better to what you're trying to do. What do you think is better here, @ematipico ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you say 'assists,' do you mean they are another kind of note or suggestion that the diagnostic function can generate? Or is there a separate mechanism for assists altogether?

Copy link
Member

@ematipico ematipico Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment our infrastructure doesn't offer multiple fixes from a single rule, that's valid for the whole analyzer infrastructure (linter and assist).

Some rules change their fix based on a situational need, maybe it's something we could evaluate.

I believe this is the first time we are in need of such a case. We could definitely evaluate adding such a feature, but not in this PR, as it requires some thoughts about how they should behave.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so you’d like me to traverse up the tree from the expression node to find the nearest function and check if it is async, correct?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that’s probably the easiest solution.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For typeScript-eslint, it offers 2 fixes: ... see: playground example

Sorry if I'm butting in annoyingly, but just to answer about what ESLint / typescript-eslint is doing:

  • We intentionally don't have adding await as a fix, because that's not a safe fix to apply in code - even if it would be syntactically valid
    • Example: the code might have been relying on Promise to float, like in a fire-and-forget pattern
  • ESLint / typescript-eslint doesn't have the ability to provide multiple fixes. Those two "fix" buttons are to apply suggestions, which are not surfaced in the CLI - only in integrations such as editors.

I do think it's kind of confusing that the typescript-eslint playground phrases a suggestion as "fix". Filed typescript-eslint/typescript-eslint#10700. Thanks! 💙

Copy link
Contributor

@arendjr arendjr Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the great advice! I think the suggestions you speak of indeed map best to Biome’s assists, although we support unsafe fixes too, so I wouldn’t mind if the rule offered the await fix as an unsafe one.

If it gets too hairy, I also don’t mind merging this PR without any fixes/assists, so we can focus on improving the analysis part first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arendjr
I have added a check to determine whether an expression is in an async function before suggesting an unsafe fix

return false;
};

let AnyJsBindingDeclaration::JsFunctionDeclaration(func_decl) = any_js_binding_decl
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have AnyFunctionLike that you can use for detecting other kinds of function/method declarations. I understand without further type-level inference there will be few situations where you can use this for now, so feel free to leave methods for a future improvement, although I think at least all the AnyJsFunction cases can be handled already.

@ematipico
Copy link
Member

This PR should go against the next branch. Can you please do so @kaykdm ?

Also, the instructions for providing a changelog are different there, can you please follow them?

@kaykdm kaykdm changed the base branch from main to next January 20, 2025 12:31
@kaykdm kaykdm force-pushed the no-floating-promises branch from 93549da to a56a19d Compare January 20, 2025 13:26
@arendjr arendjr changed the title feat(lint): implment no-floating-promises rule for reguler async function feat(lint): implement no-floating-promises rule for reguler async function Jan 20, 2025
@arendjr arendjr changed the title feat(lint): implement no-floating-promises rule for reguler async function feat(lint): implement noFloatingPromises rule for reguler async function Jan 20, 2025
@arendjr
Copy link
Contributor

arendjr commented Jan 23, 2025

Thank you!

@arendjr arendjr merged commit d400d69 into biomejs:next Jan 23, 2025
12 checks passed
@kaykdm kaykdm deleted the no-floating-promises branch January 23, 2025 13:49
@kaykdm
Copy link
Contributor Author

kaykdm commented Jan 23, 2025

@arendjr
Thank you for the review!
Next, I will work on implementing support for arrow functions and will likely create a PR tomorrow.

@ematipico
Copy link
Member

@kaykdm can you create an issue task to keep track of these works?

@kaykdm
Copy link
Contributor Author

kaykdm commented Jan 24, 2025

@kaykdm can you create an issue task to keep track of these works?

created!
#4956

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants