Skip to content

feat(lint): add nursery rule noUselessReturn#9029

Merged
dyc3 merged 3 commits intobiomejs:mainfrom
ff1451:feat/no-useless-return/#8205
Feb 11, 2026
Merged

feat(lint): add nursery rule noUselessReturn#9029
dyc3 merged 3 commits intobiomejs:mainfrom
ff1451:feat/no-useless-return/#8205

Conversation

@ff1451
Copy link
Contributor

@ff1451 ff1451 commented Feb 11, 2026

I used Claude Code to assist with generating test cases and documentation.

Summary

Closes #8205.

Port ESLint's no-useless-return rule to Biome as a nursery rule
noUselessReturn.

A bare return; at the end of a function body is redundant — removing it does not change the function's behavior. This rule
detects such cases and provides a safe fix to remove them.

The implementation uses an AST-based approach: for each JsReturnStatement without an argument, it walks ancestors to check whether the return is in tail position relative to its enclosing function. It bails out for returns inside loops, switch
statements, and finally blocks.

Test Plan

  • Snapshot tests in crates/biome_js_analyze/tests/specs/nursery/noUselessReturn/
    • invalid.js: 12 cases including end-of-function, after statements, if/else branches, nested ifs, try/catch blocks, arrow
      functions, labeled statements, and try-finally.
    • valid.js: 14 cases including return with value, early returns, loops, switch, finally block, empty function, and nested
      function scopes.
  • All tests pass with cargo test -p biome_js_analyze -- no_useless_return.

Docs

Rule documentation is provided via inline rustdoc in the rule implementation, including invalid and valid examples with
expect_diagnostic annotations.

@changeset-bot
Copy link

changeset-bot bot commented Feb 11, 2026

🦋 Changeset detected

Latest commit: 3a444e4

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@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 Feb 11, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

Walkthrough

Adds a new JavaScript lint rule NoUselessReturn that detects redundant return; statements when they are the last reachable statement in a non-top-level function. The rule skips returns with values, excludes top-level contexts, analyses tail-position through blocks/conditionals/try/catch (conservatively excluding finally), emits a diagnostic and offers an auto-fix that removes the useless return. Also adds NoUselessReturnOptions, exposes the options module, and includes comprehensive valid/invalid tests.

Suggested reviewers

  • dyc3
  • ematipico
  • Netail
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarises the main change: adding a new nursery lint rule called noUselessReturn.
Description check ✅ Passed The description is well-related to the changeset, explaining the rule's purpose, implementation approach, test coverage, and documentation.
Linked Issues check ✅ Passed The PR successfully implements the objective from #8205: porting ESLint's no-useless-return rule to Biome with the name noUselessReturn, with AST-based detection and safe auto-fix.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the noUselessReturn rule; no extraneous modifications outside the stated objective are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/biome_js_analyze/src/lint/nursery/no_useless_return.rs`:
- Line 75: The rule registration currently marks the ESLint mapping as identical
using RuleSource::Eslint("no-useless-return").same(), but the implementation
diverges from ESLint (AST-only vs CFG behavior); change the mapping to
.inspired() so it reflects different behavior. Locate the use of
RuleSource::Eslint("no-useless-return") in the no_useless_return rule
registration and replace .same() with .inspired(), ensuring any documentation or
rule metadata that relies on sameness is updated accordingly.
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/no_useless_return.rs (1)

1-7: Nit: inconsistent import style for biome_js_syntax types.

Some types (JsFinallyClause, JsFunctionBody, etc.) are imported at the top, while others (JsBlockStatement, JsIfStatement, JsModule, JsScript, etc.) are used fully-qualified in the function bodies (lines 102–103, 215–221). Either approach is fine, but mixing both is a bit untidy.

Add missing imports and use short names throughout
 use biome_js_syntax::{
-    JsDoWhileStatement, JsFinallyClause, JsForInStatement, JsForOfStatement, JsForStatement,
-    JsFunctionBody, JsReturnStatement, JsStatementList, JsSwitchStatement, JsWhileStatement,
+    JsBlockStatement, JsCatchClause, JsDoWhileStatement, JsElseClause, JsFinallyClause,
+    JsForInStatement, JsForOfStatement, JsForStatement, JsFunctionBody, JsIfStatement,
+    JsLabeledStatement, JsModule, JsReturnStatement, JsScript, JsStatementList,
+    JsSwitchStatement, JsTryFinallyStatement, JsTryStatement, JsWhileStatement,
 };

Copy link
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

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

We have ControlFlowGraph for analyzing control flow. Would that make the implementation of this rule more simple?

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 11, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 95 skipped benchmarks1


Comparing ff1451:feat/no-useless-return/#8205 (3a444e4) with main (9d4cfa3)

Open in CodSpeed

Footnotes

  1. 95 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@ff1451
Copy link
Contributor Author

ff1451 commented Feb 11, 2026

I initially tried a CFG-based implementation using ControlFlowGraph as the query type, but the code grew to ~350 lines with additional dependencies (RoaringBitmap, FxHashSet) and required traversing all CFG blocks with forward-flow analysis to determine if a return was useless.

Since the core problem — "is this return; the last statement before the function ends?" — is fundamentally a positional question rather than a reachability one, the AST approach answers it more naturally by walking ancestors to check tail position (~170 lines, no extra dependencies).

If you'd like to review the CFG-based approach, I can share that implementation for comparison.
@dyc3

@dyc3
Copy link
Contributor

dyc3 commented Feb 11, 2026

Makes sense, thanks for the explanation.

@dyc3 dyc3 merged commit 6ebf6c6 into biomejs:main Feb 11, 2026
20 checks passed
@github-actions github-actions bot mentioned this pull request Feb 11, 2026
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.

📎 port no-useless-return from eslint

2 participants