Skip to content

fix(lint): handle merged namespaces in noUnusedVariables#9678

Closed
raashish1601 wants to merge 1 commit intobiomejs:mainfrom
raashish1601:contributor-65-7664-followup
Closed

fix(lint): handle merged namespaces in noUnusedVariables#9678
raashish1601 wants to merge 1 commit intobiomejs:mainfrom
raashish1601:contributor-65-7664-followup

Conversation

@raashish1601
Copy link
Copy Markdown

Summary

  • fix noUnusedVariables for TypeScript declaration merging between namespaces and same-name values
  • treat merged namespaces as used when the paired value is exported or referenced, including local property-access cases like registry.item
  • add regression coverage for const/function/class/enum merges and for truly unused namespaces

Context

Testing

  • cargo test -p biome_js_analyze --test spec_tests namespace_declaration_merging
  • cargo test -p biome_js_analyze --test spec_tests invalid_namespace_unused

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 29, 2026

⚠️ No Changeset found

Latest commit: 3b516a6

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 Mar 29, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 29, 2026

Walkthrough

The PR enhances the noUnusedVariables lint rule to support TypeScript declaration merging. When a binding is part of a merged declaration set (namespace with function, class, enum, or variable), the rule now suppresses unused reports if the merged counterpart is exported or used. The implementation adds helpers to detect namespace declarations and same-name counterparts within scope, and refactors the is_unused function to check merge relationships. Six new test cases validate declaration merging scenarios across different declaration types and edge cases like shadowing.

Possibly related PRs

Suggested labels

A-Linter, L-JavaScript

Suggested reviewers

  • ematipico
  • dyc3
  • arendjr
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarises the main change: fixing the noUnusedVariables rule to handle TypeScript merged namespaces.
Description check ✅ Passed The description is directly related to the changeset, explaining the namespace merging fix, testing approach, and linked issues.
Linked Issues check ✅ Passed The PR successfully addresses issue #7664 by implementing logic to suppress false-positive unused warnings for namespaces when the merged counterpart is exported or used, including property-access cases.
Out of Scope Changes check ✅ Passed All changes are within scope: core fix to no_unused_variables.rs and test fixtures validating namespace merging and unused namespace scenarios.

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

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

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: 2

🤖 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/correctness/no_unused_variables.rs`:
- Around line 542-552: The loops that call is_merge_candidate_used (iterating
scope.bindings()) must not return on the first Some(..) because later bindings
may mark the name as used/exported; instead, scan all candidates: if any call
yields Some(true) return Some(true) immediately; otherwise record if any
returned Some(false) and continue; after the loop return Some(false) if a false
was seen, else return None. Apply the same change to the analogous loop in
is_value_merged_with_used_namespace (and to the other affected loop at the
568-577 region) so both functions aggregate results across all merge candidates
rather than depending on the bindings() order.
- Around line 497-505: The helper is missing export-default binding variants
causing false positives; update the is_namespace_merge_value_declaration
function to include the JsFunctionExportDefaultDeclaration,
JsClassExportDefaultDeclaration, and TsDeclareFunctionExportDefaultDeclaration
match arms so those default-export bindings are treated as value declarations,
and add a snapshot test under tests/specs/correctness/no_unused_variables/ that
exercises namespace merge behavior with default exports to prevent regressions.
🪄 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: 5c3ea453-5991-4b6c-b77f-d962d97a58dd

📥 Commits

Reviewing files that changed from the base of the PR and between c17e08e and 3b516a6.

⛔ Files ignored due to path filters (6)
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidNamespaceUnused.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMerging.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMergingClass.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMergingEnum.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMergingFunction.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMergingPropertyAccess.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (7)
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidNamespaceUnused.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMerging.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMergingClass.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMergingEnum.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMergingFunction.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMergingPropertyAccess.ts

Comment on lines +497 to +505
fn is_namespace_merge_value_declaration(decl: &AnyJsBindingDeclaration) -> bool {
matches!(
decl,
AnyJsBindingDeclaration::JsVariableDeclarator(_)
| AnyJsBindingDeclaration::JsFunctionDeclaration(_)
| AnyJsBindingDeclaration::JsClassDeclaration(_)
| AnyJsBindingDeclaration::TsEnumDeclaration(_)
)
}
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
python - <<'PY'
from pathlib import Path
import re

path = Path("crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs")
text = path.read_text()

helper = re.search(
    r"fn is_namespace_merge_value_declaration\(decl: &AnyJsBindingDeclaration\) -> bool \{\n(?P<body>.*?)\n\}",
    text,
    re.S,
)
body = helper.group("body")

for variant in [
    "JsFunctionDeclaration",
    "JsFunctionExportDefaultDeclaration",
    "JsClassDeclaration",
    "JsClassExportDefaultDeclaration",
    "TsDeclareFunctionExportDefaultDeclaration",
]:
    print(f"{variant}: {'handled in helper' if variant in body else 'MISSING from helper'}")

print("\nDistinct declaration kinds referenced elsewhere in the file:")
for variant in [
    "JsFunctionExportDefaultDeclaration",
    "JsClassExportDefaultDeclaration",
    "TsDeclareFunctionExportDefaultDeclaration",
]:
    print(f"{variant}: {'present' if variant in text else 'absent'}")
PY

Repository: biomejs/biome

Length of output: 505


Add missing export default variants to namespace merge detection and include a test case.

The is_namespace_merge_value_declaration helper skips three binding kinds: JsFunctionExportDefaultDeclaration, JsClassExportDefaultDeclaration, and TsDeclareFunctionExportDefaultDeclaration. These are referenced elsewhere in the file but not handled here, causing named default exports to fall through to the false-positive path. Add the three variants to the match and include a snapshot test in tests/specs/correctness/no_unused_variables/ to cover default export merging scenarios.

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

In `@crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs` around
lines 497 - 505, The helper is missing export-default binding variants causing
false positives; update the is_namespace_merge_value_declaration function to
include the JsFunctionExportDefaultDeclaration, JsClassExportDefaultDeclaration,
and TsDeclareFunctionExportDefaultDeclaration match arms so those default-export
bindings are treated as value declarations, and add a snapshot test under
tests/specs/correctness/no_unused_variables/ that exercises namespace merge
behavior with default exports to prevent regressions.

Comment on lines +542 to +552
for scope_binding in scope.bindings() {
let other = scope_binding.tree();
if let Some(is_used) = is_merge_candidate_used(
model,
binding,
&other,
&name,
is_namespace_merge_value_declaration,
) {
return Some(is_used);
}
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.

⚠️ Potential issue | 🟠 Major

Do not stop at the first merge candidate.

Both loops return the first Some(..) from is_merge_candidate_used, so a later used/exported counterpart is never seen. That makes the result depend on scope.bindings() order rather than the whole merged set.

♻️ Safer scan
     for scope_binding in scope.bindings() {
         let other = scope_binding.tree();
-        if let Some(is_used) = is_merge_candidate_used(
+        if is_merge_candidate_used(
             model,
             binding,
             &other,
             &name,
             is_namespace_merge_value_declaration,
-        ) {
-            return Some(is_used);
+        ) == Some(true)
+        {
+            return Some(true);
         }
     }

     Some(false)

Apply the same tweak in is_value_merged_with_used_namespace.

Also applies to: 568-577

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

In `@crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs` around
lines 542 - 552, The loops that call is_merge_candidate_used (iterating
scope.bindings()) must not return on the first Some(..) because later bindings
may mark the name as used/exported; instead, scan all candidates: if any call
yields Some(true) return Some(true) immediately; otherwise record if any
returned Some(false) and continue; after the loop return Some(false) if a false
was seen, else return None. Apply the same change to the analogous loop in
is_value_merged_with_used_namespace (and to the other affected loop at the
568-577 region) so both functions aggregate results across all merge candidates
rather than depending on the bindings() order.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 29, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 168 skipped benchmarks1


Comparing raashish1601:contributor-65-7664-followup (3b516a6) with main (c17e08e)

Open in CodSpeed

Footnotes

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

@Conaclos Conaclos added the M-Likely Agent Meta: this was likely an automated PR without a human in the loop label Mar 29, 2026
@ematipico ematipico closed this Mar 29, 2026
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 M-Likely Agent Meta: this was likely an automated PR without a human in the loop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

💅 lint/correctness/noUnusedVariables triggering on namespaces used to define component props

3 participants