diff --git a/.changeset/add-no-v-options-api.md b/.changeset/add-no-v-options-api.md
new file mode 100644
index 000000000000..b821ad6d956f
--- /dev/null
+++ b/.changeset/add-no-v-options-api.md
@@ -0,0 +1,21 @@
+---
+"@biomejs/biome": patch
+---
+
+Added a new nursery rule [`noVueOptionsApi`](https://biomejs.dev/linter/rules/no-vue-options-api/).
+
+Biome now reports Vue Options API usage, which is incompatible with Vue 3.6's Vapor Mode.
+This rule detects Options API patterns in `
+```
diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs
index 7755fc0c3289..945f2b69d902 100644
--- a/crates/biome_configuration/src/analyzer/linter/rules.rs
+++ b/crates/biome_configuration/src/analyzer/linter/rules.rs
@@ -355,6 +355,7 @@ pub enum RuleName {
NoVoidTypeReturn,
NoVueDataObjectDeclaration,
NoVueDuplicateKeys,
+ NoVueOptionsApi,
NoVueReservedKeys,
NoVueReservedProps,
NoVueSetupPropsReactivityLoss,
@@ -785,6 +786,7 @@ impl RuleName {
Self::NoVoidTypeReturn => "noVoidTypeReturn",
Self::NoVueDataObjectDeclaration => "noVueDataObjectDeclaration",
Self::NoVueDuplicateKeys => "noVueDuplicateKeys",
+ Self::NoVueOptionsApi => "noVueOptionsApi",
Self::NoVueReservedKeys => "noVueReservedKeys",
Self::NoVueReservedProps => "noVueReservedProps",
Self::NoVueSetupPropsReactivityLoss => "noVueSetupPropsReactivityLoss",
@@ -1211,6 +1213,7 @@ impl RuleName {
Self::NoVoidTypeReturn => RuleGroup::Correctness,
Self::NoVueDataObjectDeclaration => RuleGroup::Nursery,
Self::NoVueDuplicateKeys => RuleGroup::Nursery,
+ Self::NoVueOptionsApi => RuleGroup::Nursery,
Self::NoVueReservedKeys => RuleGroup::Nursery,
Self::NoVueReservedProps => RuleGroup::Nursery,
Self::NoVueSetupPropsReactivityLoss => RuleGroup::Nursery,
@@ -1646,6 +1649,7 @@ impl std::str::FromStr for RuleName {
"noVoidTypeReturn" => Ok(Self::NoVoidTypeReturn),
"noVueDataObjectDeclaration" => Ok(Self::NoVueDataObjectDeclaration),
"noVueDuplicateKeys" => Ok(Self::NoVueDuplicateKeys),
+ "noVueOptionsApi" => Ok(Self::NoVueOptionsApi),
"noVueReservedKeys" => Ok(Self::NoVueReservedKeys),
"noVueReservedProps" => Ok(Self::NoVueReservedProps),
"noVueSetupPropsReactivityLoss" => Ok(Self::NoVueSetupPropsReactivityLoss),
diff --git a/crates/biome_configuration/src/generated/domain_selector.rs b/crates/biome_configuration/src/generated/domain_selector.rs
index 03fd957d578a..e3d23ac44550 100644
--- a/crates/biome_configuration/src/generated/domain_selector.rs
+++ b/crates/biome_configuration/src/generated/domain_selector.rs
@@ -93,6 +93,7 @@ static VUE_FILTERS: LazyLock>> = LazyLock::new(|| {
vec: Disallows the Options API format, which is incompatible with Vapor Mode
+ ///
pub UseVueVapor {
version: "2.3.11",
name: "useVueVapor",
diff --git a/crates/biome_js_analyze/src/lint/nursery.rs b/crates/biome_js_analyze/src/lint/nursery.rs
index 4cd53bca6438..d74a6ffe51b6 100644
--- a/crates/biome_js_analyze/src/lint/nursery.rs
+++ b/crates/biome_js_analyze/src/lint/nursery.rs
@@ -40,6 +40,7 @@ pub mod no_useless_catch_binding;
pub mod no_useless_undefined;
pub mod no_vue_data_object_declaration;
pub mod no_vue_duplicate_keys;
+pub mod no_vue_options_api;
pub mod no_vue_reserved_keys;
pub mod no_vue_reserved_props;
pub mod no_vue_setup_props_reactivity_loss;
@@ -60,4 +61,4 @@ pub mod use_spread;
pub mod use_vue_consistent_define_props_declaration;
pub mod use_vue_define_macros_order;
pub mod use_vue_multi_word_component_names;
-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_duplicate_enum_values :: NoDuplicateEnumValues , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_excessive_lines_per_file :: NoExcessiveLinesPerFile , 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_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_destructuring :: UseDestructuring , self :: use_error_cause :: UseErrorCause , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , 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 ,] } }
+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_duplicate_enum_values :: NoDuplicateEnumValues , self :: no_duplicated_spread_props :: NoDuplicatedSpreadProps , self :: no_empty_source :: NoEmptySource , self :: no_equals_to_null :: NoEqualsToNull , self :: no_excessive_lines_per_file :: NoExcessiveLinesPerFile , 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_destructuring :: UseDestructuring , self :: use_error_cause :: UseErrorCause , self :: use_exhaustive_switch_cases :: UseExhaustiveSwitchCases , self :: use_explicit_type :: UseExplicitType , self :: use_find :: UseFind , 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 ,] } }
diff --git a/crates/biome_js_analyze/src/lint/nursery/no_vue_options_api.rs b/crates/biome_js_analyze/src/lint/nursery/no_vue_options_api.rs
new file mode 100644
index 000000000000..ed70d6532a48
--- /dev/null
+++ b/crates/biome_js_analyze/src/lint/nursery/no_vue_options_api.rs
@@ -0,0 +1,216 @@
+use biome_analyze::{Rule, RuleDiagnostic, RuleDomain, context::RuleContext, declare_lint_rule};
+use biome_console::markup;
+use biome_diagnostics::Severity;
+use biome_js_syntax::{AnyJsExpression, JsFileSource};
+use biome_rowan::{AstNode, TextRange};
+use biome_rule_options::no_vue_options_api::NoVueOptionsApiOptions;
+
+use crate::frameworks::vue::vue_call::is_vue_api_reference;
+use crate::frameworks::vue::vue_component::{
+ AnyVueComponent, VueComponent, VueComponentQuery, VueOptionsApiBasedComponent,
+};
+
+declare_lint_rule! {
+ /// Disallow the use of Vue Options API.
+ ///
+ /// Vue 3.6's Vapor Mode does not support the Options API.
+ /// Components must use the Composition API (`
+ /// ```
+ ///
+ /// ```vue,expect_diagnostic
+ ///
+ /// ```
+ ///
+ /// ```vue,expect_diagnostic
+ ///
+ /// ```
+ ///
+ /// ```vue,expect_diagnostic
+ ///
+ /// ```
+ ///
+ /// ```js,expect_diagnostic
+ /// import { defineComponent } from 'vue'
+ ///
+ /// defineComponent({
+ /// name: 'MyComponent',
+ /// data() {
+ /// return { count: 0 }
+ /// }
+ /// })
+ /// ```
+ ///
+ /// ### Valid
+ ///
+ /// ```vue
+ ///
+ /// ```
+ ///
+ /// ```vue
+ ///
+ /// ```
+ ///
+ /// ```vue
+ ///
+ /// ```
+ ///
+ /// ## Related Rules
+ ///
+ /// - [useVueVapor](https://biomejs.dev/linter/rules/use-vue-vapor): Enforces the use of Vapor mode in Vue components
+ ///
+ /// ## Resources
+ ///
+ /// - [Vue 3 Composition API](https://vuejs.org/api/composition-api-setup.html)
+ /// - [Options API vs Composition API](https://vuejs.org/guide/introduction.html#api-styles)
+ ///
+ pub NoVueOptionsApi {
+ version: "next",
+ name: "noVueOptionsApi",
+ language: "js",
+ recommended: false,
+ severity: Severity::Error,
+ domains: &[RuleDomain::Vue],
+ }
+}
+
+/// State for detected Options API component.
+pub struct RuleState {
+ range: TextRange,
+}
+
+impl Rule for NoVueOptionsApi {
+ type Query = VueComponentQuery;
+ type State = RuleState;
+ type Signals = Option;
+ type Options = NoVueOptionsApiOptions;
+
+ fn run(ctx: &RuleContext) -> Self::Signals {
+ let component = VueComponent::from_potential_component(
+ ctx.query(),
+ ctx.model(),
+ ctx.source_type::(),
+ ctx.file_path(),
+ )?;
+
+ //
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-createapp-data.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-createapp-data.vue.snap
new file mode 100644
index 000000000000..ec3885cd3bc4
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-createapp-data.vue.snap
@@ -0,0 +1,43 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-createapp-data.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// createApp with Options API is not supported in Vapor Mode
+import { createApp } from "vue";
+
+createApp({
+ data() {
+ return { count: 0 };
+ },
+}).mount("#app");
+
+```
+
+# Diagnostics
+```
+invalid-createapp-data.vue:5:11 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 3 │ import { createApp } from "vue";
+ 4 │
+ > 5 │ createApp({
+ │ ^
+ > 6 │ data() {
+ > 7 │ return { count: 0 };
+ > 8 │ },
+ > 9 │ }).mount("#app");
+ │ ^
+ 10 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-definecomponent-data.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-definecomponent-data.vue.snap
new file mode 100644
index 000000000000..d0954ab9eac3
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-definecomponent-data.vue.snap
@@ -0,0 +1,43 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-definecomponent-data.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// defineComponent with Options API is not supported in Vapor Mode
+import { defineComponent } from "vue";
+
+export default defineComponent({
+ data() {
+ return { count: 0 };
+ },
+});
+
+```
+
+# Diagnostics
+```
+invalid-definecomponent-data.vue:5:32 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 3 │ import { defineComponent } from "vue";
+ 4 │
+ > 5 │ export default defineComponent({
+ │ ^
+ > 6 │ data() {
+ > 7 │ return { count: 0 };
+ > 8 │ },
+ > 9 │ });
+ │ ^
+ 10 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-computed.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-computed.vue.snap
new file mode 100644
index 000000000000..b5cfed753e5b
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-computed.vue.snap
@@ -0,0 +1,45 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-export-default-computed.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// Options API: computed is not supported in Vapor Mode
+export default {
+ computed: {
+ doubleCount() {
+ return this.count * 2;
+ },
+ },
+};
+
+```
+
+# Diagnostics
+```
+invalid-export-default-computed.vue:3:16 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 1 │ // should generate diagnostics
+ 2 │ // Options API: computed is not supported in Vapor Mode
+ > 3 │ export default {
+ │ ^
+ > 4 │ computed: {
+ > 5 │ doubleCount() {
+ > 6 │ return this.count * 2;
+ > 7 │ },
+ > 8 │ },
+ > 9 │ };
+ │ ^
+ 10 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-data.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-data.vue.snap
new file mode 100644
index 000000000000..e61098bd6cfb
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-data.vue.snap
@@ -0,0 +1,41 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-export-default-data.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// Options API: data() is not supported in Vapor Mode
+export default {
+ data() {
+ return { count: 0 };
+ },
+};
+
+```
+
+# Diagnostics
+```
+invalid-export-default-data.vue:3:16 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 1 │ // should generate diagnostics
+ 2 │ // Options API: data() is not supported in Vapor Mode
+ > 3 │ export default {
+ │ ^
+ > 4 │ data() {
+ > 5 │ return { count: 0 };
+ > 6 │ },
+ > 7 │ };
+ │ ^
+ 8 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-methods.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-methods.vue.snap
new file mode 100644
index 000000000000..2ad434db7239
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-methods.vue.snap
@@ -0,0 +1,45 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-export-default-methods.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// Options API: methods is not supported in Vapor Mode
+export default {
+ methods: {
+ increment() {
+ this.count++
+ }
+ }
+}
+
+```
+
+# Diagnostics
+```
+invalid-export-default-methods.vue:3:16 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 1 │ // should generate diagnostics
+ 2 │ // Options API: methods is not supported in Vapor Mode
+ > 3 │ export default {
+ │ ^
+ > 4 │ methods: {
+ > 5 │ increment() {
+ > 6 │ this.count++
+ > 7 │ }
+ > 8 │ }
+ > 9 │ }
+ │ ^
+ 10 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-mounted.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-mounted.vue.snap
new file mode 100644
index 000000000000..585b9f64d6c6
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-mounted.vue.snap
@@ -0,0 +1,41 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-export-default-mounted.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// Options API: lifecycle hooks are not supported in Vapor Mode
+export default {
+ mounted() {
+ console.log('component mounted')
+ }
+}
+
+```
+
+# Diagnostics
+```
+invalid-export-default-mounted.vue:3:16 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 1 │ // should generate diagnostics
+ 2 │ // Options API: lifecycle hooks are not supported in Vapor Mode
+ > 3 │ export default {
+ │ ^
+ > 4 │ mounted() {
+ > 5 │ console.log('component mounted')
+ > 6 │ }
+ > 7 │ }
+ │ ^
+ 8 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-watch.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-watch.vue.snap
new file mode 100644
index 000000000000..9904412cd5b5
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-export-default-watch.vue.snap
@@ -0,0 +1,45 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-export-default-watch.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// Options API: watch is not supported in Vapor Mode
+export default {
+ watch: {
+ count(newVal, oldVal) {
+ console.log('count changed')
+ }
+ }
+}
+
+```
+
+# Diagnostics
+```
+invalid-export-default-watch.vue:3:16 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 1 │ // should generate diagnostics
+ 2 │ // Options API: watch is not supported in Vapor Mode
+ > 3 │ export default {
+ │ ^
+ > 4 │ watch: {
+ > 5 │ count(newVal, oldVal) {
+ > 6 │ console.log('count changed')
+ > 7 │ }
+ > 8 │ }
+ > 9 │ }
+ │ ^
+ 10 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-full-options-api.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-full-options-api.vue.snap
new file mode 100644
index 000000000000..09064d268340
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-full-options-api.vue.snap
@@ -0,0 +1,62 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-full-options-api.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// Full Options API component with multiple properties - all should be flagged
+export default {
+ name: 'MyComponent',
+ props: ['message'],
+ data() {
+ return { count: 0 }
+ },
+ computed: {
+ doubleCount() {
+ return this.count * 2
+ }
+ },
+ watch: {
+ count(newVal) {
+ console.log('changed')
+ }
+ },
+ methods: {
+ increment() {
+ this.count++
+ }
+ },
+ mounted() {
+ console.log('mounted')
+ }
+}
+
+```
+
+# Diagnostics
+```
+invalid-full-options-api.vue:3:16 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 1 │ // should generate diagnostics
+ 2 │ // Full Options API component with multiple properties - all should be flagged
+ > 3 │ export default {
+ │ ^
+ > 4 │ name: 'MyComponent',
+ ...
+ > 25 │ console.log('mounted')
+ > 26 │ }
+ > 27 │ }
+ │ ^
+ 28 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-mixed-setup-data.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-mixed-setup-data.vue.snap
new file mode 100644
index 000000000000..29513543d11b
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-mixed-setup-data.vue.snap
@@ -0,0 +1,46 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-mixed-setup-data.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// Mixed setup() with Options API is not supported in Vapor Mode
+export default {
+ setup() {
+ const someRef = ref(0);
+ return { someRef };
+ },
+ data() {
+ return { count: 0 };
+ },
+};
+
+```
+
+# Diagnostics
+```
+invalid-mixed-setup-data.vue:3:16 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 1 │ // should generate diagnostics
+ 2 │ // Mixed setup() with Options API is not supported in Vapor Mode
+ > 3 │ export default {
+ │ ^
+ > 4 │ setup() {
+ ...
+ > 9 │ return { count: 0 };
+ > 10 │ },
+ > 11 │ };
+ │ ^
+ 12 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-multiple-options.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-multiple-options.vue.snap
new file mode 100644
index 000000000000..057aa6f594e4
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-multiple-options.vue.snap
@@ -0,0 +1,47 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-multiple-options.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// Multiple Options API properties - all should be flagged
+export default {
+ data() {
+ return { count: 0 }
+ },
+ methods: {
+ increment() {
+ this.count++
+ }
+ }
+}
+
+```
+
+# Diagnostics
+```
+invalid-multiple-options.vue:3:16 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 1 │ // should generate diagnostics
+ 2 │ // Multiple Options API properties - all should be flagged
+ > 3 │ export default {
+ │ ^
+ > 4 │ data() {
+ ...
+ > 10 │ }
+ > 11 │ }
+ > 12 │ }
+ │ ^
+ 13 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-props-only.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-props-only.vue.snap
new file mode 100644
index 000000000000..578c69d04a8c
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/invalid-props-only.vue.snap
@@ -0,0 +1,37 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: invalid-props-only.vue
+---
+# Input
+```ts
+// should generate diagnostics
+// Options API with props only - still Options API, not Composition API
+export default {
+ props: ['message']
+}
+
+```
+
+# Diagnostics
+```
+invalid-props-only.vue:3:16 lint/nursery/noVueOptionsApi ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ × Using the Options API is not allowed.
+
+ 1 │ // should generate diagnostics
+ 2 │ // Options API with props only - still Options API, not Composition API
+ > 3 │ export default {
+ │ ^
+ > 4 │ props: ['message']
+ > 5 │ }
+ │ ^
+ 6 │
+
+ i Although the Options API is still supported by Vue, using the Composition API is recommended, and makes it possible to use Vue's Vapor mode for better performance.
+
+ i Use
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-definecomponent-function-signature.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-definecomponent-function-signature.vue.snap
new file mode 100644
index 000000000000..7084084cdffa
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-definecomponent-function-signature.vue.snap
@@ -0,0 +1,16 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid-definecomponent-function-signature.vue
+---
+# Input
+```ts
+// should not generate diagnostics
+// defineComponent with function signature in .vue file is Composition API (Vue 3.3+)
+import { defineComponent, ref, h } from "vue";
+
+export default defineComponent((props) => {
+ const count = ref(0);
+ return () => h("div", count.value);
+});
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-definecomponent-function-with-options.js b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-definecomponent-function-with-options.js
new file mode 100644
index 000000000000..0849cec8ab3a
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-definecomponent-function-with-options.js
@@ -0,0 +1,19 @@
+// should not generate diagnostics
+// defineComponent with function signature and extra options (Vue 3.3+)
+import { defineComponent, ref, h } from "vue";
+
+const Comp = defineComponent(
+ (props) => {
+ const count = ref(0);
+ return () => h("div", count.value);
+ },
+ // extra options - this is still Composition API, not Options API
+ {
+ props: {
+ message: String,
+ },
+ emits: ["update"],
+ }
+);
+
+export default Comp;
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-definecomponent-function-with-options.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-definecomponent-function-with-options.js.snap
new file mode 100644
index 000000000000..904d88184a91
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-definecomponent-function-with-options.js.snap
@@ -0,0 +1,27 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid-definecomponent-function-with-options.js
+---
+# Input
+```js
+// should not generate diagnostics
+// defineComponent with function signature and extra options (Vue 3.3+)
+import { defineComponent, ref, h } from "vue";
+
+const Comp = defineComponent(
+ (props) => {
+ const count = ref(0);
+ return () => h("div", count.value);
+ },
+ // extra options - this is still Composition API, not Options API
+ {
+ props: {
+ message: String,
+ },
+ emits: ["update"],
+ }
+);
+
+export default Comp;
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-functional-component.js b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-functional-component.js
new file mode 100644
index 000000000000..5034f1edf8cf
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-functional-component.js
@@ -0,0 +1,5 @@
+// should not generate diagnostics
+export default function MyComponent(props) {
+ return h('div', props.message)
+}
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-functional-component.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-functional-component.js.snap
new file mode 100644
index 000000000000..2d3cc90a3ab8
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-functional-component.js.snap
@@ -0,0 +1,13 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid-functional-component.js
+---
+# Input
+```js
+// should not generate diagnostics
+export default function MyComponent(props) {
+ return h('div', props.message)
+}
+
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-non-vue-object.js b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-non-vue-object.js
new file mode 100644
index 000000000000..5cb94910d9ec
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-non-vue-object.js
@@ -0,0 +1,7 @@
+// should not generate diagnostics
+// Regular JS file, not a Vue component
+export default {
+ data: { message: 'hello' },
+ methods: { doSomething: () => {} }
+}
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-non-vue-object.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-non-vue-object.js.snap
new file mode 100644
index 000000000000..96084c303721
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-non-vue-object.js.snap
@@ -0,0 +1,15 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid-non-vue-object.js
+---
+# Input
+```js
+// should not generate diagnostics
+// Regular JS file, not a Vue component
+export default {
+ data: { message: 'hello' },
+ methods: { doSomething: () => {} }
+}
+
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-passthrough-export.js b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-passthrough-export.js
new file mode 100644
index 000000000000..b49b2e3f0369
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-passthrough-export.js
@@ -0,0 +1,4 @@
+// should not generate diagnostics
+import MyComponent from './MyComponent.vue'
+export default MyComponent
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-passthrough-export.js.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-passthrough-export.js.snap
new file mode 100644
index 000000000000..c4ef89ccaeb9
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-passthrough-export.js.snap
@@ -0,0 +1,12 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid-passthrough-export.js
+---
+# Input
+```js
+// should not generate diagnostics
+import MyComponent from './MyComponent.vue'
+export default MyComponent
+
+
+```
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-script-setup.vue b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-script-setup.vue
new file mode 100644
index 000000000000..0590c284bcca
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-script-setup.vue
@@ -0,0 +1,12 @@
+
+
+
diff --git a/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-script-setup.vue.snap b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-script-setup.vue.snap
new file mode 100644
index 000000000000..f7ea1039d7f3
--- /dev/null
+++ b/crates/biome_js_analyze/tests/specs/nursery/noVueOptionsApi/valid-script-setup.vue.snap
@@ -0,0 +1,19 @@
+---
+source: crates/biome_js_analyze/tests/spec_tests.rs
+expression: valid-script-setup.vue
+---
+# Input
+```ts
+