Repository: vuejs/eslint-plugin-vue (js)

Issue:
Rule proposal: `prefer-script-setup`
<!--
  Before proposing new rule, please make sure it hasn't been posted already.
  You can see all open propositions here:
  https://github.com/vuejs/eslint-plugin-vue/issues?q=is%3Aopen+is%3Aissue+label%3A%22new+rule+proposition%22
-->

**Please describe what the rule should do:**
<!-- A clear and concise description of what the rule should do. -->
As `script setup` is slowing becoming the recommended way to declare a component, it would be nice to have a rule that checks the components of a project and warn the author if a component does not use `script setup`

**What category should the rule belong to?**
<!-- (place an "X" next to just one item) -->
[ ] Enforces code style (layout)
[ ] Warns about a potential error (problem)
[x] Suggests an alternate way of doing something (suggestion)
[ ] Other (please specify:)

**Provide 2-3 code examples that this rule should warn about:**

```vue
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
 //...
});
</script>
```

This should not warn:
```vue
<script setup lang="ts">
</script>
```


Pull request:
Add `vue/component-api-style` rule
This PR adds `vue/component-api-style` rule.

close #1624

Patch:
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 5f86b7fc..85f6e661 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 00000000..8472d4c1
--- /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: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+
+## :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 `<script setup>` and Composition API.  
+(This is the default for this rule.)
+
+<eslint-code-block :rules="{'vue/component-api-style': ['error']}">
+
+```vue
+<!-- ✓ GOOD -->
+<script setup>
+import { ref } from 'vue'
+const msg = ref('Hello World!')
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/component-api-style': ['error']}">
+
+```vue
+<script>
+import { ref } from 'vue'
+export default {
+  /* ✓ GOOD */
+  setup() {
+    const msg = ref('Hello World!')
+    // ...
+    return {
+      msg,
+      // ...
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/component-api-style': ['error']}">
+
+```vue
+<script>
+export default {
+  /* ✗ BAD */
+  data () {
+    return {
+      msg: 'Hello World!',
+      // ...
+    }
+  },
+  // ...
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :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 `<script setup>`.
+  - `"composition"` ... If set, allows Composition API (not `<script setup>`).
+  - `"options"` ... If set, allows Options API.
+
+### `["options"]`
+
+<eslint-code-block :rules="{'vue/component-api-style': ['error', ['options']]}">
+
+```vue
+<script>
+export default {
+  /* ✓ GOOD */
+  data () {
+    return {
+      msg: 'Hello World!',
+      // ...
+    }
+  },
+  // ...
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/component-api-style': ['error', ['options']]}">
+
+```vue
+<!-- ✗ BAD -->
+<script setup>
+import { ref } from 'vue'
+const msg = ref('Hello World!')
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/component-api-style': ['error', ['options']]}">
+
+```vue
+<script>
+import { ref } from 'vue'
+export default {
+  /* ✗ BAD */
+  setup() {
+    const msg = ref('Hello World!')
+    // ...
+    return {
+      msg,
+      // ...
+    }
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :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 af29765c..0015fb57 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 00000000..4cec3b1a
--- /dev/null
+++ b/lib/rules/component-api-style.js
@@ -0,0 +1,257 @@
+/**
+ * @author Yosuke Ota <https://github.com/ota-meshi>
+ * 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('`<script setup>`')
+  }
+  if (allowsOpt.composition) {
+    phrases.push('Composition API')
+  }
+  if (allowsOpt.options) {
+    phrases.push('Options API')
+  }
+  return phrases.length > 2
+    ? `${phrases.slice(0, -1).join(',')} or ${phrases.slice(-1)[0]}`
+    : phrases.join(' or ')
+}
+
+/**
+ * @param {object} allowsOpt
+ * @param {boolean} [allowsOpt.scriptSetup]
+ * @param {boolean} [allowsOpt.composition]
+ * @param {boolean} [allowsOpt.options]
+ */
+function isPreferScriptSetup(allowsOpt) {
+  if (!allowsOpt.scriptSetup || allowsOpt.composition || allowsOpt.options) {
+    return false
+  }
+  return true
+}
+
+/**
+ * @param {string} name
+ */
+function buildOptionPhrase(name) {
+  return LIFECYCLE_HOOK_OPTIONS.has(name)
+    ? `\`${name}\` lifecycle hook`
+    : name === 'setup' || name === 'render'
+    ? `\`${name}\` function`
+    : `\`${name}\` option`
+}
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'enforce component API style',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/component-api-style.html'
+    },
+    fixable: null,
+    schema: [
+      {
+        type: 'array',
+        items: {
+          enum: STYLE_OPTIONS,
+          uniqueItems: true,
+          additionalItems: false
+        },
+        minItems: 1
+      }
+    ],
+    messages: {
+      disallowScriptSetup:
+        '`<script setup>` is not allowed in your project. Use {{allowedApis}} instead.',
+      disallowComponentOption:
+        '{{disallowedApi}} is not allowed in your project. {{optionPhrase}} is the API of {{disallowedApi}}. Use {{allowedApis}} instead.',
+      disallowComponentOptionPreferScriptSetup:
+        '{{disallowedApi}} is not allowed in your project. Use `<script setup>` instead.'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    const options = parseOptions(context.options)
+
+    return utils.compositingVisitors(
+      {
+        Program() {
+          if (options.allowsSFC.scriptSetup) {
+            return
+          }
+          const scriptSetup = utils.getScriptSetupElement(context)
+          if (scriptSetup) {
+            context.report({
+              node: scriptSetup.startTag,
+              messageId: 'disallowScriptSetup',
+              data: {
+                allowedApis: buildAllowedPhrase(options.allowsSFC)
+              }
+            })
+          }
+        }
+      },
+      utils.defineVueVisitor(context, {
+        onVueObjectEnter(node) {
+          const allows = utils.isSFCObject(context, node)
+            ? options.allowsSFC
+            : options.allowsOther
+          if (allows.composition && allows.options) {
+            return
+          }
+          const disallows = [
+            {
+              allow: allows.composition,
+              options: COMPOSITION_API_OPTIONS,
+              api: 'Composition API'
+            },
+            {
+              allow: allows.options,
+              options: OPTIONS_API_OPTIONS,
+              api: 'Options API'
+            }
+          ].filter(({ allow }) => !allow)
+          for (const prop of node.properties) {
+            if (prop.type !== 'Property') {
+              continue
+            }
+            const name = utils.getStaticPropertyName(prop)
+            if (!name) {
+              continue
+            }
+            for (const { options, api } of disallows) {
+              if (options.has(name)) {
+                context.report({
+                  node: prop.key,
+                  messageId: isPreferScriptSetup(allows)
+                    ? 'disallowComponentOptionPreferScriptSetup'
+                    : 'disallowComponentOption',
+                  data: {
+                    disallowedApi: api,
+                    optionPhrase: buildOptionPhrase(name),
+                    allowedApis: buildAllowedPhrase(allows)
+                  }
+                })
+              }
+            }
+          }
+        }
+      })
+    )
+  }
+}
diff --git a/lib/utils/index.js b/lib/utils/index.js
index dfd5610d..b17de082 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -1296,6 +1296,13 @@ module.exports = {
    * @returns {'component' | 'mixin' | 'extend' | 'createApp' | 'defineComponent' | null}
    */
   getVueComponentDefinitionType,
+  /**
+   * Checks whether the given object is an SFC definition.
+   * @param {RuleContext} context The ESLint rule context object.
+   * @param {ObjectExpression} node Node to check
+   * @returns { boolean } `true`, the given object is an SFC definition.
+   */
+  isSFCObject,
   compositingVisitors,
 
   /**
@@ -2439,6 +2446,77 @@ function getVueObjectType(context, node) {
   return null
 }
 
+/**
+ * Checks whether the given object is an SFC definition.
+ * @param {RuleContext} context The ESLint rule context object.
+ * @param {ObjectExpression} node Node to check
+ * @returns { boolean } `true`, the given object is an SFC definition.
+ */
+function isSFCObject(context, node) {
+  if (node.type !== 'ObjectExpression') {
+    return false
+  }
+  const filePath = context.getFilename()
+  const ext = path.extname(filePath)
+  if (ext !== '.vue' && ext) {
+    return false
+  }
+  return isSFC(node)
+
+  /**
+   * @param {Expression} node
+   * @returns {boolean}
+   */
+  function isSFC(node) {
+    const parent = getParent(node)
+    if (parent.type === 'ExportDefaultDeclaration') {
+      // export default {}
+      if (skipTSAsExpression(parent.declaration) !== node) {
+        return false
+      }
+      const scriptSetup = getScriptSetupElement(context)
+      if (
+        scriptSetup &&
+        scriptSetup.range[0] <= parent.range[0] &&
+        parent.range[1] <= scriptSetup.range[1]
+      ) {
+        // `export default` in `<script setup>`
+        return false
+      }
+      return true
+    } else if (parent.type === 'CallExpression') {
+      if (parent.arguments.every((arg) => skipTSAsExpression(arg) !== node)) {
+        return false
+      }
+      const { callee } = parent
+      if (
+        (callee.type === 'Identifier' && callee.name === 'defineComponent') ||
+        (callee.type === 'MemberExpression' &&
+          callee.object.type === 'Identifier' &&
+          callee.object.name === 'Vue' &&
+          callee.property.type === 'Identifier' &&
+          callee.property.name === 'extend')
+      ) {
+        return isSFC(parent)
+      }
+      return false
+    } else if (parent.type === 'VariableDeclarator') {
+      if (
+        skipTSAsExpression(parent.init) !== node ||
+        parent.id.type !== 'Identifier'
+      ) {
+        return false
+      }
+      const variable = findVariable(context.getScope(), parent.id)
+      if (!variable) {
+        return false
+      }
+      return variable.references.some((ref) => isSFC(ref.identifier))
+    }
+    return false
+  }
+}
+
 /**
  * Gets the component comments of a given context.
  * @param {RuleContext} context The ESLint rule context object.


Test patch:
diff --git a/tests/lib/rules/component-api-style.js b/tests/lib/rules/component-api-style.js
new file mode 100644
index 00000000..aca48bb3
--- /dev/null
+++ b/tests/lib/rules/component-api-style.js
@@ -0,0 +1,566 @@
+/**
+ * @author Yosuke Ota <https://github.com/ota-meshi>
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/component-api-style')
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2015, sourceType: 'module' }
+})
+
+tester.run('component-api-style', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      import { ref } from 'vue'
+      const msg = ref('Hello World!')
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      import { ref } from 'vue'
+      export default {
+        setup() {
+          const msg = ref('Hello World!')
+          // ...
+          return {
+            msg,
+            // ...
+          }
+        }
+      }
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      options: [['options']],
+      code: `
+      <script>
+      export default {
+        data () {
+          return {
+            msg: 'Hello World!',
+            // ...
+          }
+        },
+        // ...
+      }
+      </script>
+      `
+    },
+    {
+      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: `
+      <script setup>
+      import { ref } from 'vue'
+      const msg = ref('Hello World!')
+      </script>
+      `
+    },
+    {
+      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: `
+      <script>
+      import { ref } from 'vue'
+      export default {
+        setup() {
+          const msg = ref('Hello World!')
+          // ...
+          return {
+            msg,
+            // ...
+          }
+        }
+      }
+      </script>
+      `
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        data () {
+          return {
+            msg: 'Hello World!',
+            // ...
+          }
+        },
+        // ...
+      }
+      </script>
+      `,
+      errors: [
+        {
+          message:
+            'Options API is not allowed in your project. `data` option is the API of Options API. Use `<script setup>` or Composition API instead.',
+          line: 4,
+          column: 9
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      import { ref } from 'vue'
+      const msg = ref('Hello World!')
+      </script>
+      `,
+      options: [['options']],
+      errors: [
+        {
+          message:
+            '`<script setup>` is not allowed in your project. Use Options API instead.',
+          line: 2,
+          column: 7
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      import { ref } from 'vue'
+      export default {
+        setup() {
+          const msg = ref('Hello World!')
+          // ...
+          return {
+            msg,
+            // ...
+          }
+        }
+      }
+      </script>
+      `,
+      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: `
+      <script>
+      import { ref } from 'vue'
+      export default {
+        setup() {
+          const msg = ref('Hello World!')
+          // ...
+          return {
+            msg,
+            // ...
+          }
+        }
+      }
+      </script>
+      `,
+      options: [['script-setup']],
+      errors: [
+        {
+          message:
+            'Composition API is not allowed in your project. Use `<script setup>` instead.',
+          line: 5,
+          column: 9
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      options: [['script-setup']],
+      code: `
+      <script>
+      export default {
+        data () {
+          return {
+            msg: 'Hello World!',
+            // ...
+          }
+        },
+        // ...
+      }
+      </script>
+      `,
+      errors: [
+        {
+          message:
+            'Options API is not allowed in your project. Use `<script setup>` instead.',
+          line: 4,
+          column: 9
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      </script>
+      `,
+      options: [['composition']],
+      errors: [
+        {
+          message:
+            '`<script setup>` is not allowed in your project. Use Composition API instead.',
+          line: 2,
+          column: 7
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      options: [['composition']],
+      code: `
+      <script>
+      export default {
+        data () {
+          return {
+            msg: 'Hello World!',
+            // ...
+          }
+        },
+        // ...
+      }
+      </script>
+      `,
+      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: `
+      <script>
+      export default {
+        mixins: [],
+        extends: {},
+        // state
+        data () { return {} },
+        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: [],
+      }
+      </script>
+      `,
+      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
+        }
+      ]
+    }
+  ]
+})