Skip to content

Conversation

@curatedcode
Copy link

Summary

Fixes interfaces and namespaces being incorrectly flagged by the noUnusedVariables rule in script files (files without top-level imports or exports).

Before (incorrect):

interface Array<T> {
    method: (a: T) => void;
}
// "Array" flagged as unused

After (correct):

interface Array<T> {
    method: (a: T) => void;
}
// "Array" is not flagged

The rule now treats script files differently and does not report unused top-level interface or namespace declarations.

Fixes #8331

Test Plan

Added a new test case (validScriptGlobals.ts) covering unused interfaces and namespaces in script files.

@changeset-bot
Copy link

changeset-bot bot commented Dec 19, 2025

🦋 Changeset detected

Latest commit: 90c0133

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 Dec 19, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 19, 2025

Walkthrough

Adds an early-exit to the noUnusedVariables lint rule so top-level TypeScript interface and namespace declarations in script files (files with no top-level imports/exports and package type not Module) are not reported as unused. Introduces a private helper to detect top-level TsInterfaceDeclaration/TsModuleDeclaration within the top-level module item list, gates the exemption by package.json module type, updates imports for required AST node types, and adds tests (validScriptGlobals.ts and invalidScriptGlobals.ts). Adjusts quick_test layout and test setup to exercise the change.

Suggested reviewers

  • dyc3
  • ematipico

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: fixing noUnusedVariables to ignore interfaces/namespaces in script files.
Description check ✅ Passed The description is well-structured, explains the problem and solution with clear before/after examples, and references the linked issue #8331.
Linked Issues check ✅ Passed The PR successfully addresses the objective from #8331 by preventing noUnusedVariables from falsely reporting top-level interface/namespace declarations as unused in script files.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the noUnusedVariables rule fix and supporting tests; no out-of-scope modifications were detected.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da60805 and 90c0133.

📒 Files selected for processing (2)
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
  • crates/biome_js_analyze/tests/quick_test.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use inline rustdoc documentation for rules, assists, and their options
Use the dbg!() macro for debugging output in Rust tests and code
Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests

Files:

  • crates/biome_js_analyze/tests/quick_test.rs
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
🧠 Learnings (32)
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Assist rules should detect refactoring opportunities and emit code action signals

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:06:12.048Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_service/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:12.048Z
Learning: Applies to crates/biome_service/src/workspace/watcher.tests.rs : Implement watcher tests for workspace methods in watcher.tests.rs and end-to-end tests in LSP tests

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set `language` to `jsx`, `ts`, or `tsx` for rules that only apply to specific JavaScript dialects

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `domains` field in `declare_lint_rule!` to tag rules that belong to specific concepts like testing or frameworks

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `options` code block property for rule-specific configuration snippets in documentation

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Add `sources` field with `RuleSource` to cite ESLint or other rules that inspired the implementation

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : The first paragraph of rule documentation must be a single line describing what the rule does

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `file=<path>` property for multi-file rule documentation examples

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use generic rule names if the rule could potentially be implemented for multiple languages

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set `version` field to `next` in `declare_lint_rule!` macro

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use helper functions like `map`, `filter`, and `and_then` to avoid deep indentation

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-21T21:15:03.796Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-21T21:15:03.796Z
Learning: Applies to **/*.rs : Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-11-24T18:05:20.371Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:20.371Z
Learning: Create test infrastructure with `tests/specs` folder structure and `spec_test.rs`, `spec_tests.rs`, and `language.rs` files in test directories

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).inspired()` when implementing a rule inspired by but with different behavior than an ESLint rule

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `declare_lint_rule!` macro to declare analyzer rule types and implement the RuleMeta trait

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).same()` when implementing a rule that matches the behavior of an ESLint rule

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Run `just gen-analyzer` to generate all analyzer code and rule metadata

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Rule documentation code blocks should be ordered as language, expect_diagnostic, options/full_options/use_options, ignore, file

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use language-specific rule names if the rule is meant for a specific language only

Applied to files:

  • crates/biome_js_analyze/tests/quick_test.rs
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Check if a variable is global using the semantic model to avoid false positives

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/js_module_info/collector.rs : Implement module-level (thin) inference to resolve `TypeReference::Qualifier` variants by looking up declarations in module scopes and handling import statements

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-22T09:26:56.943Z
Learnt from: ematipico
Repo: biomejs/biome PR: 8537
File: crates/biome_js_analyze/src/lint/nursery/no_leaked_render.rs:167-210
Timestamp: 2025-12-22T09:26:56.943Z
Learning: When defining lint rules (declare_lint_rule!), only specify fix_kind if the rule implements an action(...) function. Rules that only emit diagnostics without a code fix should omit fix_kind. This applies to all Rust lint rule definitions under crates/.../src/lint (e.g., crates/biome_js_analyze/src/lint/...).

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should perform static analysis of source code to detect invalid or error-prone patterns and emit diagnostics with proposed fixes

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should check syntax according to language specification and emit error diagnostics

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Specify `fix_kind: FixKind::Unsafe` in `declare_lint_rule!` for unsafe code actions

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Implement `action` function in Rule trait to provide code actions

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `ctx.model()` to access semantic model information in a rule

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/biome_rule_options/lib/**/*.rs : Use `rename_all = "camelCase"` in serde derive macro for rule options

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.413Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.413Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `Semantic<>` query type to access semantic model information like scopes and declarations

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` instead of `Arc` for types that reference other types to avoid stale cache issues when modules are replaced

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/local_inference.rs : Implement local inference in dedicated modules to derive type definitions from expressions without context of surrounding scopes

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Use `ConditionalParsedSyntax` for syntax that is only valid in specific contexts (e.g., strict mode, file types, language versions) and call `or_invalid_to_bogus()` to convert to a bogus node if not supported

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
🔇 Additional comments (7)
crates/biome_js_analyze/tests/quick_test.rs (3)

18-29: LGTM – helper function updated to accept root path and set CommonJs type.

The signature change and PackageType::CommonJs assignment align well with the rule's new behaviour of exempting script files. Clean integration.


36-44: Test content appropriately exercises the new exemption.

The interface and namespace declarations will verify that is_script_declaration correctly exempts these from being flagged as unused in a CommonJs/script context.


60-70: Rule filter and project layout setup look correct.

Switching to noUnusedVariables and deriving the root from the temporary filesystem ensures the test exercises the intended code path.

crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs (4)

1-2: Imports look appropriate for the new functionality.

The additions (Arc, JsExport, JsImport, JsModule, JsModuleItemList, PackageJson, PackageType, Utf8PathBuf) are all used by the new script declaration detection logic.

Also applies to: 12-21


71-78: Good documentation addition.

Clear explanation of the script file exemption behaviour. The example demonstrates the use case of augmenting global Array<T>.


322-329: Module type gating logic is sound.

The exemption only applies when the package is not a module type (!is_module_type). This correctly ensures that ES modules still report unused interfaces/namespaces.

One small observation: if the manifest service is unavailable (returns None), is_some_and yields false, so is_module_type becomes false. This means the exemption would apply when there's no package.json, which seems reasonable for legacy script files. Worth confirming this is the intended behaviour.


430-463: The critical logic error from the previous review has been addressed.

The function now correctly:

  1. Checks that the binding's declaration is specifically a TsInterfaceDeclaration or TsModuleDeclaration (lines 433-445).
  2. Only then verifies the file has no top-level imports/exports (lines 447-462).

This ensures that other declarations (e.g., const unused = 1) in a file containing an interface are not incorrectly exempted. Nice fix!


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

🧹 Nitpick comments (1)
crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validScriptGlobals.ts (1)

1-8: Consider adding a test case with mixed declarations.

The test validates that interfaces and namespaces aren't flagged, but doesn't verify that regular unused variables in the same script file are still correctly reported. Adding a case like:

const unused = 1; // should still be flagged
interface Foo {} // should not be flagged

would ensure the fix is properly scoped.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fc24f4 and e3a9671.

⛔ Files ignored due to path filters (1)
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validScriptGlobals.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (3)
  • .changeset/wet-memes-shop.md (1 hunks)
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs (3 hunks)
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validScriptGlobals.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
.changeset/*.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Write changesets that are concise (1-3 sentences), user-focused, use past tense for actions taken and present tense for Biome behavior, include code examples for rules, and end sentences with periods

Files:

  • .changeset/wet-memes-shop.md
crates/**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Update inline rustdoc documentation for rules, assists, and their options when adding new features or changing existing features in Rust crates

Files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
🧠 Learnings (23)
📓 Common learnings
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noUnused` prefix for rules that report unused entities (e.g., `noUnusedVariables`)
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Check if a variable is global before banning it to avoid false positives when the variable is redeclared in local scope; use the semantic model to verify global scope
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noUndeclared` prefix for rules that report undefined entities (e.g., `noUndeclaredVariables`)
📚 Learning: 2025-12-12T10:11:05.564Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-12-12T10:11:05.564Z
Learning: Create a changeset for user-facing bug fixes and features using `just new-changeset`, selecting appropriate packages and change type (`major`, `minor`, or `patch`)

Applied to files:

  • .changeset/wet-memes-shop.md
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Commit rule changes with message format: `feat(biome_<crate>): <ruleName>` to follow Biome's conventional commit style

Applied to files:

  • .changeset/wet-memes-shop.md
📚 Learning: 2025-08-05T14:43:29.581Z
Learnt from: dyc3
Repo: biomejs/biome PR: 7081
File: packages/@biomejs/biome/configuration_schema.json:7765-7781
Timestamp: 2025-08-05T14:43:29.581Z
Learning: The file `packages/biomejs/biome/configuration_schema.json` is auto-generated and should not be manually edited or reviewed for schema issues; any changes should be made at the code generation source.

Applied to files:

  • .changeset/wet-memes-shop.md
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noUnused` prefix for rules that report unused entities (e.g., `noUnusedVariables`)

Applied to files:

  • .changeset/wet-memes-shop.md
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noUndeclared` prefix for rules that report undefined entities (e.g., `noUndeclaredVariables`)

Applied to files:

  • .changeset/wet-memes-shop.md
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Check if a variable is global before banning it to avoid false positives when the variable is redeclared in local scope; use the semantic model to verify global scope

Applied to files:

  • .changeset/wet-memes-shop.md
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validScriptGlobals.ts
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noUseless` prefix for rules that report unnecessary code that could be removed or simplified (e.g., `noUselessConstructor`)

Applied to files:

  • .changeset/wet-memes-shop.md
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noRestricted` prefix for rules that report user-banned entities (e.g., `noRestrictedGlobals`)

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noRedundant` prefix for rules that report redundant code (e.g., `noRedundantUseStrict`)

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noUnknown` prefix for rules that report mistyped entities in CSS (e.g., `noUnknownUnit`)

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Framework-specific rules should be named using the `use` or `no` prefix followed by the framework name (e.g., `noVueReservedProps`)

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noEmpty` prefix for rules that report empty code (e.g., `noEmptyBlockStatements`)

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `noInvalid` prefix for rules that report runtime errors from mistyping (e.g., `noInvalidConstructorSuper`)

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Use `ConditionalParsedSyntax` for syntax that is only valid in specific contexts (e.g., strict mode, file types, language versions) and call `or_invalid_to_bogus()` to convert to a bogus node if not supported

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/local_inference.rs : Implement local inference in dedicated modules to derive type definitions from expressions without context of surrounding scopes

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/js_module_info/collector.rs : Implement module-level (thin) inference to resolve `TypeReference::Qualifier` variants by looking up declarations in module scopes and handling import statements

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use `Semantic<T>` query type instead of `Ast<T>` when a rule needs to access the semantic model for binding references and scope information

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the `FormatNode` trait and implement it for your Node when creating formatters in biome_js_formatter

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When formatting AST nodes, use mandatory tokens from the AST instead of hardcoding token strings (e.g., use `node.l_paren_token().format()` instead of `token("(")`)

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` variants (`Qualifier`, `Resolved`, `Import`, `Unknown`) to represent different phases of type resolution

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/scoped_resolver.rs : Implement full inference to resolve `TypeReference::Import` variants across the entire module graph

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `declare_node_union!` macro to query multiple node types at once by joining them into an enum with `Any*Like` naming convention

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
🔇 Additional comments (3)
crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs (2)

309-312: Short-circuit placement looks correct.

The exemption is properly integrated early in the run flow, returning None to indicate no violation. This placement is appropriate, assuming the is_script_declaration logic is corrected.

However, this depends on fixing the critical bug in is_script_declaration. Once corrected, verify with a test that includes both unused variables and interfaces/namespaces in the same script file.


11-14: Imports look correct.

All necessary types are imported to support the script detection logic.

.changeset/wet-memes-shop.md (1)

1-5: Well-written changeset.

Follows all guidelines: concise, user-focused, uses present tense, includes a code example, references the issue, and ends with a full stop. As per coding guidelines for changesets.

Comment on lines 413 to 445
/// Returns `true` if the file is considered a script
/// and is an interface or namespace
fn is_script_declaration(binding: &AnyJsIdentifierBinding) -> bool {
binding
.syntax()
.ancestors()
.find_map(JsModuleItemList::cast)
.is_some_and(|module_list| {
// Only check top-level declarations
let is_top_level = module_list.parent::<JsModule>().is_some();

if !is_top_level {
return false;
}

// Presence of imports/exports means its a module
let has_import_or_export = (&module_list).into_iter().any(|item| {
let kind = item.syntax().kind();
JsImport::can_cast(kind) || JsExport::can_cast(kind)
});

if has_import_or_export {
return false;
}

// We can't determine if an interface/namespace augments a global just from syntax
// so all of them are treated the same
module_list.into_iter().any(|item| {
let kind = item.syntax().kind();
TsInterfaceDeclaration::can_cast(kind) || TsModuleDeclaration::can_cast(kind)
})
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical logic error: function exempts all declarations, not just interfaces/namespaces.

The function checks whether the file contains ANY interface or namespace (lines 440-443), but doesn't verify that the current binding itself is from an interface or namespace declaration. This means ALL top-level declarations in a script file that happens to contain at least one interface/namespace would be incorrectly exempted.

For example:

const unused = 1; // Would incorrectly pass is_script_declaration
interface Foo {}  // Should pass

The unused variable would be exempted because the file contains an interface, even though unused itself isn't an interface or namespace.

🔎 Suggested fix
 fn is_script_declaration(binding: &AnyJsIdentifierBinding) -> bool {
+    // First verify this binding is from an interface or namespace
+    let Some(decl) = binding.declaration() else {
+        return false;
+    };
+    let is_interface_or_namespace = matches!(
+        decl,
+        AnyJsBindingDeclaration::TsInterfaceDeclaration(_)
+            | AnyJsBindingDeclaration::TsModuleDeclaration(_)
+    );
+    if !is_interface_or_namespace {
+        return false;
+    }
+
+    // Then check if it's in a script file (no imports/exports)
     binding
         .syntax()
         .ancestors()
         .find_map(JsModuleItemList::cast)
         .is_some_and(|module_list| {
-            // Only check top-level declarations
             let is_top_level = module_list.parent::<JsModule>().is_some();
-
             if !is_top_level {
                 return false;
             }
-
-            // Presence of imports/exports means its a module
-            let has_import_or_export = (&module_list).into_iter().any(|item| {
+            
+            // If there are imports/exports, it's a module, not a script
+            !module_list.into_iter().any(|item| {
                 let kind = item.syntax().kind();
                 JsImport::can_cast(kind) || JsExport::can_cast(kind)
-            });
-
-            if has_import_or_export {
-                return false;
-            }
-
-            // We can't determine if an interface/namespace augments a global just from syntax
-            // so all of them are treated the same
-            module_list.into_iter().any(|item| {
-                let kind = item.syntax().kind();
-                TsInterfaceDeclaration::can_cast(kind) || TsModuleDeclaration::can_cast(kind)
             })
         })
 }
🤖 Prompt for AI Agents
In crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs around
lines 413-445, the function currently treats any top-level file that contains
some interface or namespace as exempting all declarations; change the logic to
determine whether the specific binding itself is declared by an interface or
namespace. Concretely, instead of scanning the module list for any
interface/module, locate the binding's declaration ancestor (e.g., find the
nearest TsInterfaceDeclaration or TsModuleDeclaration that is an ancestor of the
binding), ensure that declaration is top-level in a script (no imports/exports
in the module), and only return true when the binding is actually part of that
interface/namespace declaration; keep the existing top-level and import/export
checks but apply them to the declaration containing the binding rather than to
the whole file.

Copy link
Contributor

Choose a reason for hiding this comment

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

Add a test case for what the bot suggested.

@curatedcode
Copy link
Author

I modified the function like coderabbit suggested so now it contains material generated by ai. Just an FYI

Also would you like me to add a test case like coderabbit suggested in its first nitpick? IMO I think it's a nice touch

@codspeed-hq
Copy link

codspeed-hq bot commented Dec 19, 2025

CodSpeed Performance Report

Merging #8506 will not alter performance

Comparing curatedcode:fix/no-unused-variables-script-globals (a0e5ab7) with main (d64e92d)1

Summary

✅ 58 untouched
⏩ 95 skipped2

Footnotes

  1. No successful run was found on main (90e8684) during the generation of this report, so d64e92d was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

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

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.

Thanks for the PR! Just a couple notes, also note the CI


/// Returns `true` if the file is considered a script
/// and is an interface or namespace
fn is_script_declaration(binding: &AnyJsIdentifierBinding) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm pretty sure we should be determining this from JsFileSource


namespace M {
export const n = 1;
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

I would leave a brief comment to explain why this is a valid case, because this issue is a bit convoluted.

Comment on lines 413 to 445
/// Returns `true` if the file is considered a script
/// and is an interface or namespace
fn is_script_declaration(binding: &AnyJsIdentifierBinding) -> bool {
binding
.syntax()
.ancestors()
.find_map(JsModuleItemList::cast)
.is_some_and(|module_list| {
// Only check top-level declarations
let is_top_level = module_list.parent::<JsModule>().is_some();

if !is_top_level {
return false;
}

// Presence of imports/exports means its a module
let has_import_or_export = (&module_list).into_iter().any(|item| {
let kind = item.syntax().kind();
JsImport::can_cast(kind) || JsExport::can_cast(kind)
});

if has_import_or_export {
return false;
}

// We can't determine if an interface/namespace augments a global just from syntax
// so all of them are treated the same
module_list.into_iter().any(|item| {
let kind = item.syntax().kind();
TsInterfaceDeclaration::can_cast(kind) || TsModuleDeclaration::can_cast(kind)
})
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a test case for what the bot suggested.

@curatedcode
Copy link
Author

I checked the behavior of ctx.source_type::<JsFileSource>().is_script() and it returns false for files containing a top-level interface or namespace, even when there are no imports or exports. Relying on is_script() would incorrectly exclude these declarations from the new exemption. Instead, I based the check on the absence of top-level imports/exports and the declaration kind (interface or namespace only). If there’s a preferred way to do this using JsFileSource or any other workaround, please let me know

@dyc3
Copy link
Contributor

dyc3 commented Dec 20, 2025

Yeah you're right, it looks like its only determined by if the file extension is cjs. We can keep the check you wrote so we don't have false positives, but JsFileSource::is_script() can still be used to short circuit the logic.

In the meantime, @coderabbitai can you file an issue about possibly auto detecting a js or ts file's ModuleKind based on the presence of import or export? It would be better if we could provide lint rules correct information, but the change has broader implications.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue about auto-detecting ModuleKind based on imports/exports

gh issue create \
  --title "Auto-detect ModuleKind for JS/TS files based on import/export presence" \
  --body "## Summary

Currently, \`JsFileSource::is_script()\` only determines the module kind based on the file extension (e.g., \`.cjs\`). This can lead to incorrect assumptions about whether a file is a script or module, as a \`.js\` or \`.ts\` file without imports/exports is treated as a module even though it behaves as a script.

## Proposal

Auto-detect a JS or TS file's \`ModuleKind\` based on the presence of \`import\` or \`export\` statements in the file content. Files without top-level imports or exports would be treated as scripts, whilst those with imports/exports would be treated as modules.

## Benefits

- Provides lint rules with more accurate information about the file's module context
- Reduces false positives in rules that need to distinguish between script and module scope
- Better aligns with the actual runtime behaviour of the code

## Context

This issue was identified whilst fixing #8331 in #8506. The \`noUnusedVariables\` rule needed to handle top-level interfaces/namespaces in script files differently, but couldn't rely on \`is_script()\` because it doesn't accurately detect scripts based on content.

## Considerations

This change has broader implications across the codebase, as it would affect how all lint rules and analysis tools interpret file types.

---

**Backlinks:**
- PR: #8506
- Requested by: @dyc3" \
  --assignee dyc3

Length of output: 44


@dyc3, I've filed the issue about auto-detecting ModuleKind based on import/export presence: #8526. The issue is assigned to you and includes backlinks to this PR and the discussion context.


🧠 Learnings used
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).same()` when implementing a rule that matches the behavior of an ESLint rule

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).inspired()` when implementing a rule inspired by but with different behavior than an ESLint rule

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Add `sources` field with `RuleSource` to cite ESLint or other rules that inspired the implementation

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set `language` to `jsx`, `ts`, or `tsx` for rules that only apply to specific JavaScript dialects

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Specify `fix_kind: FixKind::Safe` in `declare_lint_rule!` for safe code actions

Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should perform static analysis of source code to detect invalid or error-prone patterns and emit diagnostics with proposed fixes

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

♻️ Duplicate comments (1)
crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validScriptGlobals.ts (1)

1-4: Consider adding an explanatory comment for the interface case as well.

The namespace at line 6 has a helpful comment explaining why it's valid. The Array<T> interface merges with the global Array type (declaration merging), which is the core issue from #8331. A brief note here would help future readers understand why this shouldn't trigger a diagnostic.

🧹 Nitpick comments (2)
crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidScriptGlobals.ts (1)

1-5: Add a comment explaining the expected diagnostic behaviour.

This test file validates that const c is flagged as unused while interface I and namespace N are exempt (per the new script-file logic). Without a comment, readers might wonder why interface and namespace are in an "invalid" test file when they won't produce diagnostics.

Consider adding something like:

+/* Only `const c` should be flagged; interface and namespace are exempt in script files. */
 const c = 1;
crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs (1)

414-454: Implementation is correct; consider documenting the script-file exemption in the rule's main docs.

The logic properly checks that the binding itself is from an interface or namespace before applying the script-file exemption—addressing the earlier critical issue about exempting all declarations.

Per coding guidelines, you may want to add a brief note in the declare_lint_rule! docs (around lines 65-95 in the "Valid" section) explaining that top-level interfaces and namespaces in script files are not reported, since they may be used for global augmentation.

🔎 Example documentation addition
 /// ### Valid
 ///
+/// Top-level interfaces and namespaces in script files (files without
+/// imports or exports) are not reported, as they may augment global types:
+///
+/// ```ts
+/// interface Array<T> {
+///     customMethod: () => void;
+/// }
+/// ```
+///
 /// ```js
 /// function foo(b) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0e5ab7 and e0eccaa.

⛔ Files ignored due to path filters (2)
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidScriptGlobals.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validScriptGlobals.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • .changeset/wet-memes-shop.md (1 hunks)
  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs (3 hunks)
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidScriptGlobals.ts (1 hunks)
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validScriptGlobals.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .changeset/wet-memes-shop.md
🧰 Additional context used
📓 Path-based instructions (1)
crates/**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Update inline rustdoc documentation for rules, assists, and their options when adding new features or changing existing features in Rust crates

Files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
🧠 Learnings (14)
📚 Learning: 2025-12-19T12:53:30.399Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Check if a variable is global using the semantic model to avoid false positives

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/validScriptGlobals.ts
  • crates/biome_js_analyze/tests/specs/correctness/noUnusedVariables/invalidScriptGlobals.ts
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/js_module_info/collector.rs : Implement module-level (thin) inference to resolve `TypeReference::Qualifier` variants by looking up declarations in module scopes and handling import statements

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.399Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Set `language` to `jsx`, `ts`, or `tsx` for rules that only apply to specific JavaScript dialects

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Distinguish between `TypeData::Unknown` and `TypeData::UnknownKeyword` to measure inference effectiveness versus explicit user-provided unknown types

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/scoped_resolver.rs : Implement full inference to resolve `TypeReference::Import` variants across the entire module graph

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.399Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should check syntax according to language specification and emit error diagnostics

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.399Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Lint rules should perform static analysis of source code to detect invalid or error-prone patterns and emit diagnostics with proposed fixes

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.399Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Specify `fix_kind: FixKind::Unsafe` in `declare_lint_rule!` for unsafe code actions

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Use `ConditionalParsedSyntax` for syntax that is only valid in specific contexts (e.g., strict mode, file types, language versions) and call `or_invalid_to_bogus()` to convert to a bogus node if not supported

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/local_inference.rs : Implement local inference in dedicated modules to derive type definitions from expressions without context of surrounding scopes

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the `FormatNode` trait and implement it for your Node when creating formatters in biome_js_formatter

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-12-19T12:53:30.399Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-12-19T12:53:30.399Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/**/*.rs : Use `RuleSource::Eslint(...).same()` when implementing a rule that matches the behavior of an ESLint rule

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When formatting AST nodes, use mandatory tokens from the AST instead of hardcoding token strings (e.g., use `node.l_paren_token().format()` instead of `token("(")`)

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` variants (`Qualifier`, `Resolved`, `Import`, `Unknown`) to represent different phases of type resolution

Applied to files:

  • crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs (1)
crates/biome_analyze/src/lib.rs (1)
  • is_top_level (776-778)
🔇 Additional comments (2)
crates/biome_js_analyze/src/lint/correctness/no_unused_variables.rs (2)

10-14: LGTM!

The added imports are all utilised by the new is_script_declaration helper.


309-313: LGTM!

Clean integration. The early exit for script declarations is placed sensibly after the declaration file check and before the underscore prefix logic.

@ematipico
Copy link
Member

There's already infrastructure for that. If we pull the manifest, we can check package.json#type. If the value is module, it's not a script

@curatedcode
Copy link
Author

Thanks that makes sense. I'm still pretty new to the codebase. Could you point me to where this is exposed to the lint rules if you’d like me to update this PR to use that.

@dyc3
Copy link
Contributor

dyc3 commented Dec 22, 2025

There's a PackageJson service. noUnknownAttribute uses it, you can use that for reference.

@curatedcode
Copy link
Author

I kept the imports/exports check in is_script_declaration as a fallback since I noticed some test files with exports were getting None from the PackageJson service and being exempted incorrectly.

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.

💅 Templated merged interface incorrectly identified as unused

3 participants