Skip to content

feat(lint/js): add noUselessTypeConversion#9701

Merged
dyc3 merged 1 commit intomainfrom
dyc3/no-unnecessary-type-conversion
Mar 31, 2026
Merged

feat(lint/js): add noUselessTypeConversion#9701
dyc3 merged 1 commit intomainfrom
dyc3/no-unnecessary-type-conversion

Conversation

@dyc3
Copy link
Copy Markdown
Contributor

@dyc3 dyc3 commented Mar 29, 2026

Summary

This adds noUselessTypeConversion, a port of https://typescript-eslint.io/rules/no-unnecessary-type-conversion

generated by gpt 5.4

Test Plan

snapshots

Docs

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 29, 2026

🦋 Changeset detected

Latest commit: dd78724

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-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels Mar 29, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Mar 29, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 196 skipped benchmarks1


Comparing dyc3/no-unnecessary-type-conversion (dd78724) with main (9856a87)

Open in CodSpeed

Footnotes

  1. 196 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions github-actions Bot added the A-CLI Area: CLI label Mar 29, 2026
@dyc3 dyc3 force-pushed the dyc3/no-unnecessary-type-conversion branch from 80290f8 to ad4e684 Compare March 29, 2026 18:59
@dyc3 dyc3 marked this pull request as ready for review March 29, 2026 19:29
@dyc3

This comment was marked as resolved.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 29, 2026

Walkthrough

Adds a new nursery lint rule noUselessTypeConversion that detects redundant primitive coercions: global helpers (String, Number, Boolean, BigInt) called on values already of the target primitive; .toString() on string-typed receivers; string concatenation (+ / +=) with empty strings when the other operand is string-typed; and redundant unary/coercion patterns (+x, !!x as !(!x), ~~x as ~(~x)). Introduces rule state and a node union, conservative type checks, diagnostics with notes, rule options and module registration, TypedService changes to hold a SemanticModel and a binding check, plus new valid/invalid tests and a changeset.

Suggested reviewers

  • ematipico
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and concisely describes the main change: adding a new linter rule noUselessTypeConversion for JavaScript.
Description check ✅ Passed The description is related to the changeset, explaining the motivation as a port of TypeScript ESLint's rule, and disclosing AI assistance as requested.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dyc3/no-unnecessary-type-conversion

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/nursery/no_useless_type_conversion.rs`:
- Around line 435-446: The current has_shadowed_binding function walks the
entire syntax tree and treats any identifier named `name` as shadowing even when
it lives in a different scope; restrict the lookup to the lexical scope that
contains the call instead. Change has_shadowed_binding to find the nearest
enclosing scope node for the call (e.g., the nearest function, method, block,
module/Script node) and only search descendants of that scope node for
AnyJsIdentifierBinding with the same name, still ignoring the binding that has
the same range as the call; this prevents nested/outer unrelated bindings
(parameters or locals) from silencing a top-level String(...) diagnostic and
avoids a full-tree walk per candidate.
- Around line 282-290: The code only handles identifier assignments (using
AnyJsAssignmentPattern::AnyJsAssignment and as_js_identifier_assignment) so
member/element targets like obj.name += "" or arr[i] += "" are skipped; extend
the match to also handle member/element assignment variants (the
AnyJsAssignmentPattern branch that yields a member/element assignment — e.g.,
use the pattern that exposes as_js_member_assignment / as_js_element_assignment
or similar on the same assign value), extract the target expression from that
member/element assignment and query its static type the same way you do for
identifiers (call the appropriate ctx type-check helper, e.g., reuse
ctx.type_of_named_value or call the corresponding ctx.type_of_member_value), and
return None only if you cannot resolve the type; keep node.range() as the
location passed into the type query.
🪄 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: d562b57b-5f95-47cf-9309-641afeef6105

📥 Commits

Reviewing files that changed from the base of the PR and between fa561a5 and ad4e684.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (6)
  • .changeset/upset-women-open.md
  • crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/no_useless_type_conversion.rs

Comment on lines +282 to +290
let left = node.left().ok()?;
let ty = match left {
AnyJsAssignmentPattern::AnyJsAssignment(assign) => {
let identifier = assign.as_js_identifier_assignment()?;
let name = identifier.name_token().ok()?;
ctx.type_of_named_value(node.range(), name.text_trimmed())
}
_ => return None,
};
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 | 🟡 Minor

Handle member targets for += "" too.

This currently stops at JsIdentifierAssignment, so obj.name += "" or arr[i] += "" quietly escape even when the left-hand side is statically string. A tiny invalid snapshot for a typed member access would keep this branch honest.

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

In `@crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs`
around lines 282 - 290, The code only handles identifier assignments (using
AnyJsAssignmentPattern::AnyJsAssignment and as_js_identifier_assignment) so
member/element targets like obj.name += "" or arr[i] += "" are skipped; extend
the match to also handle member/element assignment variants (the
AnyJsAssignmentPattern branch that yields a member/element assignment — e.g.,
use the pattern that exposes as_js_member_assignment / as_js_element_assignment
or similar on the same assign value), extract the target expression from that
member/element assignment and query its static type the same way you do for
identifiers (call the appropriate ctx type-check helper, e.g., reuse
ctx.type_of_named_value or call the corresponding ctx.type_of_member_value), and
return None only if you cannot resolve the type; keep node.range() as the
location passed into the type query.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this feels valid

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

turns out our APIs don't make this easy to do, so I'm going to leave this alone for now

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it's worth adding issue_number to the metadata of the rule at this point, so we know things are still missing, but planned

Comment thread crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs Outdated
@dyc3 dyc3 force-pushed the dyc3/no-unnecessary-type-conversion branch from ad4e684 to d46083f Compare March 29, 2026 20:02
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.

♻️ Duplicate comments (2)
crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs (2)

435-446: ⚠️ Potential issue | 🟠 Major

Make the shadowing lookup lexical-scope aware.

This scan treats any nested String/Number/Boolean/BigInt binding as shadowing, so real top-level diagnostics can disappear even when that binding is out of scope. It also walks the whole file per candidate call, which is a strong suspect for the slowdown mentioned on the PR.

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

In `@crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs`
around lines 435 - 446, The current has_shadowed_binding function scans from the
file root and treats any identifier anywhere as shadowing; change it to find the
nearest enclosing lexical scope for the JsCallExpression (e.g., nearest
function, block, module/Script node) instead of using
call.syntax().ancestors().last(), then restrict the search to descendants of
that scope only and ensure bindings are actually in-scope (e.g., declared in the
same scope or an inner scope that would shadow the call), keeping the existing
checks on AnyJsIdentifierBinding, binding.name_token(), and binding.range() !=
call.range() to avoid self-matches; this both makes shadow detection lexically
correct and avoids walking the whole file for each call.

282-290: ⚠️ Potential issue | 🟡 Minor

+= "" still misses member and element targets.

This path only handles JsIdentifierAssignment, so cases like obj.name += "" and arr[i] += "" still slip past the rule even when the target is statically string.

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

In `@crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs`
around lines 282 - 290, The current match only handles JsIdentifierAssignment
via AnyJsAssignmentPattern -> AnyJsAssignment -> as_js_identifier_assignment, so
member (obj.name += "") and element (arr[i] += "") targets are skipped; update
the match for left (from node.left()) to, after trying
as_js_identifier_assignment, also detect member and element assignment targets
(e.g., as member/element variants on the AnyJsAssignment) and extract the target
expression, then call the appropriate type check (use
ctx.type_of_expression(node.range(), target_expr) or the existing helper that
computes a target's type) instead of only ctx.type_of_named_value, ensuring
member and element targets return their static type so the rule catches += "" on
those as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs`:
- Around line 435-446: The current has_shadowed_binding function scans from the
file root and treats any identifier anywhere as shadowing; change it to find the
nearest enclosing lexical scope for the JsCallExpression (e.g., nearest
function, block, module/Script node) instead of using
call.syntax().ancestors().last(), then restrict the search to descendants of
that scope only and ensure bindings are actually in-scope (e.g., declared in the
same scope or an inner scope that would shadow the call), keeping the existing
checks on AnyJsIdentifierBinding, binding.name_token(), and binding.range() !=
call.range() to avoid self-matches; this both makes shadow detection lexically
correct and avoids walking the whole file for each call.
- Around line 282-290: The current match only handles JsIdentifierAssignment via
AnyJsAssignmentPattern -> AnyJsAssignment -> as_js_identifier_assignment, so
member (obj.name += "") and element (arr[i] += "") targets are skipped; update
the match for left (from node.left()) to, after trying
as_js_identifier_assignment, also detect member and element assignment targets
(e.g., as member/element variants on the AnyJsAssignment) and extract the target
expression, then call the appropriate type check (use
ctx.type_of_expression(node.range(), target_expr) or the existing helper that
computes a target's type) instead of only ctx.type_of_named_value, ensuring
member and element targets return their static type so the rule catches += "" on
those as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 33e9ca98-5f4e-47c4-8187-9da06bb7563d

📥 Commits

Reviewing files that changed from the base of the PR and between ad4e684 and d46083f.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (6)
  • .changeset/upset-women-open.md
  • crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/no_useless_type_conversion.rs
✅ Files skipped from review due to trivial changes (4)
  • .changeset/upset-women-open.md
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts
  • crates/biome_rule_options/src/no_useless_type_conversion.rs

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.

Please make sure that all these internal functions have proper docstrings that explain their business logic

@dyc3 dyc3 force-pushed the dyc3/no-unnecessary-type-conversion branch 2 times, most recently from 2b25728 to 0fc7465 Compare March 30, 2026 00:58
@dyc3 dyc3 requested a review from ematipico March 30, 2026 00:59
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/no_useless_type_conversion.rs (1)

434-436: Prefer is_empty() over len() == 0.

Minor idiomatic nit.

Suggested change
         AnyJsExpression::JsTemplateExpression(template_expression) => {
-            template_expression.elements().len() == 0
+            template_expression.elements().is_empty()
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs`
around lines 434 - 436, Replace the idiomatic check using len() == 0 with
is_empty(): in the match arm for AnyJsExpression::JsTemplateExpression, change
the conditional that calls template_expression.elements().len() == 0 to use
template_expression.elements().is_empty() instead; update the expression in that
match arm to call is_empty() on elements() to make the code idiomatic.
🤖 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/no_useless_type_conversion.rs`:
- Around line 434-436: Replace the idiomatic check using len() == 0 with
is_empty(): in the match arm for AnyJsExpression::JsTemplateExpression, change
the conditional that calls template_expression.elements().len() == 0 to use
template_expression.elements().is_empty() instead; update the expression in that
match arm to call is_empty() on elements() to make the code idiomatic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d5f6734-40d4-42ff-af41-a176ddb89fe9

📥 Commits

Reviewing files that changed from the base of the PR and between d46083f and 0fc7465.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (6)
  • .changeset/upset-women-open.md
  • crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/no_useless_type_conversion.rs
✅ Files skipped from review due to trivial changes (4)
  • .changeset/upset-women-open.md
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts
  • crates/biome_rule_options/src/no_useless_type_conversion.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_rule_options/src/lib.rs

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.

  • Docs need some rewording
  • We should avoid recursions
  • I believe we should add more targeted tests

Comment thread crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs Outdated
Comment thread crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs Outdated
Comment thread crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs Outdated
Comment thread crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs Outdated
Comment thread crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs Outdated
Comment thread crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs Outdated
Comment thread crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs Outdated
@dyc3 dyc3 force-pushed the dyc3/no-unnecessary-type-conversion branch 2 times, most recently from 975e8ad to a3cac04 Compare March 30, 2026 13:53
@dyc3 dyc3 requested a review from ematipico March 30, 2026 13:59
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/tests/specs/nursery/noUselessTypeConversion/invalid.ts`:
- Around line 50-51: The double bitwise-not operator usage on the BigInt union
variable threeOrFourBigInt will throw; replace the `~~threeOrFourBigInt` usage
with a BigInt-safe alternative such as converting to a Number
(Number(threeOrFourBigInt)) if you need numeric coercion, or remove the `~~`
entirely and use an explicit check/operation that supports BigInt (e.g., compare
against 3n/4n or use BigInt arithmetic) to avoid the TypeError.
🪄 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: f1679837-64f0-4e01-a9b1-5c0156373efc

📥 Commits

Reviewing files that changed from the base of the PR and between 975e8ad and a3cac04.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (7)
  • .changeset/upset-women-open.md
  • crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs
  • crates/biome_js_analyze/src/services/typed.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/no_useless_type_conversion.rs
✅ Files skipped from review due to trivial changes (4)
  • crates/biome_rule_options/src/lib.rs
  • .changeset/upset-women-open.md
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts
  • crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/biome_js_analyze/src/services/typed.rs
  • crates/biome_rule_options/src/no_useless_type_conversion.rs

Comment on lines +282 to +290
let left = node.left().ok()?;
let ty = match left {
AnyJsAssignmentPattern::AnyJsAssignment(assign) => {
let identifier = assign.as_js_identifier_assignment()?;
let name = identifier.name_token().ok()?;
ctx.type_of_named_value(node.range(), name.text_trimmed())
}
_ => return None,
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe it's worth adding issue_number to the metadata of the rule at this point, so we know things are still missing, but planned

@dyc3 dyc3 force-pushed the dyc3/no-unnecessary-type-conversion branch from a3cac04 to dd78724 Compare March 31, 2026 13:52
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/services/typed.rs`:
- Around line 22-23: has_binding currently treats a missing SemanticModel (the
struct field model: Option<SemanticModel>) the same as "no binding", which hides
unavailability of semantic data; change has_binding to fail fast when model is
None instead of returning false (e.g., make has_binding return a Result<bool,
Error> or explicitly propagate an error/early return when model.is_none(), or
unwrap with a clear context if the phase guarantees availability), update all
callers of has_binding to handle the Result/propagated error (the call sites
noted around the other checks that rely on has_binding), and ensure any logic
that checks for global bindings uses SemanticModel directly so shadowed locals
aren’t misclassified as unbound.
🪄 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: 40df50f9-b521-49d3-8bf1-449e8a8c0a54

📥 Commits

Reviewing files that changed from the base of the PR and between a3cac04 and dd78724.

⛔ Files ignored due to path filters (9)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
📒 Files selected for processing (7)
  • .changeset/upset-women-open.md
  • crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs
  • crates/biome_js_analyze/src/services/typed.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/no_useless_type_conversion.rs
✅ Files skipped from review due to trivial changes (3)
  • .changeset/upset-women-open.md
  • crates/biome_rule_options/src/no_useless_type_conversion.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/valid.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUselessTypeConversion/invalid.ts
  • crates/biome_js_analyze/src/lint/nursery/no_useless_type_conversion.rs

Comment on lines +22 to 23
model: Option<SemanticModel>,
}
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

Don’t treat “missing semantic model” as “no binding”.

Right now has_binding returns false both when a reference is truly unbound and when SemanticModel is absent. That can leak false positives for shadowed globals. Since this service runs in Phases::Semantic, it should fail fast if SemanticModel is unavailable.

Suggested fix
 pub struct TypedService {
     resolver: Option<Arc<ModuleResolver>>,
-    model: Option<SemanticModel>,
+    model: SemanticModel,
 }
@@
     pub fn has_binding(&self, reference: &JsReferenceIdentifier) -> bool {
-        self.model
-            .as_ref()
-            .is_some_and(|model| model.binding(reference).is_some())
+        self.model.binding(reference).is_some()
     }
@@
-        let model = services.get_service::<SemanticModel>().cloned();
-        Ok(Self { resolver, model })
+        let model = services
+            .get_service::<SemanticModel>()
+            .cloned()
+            .ok_or_else(|| ServicesDiagnostic::new(rule_key.rule_name(), &["SemanticModel"]))?;
+        Ok(Self { resolver, model })
     }

Based on learnings: "Check if a variable is global using the semantic model before reporting rules that ban certain functions or variables to avoid false positives on locally redeclared variables."

Also applies to: 72-76, 97-98

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

In `@crates/biome_js_analyze/src/services/typed.rs` around lines 22 - 23,
has_binding currently treats a missing SemanticModel (the struct field model:
Option<SemanticModel>) the same as "no binding", which hides unavailability of
semantic data; change has_binding to fail fast when model is None instead of
returning false (e.g., make has_binding return a Result<bool, Error> or
explicitly propagate an error/early return when model.is_none(), or unwrap with
a clear context if the phase guarantees availability), update all callers of
has_binding to handle the Result/propagated error (the call sites noted around
the other checks that rely on has_binding), and ensure any logic that checks for
global bindings uses SemanticModel directly so shadowed locals aren’t
misclassified as unbound.

@dyc3 dyc3 merged commit 1417c3b into main Mar 31, 2026
20 checks passed
@dyc3 dyc3 deleted the dyc3/no-unnecessary-type-conversion branch March 31, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants