Skip to content

feat(lint): add ||= to ??= detection in useNullishCoalescing#9257

Merged
dyc3 merged 2 commits intobiomejs:mainfrom
pkallos:feat/9230-nullish-assignment
Feb 28, 2026
Merged

feat(lint): add ||= to ??= detection in useNullishCoalescing#9257
dyc3 merged 2 commits intobiomejs:mainfrom
pkallos:feat/9230-nullish-assignment

Conversation

@pkallos
Copy link
Copy Markdown
Contributor

@pkallos pkallos commented Feb 26, 2026

AI Disclosure: This PR was developed with AI assistance (Claude).

Summary

Closes #9230

Extends useNullishCoalescing to detect ||= assignments and suggest ??= when the left-hand side is possibly nullish.

Added type_of_named_value to TypedService since type_of_expression returns unknown for assignment expressions (the module graph doesn't register them). This method looks up the variable's declared type by name in the enclosing scope.

Only simple identifier assignments are handled (a ||= b). Member expressions (obj.prop ||= b) are not supported yet because Biome's type resolution doesn't cover those patterns.

Test plan

  • assignmentInvalid.ts: 8 cases covering string|null, number|undefined, string|null|undefined, null/undefined literals, object types, and mixed falsy unions
  • assignmentValid.ts: 7 cases covering ??=, &&=, non-nullish types, non-nullish unions, and plain objects
  • assignmentCommentTrivia.ts: 3 cases verifying comment preservation in the ||= to ??= fix
  • All existing useNullishCoalescing tests still pass
cargo test -p biome_js_analyze -- nursery::use_nullish_coalescing

Docs

Rustdoc updated with ||= example in ### Invalid section.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 26, 2026

⚠️ No Changeset found

Latest commit: b831f63

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@github-actions github-actions Bot added A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Feb 26, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 27, 2026

Walkthrough

This PR extends the useNullishCoalescing lint to handle logical OR assignment expressions (||=) in addition to logical OR (||). It introduces a public union query UseNullishCoalescingQuery = JsLogicalExpression | JsAssignmentExpression and a UseNullishCoalescingState with LogicalOr and LogicalOrAssignment variants. Rule dispatch is split to handle JsLogicalExpression and JsAssignmentExpression separately, with dedicated diagnostics and token replacements (?? and ??=). Adds helper functions for applicability/fixability, a new TypedService::type_of_named_value method, and test cases including comment-trivia preservation.

Possibly related PRs

  • PR #8952: Introduced the original useNullishCoalescing rule for ||??, which this change extends to cover ||=??=.

Suggested labels

A-Type-Inference, A-Diagnostic

Suggested reviewers

  • ematipico
  • dyc3
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main feature added: extending useNullishCoalescing to detect ||= and suggest ??= replacement.
Description check ✅ Passed The description is well-structured and directly related to the changeset, explaining the feature, implementation approach, test coverage, and acknowledging AI assistance.
Linked Issues check ✅ Passed The PR fully addresses issue #9230 by adding query target for JsAssignmentExpression with ||= operator, reusing existing type-checking logic, and providing diagnostic with token replacement fix for simple identifiers [#9230].
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked objectives: the rule implementation, the new TypedService method, and comprehensive test coverage for ||= to ??= detection and fixing.

✏️ 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

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.

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

103-112: Move the state enum below impl Rule for consistency.

Tiny layout nit: keep helper enums below the rule impl (the node union can stay where it is).

Based on learnings: in crates/biome_analyze/**/*.rs rule files, helper functions/structs/enums should be placed below the impl Rule block, with node unions as the exception.

🤖 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/use_nullish_coalescing.rs` around
lines 103 - 112, Move the helper enum UseNullishCoalescingState so it appears
after the impl Rule block for the UseNullishCoalescing rule (keep the existing
node union where it is); update any references inside the impl (e.g.,
constructors, pattern matches) to remain in scope—essentially cut the enum
declaration and paste it below impl Rule to follow the project's ordering
convention for helper types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_nullish_coalescing.rs`:
- Around line 103-112: Move the helper enum UseNullishCoalescingState so it
appears after the impl Rule block for the UseNullishCoalescing rule (keep the
existing node union where it is); update any references inside the impl (e.g.,
constructors, pattern matches) to remain in scope—essentially cut the enum
declaration and paste it below impl Rule to follow the project's ordering
convention for helper types.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d0b8e6 and d2e11c2.

⛔ Files ignored due to path filters (3)
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentCommentTrivia.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentInvalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentValid.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (6)
  • .changeset/nullish-coalescing-assignment.md
  • crates/biome_js_analyze/src/lint/nursery/use_nullish_coalescing.rs
  • crates/biome_js_analyze/src/services/typed.rs
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentCommentTrivia.ts
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentInvalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentValid.ts

@pkallos
Copy link
Copy Markdown
Contributor Author

pkallos commented Feb 27, 2026

@ematipico this one was substantially less complex logically than #9248, did my best to learn from prior feedback sessions.

Can be merged in any order and i'll resolve whatever conflicts as necessary!

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Feb 27, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 156 skipped benchmarks1


Comparing pkallos:feat/9230-nullish-assignment (b831f63) with main (9bbdf4d)2

Open in CodSpeed

Footnotes

  1. 156 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 (412a08d) during the generation of this report, so 9bbdf4d was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.


declare_lint_rule! {
/// Enforce using nullish coalescing operator (`??`) instead of logical or (`||`).
/// Enforce using the nullish coalescing operator (`??`).
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: I like this wording

Suggested change
/// Enforce using the nullish coalescing operator (`??`).
/// Enforce using the nullish coalescing operator (`??``) instead of logical or (`||`).

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.

no changeset needed, the rule hasn't been released yet.

@pkallos pkallos force-pushed the feat/9230-nullish-assignment branch from c2c6703 to 5ea0729 Compare February 27, 2026 22:15
@pkallos pkallos force-pushed the feat/9230-nullish-assignment branch from 5ea0729 to 98d7b56 Compare February 27, 2026 22:20
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)
crates/biome_js_analyze/src/lint/nursery/use_nullish_coalescing.rs (1)

240-248: Consider a brief inline comment for future maintainers.

The _ => return None silently skips member expressions and other patterns, which is intentional per the PR scope. A short comment would help future contributors understand this is deliberate, not an oversight.

📝 Suggested comment
         AnyJsAssignmentPattern::AnyJsAssignment(assign) => {
             let id = assign.as_js_identifier_assignment()?;
             let name = id.name_token().ok()?;
             ctx.type_of_named_value(assignment.range(), name.text_trimmed())
         }
+        // Member expressions (obj.prop ||= b) are not yet supported
         _ => return None,
🤖 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/use_nullish_coalescing.rs` around
lines 240 - 248, The match on `left` in `use_nullish_coalescing.rs` (where `let
left = assignment.left().ok()?;` and the arms handle
`AnyJsAssignmentPattern::AnyJsAssignment(assign)` and `_ => return None`)
intentionally ignores non-identifier patterns (e.g., member expressions) for the
current PR scope; add a short inline comment above the `_ => return None` arm
stating this is deliberate and why (e.g., "skip member expressions and other
patterns intentionally; only handle simple identifier assignments here"), so
future maintainers know this behavior is intentional—no logic change, just a
clarifying comment near the `match` and the `ctx.type_of_named_value` call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/biome_js_analyze/src/lint/nursery/use_nullish_coalescing.rs`:
- Around line 240-248: The match on `left` in `use_nullish_coalescing.rs` (where
`let left = assignment.left().ok()?;` and the arms handle
`AnyJsAssignmentPattern::AnyJsAssignment(assign)` and `_ => return None`)
intentionally ignores non-identifier patterns (e.g., member expressions) for the
current PR scope; add a short inline comment above the `_ => return None` arm
stating this is deliberate and why (e.g., "skip member expressions and other
patterns intentionally; only handle simple identifier assignments here"), so
future maintainers know this behavior is intentional—no logic change, just a
clarifying comment near the `match` and the `ctx.type_of_named_value` call.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ea0729 and 98d7b56.

⛔ Files ignored due to path filters (3)
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentCommentTrivia.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentInvalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentValid.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (5)
  • crates/biome_js_analyze/src/lint/nursery/use_nullish_coalescing.rs
  • crates/biome_js_analyze/src/services/typed.rs
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentCommentTrivia.ts
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentInvalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentValid.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentValid.ts
  • crates/biome_js_analyze/src/services/typed.rs
  • crates/biome_js_analyze/tests/specs/nursery/useNullishCoalescing/assignmentInvalid.ts

@dyc3 dyc3 merged commit edf8bb6 into biomejs:main Feb 28, 2026
18 checks passed
@pkallos
Copy link
Copy Markdown
Contributor Author

pkallos commented Mar 1, 2026

thanks!

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

Labels

A-Linter Area: linter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📎 useNullishCoalescing: suggest ??= for logical OR assignment (||=)

2 participants