-
-
Notifications
You must be signed in to change notification settings - Fork 840
feat(analyze/js/vue): add noVueArrowFuncInWatch
#8602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🦋 Changeset detectedLatest commit: 22bdc5d The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
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 |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
a8e3c24 to
67c592b
Compare
ematipico
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This rule would benefit from more tests
crates/biome_js_analyze/tests/specs/nursery/noVueArrowFuncInWatch/invalid.vue.snap
Show resolved
Hide resolved
crates/biome_js_analyze/tests/specs/nursery/noVueArrowFuncInWatch/invalid.vue
Show resolved
Hide resolved
67c592b to
361fe64
Compare
391e88a to
9a8c98d
Compare
361fe64 to
3350e07
Compare
CodSpeed Performance ReportMerging this PR will not alter performanceComparing Summary
Footnotes
|
WalkthroughAdds a new nursery lint rule Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@crates/biome_js_analyze/src/lint/nursery/no_vue_arrow_func_in_watch.rs`:
- Around line 174-179: The current transformation for single-parameter arrow
functions uses binding =
arrow_params.as_any_js_binding()?.clone().trim_trailing_trivia()? which removes
trailing inline comments (e.g., in `x /* comment */ =>`); instead preserve
trailing trivia by cloning the binding without calling trim_trailing_trivia(),
then use that preserved binding when constructing the formal parameter passed to
make::js_formal_parameter so the comment remains attached to
`binding`/`arrow_params` during conversion; update the code paths that reference
trim_trailing_trivia() for single binding handling (arrow_params, binding, and
the formal parameter creation) to avoid dropping trailing comments.
- Around line 135-142: The loop over watchers currently only checks a single
handler returned by extract_watcher_handler and thus misses array-style
handlers; modify extract_watcher_handler (or add a helper used in the for
watcher in watchers loop) to detect when a watcher value is an array and iterate
its elements, collecting any functions or `{ handler }` objects into a list,
then in the loop iterate that list and push each
AnyJsFunctionExpression::JsArrowFunctionExpression (arrow_func) into violations;
update references to handler in the for watcher loop to handle multiple handlers
instead of a single Option.
- Around line 122-131: The match on component.kind() in the watcher collection
code omits the CreateApp variant so watchers inside createApp(...) options
aren't collected; add a CreateApp arm alongside AnyVueComponent::OptionsApi and
AnyVueComponent::DefineComponent that calls
component.declarations(VueDeclarationCollectionFilter::Watcher.into()) and
pushes those into the same watchers handling path (the same place that uses the
variable watchers and later builds violations). Ensure the new arm mirrors the
logic used for OptionsApi/DefineComponent so CreateApp watch handlers are
analysed.
In
`@crates/biome_js_analyze/tests/specs/nursery/noVueArrowFuncInWatch/invalid-define.vue`:
- Around line 14-15: Add the missing comma between the object properties so the
object literal is valid: after the property named "bar" (the preceding property
on the same object) insert a comma before the "short" property (the arrow
function property named short) to separate the entries and fix the syntax error
in the invalid-define.vue block.
In
`@crates/biome_js_analyze/tests/specs/nursery/noVueArrowFuncInWatch/valid-setup.vue`:
- Around line 7-9: The arrow function passed to watch uses the reserved keyword
"new" as a parameter (watch(foo, (new, old) => { ... })), causing a syntax
error; rename the parameters (e.g., newVal and oldVal) in the watch callback and
update all references inside the function (the console.log call) to the new
names so the callback compiles correctly.
🧹 Nitpick comments (1)
crates/biome_rule_options/src/no_vue_arrow_func_in_watch.rs (1)
1-6: Add rustdoc for the options struct.
Guidelines ask for inline rustdoc on rule options; this struct is currently undocumented.📘 Suggested doc comment
+/// Options for the `noVueArrowFuncInWatch` rule. pub struct NoVueArrowFuncInWatchOptions {}As per coding guidelines, please add rustdoc for rule options.
| let mut violations = vec![]; | ||
| let watchers = match component.kind() { | ||
| AnyVueComponent::OptionsApi(component) => { | ||
| component.declarations(VueDeclarationCollectionFilter::Watcher.into()) | ||
| } | ||
| AnyVueComponent::DefineComponent(component) => { | ||
| component.declarations(VueDeclarationCollectionFilter::Watcher.into()) | ||
| } | ||
| _ => return [].into(), | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find the AnyVueComponent enum definition
rg "enum AnyVueComponent" -A 20Repository: biomejs/biome
Length of output: 2140
🏁 Script executed:
# Search for CreateApp variant and its methods
rg "CreateApp" crates/biome_js_analyze/src/lint/nursery/no_vue_arrow_func_in_watch.rs -B 2 -A 2Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Look for CreateApp type definition and structure
rg "pub struct CreateApp" -A 15Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Check if CreateApp has declarations method
rg "impl.*CreateApp" -A 20 | head -100Repository: biomejs/biome
Length of output: 1954
🏁 Script executed:
# Check VueOptionsApiBasedComponent trait and VueCreateApp struct definition
rg "struct VueCreateApp" -A 10Repository: biomejs/biome
Length of output: 1030
🏁 Script executed:
# Check if VueOptionsApiBasedComponent or related traits have declarations method
rg "fn declarations" crates/biome_js_analyze/src/frameworks/vue/ -B 2 -A 5Repository: biomejs/biome
Length of output: 3805
🏁 Script executed:
# Look at the entire match in the file to understand current coverage
rg "let watchers = match component.kind()" -A 15 crates/biome_js_analyze/src/lint/nursery/no_vue_arrow_func_in_watch.rsRepository: biomejs/biome
Length of output: 708
🏁 Script executed:
# Check if there are any tests for CreateApp watcher scenarios
rg "createApp" crates/biome_js_analyze/src/lint/nursery/no_vue_arrow_func_in_watch.rsRepository: biomejs/biome
Length of output: 39
🏁 Script executed:
# Verify that VueCreateApp implements VueOptionsApiBasedComponent
rg "impl VueOptionsApiBasedComponent for VueCreateApp" -A 20Repository: biomejs/biome
Length of output: 1954
🏁 Script executed:
# Check the AnyVueComponent::declarations implementation to see all branches
rg "impl VueComponentDeclarations for AnyVueComponent" -A 15 crates/biome_js_analyze/src/frameworks/vue/vue_component.rsRepository: biomejs/biome
Length of output: 750
🌐 Web query:
Vue 3 createApp watch handlers options API
💡 Result:
In Vue 3, createApp() takes a root component options object, so you can use the Options API (including watch) directly there and in any component. createApp() creates the app instance; you then mount() it. [1]
Options API watch (handlers / forms)
watch is an object where:
- keys are reactive instance properties (from
data,computed, etc.), including dot-paths like'a.b.c'(but not arbitrary expressions) - values can be:
- a function handler
- a string method name (in
methods) - an object with
handler+ options (immediate,deep,flush, etc.) - an array of any of the above (multiple handlers called in order) [2]
import { createApp } from 'vue'
createApp({
data() {
return { count: 0, nested: { x: 1 }, items: [] }
},
watch: {
// 1) function handler
count(newVal, oldVal) {
// ...
},
// 2) method name
nested: 'onNestedChanged',
// 3) object form with `handler` + options
'nested.x': {
handler(newVal, oldVal) {
// ...
},
immediate: true,
deep: false,
flush: 'post',
},
// 4) multiple handlers
items: [
'onItems1',
function onItems2(newVal, oldVal) { /* ... */ },
{ handler(newVal, oldVal) { /* ... */ }, deep: true }
]
},
methods: {
onNestedChanged() {},
onItems1() {}
}
}).mount('#app')Note on watching arrays
In Vue 3, watching an array only triggers when the array reference is replaced; to react to mutations (e.g. push), specify deep (in Vue 3.5+, deep: 1 is recommended for arrays). [3]
Sources: Vue Application API (createApp) [1], Vue Options API watch reference [2], Vue 3 Migration Guide (“Watch on Arrays”) [3].
CreateApp components with watchers aren't analysed. The match expression skips the CreateApp variant, causing any watch handlers defined in createApp() options to be ignored. Since Vue 3's createApp accepts the full Options API (including watch), this creates a coverage gap. Adding a CreateApp arm to the match will fix it:
🛠️ Suggested fix
let watchers = match component.kind() {
AnyVueComponent::OptionsApi(component) => {
component.declarations(VueDeclarationCollectionFilter::Watcher.into())
}
+ AnyVueComponent::CreateApp(component) => {
+ component.declarations(VueDeclarationCollectionFilter::Watcher.into())
+ }
AnyVueComponent::DefineComponent(component) => {
component.declarations(VueDeclarationCollectionFilter::Watcher.into())
}
_ => return [].into(),
}📝 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.
| let mut violations = vec![]; | |
| let watchers = match component.kind() { | |
| AnyVueComponent::OptionsApi(component) => { | |
| component.declarations(VueDeclarationCollectionFilter::Watcher.into()) | |
| } | |
| AnyVueComponent::DefineComponent(component) => { | |
| component.declarations(VueDeclarationCollectionFilter::Watcher.into()) | |
| } | |
| _ => return [].into(), | |
| } | |
| let mut violations = vec![]; | |
| let watchers = match component.kind() { | |
| AnyVueComponent::OptionsApi(component) => { | |
| component.declarations(VueDeclarationCollectionFilter::Watcher.into()) | |
| } | |
| AnyVueComponent::CreateApp(component) => { | |
| component.declarations(VueDeclarationCollectionFilter::Watcher.into()) | |
| } | |
| AnyVueComponent::DefineComponent(component) => { | |
| component.declarations(VueDeclarationCollectionFilter::Watcher.into()) | |
| } | |
| _ => return [].into(), | |
| } |
🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/lint/nursery/no_vue_arrow_func_in_watch.rs`
around lines 122 - 131, The match on component.kind() in the watcher collection
code omits the CreateApp variant so watchers inside createApp(...) options
aren't collected; add a CreateApp arm alongside AnyVueComponent::OptionsApi and
AnyVueComponent::DefineComponent that calls
component.declarations(VueDeclarationCollectionFilter::Watcher.into()) and
pushes those into the same watchers handling path (the same place that uses the
variable watchers and later builds violations). Ensure the new arm mirrors the
logic used for OptionsApi/DefineComponent so CreateApp watch handlers are
analysed.
| for watcher in watchers { | ||
| let Some(handler) = extract_watcher_handler(&watcher) else { | ||
| continue; | ||
| }; | ||
| if let AnyJsFunctionExpression::JsArrowFunctionExpression(arrow_func) = handler { | ||
| violations.push(arrow_func); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Vue Options API watch handlers array support documentation
💡 Result:
Vue’s Options API watch option supports an array of handlers for a single watched key.
- In the official Vue 3 docs, the
watchtype is declared as:watch?: { [key: string]: WatchOptionItem | WatchOptionItem[] }— i.e., each entry can be one watcher or an array of watcher items. [1]
- The docs’ example explicitly shows this pattern and states: “you can pass array of callbacks, they will be called one-by-one”. [1]
Example (Options API):
export default {
data: () => ({ f: 0 }),
watch: {
f: [
'handle1',
function handle2(val, oldVal) { /* ... */ },
{ handler(val, oldVal) { /* ... */ }, immediate: true }
]
},
methods: { handle1() {} }
}Sources: [1]
🏁 Script executed:
cat -n crates/biome_js_analyze/src/lint/nursery/no_vue_arrow_func_in_watch.rs | head -150Repository: biomejs/biome
Length of output: 5854
🏁 Script executed:
cat -n crates/biome_js_analyze/src/lint/nursery/no_vue_arrow_func_in_watch.rs | sed -n '200,280p'Repository: biomejs/biome
Length of output: 3881
Array-style watch handlers aren't detected. Vue's watch option supports arrays of handlers, but the current extractor only checks direct functions and { handler } objects—arrow handlers inside arrays will be missed. The rule's own doctest (lines 78–85) even shows array handlers as valid code, yet they're not being validated.
Fix the handler extraction to iterate through array elements and collect all handlers from them.
🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/lint/nursery/no_vue_arrow_func_in_watch.rs`
around lines 135 - 142, The loop over watchers currently only checks a single
handler returned by extract_watcher_handler and thus misses array-style
handlers; modify extract_watcher_handler (or add a helper used in the for
watcher in watchers loop) to detect when a watcher value is an array and iterate
its elements, collecting any functions or `{ handler }` objects into a list,
then in the loop iterate that list and push each
AnyJsFunctionExpression::JsArrowFunctionExpression (arrow_func) into violations;
update references to handler in the for watcher loop to handle multiple handlers
instead of a single Option.
| // Single binding like `x => {}` must become formal parameter list `(x)` | ||
| let binding = arrow_params | ||
| .as_any_js_binding()? | ||
| .clone() | ||
| .trim_trailing_trivia()?; | ||
| let formal = make::js_formal_parameter( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix drops trailing comments on single-parameter arrows.
trim_trailing_trivia() will strip inline comments such as x /* comment */ =>, which your fixture explicitly covers.
🛠️ Suggested fix
- let binding = arrow_params
- .as_any_js_binding()?
- .clone()
- .trim_trailing_trivia()?;
+ let binding = arrow_params.as_any_js_binding()?.clone();📝 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.
| // Single binding like `x => {}` must become formal parameter list `(x)` | |
| let binding = arrow_params | |
| .as_any_js_binding()? | |
| .clone() | |
| .trim_trailing_trivia()?; | |
| let formal = make::js_formal_parameter( | |
| // Single binding like `x => {}` must become formal parameter list `(x)` | |
| let binding = arrow_params.as_any_js_binding()?.clone(); | |
| let formal = make::js_formal_parameter( |
🤖 Prompt for AI Agents
In `@crates/biome_js_analyze/src/lint/nursery/no_vue_arrow_func_in_watch.rs`
around lines 174 - 179, The current transformation for single-parameter arrow
functions uses binding =
arrow_params.as_any_js_binding()?.clone().trim_trailing_trivia()? which removes
trailing inline comments (e.g., in `x /* comment */ =>`); instead preserve
trailing trivia by cloning the binding without calling trim_trailing_trivia(),
then use that preserved binding when constructing the formal parameter passed to
make::js_formal_parameter so the comment remains attached to
`binding`/`arrow_params` during conversion; update the code paths that reference
trim_trailing_trivia() for single binding handling (arrow_params, binding, and
the formal parameter creation) to avoid dropping trailing comments.
| } | ||
| short: (val, oldVal) => console.log('new: %s, old: %s', val, oldVal) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing comma between object properties.
There's a missing comma after the bar property (line 14) before short (line 15). This will cause a syntax error.
🐛 Proposed fix
bar: {
handler: (val, oldVal) => {
console.log('new: %s, old: %s', val, oldVal)
}
- }
+ },
short: (val, oldVal) => console.log('new: %s, old: %s', val, oldVal)📝 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.
| } | |
| short: (val, oldVal) => console.log('new: %s, old: %s', val, oldVal) | |
| }, | |
| short: (val, oldVal) => console.log('new: %s, old: %s', val, oldVal) |
🤖 Prompt for AI Agents
In
`@crates/biome_js_analyze/tests/specs/nursery/noVueArrowFuncInWatch/invalid-define.vue`
around lines 14 - 15, Add the missing comma between the object properties so the
object literal is valid: after the property named "bar" (the preceding property
on the same object) insert a comma before the "short" property (the arrow
function property named short) to separate the entries and fix the syntax error
in the invalid-define.vue block.
| watch(foo, (new, old) => { | ||
| console.log('new: %s, old: %s', new, old); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reserved keyword new used as parameter name.
new is a reserved keyword in JavaScript and cannot be used as a parameter name. This will cause a syntax error, preventing the test from running correctly.
🐛 Proposed fix
-watch(foo, (new, old) => {
- console.log('new: %s, old: %s', new, old);
+watch(foo, (newVal, oldVal) => {
+ console.log('new: %s, old: %s', newVal, oldVal);
});📝 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.
| watch(foo, (new, old) => { | |
| console.log('new: %s, old: %s', new, old); | |
| }); | |
| watch(foo, (newVal, oldVal) => { | |
| console.log('new: %s, old: %s', newVal, oldVal); | |
| }); |
🤖 Prompt for AI Agents
In
`@crates/biome_js_analyze/tests/specs/nursery/noVueArrowFuncInWatch/valid-setup.vue`
around lines 7 - 9, The arrow function passed to watch uses the reserved keyword
"new" as a parameter (watch(foo, (new, old) => { ... })), causing a syntax
error; rename the parameters (e.g., newVal and oldVal) in the watch callback and
update all references inside the function (the console.log call) to the new
names so the callback compiles correctly.
3350e07 to
22bdc5d
Compare
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In @.changeset/curly-jeans-fly.md:
- Line 5: Edit the changelog sentence describing the new nursery rule
noVueArrowFuncInWatch to remove the comma before "because" so it reads
"...watchers in Vue components because arrow functions do not give access..."
(keep the rule name noVueArrowFuncInWatch and the rest of the explanation
unchanged).
| "@biomejs/biome": patch | ||
| --- | ||
|
|
||
| Added the new nursery rule [`noVueArrowFuncInWatch`](https://biomejs.dev/linter/rules/no-vue-arrow-func-in-watch/). This rule forbids using arrow functions in watchers in Vue components, because arrow functions do not give access to the component instance (via `this`), while regular functions do. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor grammar tweak: drop the comma before “because”.
🛠️ Suggested fix
-Added the new nursery rule [`noVueArrowFuncInWatch`](https://biomejs.dev/linter/rules/no-vue-arrow-func-in-watch/). This rule forbids using arrow functions in watchers in Vue components, because arrow functions do not give access to the component instance (via `this`), while regular functions do.
+Added the new nursery rule [`noVueArrowFuncInWatch`](https://biomejs.dev/linter/rules/no-vue-arrow-func-in-watch/). This rule forbids using arrow functions in watchers in Vue components because arrow functions do not give access to the component instance (via `this`), while regular functions do.📝 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.
| Added the new nursery rule [`noVueArrowFuncInWatch`](https://biomejs.dev/linter/rules/no-vue-arrow-func-in-watch/). This rule forbids using arrow functions in watchers in Vue components, because arrow functions do not give access to the component instance (via `this`), while regular functions do. | |
| Added the new nursery rule [`noVueArrowFuncInWatch`](https://biomejs.dev/linter/rules/no-vue-arrow-func-in-watch/). This rule forbids using arrow functions in watchers in Vue components because arrow functions do not give access to the component instance (via `this`), while regular functions do. |
🧰 Tools
🪛 LanguageTool
[formatting] ~5-~5: If the ‘because’ clause is essential to the meaning, do not use a comma before the clause.
Context: ... functions in watchers in Vue components, because arrow functions do not give access to t...
(COMMA_BEFORE_BECAUSE)
[style] ~5-~5: Try using a synonym here to strengthen your writing.
Context: ...ponents, because arrow functions do not give access to the component instance (via `...
(GIVE_PROVIDE)
🤖 Prompt for AI Agents
In @.changeset/curly-jeans-fly.md at line 5, Edit the changelog sentence
describing the new nursery rule noVueArrowFuncInWatch to remove the comma before
"because" so it reads "...watchers in Vue components because arrow functions do
not give access..." (keep the rule name noVueArrowFuncInWatch and the rest of
the explanation unchanged).

Summary
This adds
noVueArrowFuncInWatch, a port of https://eslint.vuejs.org/rules/no-arrow-functions-in-watch.htmlOnly the code fix was generated by AI.
Test Plan
snapshots, also checked the test cases for the source rule
Docs