diff --git a/docs/rules/README.md b/docs/rules/README.md
index 5f86b7fc7..85f6e661b 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -288,6 +288,7 @@ For example:
|:--------|:------------|:---|
| [vue/block-lang](./block-lang.md) | disallow use other than available `lang` | |
| [vue/block-tag-newline](./block-tag-newline.md) | enforce line breaks after opening and before closing block-level tags | :wrench: |
+| [vue/component-api-style](./component-api-style.md) | enforce component API style | |
| [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: |
| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce specific casing for custom event name | |
| [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute | |
diff --git a/docs/rules/component-api-style.md b/docs/rules/component-api-style.md
new file mode 100644
index 000000000..8472d4c1b
--- /dev/null
+++ b/docs/rules/component-api-style.md
@@ -0,0 +1,144 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/component-api-style
+description: enforce component API style
+---
+# vue/component-api-style
+
+> enforce component API style
+
+- :exclamation: ***This rule has not been released yet.***
+
+## :book: Rule Details
+
+This rule aims to make the API style you use to define Vue components consistent in your project.
+
+For example, if you want to allow only `
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/component-api-style": ["error",
+ ["script-setup", "composition"] // "script-setup", "composition", or "options"
+ ]
+}
+```
+
+- Array options ... Defines the API styles you want to allow. Default is `["script-setup", "composition"]`. You can use the following values.
+ - `"script-setup"` ... If set, allows `
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-api-style.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-api-style.js)
diff --git a/lib/index.js b/lib/index.js
index af29765ca..0015fb579 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -21,6 +21,7 @@ module.exports = {
'comma-spacing': require('./rules/comma-spacing'),
'comma-style': require('./rules/comma-style'),
'comment-directive': require('./rules/comment-directive'),
+ 'component-api-style': require('./rules/component-api-style'),
'component-definition-name-casing': require('./rules/component-definition-name-casing'),
'component-name-in-template-casing': require('./rules/component-name-in-template-casing'),
'component-tags-order': require('./rules/component-tags-order'),
diff --git a/lib/rules/component-api-style.js b/lib/rules/component-api-style.js
new file mode 100644
index 000000000..4cec3b1ab
--- /dev/null
+++ b/lib/rules/component-api-style.js
@@ -0,0 +1,257 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @typedef { 'script-setup' | 'composition' | 'options' } PreferOption
+ *
+ * @typedef {PreferOption[]} UserPreferOption
+ *
+ * @typedef {object} NormalizeOptions
+ * @property {object} allowsSFC
+ * @property {boolean} [allowsSFC.scriptSetup]
+ * @property {boolean} [allowsSFC.composition]
+ * @property {boolean} [allowsSFC.options]
+ * @property {object} allowsOther
+ * @property {boolean} [allowsOther.composition]
+ * @property {boolean} [allowsOther.options]
+ */
+
+/** @type {PreferOption[]} */
+const STYLE_OPTIONS = ['script-setup', 'composition', 'options']
+
+/**
+ * Normalize options.
+ * @param {any[]} options The options user configured.
+ * @returns {NormalizeOptions} The normalized options.
+ */
+function parseOptions(options) {
+ /** @type {NormalizeOptions} */
+ const opts = { allowsSFC: {}, allowsOther: {} }
+
+ /** @type {UserPreferOption} */
+ const preferOptions = options[0] || ['script-setup', 'composition']
+ for (const prefer of preferOptions) {
+ if (prefer === 'script-setup') {
+ opts.allowsSFC.scriptSetup = true
+ } else if (prefer === 'composition') {
+ opts.allowsSFC.composition = true
+ opts.allowsOther.composition = true
+ } else if (prefer === 'options') {
+ opts.allowsSFC.options = true
+ opts.allowsOther.options = true
+ }
+ }
+
+ if (!opts.allowsOther.composition && !opts.allowsOther.options) {
+ opts.allowsOther.composition = true
+ opts.allowsOther.options = true
+ }
+
+ return opts
+}
+
+const OPTIONS_API_OPTIONS = new Set([
+ 'mixins',
+ 'extends',
+ // state
+ 'data',
+ 'computed',
+ 'methods',
+ 'watch',
+ 'provide',
+ 'inject',
+ // lifecycle
+ 'beforeCreate',
+ 'created',
+ 'beforeMount',
+ 'mounted',
+ 'beforeUpdate',
+ 'updated',
+ 'activated',
+ 'deactivated',
+ 'beforeDestroy',
+ 'beforeUnmount',
+ 'destroyed',
+ 'unmounted',
+ 'render',
+ 'renderTracked',
+ 'renderTriggered',
+ 'errorCaptured',
+ // public API
+ 'expose'
+])
+const COMPOSITION_API_OPTIONS = new Set(['setup'])
+
+const LIFECYCLE_HOOK_OPTIONS = new Set([
+ 'beforeCreate',
+ 'created',
+ 'beforeMount',
+ 'mounted',
+ 'beforeUpdate',
+ 'updated',
+ 'activated',
+ 'deactivated',
+ 'beforeDestroy',
+ 'beforeUnmount',
+ 'destroyed',
+ 'unmounted',
+ 'renderTracked',
+ 'renderTriggered',
+ 'errorCaptured'
+])
+
+/**
+ * @typedef { 'script-setup' | 'composition' | 'options' } ApiStyle
+ */
+
+/**
+ * @param {object} allowsOpt
+ * @param {boolean} [allowsOpt.scriptSetup]
+ * @param {boolean} [allowsOpt.composition]
+ * @param {boolean} [allowsOpt.options]
+ */
+function buildAllowedPhrase(allowsOpt) {
+ const phrases = []
+ if (allowsOpt.scriptSetup) {
+ phrases.push('`
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ options: [['options']],
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.js',
+ code: `
+ import { ref, defineComponent } from 'vue'
+ defineComponent({
+ setup() {
+ const msg = ref('Hello World!')
+ // ...
+ return {
+ msg,
+ // ...
+ }
+ }
+ })
+ `
+ },
+ {
+ filename: 'test.js',
+ options: [['options']],
+ code: `
+ import { defineComponent } from 'vue'
+ defineComponent({
+ data () {
+ return {
+ msg: 'Hello World!',
+ // ...
+ }
+ },
+ // ...
+ })
+ `
+ },
+ {
+ filename: 'test.vue',
+ options: [['script-setup']],
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.js',
+ options: [['script-setup']],
+ code: `
+ import { ref, defineComponent } from 'vue'
+ defineComponent({
+ setup() {
+ const msg = ref('Hello World!')
+ // ...
+ return {
+ msg,
+ // ...
+ }
+ }
+ })
+ `
+ },
+ {
+ filename: 'test.js',
+ options: [['script-setup']],
+ code: `
+ import { defineComponent } from 'vue'
+ defineComponent({
+ data () {
+ return {
+ msg: 'Hello World!',
+ // ...
+ }
+ },
+ // ...
+ })
+ `
+ },
+ {
+ filename: 'test.vue',
+ options: [['composition']],
+ code: `
+
+ `
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is the API of Options API. Use `
+ `,
+ options: [['options']],
+ errors: [
+ {
+ message:
+ '`
+ `,
+ options: [['options']],
+ errors: [
+ {
+ message:
+ 'Composition API is not allowed in your project. `setup` function is the API of Composition API. Use Options API instead.',
+ line: 5,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.js',
+ code: `
+ import { defineComponent } from 'vue'
+ defineComponent({
+ data () {
+ return {
+ msg: 'Hello World!',
+ // ...
+ }
+ },
+ // ...
+ })
+ `,
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is the API of Options API. Use Composition API instead.',
+ line: 4,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.js',
+ code: `
+ import { ref, defineComponent } from 'vue'
+ defineComponent({
+ setup() {
+ const msg = ref('Hello World!')
+ // ...
+ return {
+ msg,
+ // ...
+ }
+ }
+ })
+ `,
+ options: [['options']],
+ errors: [
+ {
+ message:
+ 'Composition API is not allowed in your project. `setup` function is the API of Composition API. Use Options API instead.',
+ line: 4,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: [['script-setup']],
+ errors: [
+ {
+ message:
+ 'Composition API is not allowed in your project. Use `
+ `,
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. Use `
+ `,
+ options: [['composition']],
+ errors: [
+ {
+ message:
+ '`
+ `,
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is the API of Options API. Use Composition API instead.',
+ line: 4,
+ column: 9
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ options: [['composition']],
+ code: `
+
+ `,
+ errors: [
+ {
+ message:
+ 'Options API is not allowed in your project. `mixins` option is the API of Options API. Use Composition API instead.',
+ line: 4,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `extends` option is the API of Options API. Use Composition API instead.',
+ line: 5,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `data` option is the API of Options API. Use Composition API instead.',
+ line: 7,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `computed` option is the API of Options API. Use Composition API instead.',
+ line: 8,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `methods` option is the API of Options API. Use Composition API instead.',
+ line: 9,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `watch` option is the API of Options API. Use Composition API instead.',
+ line: 10,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `provide` option is the API of Options API. Use Composition API instead.',
+ line: 11,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `inject` option is the API of Options API. Use Composition API instead.',
+ line: 12,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeCreate` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 14,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `created` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 15,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeMount` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 16,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `mounted` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 17,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeUpdate` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 18,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `updated` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 19,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `activated` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 20,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `deactivated` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 21,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeDestroy` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 22,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `beforeUnmount` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 23,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `destroyed` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 24,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `unmounted` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 25,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `render` function is the API of Options API. Use Composition API instead.',
+ line: 26,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `renderTracked` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 27,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `renderTriggered` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 28,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `errorCaptured` lifecycle hook is the API of Options API. Use Composition API instead.',
+ line: 29,
+ column: 9
+ },
+ {
+ message:
+ 'Options API is not allowed in your project. `expose` option is the API of Options API. Use Composition API instead.',
+ line: 31,
+ column: 9
+ }
+ ]
+ }
+ ]
+})