Skip to content

fix(noVueDuplicateKeys): fix false positives with toRefs patterns#9072

Merged
dyc3 merged 2 commits intomainfrom
dyc3/fix-no-vue-dupe-keys-false-positive
Feb 15, 2026
Merged

fix(noVueDuplicateKeys): fix false positives with toRefs patterns#9072
dyc3 merged 2 commits intomainfrom
dyc3/fix-no-vue-dupe-keys-false-positive

Conversation

@dyc3
Copy link
Contributor

@dyc3 dyc3 commented Feb 14, 2026

Summary

implemented by minimax 2.5

fixes #9068

Test Plan

snapshots

Docs

@changeset-bot
Copy link

changeset-bot bot commented Feb 14, 2026

🦋 Changeset detected

Latest commit: b845eca

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

@dyc3 dyc3 added this to the Biome v2.4 milestone Feb 14, 2026
@github-actions github-actions bot added A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Feb 14, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 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 Vue-specific API detection and handling for toRefs(props). Introduces is_vue_api_reference and is_to_refs_call in vue_call.rs, and AnyVueSetupDeclaration::is_assigned_to_to_refs in vue_component.rs to recognise assignments originating from toRefs(props). The noVueDuplicateKeys lint now skips setup declarations assigned from toRefs. Tests covering valid and invalid toRefs scenarios and a changeset addressing issue #9068 were added.

Possibly related PRs

Suggested labels

D-Vue, A-Diagnostic

Suggested reviewers

  • dyc3
  • ematipico
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (7 files):

⚔️ .gitignore (content)
⚔️ crates/biome_cli/tests/cases/handle_svelte_files.rs (content)
⚔️ crates/biome_cli/tests/cases/handle_vue_files.rs (content)
⚔️ crates/biome_js_analyze/src/frameworks/vue/vue_call.rs (content)
⚔️ crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (content)
⚔️ crates/biome_js_analyze/src/lint/complexity/no_comma_operator.rs (content)
⚔️ crates/biome_js_analyze/src/lint/correctness/no_vue_duplicate_keys.rs (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing false positives in the noVueDuplicateKeys rule when using toRefs patterns.
Description check ✅ Passed The description references issue #9068 and mentions test snapshots, directly relating to the changeset's purpose of fixing toRefs-related false positives.
Linked Issues check ✅ Passed The PR successfully addresses issue #9068 by adding toRefs pattern detection to the noVueDuplicateKeys rule via new helper functions and updated guard logic.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the noVueDuplicateKeys rule for toRefs patterns; no extraneous modifications detected.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dyc3/fix-no-vue-dupe-keys-false-positive
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch dyc3/fix-no-vue-dupe-keys-false-positive
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (1)

752-791: Optional: consider extracting the shared declarator lookup pattern.

Both is_assigned_to_props and is_assigned_to_to_refs share the same let-chain for finding the parent declarator and extracting the initialiser's call expression. If you add more similar checks in future, a small helper like fn get_initializer_call(&self) -> Option<JsCallExpression> could reduce duplication.

Not blocking — the current code is perfectly readable.


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

🤖 Fix all issues with AI agents
In `@crates/biome_js_analyze/src/frameworks/vue/vue_component.rs`:
- Around line 770-789: The is_assigned_to_to_refs method is too permissive: it
treats any toRefs(...) call as coming from props; narrow it by verifying the
toRefs call argument is actually the props source (a defineProps() call or an
identifier bound to defineProps). Update is_assigned_to_to_refs so after
discovering the JsCallExpression via AnyJsExpression::JsCallExpression(call) it
inspects call.arguments() to get the first argument and returns true only when
that argument is either a defineProps call (e.g., passes an expression that
satisfies whatever existing is_define_props_call helper or pattern matches
JsCallExpression with callee defineProps) or is a JsIdentifier whose binding
resolves (via model/semantic lookup) to a variable initialised from defineProps;
keep using the existing is_to_refs_call check but AND it with the new validation
of the call's argument source.

In
`@crates/biome_js_analyze/tests/specs/correctness/noVueDuplicateKeys/valid-script-setup-to-refs.vue`:
- Around line 2-3: The code calls toRefs(props) but never captures defineProps()
into a variable; change the usage so you assign the result of defineProps() to a
local symbol (e.g., props = defineProps<{ toast: string; number?: number }>()),
then pass that props variable into toRefs(props) so toRefs has the actual props
object to destructure; update references to the returned prop refs accordingly
(symbols: defineProps, props, toRefs).

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 14, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 95 skipped benchmarks1


Comparing dyc3/fix-no-vue-dupe-keys-false-positive (b845eca) with main (fbe460f)

Open in CodSpeed

Footnotes

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

@dyc3 dyc3 force-pushed the dyc3/fix-no-vue-dupe-keys-false-positive branch 2 times, most recently from 2ed931b to b8f456e Compare February 14, 2026 21:22
@dyc3 dyc3 requested review from a team February 14, 2026 21:23
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/biome_js_analyze/src/frameworks/vue/vue_call.rs (1)

36-69: ⚠️ Potential issue | 🟡 Minor

Add a doctest block to the new rustdoc.
These new rustdoc comments lack a doctest code block; please add a minimal example with a passing assertion.

As per coding guidelines: **/*.rs: Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests.

crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (1)

747-828: ⚠️ Potential issue | 🟡 Minor

Rustdoc needs a doctest example here too.
Please add a small doctest code block (with a passing assertion) to the new is_assigned_to_to_refs docs.

As per coding guidelines: **/*.rs: Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests.

@dyc3 dyc3 force-pushed the dyc3/fix-no-vue-dupe-keys-false-positive branch from b8f456e to 1568093 Compare February 15, 2026 13:15
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: 3

🤖 Fix all issues with AI agents
In `@crates/biome_js_analyze/src/frameworks/vue/vue_call.rs`:
- Around line 51-69: Update the rustdoc for is_to_refs_call to use a fenced code
block in doctest format: replace the inline example comment with a
triple-backtick fenced block specifying the language (js) and include the
example line(s) inside (e.g., const { foo, bar } = toRefs(props);) followed by
closing triple backticks so the example becomes a proper rustdoc code block that
can be validated as a doctest.

In `@crates/biome_js_analyze/src/frameworks/vue/vue_component.rs`:
- Around line 748-752: The rustdoc for the method is_assigned_to_props should
put the example usage into a doctest code block: replace the inline example
"const { foo } = defineProps<{ foo: string }>()" with a fenced code block using
triple backticks and the js language tag (/// ```js ... /// ```), so the comment
becomes a proper rustdoc code block that will be picked up by doctests and keep
the surrounding explanatory sentence intact; update the doc comment above pub fn
is_assigned_to_props accordingly.
- Around line 770-825: Update the rustdoc above the is_assigned_to_to_refs
method to replace the inline snippet `const { foo } = toRefs(props)` with a
fenced doctest code block (use triple backticks and an optional language tag
like "js") so the example is rendered/parsed as a rustdoc code block; ensure the
surrounding explanatory lines remain and the fenced block is properly closed to
satisfy doctest-style formatting.

Comment on lines +51 to +69
/// Checks if the given call expression is a call to `toRefs` from the Vue package.
///
/// `toRefs` is used to convert a reactive object to a plain object where each property
/// is a ref pointing to the corresponding property of the original object. This is commonly
/// used with `defineProps` to destructure props while maintaining reactivity.
///
/// This function handles both imported `toRefs` and auto-imported `toRefs` in Vue `<script setup>`.
///
/// Example: `const { foo, bar } = toRefs(props)`
pub fn is_to_refs_call(call: &JsCallExpression, model: &SemanticModel) -> bool {
let Some(callee) = call.callee().ok() else {
return false;
};
let Some(expression) = callee.inner_expression() else {
return false;
};

is_vue_api_reference(&expression, model, "toRefs")
}
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 | 🟡 Minor

Use a fenced rustdoc code block for the example.
Inline snippets make the docs harder to validate.

✍️ Suggested rustdoc tweak
-/// Example: `const { foo, bar } = toRefs(props)`
+/// Example:
+/// ```js
+/// const { foo, bar } = toRefs(props);
+/// ```

As per coding guidelines, "Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Checks if the given call expression is a call to `toRefs` from the Vue package.
///
/// `toRefs` is used to convert a reactive object to a plain object where each property
/// is a ref pointing to the corresponding property of the original object. This is commonly
/// used with `defineProps` to destructure props while maintaining reactivity.
///
/// This function handles both imported `toRefs` and auto-imported `toRefs` in Vue `<script setup>`.
///
/// Example: `const { foo, bar } = toRefs(props)`
pub fn is_to_refs_call(call: &JsCallExpression, model: &SemanticModel) -> bool {
let Some(callee) = call.callee().ok() else {
return false;
};
let Some(expression) = callee.inner_expression() else {
return false;
};
is_vue_api_reference(&expression, model, "toRefs")
}
/// Checks if the given call expression is a call to `toRefs` from the Vue package.
///
/// `toRefs` is used to convert a reactive object to a plain object where each property
/// is a ref pointing to the corresponding property of the original object. This is commonly
/// used with `defineProps` to destructure props while maintaining reactivity.
///
/// This function handles both imported `toRefs` and auto-imported `toRefs` in Vue `<script setup>`.
///
/// Example:
///
🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/frameworks/vue/vue_call.rs` around lines 51 - 69,
Update the rustdoc for is_to_refs_call to use a fenced code block in doctest
format: replace the inline example comment with a triple-backtick fenced block
specifying the language (js) and include the example line(s) inside (e.g., const
{ foo, bar } = toRefs(props);) followed by closing triple backticks so the
example becomes a proper rustdoc code block that can be validated as a doctest.

Comment on lines +748 to 752
/// Checks if this setup declaration is assigned directly from `defineProps`.
///
/// This handles cases like `const { foo } = defineProps<{ foo: string }>()` where
/// the destructured property comes from the props definition.
pub fn is_assigned_to_props(&self, model: &SemanticModel) -> bool {
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 | 🟡 Minor

Move the defineProps example into a rustdoc code block.
Keeps docs consistent with doctest formatting expectations.

✍️ Suggested rustdoc tweak
-/// This handles cases like `const { foo } = defineProps<{ foo: string }>()` where
-/// the destructured property comes from the props definition.
+/// This handles cases like:
+/// ```js
+/// const { foo } = defineProps<{ foo: string }>();
+/// ```
+/// where the destructured property comes from the props definition.

As per coding guidelines, "Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests".

🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/frameworks/vue/vue_component.rs` around lines 748
- 752, The rustdoc for the method is_assigned_to_props should put the example
usage into a doctest code block: replace the inline example "const { foo } =
defineProps<{ foo: string }>()" with a fenced code block using triple backticks
and the js language tag (/// ```js ... /// ```), so the comment becomes a proper
rustdoc code block that will be picked up by doctests and keep the surrounding
explanatory sentence intact; update the doc comment above pub fn
is_assigned_to_props accordingly.

Comment on lines 770 to 825
/// Checks if this setup declaration is assigned from `toRefs(props)`.
///
/// This handles cases like `const { foo } = toRefs(props)` where the destructured
/// property comes from converting props to refs. These should not be flagged as
/// duplicates of the props themselves.
///
/// Only returns true if the argument to `toRefs` is either:
/// - A direct `defineProps()` call
/// - An identifier whose binding resolves to a variable initialized from `defineProps`
pub fn is_assigned_to_to_refs(&self, model: &SemanticModel) -> bool {
if let Self::JsIdentifierBinding(binding) = self
&& let Some(declarator) = binding
.syntax()
.ancestors()
.find_map(|syntax| JsVariableDeclarator::try_cast(syntax).ok())
&& let Some(initializer) = declarator.initializer()
&& let Some(expression) = initializer
.expression()
.ok()
.and_then(|expression| expression.inner_expression())
&& let AnyJsExpression::JsCallExpression(call) = expression
&& is_to_refs_call(&call, model)
{
// Check that the first argument to `toRefs` is the props source
if let Some(Ok(first_arg)) = call
.arguments()
.ok()
.and_then(|args| args.args().iter().next())
&& let Some(arg_expr) = first_arg.as_any_js_expression()
&& let Some(arg_expr) = arg_expr.inner_expression()
{
// Case 1: Direct defineProps() call: `toRefs(defineProps(...))`
if let AnyJsExpression::JsCallExpression(arg_call) = &arg_expr
&& is_vue_compiler_macro_call(arg_call, model, "defineProps") {
return true;
}

// Case 2: Identifier bound to defineProps: `const props = defineProps(...); toRefs(props)`
if let Some(ident_ref) = arg_expr.as_js_reference_identifier()
&& let Some(binding) = model.binding(&ident_ref)
&& let Some(declarator) = binding
.syntax()
.ancestors()
.find_map(|syntax| JsVariableDeclarator::try_cast(syntax).ok())
&& let Some(decl_initializer) = declarator.initializer()
&& let Some(decl_expr) = decl_initializer
.expression()
.ok()
.and_then(|expr| expr.inner_expression())
&& let AnyJsExpression::JsCallExpression(decl_call) = decl_expr
{
return is_vue_compiler_macro_call(&decl_call, model, "defineProps");
}
}
}
false
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 | 🟡 Minor

Use a fenced code block for the toRefs example.
Inline snippets don’t meet the doctest-style doc format.

✍️ Suggested rustdoc tweak
-/// This handles cases like `const { foo } = toRefs(props)` where the destructured
-/// property comes from converting props to refs. These should not be flagged as
-/// duplicates of the props themselves.
+/// This handles cases like:
+/// ```js
+/// const { foo } = toRefs(props);
+/// ```
+/// where the destructured property comes from converting props to refs. These should
+/// not be flagged as duplicates of the props themselves.

As per coding guidelines, "Use doc tests (doctest) format with code blocks in rustdoc comments; ensure assertions pass in tests".

🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/frameworks/vue/vue_component.rs` around lines 770
- 825, Update the rustdoc above the is_assigned_to_to_refs method to replace the
inline snippet `const { foo } = toRefs(props)` with a fenced doctest code block
(use triple backticks and an optional language tag like "js") so the example is
rendered/parsed as a rustdoc code block; ensure the surrounding explanatory
lines remain and the fenced block is properly closed to satisfy doctest-style
formatting.

@dyc3 dyc3 merged commit 8656fc0 into main Feb 15, 2026
18 checks passed
@dyc3 dyc3 deleted the dyc3/fix-no-vue-dupe-keys-false-positive branch February 15, 2026 13:38
@github-actions github-actions bot mentioned this pull request Feb 15, 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.

💅 noVueDuplicateKeys false positive

1 participant