Skip to content

fix(lint): handle namespace declaration merging in noUnusedVariables#9320

Merged
dyc3 merged 7 commits intobiomejs:mainfrom
taberoajorge:fix/namespace-declaration-merging-unused
Mar 29, 2026
Merged

fix(lint): handle namespace declaration merging in noUnusedVariables#9320
dyc3 merged 7 commits intobiomejs:mainfrom
taberoajorge:fix/namespace-declaration-merging-unused

Conversation

@taberoajorge
Copy link
Copy Markdown
Contributor

Summary

Fixes #7664.

TypeScript allows declaration merging between namespaces and value declarations (const, function, class) of the same name. Before this fix, noUnusedVariables flagged both sides of a merged declaration as unused even when one was exported or referenced, because Biome's semantic model treats each binding independently.

This PR adds is_declaration_merged_with_used() to is_unused(). When a namespace binding appears unused, it checks whether a non-namespace binding with the same name exists and is exported or has external references. The reverse is also handled: when a value binding appears unused, it checks whether a namespace with the same name is exported. This avoids infinite recursion by only checking is_exported (not is_unused) in the value→namespace direction.

Covered patterns

// const + namespace + export default
const MyComponent = () => {};
namespace MyComponent {
    export type Props = { id: string };
}
export default MyComponent;

// export function + namespace
export function MyFunction() {}
namespace MyFunction {
    export type Config = { timeout: number };
}

// function + namespace + export default
function foo() {}
namespace foo {
    export function bar() { foo(); }
}
export default foo;

All three patterns are now correctly identified as used through declaration merging.

AI Disclosure

This PR was written with assistance from Cursor (Claude). The approach, code, and tests were reviewed and validated manually.

Test Plan

  • Added validNamespaceDeclarationMerging.ts — const + namespace + export default (no diagnostics)
  • Added validNamespaceDeclarationMerging2.ts — export function + namespace (no diagnostics)
  • Added validNamespaceDeclarationMerging3.ts — function + namespace + export default (no diagnostics)
  • Added invalidNamespaceUnused.ts — standalone namespace and both-unused const+namespace (diagnostics emitted correctly)
  • All 51 existing noUnusedVariables tests pass
  • cargo clippy -p biome_js_analyze -- -D warnings passes clean
  • rustfmt --edition 2024 --check passes clean

Documentation

Rule documentation updated inline (valid example added to declare_lint_rule! doc comment).

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 3, 2026

🦋 Changeset detected

Latest commit: 55b1008

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 labels Mar 3, 2026
@taberoajorge taberoajorge marked this pull request as ready for review March 4, 2026 00:13
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 4, 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

Adds detection for TypeScript declaration merging between a namespace and a value declaration (const, function, or class) that share the same name. Introduces private helpers to locate a binding's containing statement list, discover merged declarations, and determine whether a merged counterpart is exported or otherwise used; the noUnusedVariables unused check short‑circuits for such merged pairs. Adds a changeset entry and several tests covering valid and invalid namespace/value merge scenarios.

Possibly related PRs

Suggested reviewers

  • ematipico
  • dyc3
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarises the main change: fixing the noUnusedVariables lint rule to handle TypeScript namespace declaration merging.
Description check ✅ Passed The description is detailed and well-organised, explaining the problem, solution approach, covered patterns, test plan, and documentation updates—all directly related to the changeset.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #7664 by implementing detection and handling of TypeScript declaration merging in the noUnusedVariables rule, covering all patterns and adding appropriate tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the noUnusedVariables rule for namespace declaration merging; no unrelated modifications detected.

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


ℹ️ 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 1022662 and 2f0117e.

⛔ Files ignored due to path filters (4)
  • 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/validNamespaceDeclarationMerging2.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMerging3.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (6)
  • .changeset/fix-namespace-declaration-merging-unused.md
  • 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/validNamespaceDeclarationMerging2.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMerging3.ts

@taberoajorge taberoajorge force-pushed the fix/namespace-declaration-merging-unused branch from 2f0117e to f2f404f Compare March 4, 2026 00:32
@taberoajorge
Copy link
Copy Markdown
Contributor Author

@ematipico @dyc3 It's a pleasure contributing in this project!

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/correctness/no_unused_variables.rs`:
- Around line 532-549: The merge-pair logic currently allows any non-namespace
binding to match a namespace (is_namespace && !other_is_namespace) which can
hide unused-namespace diagnostics; define other_is_value analogous to is_value
(i.e., check other_decl for the same explicit value declaration kinds used to
set is_value) and replace the is_merge_pair condition with (is_namespace &&
other_is_value) || (is_value && other_is_namespace), leaving the subsequent
checks (other_list_range, other.name_token, and the export/unused check)
unchanged so only explicit value declarations are considered namespace-merge
candidates.

ℹ️ 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 2f0117e and f2f404f.

⛔ Files ignored due to path filters (4)
  • 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/validNamespaceDeclarationMerging2.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMerging3.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (6)
  • .changeset/fix-namespace-declaration-merging-unused.md
  • 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/validNamespaceDeclarationMerging2.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMerging3.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMerging.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMerging3.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidNamespaceUnused.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validNamespaceDeclarationMerging2.ts

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 4, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 156 skipped benchmarks1


Comparing taberoajorge:fix/namespace-declaration-merging-unused (55b1008) with main (3064906)

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.

@dyc3
Copy link
Copy Markdown
Contributor

dyc3 commented Mar 4, 2026

#9320 (comment)

If we can, lets try to address the perf issue.

Scope-gate merge candidates: only match bindings whose declarations
share the same containing statement list (JsModuleItemList or
JsStatementList) to prevent a same-name binding in a nested scope
from suppressing a real unused diagnostic.

Added regression fixture for shadowed same-name bindings.
…edVariables

Replace `model.all_bindings().any(...)` with `model.scope(&statement_list).bindings().any(...)`
in `is_declaration_merged_with_used`. This reduces iteration from O(total_bindings) to
O(scope_bindings) per check, eliminating the O(N²) behavior on large files.

Additional improvements:
- Reorder checks: name comparison (cheap) before declaration type (expensive .declaration() call)
- Constrain merge-pair candidates: replace `!other_is_namespace` with explicit `other_is_value`
  to prevent imports/parameters from suppressing namespace diagnostics
Add `/* should generate diagnostics */` to ensure the test framework
asserts that diagnostics are actually produced. Update snapshot to
reflect the new line numbers.
@taberoajorge taberoajorge force-pushed the fix/namespace-declaration-merging-unused branch from 5160ac7 to 3976c5b Compare March 5, 2026 01:37
…used

Move `is_declaration_merged_with_used` to run only after the reference
check determines a binding appears unused. Most bindings have external
references and now skip the merge check entirely, avoiding the cost of
`declaration()`, `containing_statement_list()`, `scope()`, and
`scope.bindings()` iteration for the common case.
@taberoajorge taberoajorge requested a review from dyc3 March 5, 2026 23:19
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.

There's some opportunity to make the code a bit more readable, can you please address that ?

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/correctness/no_unused_variables.rs`:
- Around line 493-500: The function is_namespace_merge_value_declaration
currently checks JsVariableDeclarator, JsFunctionDeclaration, and
JsClassDeclaration but misses TypeScript enums; update
is_namespace_merge_value_declaration to also treat TsEnumDeclaration as a value
declaration so enum+namespace merges (e.g., export enum Foo {} with namespace
Foo {}) are recognized as valid merges and not flagged as unused—add
AnyJsBindingDeclaration::TsEnumDeclaration(_) to the matches arm for
is_namespace_merge_value_declaration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6873cdaf-a0b7-4ad7-a4a9-344cedab2fc3

📥 Commits

Reviewing files that changed from the base of the PR and between c9cb35d and 5747a15.

📒 Files selected for processing (1)
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs

@taberoajorge taberoajorge requested a review from ematipico March 6, 2026 16:41
@raashish1601

This comment was marked as spam.

@dyc3 dyc3 self-assigned this Mar 29, 2026
@dyc3 dyc3 merged commit 93c3b6c into biomejs:main Mar 29, 2026
18 checks passed
@github-actions github-actions bot mentioned this pull request 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

4 participants