Skip to content

Commit 3350e07

Browse files
committed
feat(analyze/js/vue): add noVueArrowFuncInWatch
1 parent 21dba4a commit 3350e07

File tree

24 files changed

+1352
-1
lines changed

24 files changed

+1352
-1
lines changed

.changeset/curly-jeans-fly.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
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.

crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_configuration/src/analyzer/linter/rules.rs

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_configuration/src/generated/domain_selector.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_diagnostics_categories/src/categories.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_js_analyze/src/frameworks/vue/vue_component.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ pub enum VueDeclarationCollectionFilter {
5959
Method = 1 << 5,
6060
/// Computed properties in a Vue component.
6161
Computed = 1 << 6,
62+
/// Watchers in a Vue component.
63+
Watcher = 1 << 7,
6264
}
6365

66+
#[derive(Debug)]
6467
pub struct VueComponent<'a> {
6568
kind: AnyVueComponent,
6669
path: &'a Utf8Path,
@@ -106,6 +109,7 @@ impl<'a> VueComponent<'a> {
106109

107110
/// An abstraction over multiple ways to define a vue component.
108111
/// Provides a list of declarations for a component.
112+
#[derive(Debug)]
109113
pub enum AnyVueComponent {
110114
/// Options API style Vue component.
111115
/// ```html
@@ -153,6 +157,23 @@ impl AnyVueComponent {
153157
if !source.as_embedding_kind().is_vue() {
154158
return None;
155159
}
160+
if let Some(call_expression) = default_expression_clause
161+
.expression()
162+
.ok()?
163+
.as_js_call_expression()
164+
{
165+
// export default defineComponent({ ... });
166+
let callee = call_expression
167+
.callee()
168+
.ok()
169+
.and_then(|callee| callee.inner_expression())?;
170+
171+
if is_vue_api_reference(&callee, model, "defineComponent") {
172+
return Some(Self::DefineComponent(VueDefineComponent {
173+
call_expression: call_expression.clone(),
174+
}));
175+
}
176+
}
156177
Some(Self::OptionsApi(VueOptionsApiComponent {
157178
default_expression_clause: default_expression_clause.clone(),
158179
}))
@@ -182,6 +203,7 @@ impl AnyVueComponent {
182203
/// ```html
183204
/// <script> export default { props: [ ... ], data: { ... }, ... }; </script>
184205
/// ```
206+
#[derive(Debug)]
185207
pub struct VueOptionsApiComponent {
186208
default_expression_clause: JsExportDefaultExpressionClause,
187209
}
@@ -190,6 +212,7 @@ pub struct VueOptionsApiComponent {
190212
/// ```js
191213
/// createApp({ props: [ ... ], ... });
192214
/// ```
215+
#[derive(Debug)]
193216
pub struct VueCreateApp {
194217
call_expression: JsCallExpression,
195218
}
@@ -199,6 +222,7 @@ pub struct VueCreateApp {
199222
/// defineComponent((...) => { ... }, { props: [ ... ], ... });
200223
/// defineComponent({ props: [ ... ], ... });
201224
/// ```
225+
#[derive(Debug)]
202226
pub struct VueDefineComponent {
203227
call_expression: JsCallExpression,
204228
}
@@ -207,6 +231,7 @@ pub struct VueDefineComponent {
207231
/// ```html
208232
/// <script setup> defineProps({ ... }); const someData = { ... }; </script>
209233
/// ```
234+
#[derive(Debug)]
210235
pub struct VueSetupComponent {
211236
model: SemanticModel,
212237
js_module: JsModule,
@@ -557,6 +582,15 @@ impl<T: VueOptionsApiBasedComponent> VueComponentDeclarations for T {
557582
.map(VueDeclaration::Setup),
558583
);
559584
}
585+
"watch" => {
586+
if !filter.contains(VueDeclarationCollectionFilter::Watcher) {
587+
continue;
588+
}
589+
result.extend(
590+
iter_declaration_group_properties(group_object_member)
591+
.map(VueDeclaration::Watcher),
592+
);
593+
}
560594
_ => {}
561595
}
562596
}
@@ -601,6 +635,18 @@ pub enum VueDeclaration {
601635
Method(AnyVueMethod),
602636
/// Computed properties in a Vue component.
603637
Computed(AnyVueMethod),
638+
/// Watchers in a Vue component.
639+
Watcher(JsPropertyObjectMember),
640+
}
641+
642+
impl VueDeclaration {
643+
pub fn as_watcher(&self) -> Option<&JsPropertyObjectMember> {
644+
if let Self::Watcher(watcher) = self {
645+
Some(watcher)
646+
} else {
647+
None
648+
}
649+
}
604650
}
605651

606652
pub trait VueDeclarationName {
@@ -622,6 +668,7 @@ impl VueDeclarationName for VueDeclaration {
622668
Self::Method(method_or_property) | Self::Computed(method_or_property) => {
623669
method_or_property.declaration_name()
624670
}
671+
Self::Watcher(object_property) => object_property.declaration_name(),
625672
}
626673
}
627674

@@ -636,6 +683,7 @@ impl VueDeclarationName for VueDeclaration {
636683
Self::Method(method_or_property) | Self::Computed(method_or_property) => {
637684
method_or_property.declaration_name_range()
638685
}
686+
Self::Watcher(object_property) => object_property.declaration_name_range(),
639687
}
640688
}
641689
}

crates/biome_js_analyze/src/lint/nursery.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub mod no_unresolved_imports;
4141
pub mod no_unused_expressions;
4242
pub mod no_useless_catch_binding;
4343
pub mod no_useless_undefined;
44+
pub mod no_vue_arrow_func_in_watch;
4445
pub mod no_vue_data_object_declaration;
4546
pub mod no_vue_duplicate_keys;
4647
pub mod no_vue_options_api;
@@ -66,4 +67,4 @@ pub mod use_spread;
6667
pub mod use_vue_consistent_define_props_declaration;
6768
pub mod use_vue_define_macros_order;
6869
pub mod use_vue_multi_word_component_names;
69-
declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_ambiguous_anchor_text :: NoAmbiguousAnchorText , self :: no_before_interactive_script_outside_document :: NoBeforeInteractiveScriptOutsideDocument , self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_div_regex :: NoDivRegex , self :: no_duplicate_enum_values :: NoDuplicateEnumValues , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_excessive_classes_per_file :: NoExcessiveClassesPerFile , self :: no_excessive_lines_per_file :: NoExcessiveLinesPerFile , self :: no_floating_classes :: NoFloatingClasses , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_jsx_props_bind :: NoJsxPropsBind , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_multi_assign :: NoMultiAssign , self :: no_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_return_assign :: NoReturnAssign , self :: no_script_url :: NoScriptUrl , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_undeclared_env_vars :: NoUndeclaredEnvVars , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_options_api :: NoVueOptionsApi , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLoss , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_await_thenable :: UseAwaitThenable , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_consistent_enum_value_type :: UseConsistentEnumValueType , self :: use_destructuring :: UseDestructuring , self :: use_error_cause :: UseErrorCause , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_inline_script_id :: UseInlineScriptId , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_regexp_exec :: UseRegexpExec , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_consistent_define_props_declaration :: UseVueConsistentDefinePropsDeclaration , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } }
70+
declare_lint_group! { pub Nursery { name : "nursery" , rules : [self :: no_ambiguous_anchor_text :: NoAmbiguousAnchorText , self :: no_before_interactive_script_outside_document :: NoBeforeInteractiveScriptOutsideDocument , self :: no_continue :: NoContinue , self :: no_deprecated_imports :: NoDeprecatedImports , self :: no_div_regex :: NoDivRegex , self :: no_duplicate_enum_values :: NoDuplicateEnumValues , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_excessive_classes_per_file :: NoExcessiveClassesPerFile , self :: no_excessive_lines_per_file :: NoExcessiveLinesPerFile , self :: no_floating_classes :: NoFloatingClasses , self :: no_floating_promises :: NoFloatingPromises , self :: no_for_in :: NoForIn , self :: no_import_cycles :: NoImportCycles , self :: no_increment_decrement :: NoIncrementDecrement , self :: no_jsx_literals :: NoJsxLiterals , self :: no_jsx_props_bind :: NoJsxPropsBind , self :: no_leaked_render :: NoLeakedRender , self :: no_misused_promises :: NoMisusedPromises , self :: no_multi_assign :: NoMultiAssign , self :: no_multi_str :: NoMultiStr , self :: no_next_async_client_component :: NoNextAsyncClientComponent , self :: no_parameters_only_used_in_recursion :: NoParametersOnlyUsedInRecursion , self :: no_proto :: NoProto , self :: no_react_forward_ref :: NoReactForwardRef , self :: no_return_assign :: NoReturnAssign , self :: no_script_url :: NoScriptUrl , self :: no_shadow :: NoShadow , self :: no_sync_scripts :: NoSyncScripts , self :: no_ternary :: NoTernary , self :: no_undeclared_env_vars :: NoUndeclaredEnvVars , self :: no_unknown_attribute :: NoUnknownAttribute , self :: no_unnecessary_conditions :: NoUnnecessaryConditions , self :: no_unresolved_imports :: NoUnresolvedImports , self :: no_unused_expressions :: NoUnusedExpressions , self :: no_useless_catch_binding :: NoUselessCatchBinding , self :: no_useless_undefined :: NoUselessUndefined , self :: no_vue_arrow_func_in_watch :: NoVueArrowFuncInWatch , self :: no_vue_data_object_declaration :: NoVueDataObjectDeclaration , self :: no_vue_duplicate_keys :: NoVueDuplicateKeys , self :: no_vue_options_api :: NoVueOptionsApi , self :: no_vue_reserved_keys :: NoVueReservedKeys , self :: no_vue_reserved_props :: NoVueReservedProps , self :: no_vue_setup_props_reactivity_loss :: NoVueSetupPropsReactivityLoss , self :: use_array_sort_compare :: UseArraySortCompare , self :: use_await_thenable :: UseAwaitThenable , self :: use_consistent_arrow_return :: UseConsistentArrowReturn , self :: use_consistent_enum_value_type :: UseConsistentEnumValueType , self :: use_destructuring :: UseDestructuring , self :: use_error_cause :: UseErrorCause , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , self :: use_inline_script_id :: UseInlineScriptId , self :: use_max_params :: UseMaxParams , self :: use_qwik_method_usage :: UseQwikMethodUsage , self :: use_qwik_valid_lexical_scope :: UseQwikValidLexicalScope , self :: use_regexp_exec :: UseRegexpExec , self :: use_sorted_classes :: UseSortedClasses , self :: use_spread :: UseSpread , self :: use_vue_consistent_define_props_declaration :: UseVueConsistentDefinePropsDeclaration , self :: use_vue_define_macros_order :: UseVueDefineMacrosOrder , self :: use_vue_multi_word_component_names :: UseVueMultiWordComponentNames ,] } }

0 commit comments

Comments
 (0)