Skip to content

fix(lint/noUnnecessaryConditions): extend detection coverage#10108

Open
IxxyDev wants to merge 6 commits into
biomejs:mainfrom
IxxyDev:fix/no-unnecessary-conditions-6611
Open

fix(lint/noUnnecessaryConditions): extend detection coverage#10108
IxxyDev wants to merge 6 commits into
biomejs:mainfrom
IxxyDev:fix/no-unnecessary-conditions-6611

Conversation

@IxxyDev
Copy link
Copy Markdown

@IxxyDev IxxyDev commented Apr 24, 2026

Summary

Closes #6611.

Expands noUnnecessaryConditions detection:

  • Optional chaining (?.) and nullish coalescing (??) on non-nullish types.
  • Logical expressions on member/call access with non-nullish types.
  • null/undefined comparisons with non-nullish types.
  • Always-falsy via logical-not (!expr).
  • Unreachable case clauses in switch on literal unions (replacing the prior incorrect discriminant-wide flag).

Test plan

  • cargo test -p biome_js_analyze (2377 spec + 10 doctests)
  • cargo test -p biome_js_type_info --test conditionals
  • cargo clippy -p biome_js_analyze --all-targets -- -D warnings
  • cargo fmt --all -- --check

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 24, 2026

🦋 Changeset detected

Latest commit: c490e5c

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

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

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-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Type-Inference Area: type inference labels Apr 24, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The lint rule noUnnecessaryConditions was enhanced to use type information. It now reports redundant optional chaining (?.) and nullish coalescing (??) when operands are statically non‑nullish, flags redundant ||/&& and null/undefined comparisons against non‑nullish types, handles unary ! for always‑truthy/always‑falsy expressions, and emits unreachable diagnostics for individual case clauses when a switch discriminant cannot match a case literal. Tests and type‑info test helpers were added or updated to cover these behaviours.

Possibly related PRs

Suggested reviewers

  • dyc3
  • ematipico
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main change: extending detection coverage of the noUnnecessaryConditions lint rule with type-aware analysis.
Description check ✅ Passed The description is directly related to the changeset, clearly outlining what noUnnecessaryConditions detection was expanded to cover and providing a test plan.
Linked Issues check ✅ Passed The PR fully addresses issue #6611 by implementing the requested type-aware detection of unnecessary conditions, optional chaining, nullish coalescing, and comparisons with null/undefined.
Out of Scope Changes check ✅ Passed All changes directly support the objective of extending noUnnecessaryConditions detection: rule implementation, test fixtures, and type inference tests remain in scope.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown
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

🧹 Nitpick comments (3)
crates/biome_js_type_info/tests/conditionals.rs (1)

103-112: Nit: add symmetric !is_nullish() assertion for parity.

The other two tests assert both !is_truthy() and !is_nullish(); the string case only asserts non-truthy. Adding the symmetric check would make the regression guard uniform with minimal cost.

🔧 Suggested addition
     assert!(
         !conditional.is_truthy(),
         "optional primitive parameter should not be classified as truthy, got {conditional:?}"
     );
+    assert!(
+        !conditional.is_nullish(),
+        "optional primitive parameter should not be classified as nullish, got {conditional:?}"
+    );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_type_info/tests/conditionals.rs` around lines 103 - 112, The
test optional_string_param_is_not_truthy only asserts !conditional.is_truthy();
add the symmetric nullish check by asserting !conditional.is_nullish() as well
so the string-parameter regression guard matches the other tests; locate the
test function optional_string_param_is_not_truthy and add an assertion using the
conditional_type_of_first_param result: assert!(!conditional.is_nullish(),
"optional primitive parameter should not be classified as nullish, got
{conditional:?}");
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs (2)

749-781: Avoid the String allocation — store TokenText instead.

CaseLiteral::String(String) via inner_string_text(&token).text().to_string() forces a heap allocation for every case clause. Since inner_string_text already returns a TokenText, you can carry it through and call .text() at comparison time.

♻️ Suggested refactor
-enum CaseLiteral {
-    String(String),
+enum CaseLiteral {
+    String(TokenText),
     Number(f64),
     Boolean(bool),
     Null,
 }
@@
         AnyJsLiteralExpression::JsStringLiteralExpression(s) => {
             let token = s.value_token().ok()?;
-            Some(CaseLiteral::String(inner_string_text(&token).text().to_string()))
+            Some(CaseLiteral::String(inner_string_text(&token)))
         }
@@
         CaseLiteral::String(s) => {
             if ty.is_string_or_string_literal() {
                 if matches!(raw, TypeData::String) {
                     return true;
                 }
-                return ty.is_string_literal(s.as_str());
+                return ty.is_string_literal(s.text());
             }
             false
         }

As per coding guidelines: "Avoid string allocations in rule implementations by using string references (&str) or TokenText instead of calling to_string()".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs` around
lines 749 - 781, The CaseLiteral::String variant allocates a new String; update
the CaseLiteral enum and extract_case_literal to store TokenText (or &TokenText)
instead of String and return that from inner_string_text(&token) without calling
.to_string(), then adjust all places that match on CaseLiteral::String to
compare using .text() (or otherwise access TokenText) rather than relying on
owned String; specifically modify the CaseLiteral enum, the extract_case_literal
function (replace inner_string_text(&token).text().to_string() with returning
the TokenText) and any downstream match/compare logic to use TokenText.text()
for comparisons.

789-850: type_could_equal_literal — conservative null handling is fine, keep the TODO.

Treating Undefined/VoidKeyword as possibly matching case null: is the right call until === disjointness is modelled. The inline TODO at line 845 is an adequate placeholder; consider linking it to a tracking issue so it doesn't get orphaned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs` around
lines 789 - 850, The conservative handling of Null in
single_type_could_equal_literal (called by type_could_equal_literal) is correct;
do not change the matching logic, but update the inline TODO inside the
CaseLiteral::Null arm to reference a tracking issue/bug ID so it doesn't get
orphaned—locate the TODO in single_type_could_equal_literal (the Comment above
the matches!(raw, TypeData::Null | TypeData::Undefined | TypeData::VoidKeyword)
line) and replace the generic TODO with a short note linking the issue number or
URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs`:
- Around line 769-772: The JsNumberLiteralExpression branch in
extract_case_literal currently uses token.text_trimmed().parse::<f64>().ok()
which silently skips non-decimal JS numeric forms (hex/bin/oct, underscores,
bigint suffix), causing those case literals to be ignored; update the
AnyJsLiteralExpression::JsNumberLiteralExpression handling to either (a) parse
JS numeric literal forms correctly (handle 0x/0b/0o prefixes, numeric separators
`_`, and strip a trailing `n` for bigint then convert/ceil to f64 or return a
distinct CaseLiteral variant) or (b) at minimum add a TODO and a clear comment
noting unsupported formats and conservatively return None, referencing the
AnyJsLiteralExpression::JsNumberLiteralExpression branch and CaseLiteral::Number
to locate the code to change.

---

Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs`:
- Around line 749-781: The CaseLiteral::String variant allocates a new String;
update the CaseLiteral enum and extract_case_literal to store TokenText (or
&TokenText) instead of String and return that from inner_string_text(&token)
without calling .to_string(), then adjust all places that match on
CaseLiteral::String to compare using .text() (or otherwise access TokenText)
rather than relying on owned String; specifically modify the CaseLiteral enum,
the extract_case_literal function (replace
inner_string_text(&token).text().to_string() with returning the TokenText) and
any downstream match/compare logic to use TokenText.text() for comparisons.
- Around line 789-850: The conservative handling of Null in
single_type_could_equal_literal (called by type_could_equal_literal) is correct;
do not change the matching logic, but update the inline TODO inside the
CaseLiteral::Null arm to reference a tracking issue/bug ID so it doesn't get
orphaned—locate the TODO in single_type_could_equal_literal (the Comment above
the matches!(raw, TypeData::Null | TypeData::Undefined | TypeData::VoidKeyword)
line) and replace the generic TODO with a short note linking the issue number or
URL.

In `@crates/biome_js_type_info/tests/conditionals.rs`:
- Around line 103-112: The test optional_string_param_is_not_truthy only asserts
!conditional.is_truthy(); add the symmetric nullish check by asserting
!conditional.is_nullish() as well so the string-parameter regression guard
matches the other tests; locate the test function
optional_string_param_is_not_truthy and add an assertion using the
conditional_type_of_first_param result: assert!(!conditional.is_nullish(),
"optional primitive parameter should not be classified as nullish, got
{conditional:?}");
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f000c5fb-cc1f-4e1c-860a-80c0d58d81fc

📥 Commits

Reviewing files that changed from the base of the PR and between 5f79e0c and 6de452f.

⛔ Files ignored due to path filters (4)
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (6)
  • .changeset/fix-no-unnecessary-conditions-6611.md
  • crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.js
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.ts
  • crates/biome_js_type_info/tests/conditionals.rs

Comment thread crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs Outdated
@IxxyDev IxxyDev force-pushed the fix/no-unnecessary-conditions-6611 branch from 6de452f to 4fb9528 Compare April 24, 2026 17:03
Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
.changeset/fix-no-unnecessary-conditions-6611.md (1)

5-12: Please condense this changeset body to 1–3 sentences.

Great content, but this reads like mini release notes; the project guideline asks for concise changesets. Keep the issue/rule links and one compact example, and let the PR carry the full detail.

✂️ Suggested concise rewrite
-Fixed [`#6611`](https://github.com/biomejs/biome/issues/6611): the lint rule [`noUnnecessaryConditions`](https://biomejs.dev/linter/rules/no-unnecessary-conditions/) now correctly handles several cases that were previously missed.
-
-- Optional chaining on a non-nullish type is now flagged based on type information. Example: `function bar(arg: string) { return arg?.length }` now reports an unnecessary `?.`.
-- Nullish coalescing on a non-nullish type is now flagged: `function bar(arg: string) { return arg ?? "default" }` now reports `??` as unnecessary.
-- Logical expressions on member and call access use type information: `interface Foo { items: string[] }; function f(foo: Foo) { return foo.items || [] }` now reports `|| []` as unnecessary.
-- Comparisons between a non-nullish type and `null`/`undefined` are flagged: `function f(x: string) { return x === null }` now reports.
-- The `!expr` form of a condition now detects always-falsy: `const a = []; if (!a) {}` is now reported.
-- `switch` statements are no longer wrongly flagged on the discriminant for literal unions like `'a' | 'b' | 'c'`. Instead, matching the behavior of `@typescript-eslint/no-unnecessary-condition`, individual `case` clauses that are statically unreachable — such as `case 'd':` inside `switch (value: 'a' | 'b' | 'c')` — are now flagged.
+Fixed [`#6611`](https://github.com/biomejs/biome/issues/6611): the lint rule [`noUnnecessaryConditions`](https://biomejs.dev/linter/rules/no-unnecessary-conditions/) now uses type information to report missed unnecessary conditions, including redundant `?.`, `??`, `||`, and null/undefined comparisons.
+Biome now also reports always-false negated conditions (for example, `if (![]) {}`) and unreachable `case` clauses for literal-union `switch` statements.

As per coding guidelines: ".changeset/**: Write changeset descriptions concisely and clearly (1-3 sentences)...".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/fix-no-unnecessary-conditions-6611.md around lines 5 - 12,
Condense the changeset body to 1–3 sentences: keep the issue link (`#6611`) and
the linter rule link (noUnnecessaryConditions), summarize the key fix in one
compact sentence (e.g., "noUnnecessaryConditions now uses type information to
catch unnecessary optional chaining, nullish coalescing, logical expressions,
null comparisons, always-falsy negations, and unreachable switch cases"), and
include one short example such as `function bar(arg: string) { return
arg?.length }` to illustrate the change; remove the multiline bullet list and
detailed examples so the file reads as a concise summary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.changeset/fix-no-unnecessary-conditions-6611.md:
- Around line 5-12: Condense the changeset body to 1–3 sentences: keep the issue
link (`#6611`) and the linter rule link (noUnnecessaryConditions), summarize the
key fix in one compact sentence (e.g., "noUnnecessaryConditions now uses type
information to catch unnecessary optional chaining, nullish coalescing, logical
expressions, null comparisons, always-falsy negations, and unreachable switch
cases"), and include one short example such as `function bar(arg: string) {
return arg?.length }` to illustrate the change; remove the multiline bullet list
and detailed examples so the file reads as a concise summary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 619ef3b4-9b8f-42e2-a606-f948f92f5848

📥 Commits

Reviewing files that changed from the base of the PR and between 6de452f and 4fb9528.

📒 Files selected for processing (1)
  • .changeset/fix-no-unnecessary-conditions-6611.md

Closes biomejs#6611.

- Flag always-falsy conditions via logical-not (`!expr`).
- Detect unnecessary optional chaining and nullish coalescing on
  non-nullish types, including on member and call expressions.
- Detect null/undefined comparisons on non-nullish types.
- Replace the prior incorrect discriminant-wide flag on `switch` with
  per-`case` unreachability analysis (matching
  `@typescript-eslint/no-unnecessary-condition`).
- Handle hex, binary, octal, and numeric-separator literals in
  `case` clauses via `as_number`, rather than raw `parse::<f64>`.
@IxxyDev IxxyDev force-pushed the fix/no-unnecessary-conditions-6611 branch from 4fb9528 to 5074ca0 Compare April 24, 2026 17:06
Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs (1)

445-450: Deduplicate with the new is_undefined_identifier helper.

You just added is_undefined_identifier for exactly this check — might as well use it here too so the "undefined" string literal only lives in one place.

♻️ Proposed refactor
         AnyJsExpression::JsIdentifierExpression(id_expr) => {
-            if let Ok(name) = id_expr.name()
-                && name.value_token().ok()?.text_trimmed() == "undefined"
-            {
+            if is_undefined_identifier(expr) {
                 return Some(IssueKind::AlwaysFalsyCondition(expr.range()));
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs` around
lines 445 - 450, The identifier check inside the
AnyJsExpression::JsIdentifierExpression arm duplicates the `"undefined"`
literal; replace the manual name/value_token check with the new helper
is_undefined_identifier to deduplicate the logic. Specifically, in the match arm
for AnyJsExpression::JsIdentifierExpression(id_expr) call
is_undefined_identifier(id_expr) and, if true, return
Some(IssueKind::AlwaysFalsyCondition(expr.range())); leave the surrounding match
and return behavior unchanged so semantics remain identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs`:
- Around line 378-383: The helper is_undefined_identifier matches the identifier
purely by text which can misidentify a shadowed binding (e.g., const undefined =
1) as the real undefined; add a TODO above is_undefined_identifier describing
this limitation and why it’s acceptable (mirrors `@typescript-eslint` behavior and
strict mode forbids rebinding), so future maintainers know the known blind spot
and won’t chase it as a bug.

---

Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs`:
- Around line 445-450: The identifier check inside the
AnyJsExpression::JsIdentifierExpression arm duplicates the `"undefined"`
literal; replace the manual name/value_token check with the new helper
is_undefined_identifier to deduplicate the logic. Specifically, in the match arm
for AnyJsExpression::JsIdentifierExpression(id_expr) call
is_undefined_identifier(id_expr) and, if true, return
Some(IssueKind::AlwaysFalsyCondition(expr.range())); leave the surrounding match
and return behavior unchanged so semantics remain identical.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 50db4a5a-3bfe-42e7-a3c3-30db01298d5e

📥 Commits

Reviewing files that changed from the base of the PR and between 4fb9528 and 5074ca0.

⛔ Files ignored due to path filters (4)
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (6)
  • .changeset/fix-no-unnecessary-conditions-6611.md
  • crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.js
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.ts
  • crates/biome_js_type_info/tests/conditionals.rs
✅ Files skipped from review due to trivial changes (2)
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.js
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/biome_js_type_info/tests/conditionals.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.ts

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 24, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 196 skipped benchmarks1


Comparing IxxyDev:fix/no-unnecessary-conditions-6611 (5074ca0) with main (4a664c1)2

Open in CodSpeed

Footnotes

  1. 196 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.

  2. No successful run was found on main (5f79e0c) during the generation of this report, so 4a664c1 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

130 │ break;

i The type being checked can never be falsy, making this condition redundant.
i The discriminant's type can never equal this value.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: "discriminant" is a bit jargony.

Copy link
Copy Markdown
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

Thank you. The PR needs some work. Docstrings needs be reviewed and revisited by a human, same for the diagnostic. They are clearly AI generated and not reviewed.

Changeset also is too lengthy, and we're missing new, invalid examples.

Comment on lines +5 to +12
Fixed [#6611](https://github.com/biomejs/biome/issues/6611): the lint rule [`noUnnecessaryConditions`](https://biomejs.dev/linter/rules/no-unnecessary-conditions/) now correctly handles several cases that were previously missed.

- Optional chaining on a non-nullish type is now flagged based on type information. Example: `function bar(arg: string) { return arg?.length }` now reports an unnecessary `?.`.
- Nullish coalescing on a non-nullish type is now flagged: `function bar(arg: string) { return arg ?? "default" }` now reports `??` as unnecessary.
- Logical expressions on member and call access use type information: `interface Foo { items: string[] }; function f(foo: Foo) { return foo.items || [] }` now reports `|| []` as unnecessary.
- Comparisons between a non-nullish type and `null`/`undefined` are flagged: `function f(x: string) { return x === null }` now reports.
- The `!expr` form of a condition now detects always-falsy: `const a = []; if (!a) {}` is now reported.
- `switch` statements are no longer wrongly flagged on the discriminant for literal unions like `'a' | 'b' | 'c'`. Instead, matching the behavior of `@typescript-eslint/no-unnecessary-condition`, individual `case` clauses that are statically unreachable — such as `case 'd':` inside `switch (value: 'a' | 'b' | 'c')` — are now flagged.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changesets aren't places for technical details nor documentation. This is written in our guidelines. Reduce it to a few lines, and instead pour more time into expanding the documentation

Comment on lines +49 to +71
/// ```ts
/// interface Foo { items: string[] }
/// function f(x: Foo) {
/// return x.items || []; // x.items is string[], always truthy
/// }
/// ```
///
/// ```ts
/// const a = [];
/// if (!a) {} // always false — array literals are always truthy
/// ```
///
/// ```ts
/// function f(x: string) {
/// return x === null; // always false — x is never null
/// }
/// ```
///
/// ```ts
/// function f(v: 'a' | 'b') {
/// switch (v) { case 'c': return 1; } // 'c' is unreachable
/// }
/// ```
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There's more value in showing incorrect cases than showing valid cases. Still, in this PR there are zero new incorrect cases.

We should:

  • reduce the valid blocks (they can all exist in a single block)
  • add more invalid cases

/// switch (v) {
/// case 'a': break;
/// case 'b': break;
/// case 'c': break; // all cases reachable — no flag
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's a flag in lint rule? Please reword the comment

Comment on lines +353 to +358
.note(markup! {
"The discriminant's type can never equal this value."
})
.note(markup! {
"Remove the case, or change the discriminant."
}),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please use beginner friendly terminology if possible. This is also covered in the contribution guide

Comment on lines +379 to +383
matches!(expr, AnyJsExpression::JsIdentifierExpression(id)
if id.name().ok()
.and_then(|n| n.value_token().ok())
.is_some_and(|t| t.text_trimmed() == "undefined"))
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This can be written in a cleaner way

JsIdentifierExpression::cast_ref(...).is_some_and(...)

Plus the bot is right

Comment on lines +767 to +768
Some(CaseLiteral::String(inner_string_text(&token).text().to_string()))
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don't allocate string. Use either Text or TokenText

Comment on lines +782 to +787
/// Returns whether the given `ty` could possibly equal the given literal value.
///
/// This is a narrow, conservative predicate — when in doubt (unknown types,
/// unresolved references, type variables, complex types), it returns `true`.
/// Used to determine case-clause reachability: if this returns `false`, the
/// `case` is statically guaranteed to never match.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please reword this comment. There are grammar errors, and it's hard to read. Remove those em dashes please

}

fn single_type_could_equal_literal(ty: &Type, literal: &CaseLiteral) -> bool {
use biome_js_type_info::TypeData;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Move at the top of the file

single_type_could_equal_literal(ty, literal)
}

fn single_type_could_equal_literal(ty: &Type, literal: &CaseLiteral) -> bool {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please document this function. It accepts the same types as the previous function, and the names are very similar

Comment on lines +851 to +854
/// Checks whether a `case <literal>:` clause is reachable given the type of
/// the enclosing `switch`'s discriminant. Mirrors the behavior of
/// `@typescript-eslint/no-unnecessary-condition`, which flags each case that
/// can never equal the discriminant.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't care whether a function mirrors eslint, that's granted if we use the same() metadata.

Instead, focus on explaining the business logic

- Trim the changeset to a concise summary per project guidelines.
- Rewrite the rustdoc with grouped invalid examples and a short valid section.
- Reword diagnostic messages to avoid jargon (drop "discriminant",
  "Mirrors @typescript-eslint", em-dashes) and clarify advice.
- Switch is_undefined_identifier to JsIdentifierExpression::cast_ref and
  document the syntactic-only matching.
- Avoid allocating a String in CaseLiteral::String by storing TokenText.
- Document type_could_equal_literal / single_type_could_equal_literal and
  the per-case reachability check; remove eslint-mirroring wording.
- Update test fixtures and snapshots accordingly.
Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs`:
- Around line 719-750: The type-aware null/undefined checks should only run for
equality operators — add a guard so this block executes only when the binary
operator is one of ==, ===, !=, !==; return None otherwise. Additionally, for
strict equality (=== and !==) use stricter narrowing instead of
conditional.is_nullish() (e.g., conditional.is_null() or
conditional.is_undefined() as appropriate) while keeping the existing
conditional.is_non_nullish() logic for the non-strict cases; update the logic
around typed_side, ty, and conditional (and the branches using
conditional.is_non_nullish() / conditional.is_nullish()) accordingly so you
don't apply these checks to relational operators like < or >.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 281aa05a-5b6c-40a6-b725-b83506d9d9c5

📥 Commits

Reviewing files that changed from the base of the PR and between 5074ca0 and 8f0cdf0.

⛔ Files ignored due to path filters (3)
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.js.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.js.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (6)
  • .changeset/fix-no-unnecessary-conditions-6611.md
  • crates/biome_js_analyze/src/lint/nursery/no_unnecessary_conditions.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.js
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.js
  • crates/biome_js_type_info/tests/conditionals.rs
✅ Files skipped from review due to trivial changes (2)
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/invalid.js
  • crates/biome_js_analyze/tests/specs/nursery/noUnnecessaryConditions/valid.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_js_type_info/tests/conditionals.rs

IxxyDev added 4 commits May 4, 2026 02:07
- Gate type-aware null/undefined comparison to equality operators only,
  so `<`/`>` against null/undefined never trigger the rule.
- Restrict the dual `is_nullish` branch to loose equality, since strict
  equality distinguishes null from undefined and `is_nullish` conflates
  them.
- Reword comments to drop em-dashes and the "flag" terminology.
- Merge valid rustdoc examples into a single code block.
- Trim the changeset to one sentence per the contributing guide.
@IxxyDev
Copy link
Copy Markdown
Author

IxxyDev commented May 11, 2026

Tried to address your comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter A-Type-Inference Area: type inference L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📎 Implement noUnnecessaryConditions

3 participants