Repository: vuejs/eslint-plugin-vue
Pull request #1849 (2022-04-14 09:26:53)

Upgrade vue-eslint-parser
This PR upgrades the parser to v9.

As a result, the following features have been deprecated.

- `vue/script-setup-uses-vars` rule
- `vue/setup-compiler-macros` environment


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index e695f422..0ba3d33d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,7 +5,6 @@ workflows:
       - eslint-v6
       - eslint-v7
       - ts-eslint-v4
-      - node-v12
       - node-v14
       - node-v16
       - lint
@@ -36,7 +35,7 @@ jobs:
 
   eslint-v6:
     docker:
-      - image: node:12
+      - image: node:14
     steps:
       - run:
           name: Versions
@@ -88,10 +87,6 @@ jobs:
       - run:
           name: Test
           command: npm test
-  node-v12:
-    <<: *node-base
-    docker:
-      - image: node:12
   node-v14:
     <<: *node-base
     docker:
diff --git a/.eslintrc.js b/.eslintrc.js
index 6a944b5e..da047d7b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,7 +3,7 @@
 module.exports = {
   root: true,
   parserOptions: {
-    ecmaVersion: 2018
+    ecmaVersion: 'latest'
   },
   env: {
     es6: true,
@@ -137,7 +137,7 @@ module.exports = {
       files: ['./**/*.vue'],
       parser: require.resolve('vue-eslint-parser'),
       parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: 'latest',
         sourceType: 'module'
       }
     },
diff --git a/docs/.vuepress/components/eslint-code-block.vue b/docs/.vuepress/components/eslint-code-block.vue
index 2c6abd2c..f71e606a 100644
--- a/docs/.vuepress/components/eslint-code-block.vue
+++ b/docs/.vuepress/components/eslint-code-block.vue
@@ -90,7 +90,7 @@ export default {
         rules: this.rules,
         parser: 'vue-eslint-parser',
         parserOptions: {
-          ecmaVersion: 2020,
+          ecmaVersion: 'latest',
           sourceType: 'module',
           ecmaFeatures: {
             jsx: true
diff --git a/docs/rules/README.md b/docs/rules/README.md
index 867cbe2b..4492988e 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -29,7 +29,6 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
 |:--------|:------------|:--:|:--:|
 | [vue/comment-directive](./comment-directive.md) | support comment-directives in `<template>` |  | :warning: |
 | [vue/jsx-uses-vars](./jsx-uses-vars.md) | prevent variables used in JSX to be marked as unused |  | :warning: |
-| [vue/script-setup-uses-vars](./script-setup-uses-vars.md) | prevent `<script setup>` variables used in `<template>` to be marked as unused |  | :warning: |
 
 </rules-table>
 
@@ -45,6 +44,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
 | [vue/multi-word-component-names](./multi-word-component-names.md) | require component names to be always multi-word |  | :three::two::hammer: |
 | [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher |  | :three::two::warning: |
 | [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties |  | :three::two::warning: |
+| [vue/no-child-content](./no-child-content.md) | disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text` | :bulb: | :three::two::warning: |
 | [vue/no-computed-properties-in-data](./no-computed-properties-in-data.md) | disallow accessing computed properties in `data`. |  | :three::two::warning: |
 | [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component |  | :two::warning: |
 | [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: | :three::warning: |
@@ -69,27 +69,32 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
 | [vue/no-dupe-keys](./no-dupe-keys.md) | disallow duplication of field names |  | :three::two::warning: |
 | [vue/no-dupe-v-else-if](./no-dupe-v-else-if.md) | disallow duplicate conditions in `v-if` / `v-else-if` chains |  | :three::two::warning: |
 | [vue/no-duplicate-attributes](./no-duplicate-attributes.md) | disallow duplication of attributes |  | :three::two::warning: |
-| [vue/no-export-in-script-setup](./no-export-in-script-setup.md) | disallow `export` in `<script setup>` |  | :three::warning: |
+| [vue/no-export-in-script-setup](./no-export-in-script-setup.md) | disallow `export` in `<script setup>` |  | :three::two::warning: |
+| [vue/no-expose-after-await](./no-expose-after-await.md) | disallow asynchronously registered `expose` |  | :three::warning: |
 | [vue/no-lifecycle-after-await](./no-lifecycle-after-await.md) | disallow asynchronously registered lifecycle hooks |  | :three::hammer: |
 | [vue/no-multiple-template-root](./no-multiple-template-root.md) | disallow adding multiple root nodes to the template |  | :two::warning: |
 | [vue/no-mutating-props](./no-mutating-props.md) | disallow mutation of component props |  | :three::two::hammer: |
 | [vue/no-parsing-error](./no-parsing-error.md) | disallow parsing errors in `<template>` |  | :three::two::warning: |
-| [vue/no-ref-as-operand](./no-ref-as-operand.md) | disallow use of value wrapped by `ref()` (Composition API) as an operand | :wrench: | :three::hammer: |
+| [vue/no-ref-as-operand](./no-ref-as-operand.md) | disallow use of value wrapped by `ref()` (Composition API) as an operand | :wrench: | :three::two::hammer: |
+| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions |  | :three::two::hammer: |
 | [vue/no-reserved-keys](./no-reserved-keys.md) | disallow overwriting reserved keys |  | :three::two::hammer: |
 | [vue/no-reserved-props](./no-reserved-props.md) | disallow reserved names in props |  | :three::two::warning: |
-| [vue/no-setup-props-destructure](./no-setup-props-destructure.md) | disallow destructuring of `props` passed to `setup` |  | :three::hammer: |
+| [vue/no-setup-props-destructure](./no-setup-props-destructure.md) | disallow destructuring of `props` passed to `setup` |  | :three::two::hammer: |
 | [vue/no-shared-component-data](./no-shared-component-data.md) | enforce component's data property to be a function | :wrench: | :three::two::warning: |
 | [vue/no-side-effects-in-computed-properties](./no-side-effects-in-computed-properties.md) | disallow side effects in computed properties |  | :three::two::warning: |
 | [vue/no-template-key](./no-template-key.md) | disallow `key` attribute on `<template>` |  | :three::two::warning: |
 | [vue/no-textarea-mustache](./no-textarea-mustache.md) | disallow mustaches in `<textarea>` |  | :three::two::warning: |
 | [vue/no-unused-components](./no-unused-components.md) | disallow registering components that are not used inside templates |  | :three::two::hammer: |
 | [vue/no-unused-vars](./no-unused-vars.md) | disallow unused variable definitions of v-for directives or scope attributes | :bulb: | :three::two::hammer: |
+| [vue/no-use-computed-property-like-method](./no-use-computed-property-like-method.md) | disallow use computed property like method |  | :three::two::warning: |
 | [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md) | disallow use v-if on the same element as v-for |  | :three::two::hammer: |
 | [vue/no-useless-template-attributes](./no-useless-template-attributes.md) | disallow useless attribute on `<template>` |  | :three::two::warning: |
 | [vue/no-v-for-template-key-on-child](./no-v-for-template-key-on-child.md) | disallow key of `<template v-for>` placed on child elements |  | :three::warning: |
 | [vue/no-v-for-template-key](./no-v-for-template-key.md) | disallow `key` attribute on `<template v-for>` |  | :two::warning: |
 | [vue/no-v-model-argument](./no-v-model-argument.md) | disallow adding an argument to `v-model` used in custom component |  | :two::warning: |
+| [vue/no-v-text-v-html-on-component](./no-v-text-v-html-on-component.md) | disallow v-text / v-html on component |  | :three::two::warning: |
 | [vue/no-watch-after-await](./no-watch-after-await.md) | disallow asynchronously registered `watch` |  | :three::hammer: |
+| [vue/prefer-import-from-vue](./prefer-import-from-vue.md) | enforce import from 'vue' instead of import from '@vue/*' | :wrench: | :three::hammer: |
 | [vue/require-component-is](./require-component-is.md) | require `v-bind:is` of `<component>` elements |  | :three::two::warning: |
 | [vue/require-prop-type-constructor](./require-prop-type-constructor.md) | require prop type to be a constructor | :wrench: | :three::two::hammer: |
 | [vue/require-render-return](./require-render-return.md) | enforce render function to always return value |  | :three::two::warning: |
@@ -98,10 +103,11 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
 | [vue/require-v-for-key](./require-v-for-key.md) | require `v-bind:key` with `v-for` directives |  | :three::two::warning: |
 | [vue/require-valid-default-prop](./require-valid-default-prop.md) | enforce props default values to be valid |  | :three::two::hammer: |
 | [vue/return-in-computed-property](./return-in-computed-property.md) | enforce that a return statement is present in computed property |  | :three::two::warning: |
-| [vue/return-in-emits-validator](./return-in-emits-validator.md) | enforce that a return statement is present in emits validator |  | :three::warning: |
+| [vue/return-in-emits-validator](./return-in-emits-validator.md) | enforce that a return statement is present in emits validator |  | :three::two::warning: |
 | [vue/use-v-on-exact](./use-v-on-exact.md) | enforce usage of `exact` modifier on `v-on` |  | :three::two::hammer: |
-| [vue/valid-define-emits](./valid-define-emits.md) | enforce valid `defineEmits` compiler macro |  | :three::warning: |
-| [vue/valid-define-props](./valid-define-props.md) | enforce valid `defineProps` compiler macro |  | :three::warning: |
+| [vue/valid-define-emits](./valid-define-emits.md) | enforce valid `defineEmits` compiler macro |  | :three::two::warning: |
+| [vue/valid-define-props](./valid-define-props.md) | enforce valid `defineProps` compiler macro |  | :three::two::warning: |
+| [vue/valid-model-definition](./valid-model-definition.md) | require valid keys in model option |  | :two::warning: |
 | [vue/valid-next-tick](./valid-next-tick.md) | enforce valid `nextTick` function calls | :wrench::bulb: | :three::two::warning: |
 | [vue/valid-template-root](./valid-template-root.md) | enforce valid template root |  | :three::two::warning: |
 | [vue/valid-v-bind-sync](./valid-v-bind-sync.md) | enforce valid `.sync` modifier on `v-bind` directives |  | :two::warning: |
@@ -216,14 +222,10 @@ For example:
 | [vue/next-tick-style](./next-tick-style.md) | enforce Promise or callback style in `nextTick` | :wrench: | :hammer: |
 | [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in `<template>` |  | :hammer: |
 | [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: | :hammer: |
-| [vue/no-child-content](./no-child-content.md) | disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text` | :bulb: | :warning: |
 | [vue/no-duplicate-attr-inheritance](./no-duplicate-attr-inheritance.md) | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` |  | :hammer: |
 | [vue/no-empty-component-block](./no-empty-component-block.md) | disallow the `<template>` `<script>` `<style>` block to be empty |  | :hammer: |
-| [vue/no-expose-after-await](./no-expose-after-await.md) | disallow asynchronously registered `expose` |  | :warning: |
-| [vue/no-invalid-model-keys](./no-invalid-model-keys.md) | require valid keys in model option |  | :warning: |
 | [vue/no-multiple-objects-in-class](./no-multiple-objects-in-class.md) | disallow to pass multiple objects into array to class |  | :hammer: |
 | [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | :bulb: | :hammer: |
-| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions |  | :hammer: |
 | [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block |  | :hammer: |
 | [vue/no-restricted-call-after-await](./no-restricted-call-after-await.md) | disallow asynchronously called restricted methods |  | :hammer: |
 | [vue/no-restricted-class](./no-restricted-class.md) | disallow specific classes in Vue components |  | :warning: |
@@ -241,13 +243,10 @@ For example:
 | [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: | :hammer: |
 | [vue/no-unused-properties](./no-unused-properties.md) | disallow unused properties |  | :hammer: |
 | [vue/no-unused-refs](./no-unused-refs.md) | disallow unused refs |  | :hammer: |
-| [vue/no-use-computed-property-like-method](./no-use-computed-property-like-method.md) | disallow use computed property like method |  | :warning: |
 | [vue/no-useless-mustaches](./no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: | :hammer: |
 | [vue/no-useless-v-bind](./no-useless-v-bind.md) | disallow unnecessary `v-bind` directives | :wrench: | :hammer: |
-| [vue/no-v-text-v-html-on-component](./no-v-text-v-html-on-component.md) | disallow v-text / v-html on component |  | :warning: |
 | [vue/no-v-text](./no-v-text.md) | disallow use of v-text |  | :hammer: |
 | [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: | :lipstick: |
-| [vue/prefer-import-from-vue](./prefer-import-from-vue.md) | enforce import from 'vue' instead of import from '@vue/*' | :wrench: | :hammer: |
 | [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md) | enforce `Boolean` comes first in component prop types | :bulb: | :warning: |
 | [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: | :hammer: |
 | [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: | :hammer: |
@@ -319,4 +318,6 @@ The following rules extend the rules provided by ESLint itself and apply them to
 | [vue/experimental-script-setup-vars](./experimental-script-setup-vars.md) | (no replacement) |
 | [vue/name-property-casing](./name-property-casing.md) | [vue/component-definition-name-casing](./component-definition-name-casing.md) |
 | [vue/no-confusing-v-for-v-if](./no-confusing-v-for-v-if.md) | [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md) |
+| [vue/no-invalid-model-keys](./no-invalid-model-keys.md) | [vue/valid-model-definition](./valid-model-definition.md) |
 | [vue/no-unregistered-components](./no-unregistered-components.md) | [vue/no-undef-components](./no-undef-components.md) |
+| [vue/script-setup-uses-vars](./script-setup-uses-vars.md) | (no replacement) |
diff --git a/docs/rules/custom-event-name-casing.md b/docs/rules/custom-event-name-casing.md
index a2c4d364..29afde05 100644
--- a/docs/rules/custom-event-name-casing.md
+++ b/docs/rules/custom-event-name-casing.md
@@ -13,7 +13,7 @@ Define a style for custom event name casing for consistency purposes.
 
 ## :book: Rule Details
 
-This rule aims to warn the custom event names other than the configured casing.
+This rule aims to warn the custom event names other than the configured casing. (Default is **camelCase**.)
 
 Vue 2 recommends using kebab-case for custom event names.
 
@@ -27,28 +27,28 @@ In Vue 3, using either camelCase or kebab-case for your custom event name does n
 
 See [Guide - Custom Events] for more details.
 
-This rule enforces kebab-case by default.
+This rule enforces camelCase by default.
 
 <eslint-code-block :rules="{'vue/custom-event-name-casing': ['error']}">
 
 ```vue
 <template>
   <!-- ✓ GOOD -->
-  <button @click="$emit('my-event')" />
+  <button @click="$emit('myEvent')" />
 
   <!-- ✗ BAD -->
-  <button @click="$emit('myEvent')" />
+  <button @click="$emit('my-event')" />
 </template>
 <script>
 export default {
   methods: {
     onClick () {
       /* ✓ GOOD */
-      this.$emit('my-event')
+      this.$emit('myEvent')
       this.$emit('update:myProp', myProp)
 
       /* ✗ BAD */
-      this.$emit('myEvent')
+      this.$emit('my-event')
     }
   }
 }
@@ -62,7 +62,7 @@ export default {
 ```json
 {
   "vue/custom-event-name-casing": ["error",
-    "kebab-case" | "camelCase",
+    "camelCase" | "kebab-case",
     {
       "ignores": []
     }
@@ -70,8 +70,8 @@ export default {
 }
 ```
 
-- `"kebab-case"` (default) ... Enforce custom event names to kebab-case.
-- `"camelCase"` ... Enforce custom event names to camelCase.
+- `"camelCase"` (default) ... Enforce custom event names to camelCase.
+- `"kebab-case"` ... Enforce custom event names to kebab-case.
 - `ignores` (`string[]`) ... The event names to ignore. Sets the event name to allow. For example, custom event names, Vue components event with special name, or Vue library component event name. You can set the regexp by writing it like `"/^name/"` or `click:row` or `fooBar`.
 
 ### `"kebab-case"`
@@ -132,18 +132,18 @@ export default {
 
 </eslint-code-block>
 
-### `"ignores": ["fooBar", "/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u"]`
+### `"ignores": ["foo-bar", "/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u"]`
 
-<eslint-code-block :rules="{'vue/custom-event-name-casing': ['error', { ignores: ['fooBar', '/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }]}">
+<eslint-code-block :rules="{'vue/custom-event-name-casing': ['error', 'camelCase', { ignores: ['foo-bar', '/^[a-z]+(?:-[a-z]+)*:[a-z]+(?:-[a-z]+)*$/u'] }]}">
 
 ```vue
 <template>
   <!-- ✓ GOOD -->
   <button @click="$emit('click:row')" />
-  <button @click="$emit('fooBar')" />
+  <button @click="$emit('foo-bar')" />
 
   <!-- ✗ BAD -->
-  <button @click="$emit('myEvent')" />
+  <button @click="$emit('my-event')" />
 </template>
 <script>
 export default {
@@ -151,10 +151,10 @@ export default {
     onClick () {
       /* ✓ GOOD */
       this.$emit('click:row')
-      this.$emit('fooBar')
+      this.$emit('foo-bar')
 
       /* ✗ BAD */
-      this.$emit('myEvent')
+      this.$emit('my-event')
     }
   }
 }
diff --git a/docs/rules/jsx-uses-vars.md b/docs/rules/jsx-uses-vars.md
index a21279b1..1c854a62 100644
--- a/docs/rules/jsx-uses-vars.md
+++ b/docs/rules/jsx-uses-vars.md
@@ -40,7 +40,6 @@ If you are not using JSX or if you do not use the `no-unused-vars` rule then you
 
 ## :couple: Related Rules
 
-- [vue/script-setup-uses-vars](./script-setup-uses-vars.md)
 - [no-unused-vars](https://eslint.org/docs/rules/no-unused-vars)
 
 ## :rocket: Version
diff --git a/docs/rules/no-child-content.md b/docs/rules/no-child-content.md
index 29523230..940ea183 100644
--- a/docs/rules/no-child-content.md
+++ b/docs/rules/no-child-content.md
@@ -9,6 +9,7 @@ since: v8.1.0
 
 > disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text`
 
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
 - :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
 
 ## :book: Rule Details
diff --git a/docs/rules/no-export-in-script-setup.md b/docs/rules/no-export-in-script-setup.md
index b059305b..c16899c8 100644
--- a/docs/rules/no-export-in-script-setup.md
+++ b/docs/rules/no-export-in-script-setup.md
@@ -9,7 +9,7 @@ since: v7.13.0
 
 > disallow `export` in `<script setup>`
 
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
 
 ## :book: Rule Details
 
diff --git a/docs/rules/no-expose-after-await.md b/docs/rules/no-expose-after-await.md
index 3bd436f4..54c58711 100644
--- a/docs/rules/no-expose-after-await.md
+++ b/docs/rules/no-expose-after-await.md
@@ -9,6 +9,8 @@ since: v8.1.0
 
 > disallow asynchronously registered `expose`
 
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+
 ## :book: Rule Details
 
 This rule reports usages of `expose()` after an `await` expression.  
diff --git a/docs/rules/no-invalid-model-keys.md b/docs/rules/no-invalid-model-keys.md
index 1a46e9a4..34f65d88 100644
--- a/docs/rules/no-invalid-model-keys.md
+++ b/docs/rules/no-invalid-model-keys.md
@@ -9,6 +9,8 @@ since: v7.9.0
 
 > require valid keys in model option
 
+- :warning: This rule was **deprecated** and replaced by [vue/valid-model-definition](valid-model-definition.md) rule.
+
 ## :book: Rule Details
 
 This rule is aimed at preventing invalid keys in model option.
diff --git a/docs/rules/no-ref-as-operand.md b/docs/rules/no-ref-as-operand.md
index 2610d462..df3549dc 100644
--- a/docs/rules/no-ref-as-operand.md
+++ b/docs/rules/no-ref-as-operand.md
@@ -9,7 +9,7 @@ since: v7.0.0
 
 > disallow use of value wrapped by `ref()` (Composition API) as an operand
 
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
 - :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
 
 ## :book: Rule Details
diff --git a/docs/rules/no-reserved-component-names.md b/docs/rules/no-reserved-component-names.md
index a0d9c5f2..94e761ea 100644
--- a/docs/rules/no-reserved-component-names.md
+++ b/docs/rules/no-reserved-component-names.md
@@ -9,6 +9,8 @@ since: v6.1.0
 
 > disallow the use of reserved names in component definitions
 
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+
 ## :book: Rule Details
 
 This rule prevents name collisions between Vue components and standard HTML elements and built-in components.
diff --git a/docs/rules/no-setup-props-destructure.md b/docs/rules/no-setup-props-destructure.md
index 91504a94..d618cf01 100644
--- a/docs/rules/no-setup-props-destructure.md
+++ b/docs/rules/no-setup-props-destructure.md
@@ -9,7 +9,7 @@ since: v7.0.0
 
 > disallow destructuring of `props` passed to `setup`
 
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
 
 ## :book: Rule Details
 
diff --git a/docs/rules/no-use-computed-property-like-method.md b/docs/rules/no-use-computed-property-like-method.md
index 27d6363d..6e267697 100644
--- a/docs/rules/no-use-computed-property-like-method.md
+++ b/docs/rules/no-use-computed-property-like-method.md
@@ -9,6 +9,8 @@ since: v7.15.0
 
 > disallow use computed property like method
 
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
+
 ## :book: Rule Details
 
 This rule disallows to use computed property like method.
diff --git a/docs/rules/no-v-text-v-html-on-component.md b/docs/rules/no-v-text-v-html-on-component.md
index 377217dc..420eefda 100644
--- a/docs/rules/no-v-text-v-html-on-component.md
+++ b/docs/rules/no-v-text-v-html-on-component.md
@@ -9,6 +9,8 @@ since: v8.4.0
 
 > disallow v-text / v-html on component
 
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/vue3-essential"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/recommended"` and `"plugin:vue/vue3-recommended"`.
+
 ## :book: Rule Details
 
 This rule disallows the use of v-text / v-html on component.
diff --git a/docs/rules/prefer-import-from-vue.md b/docs/rules/prefer-import-from-vue.md
index 3190beac..f1cab9f0 100644
--- a/docs/rules/prefer-import-from-vue.md
+++ b/docs/rules/prefer-import-from-vue.md
@@ -9,6 +9,7 @@ since: v8.5.0
 
 > enforce import from 'vue' instead of import from '@vue/*'
 
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
 - :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
 
 ## :book: Rule Details
diff --git a/docs/rules/return-in-emits-validator.md b/docs/rules/return-in-emits-validator.md
index f952d0a0..08b8b340 100644
--- a/docs/rules/return-in-emits-validator.md
+++ b/docs/rules/return-in-emits-validator.md
@@ -9,7 +9,7 @@ since: v7.0.0
 
 > enforce that a return statement is present in emits validator
 
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
 
 ## :book: Rule Details
 
diff --git a/docs/rules/script-setup-uses-vars.md b/docs/rules/script-setup-uses-vars.md
index 42de582b..f4d68926 100644
--- a/docs/rules/script-setup-uses-vars.md
+++ b/docs/rules/script-setup-uses-vars.md
@@ -9,7 +9,13 @@ since: v7.13.0
 
 > prevent `<script setup>` variables used in `<template>` to be marked as unused
 
-- :gear: This rule is included in all of `"plugin:vue/base"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-essential"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/recommended"` and `"plugin:vue/vue3-recommended"`.
+- :warning: This rule was **deprecated**.
+
+::: tip
+
+This rule is not needed when using `vue-eslint-parser` v9.0.0 or later.
+
+:::
 
 ESLint `no-unused-vars` rule does not detect variables in `<script setup>` used in `<template>`.
 This rule will find variables in `<script setup>` used in `<template>` and mark them as used.
@@ -47,7 +53,11 @@ After turning on, `Foo` is being marked as used and `no-unused-vars` rule doesn'
 
 ## :mute: When Not To Use It
 
-If you are not using `<script setup>` or if you do not use the `no-unused-vars` rule then you can disable this rule.
+You can disable this rule in any of the following cases:
+
+- You are using `vue-eslint-parser` v9.0.0 or later.
+- You are not using `<script setup>`.
+- You do not use the `no-unused-vars` rule.
 
 ## :couple: Related Rules
 
diff --git a/docs/rules/valid-define-emits.md b/docs/rules/valid-define-emits.md
index 7e00bb14..5718794c 100644
--- a/docs/rules/valid-define-emits.md
+++ b/docs/rules/valid-define-emits.md
@@ -9,7 +9,7 @@ since: v7.13.0
 
 > enforce valid `defineEmits` compiler macro
 
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
 
 This rule checks whether `defineEmits` compiler macro is valid.
 
diff --git a/docs/rules/valid-define-props.md b/docs/rules/valid-define-props.md
index b2c47cb5..bd7d5f75 100644
--- a/docs/rules/valid-define-props.md
+++ b/docs/rules/valid-define-props.md
@@ -9,7 +9,7 @@ since: v7.13.0
 
 > enforce valid `defineProps` compiler macro
 
-- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
 
 This rule checks whether `defineProps` compiler macro is valid.
 
diff --git a/docs/rules/valid-model-definition.md b/docs/rules/valid-model-definition.md
new file mode 100644
index 00000000..66fe8f03
--- /dev/null
+++ b/docs/rules/valid-model-definition.md
@@ -0,0 +1,116 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/valid-model-definition
+description: require valid keys in model option
+---
+# vue/valid-model-definition
+
+> require valid keys in model option
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+
+## :book: Rule Details
+
+This rule is aimed at preventing invalid keys in model option.
+
+<eslint-code-block :rules="{'vue/valid-model-definition': ['error']}">
+
+```vue
+<script>
+/* ✓ GOOD */
+export default {
+  model: {
+    prop: 'list',
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/valid-model-definition': ['error']}">
+
+```vue
+<script>
+/* ✓ GOOD */
+export default {
+  model: {
+    event: 'update'
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/valid-model-definition': ['error']}">
+
+```vue
+<script>
+/* ✓ GOOD */
+export default {
+  model: {
+    prop: 'list',
+    event: 'update'
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/valid-model-definition': ['error']}">
+
+```vue
+<script>
+/* ✗ BAD */
+export default {
+  model: {
+    prop: 'list',
+    events: 'update'
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/valid-model-definition': ['error']}">
+
+```vue
+<script>
+/* ✗ BAD */
+export default {
+  model: {
+    props: 'list',
+    events: 'update'
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/valid-model-definition': ['error']}">
+
+```vue
+<script>
+/* ✗ BAD */
+export default {
+  model: {
+    name: 'checked',
+    props: 'list',
+    event: 'update'
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-model-definition.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-model-definition.js)
diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md
index b083a6bd..8129cad2 100644
--- a/docs/user-guide/README.md
+++ b/docs/user-guide/README.md
@@ -23,7 +23,7 @@ yarn add -D eslint eslint-plugin-vue
 ::: tip Requirements
 
 - ESLint v6.2.0 and above
-- Node.js v12.22.x, v14.17.x, v16.x and above
+- Node.js v14.17.x, v16.x and above
 
 :::
 
@@ -342,36 +342,15 @@ See also: "[Visual Studio Code](#editor-integrations)" section and [Vetur - Lint
 
 #### The variables used in the `<template>` are warned by `no-unused-vars` rule
 
-You must use [vue/script-setup-uses-vars](../rules/script-setup-uses-vars.md) rule.  
-In your configuration, use the rule set provided by `eslint-plugin-vue` or enable it rule.
+You need to use [vue-eslint-parser] v9.0.0 or later.
 
-Example **.eslintrc.js**:
-
-```js
-module.exports = {
-  // Use the rule set.
-  extends: ['plugin:vue/base'],
-  rules: {
-    // Enable vue/script-setup-uses-vars rule
-    'vue/script-setup-uses-vars': 'error',
-  }
-}
-```
+Previously you had to use the [vue/script-setup-uses-vars](../rules/script-setup-uses-vars.md) rule, this is no longer needed.
 
 #### Compiler macros such as `defineProps` and `defineEmits` generate `no-undef` warnings
 
-You need to enable the compiler macros environment in your ESLint configuration file.
-If you don't want to expose these variables globally, you can use `/* global defineProps, defineEmits */` instead.
+You need to use [vue-eslint-parser] v9.0.0 or later.
 
-Example **.eslintrc.js**:
-
-```js
-module.exports = {
-  env: {
-    'vue/setup-compiler-macros': true
-  }
-}
-```
+Previously you had to use the `vue/setup-compiler-macros` environment, this is no longer needed.
 
 #### Parsing error with Top Level `await`
 
@@ -411,3 +390,5 @@ module.exports = {
 
 Try searching for existing issues.
 If it does not exist, you should open a new issue and share your repository to reproduce the issue.
+
+[vue-eslint-parser]: https://github.com/vuejs/vue-eslint-parser
diff --git a/eslint-internal-rules/no-invalid-meta-docs-categories.js b/eslint-internal-rules/no-invalid-meta-docs-categories.js
index b4812548..c95691c3 100644
--- a/eslint-internal-rules/no-invalid-meta-docs-categories.js
+++ b/eslint-internal-rules/no-invalid-meta-docs-categories.js
@@ -18,10 +18,8 @@
  */
 function getPropertyFromObject(propertyName, node) {
   if (node && node.type === 'ObjectExpression') {
-    const properties = node.properties
-
-    for (const property of properties) {
-      if (property.key.name === propertyName) {
+    for (const property of node.properties) {
+      if (property.type === 'Property' && property.key.name === propertyName) {
         return property
       }
     }
diff --git a/eslint-internal-rules/no-invalid-meta.js b/eslint-internal-rules/no-invalid-meta.js
index f1877ea4..45057c9b 100644
--- a/eslint-internal-rules/no-invalid-meta.js
+++ b/eslint-internal-rules/no-invalid-meta.js
@@ -18,10 +18,8 @@
  */
 function getPropertyFromObject(propertyName, node) {
   if (node && node.type === 'ObjectExpression') {
-    const properties = node.properties
-
-    for (const property of properties) {
-      if (property.key.name === propertyName) {
+    for (const property of node.properties) {
+      if (property.type === 'Property' && property.key.name === propertyName) {
         return property
       }
     }
diff --git a/lib/configs/base.js b/lib/configs/base.js
index 14599197..3724461e 100644
--- a/lib/configs/base.js
+++ b/lib/configs/base.js
@@ -16,7 +16,6 @@ module.exports = {
   plugins: ['vue'],
   rules: {
     'vue/comment-directive': 'error',
-    'vue/jsx-uses-vars': 'error',
-    'vue/script-setup-uses-vars': 'error'
+    'vue/jsx-uses-vars': 'error'
   }
 }
diff --git a/lib/configs/essential.js b/lib/configs/essential.js
index 970473a4..33cfe64e 100644
--- a/lib/configs/essential.js
+++ b/lib/configs/essential.js
@@ -9,14 +9,18 @@ module.exports = {
     'vue/multi-word-component-names': 'error',
     'vue/no-arrow-functions-in-watch': 'error',
     'vue/no-async-in-computed-properties': 'error',
+    'vue/no-child-content': 'error',
     'vue/no-computed-properties-in-data': 'error',
     'vue/no-custom-modifiers-on-v-model': 'error',
     'vue/no-dupe-keys': 'error',
     'vue/no-dupe-v-else-if': 'error',
     'vue/no-duplicate-attributes': 'error',
+    'vue/no-export-in-script-setup': 'error',
     'vue/no-multiple-template-root': 'error',
     'vue/no-mutating-props': 'error',
     'vue/no-parsing-error': 'error',
+    'vue/no-ref-as-operand': 'error',
+    'vue/no-reserved-component-names': 'error',
     'vue/no-reserved-keys': 'error',
     'vue/no-reserved-props': [
       'error',
@@ -24,23 +28,30 @@ module.exports = {
         vueVersion: 2
       }
     ],
+    'vue/no-setup-props-destructure': 'error',
     'vue/no-shared-component-data': 'error',
     'vue/no-side-effects-in-computed-properties': 'error',
     'vue/no-template-key': 'error',
     'vue/no-textarea-mustache': 'error',
     'vue/no-unused-components': 'error',
     'vue/no-unused-vars': 'error',
+    'vue/no-use-computed-property-like-method': 'error',
     'vue/no-use-v-if-with-v-for': 'error',
     'vue/no-useless-template-attributes': 'error',
     'vue/no-v-for-template-key': 'error',
     'vue/no-v-model-argument': 'error',
+    'vue/no-v-text-v-html-on-component': 'error',
     'vue/require-component-is': 'error',
     'vue/require-prop-type-constructor': 'error',
     'vue/require-render-return': 'error',
     'vue/require-v-for-key': 'error',
     'vue/require-valid-default-prop': 'error',
     'vue/return-in-computed-property': 'error',
+    'vue/return-in-emits-validator': 'error',
     'vue/use-v-on-exact': 'error',
+    'vue/valid-define-emits': 'error',
+    'vue/valid-define-props': 'error',
+    'vue/valid-model-definition': 'error',
     'vue/valid-next-tick': 'error',
     'vue/valid-template-root': 'error',
     'vue/valid-v-bind-sync': 'error',
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index 15577464..1a150733 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -9,6 +9,7 @@ module.exports = {
     'vue/multi-word-component-names': 'error',
     'vue/no-arrow-functions-in-watch': 'error',
     'vue/no-async-in-computed-properties': 'error',
+    'vue/no-child-content': 'error',
     'vue/no-computed-properties-in-data': 'error',
     'vue/no-deprecated-data-object-declaration': 'error',
     'vue/no-deprecated-destroyed-lifecycle': 'error',
@@ -33,10 +34,12 @@ module.exports = {
     'vue/no-dupe-v-else-if': 'error',
     'vue/no-duplicate-attributes': 'error',
     'vue/no-export-in-script-setup': 'error',
+    'vue/no-expose-after-await': 'error',
     'vue/no-lifecycle-after-await': 'error',
     'vue/no-mutating-props': 'error',
     'vue/no-parsing-error': 'error',
     'vue/no-ref-as-operand': 'error',
+    'vue/no-reserved-component-names': 'error',
     'vue/no-reserved-keys': 'error',
     'vue/no-reserved-props': 'error',
     'vue/no-setup-props-destructure': 'error',
@@ -46,10 +49,13 @@ module.exports = {
     'vue/no-textarea-mustache': 'error',
     'vue/no-unused-components': 'error',
     'vue/no-unused-vars': 'error',
+    'vue/no-use-computed-property-like-method': 'error',
     'vue/no-use-v-if-with-v-for': 'error',
     'vue/no-useless-template-attributes': 'error',
     'vue/no-v-for-template-key-on-child': 'error',
+    'vue/no-v-text-v-html-on-component': 'error',
     'vue/no-watch-after-await': 'error',
+    'vue/prefer-import-from-vue': 'error',
     'vue/require-component-is': 'error',
     'vue/require-prop-type-constructor': 'error',
     'vue/require-render-return': 'error',
diff --git a/lib/index.js b/lib/index.js
index 39f91b51..1c278c63 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -202,6 +202,7 @@ module.exports = {
     'v-slot-style': require('./rules/v-slot-style'),
     'valid-define-emits': require('./rules/valid-define-emits'),
     'valid-define-props': require('./rules/valid-define-props'),
+    'valid-model-definition': require('./rules/valid-model-definition'),
     'valid-next-tick': require('./rules/valid-next-tick'),
     'valid-template-root': require('./rules/valid-template-root'),
     'valid-v-bind-sync': require('./rules/valid-v-bind-sync'),
@@ -236,6 +237,7 @@ module.exports = {
     '.vue': require('./processor')
   },
   environments: {
+    // Deprecated
     'setup-compiler-macros': {
       globals: {
         defineProps: 'readonly',
diff --git a/lib/rules/custom-event-name-casing.js b/lib/rules/custom-event-name-casing.js
index f59fa9c2..5db15a87 100644
--- a/lib/rules/custom-event-name-casing.js
+++ b/lib/rules/custom-event-name-casing.js
@@ -22,7 +22,7 @@ const { toRegExp } = require('../utils/regexp')
 // ------------------------------------------------------------------------------
 
 const ALLOWED_CASE_OPTIONS = ['kebab-case', 'camelCase']
-const DEFAULT_CASE = 'kebab-case'
+const DEFAULT_CASE = 'camelCase'
 
 /**
  * Get the name param node from the given CallExpression
diff --git a/lib/rules/no-child-content.js b/lib/rules/no-child-content.js
index 138a425c..1eb7fbdf 100644
--- a/lib/rules/no-child-content.js
+++ b/lib/rules/no-child-content.js
@@ -79,7 +79,7 @@ module.exports = {
     docs: {
       description:
         "disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text`",
-      categories: undefined,
+      categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/no-child-content.html'
     },
     fixable: null,
diff --git a/lib/rules/no-export-in-script-setup.js b/lib/rules/no-export-in-script-setup.js
index fe825fdd..0105d52f 100644
--- a/lib/rules/no-export-in-script-setup.js
+++ b/lib/rules/no-export-in-script-setup.js
@@ -17,7 +17,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow `export` in `<script setup>`',
-      categories: ['vue3-essential'],
+      categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/no-export-in-script-setup.html'
     },
     fixable: null,
diff --git a/lib/rules/no-expose-after-await.js b/lib/rules/no-expose-after-await.js
index e1e3104d..9d753ab8 100644
--- a/lib/rules/no-expose-after-await.js
+++ b/lib/rules/no-expose-after-await.js
@@ -40,8 +40,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow asynchronously registered `expose`',
-      categories: undefined,
-      // categories: ['vue3-essential'], TODO Change with the major version
+      categories: ['vue3-essential'],
       url: 'https://eslint.vuejs.org/rules/no-expose-after-await.html'
     },
     fixable: null,
diff --git a/lib/rules/no-invalid-model-keys.js b/lib/rules/no-invalid-model-keys.js
index f3a08eda..1695e6fa 100644
--- a/lib/rules/no-invalid-model-keys.js
+++ b/lib/rules/no-invalid-model-keys.js
@@ -1,57 +1,22 @@
-/**
- * @fileoverview Requires valid keys in model option.
- * @author Alex Sokolov
- */
 'use strict'
 
-const utils = require('../utils')
-
-// ------------------------------------------------------------------------------
-// Rule Definition
-// ------------------------------------------------------------------------------
-const VALID_MODEL_KEYS = new Set(['prop', 'event'])
+const baseRule = require('./valid-model-definition')
 
 module.exports = {
   meta: {
-    type: 'problem',
+    ...baseRule.meta,
+    type: baseRule.meta.type,
     docs: {
-      description: 'require valid keys in model option',
+      description: baseRule.meta.docs.description,
       categories: undefined,
       url: 'https://eslint.vuejs.org/rules/no-invalid-model-keys.html'
     },
-    fixable: null,
+    deprecated: true,
+    replacedBy: ['valid-model-definition'],
     schema: []
   },
   /** @param {RuleContext} context */
   create(context) {
-    // ----------------------------------------------------------------------
-    // Public
-    // ----------------------------------------------------------------------
-
-    return utils.executeOnVue(context, (obj) => {
-      const modelProperty = utils.findProperty(obj, 'model')
-      if (!modelProperty || modelProperty.value.type !== 'ObjectExpression') {
-        return
-      }
-
-      for (const p of modelProperty.value.properties) {
-        if (p.type !== 'Property') {
-          continue
-        }
-        const name = utils.getStaticPropertyName(p)
-        if (!name) {
-          continue
-        }
-        if (!VALID_MODEL_KEYS.has(name)) {
-          context.report({
-            node: p,
-            message: "Invalid key '{{name}}' in model option.",
-            data: {
-              name
-            }
-          })
-        }
-      }
-    })
+    return baseRule.create(context)
   }
 }
diff --git a/lib/rules/no-ref-as-operand.js b/lib/rules/no-ref-as-operand.js
index 465ec50b..4f536daf 100644
--- a/lib/rules/no-ref-as-operand.js
+++ b/lib/rules/no-ref-as-operand.js
@@ -12,7 +12,7 @@ module.exports = {
     docs: {
       description:
         'disallow use of value wrapped by `ref()` (Composition API) as an operand',
-      categories: ['vue3-essential'],
+      categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/no-ref-as-operand.html'
     },
     fixable: 'code',
diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js
index 68d2a2b7..81b03530 100644
--- a/lib/rules/no-reserved-component-names.js
+++ b/lib/rules/no-reserved-component-names.js
@@ -81,7 +81,7 @@ module.exports = {
     docs: {
       description:
         'disallow the use of reserved names in component definitions',
-      categories: undefined,
+      categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/no-reserved-component-names.html'
     },
     fixable: null,
diff --git a/lib/rules/no-setup-props-destructure.js b/lib/rules/no-setup-props-destructure.js
index ee3699c5..4c8f6109 100644
--- a/lib/rules/no-setup-props-destructure.js
+++ b/lib/rules/no-setup-props-destructure.js
@@ -11,7 +11,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'disallow destructuring of `props` passed to `setup`',
-      categories: ['vue3-essential'],
+      categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/no-setup-props-destructure.html'
     },
     fixable: null,
diff --git a/lib/rules/no-use-computed-property-like-method.js b/lib/rules/no-use-computed-property-like-method.js
index e865f044..957f2b6a 100644
--- a/lib/rules/no-use-computed-property-like-method.js
+++ b/lib/rules/no-use-computed-property-like-method.js
@@ -499,7 +499,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow use computed property like method',
-      categories: undefined,
+      categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/no-use-computed-property-like-method.html'
     },
     fixable: null,
diff --git a/lib/rules/no-v-text-v-html-on-component.js b/lib/rules/no-v-text-v-html-on-component.js
index 4a208627..d6af976b 100644
--- a/lib/rules/no-v-text-v-html-on-component.js
+++ b/lib/rules/no-v-text-v-html-on-component.js
@@ -19,9 +19,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow v-text / v-html on component',
-      // TODO We will change it in the next major version.
-      // categories: ['essential', 'vue3-essential'],
-      categories: undefined,
+      categories: ['essential', 'vue3-essential'],
       url: 'https://eslint.vuejs.org/rules/no-v-text-v-html-on-component.html'
     },
     fixable: null,
diff --git a/lib/rules/prefer-import-from-vue.js b/lib/rules/prefer-import-from-vue.js
index ef154854..2b3fc5b8 100644
--- a/lib/rules/prefer-import-from-vue.js
+++ b/lib/rules/prefer-import-from-vue.js
@@ -54,9 +54,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: "enforce import from 'vue' instead of import from '@vue/*'",
-      // TODO We will change it in the next major version.
-      // categories: ['vue3-essential'],
-      categories: undefined,
+      categories: ['vue3-essential'],
       url: 'https://eslint.vuejs.org/rules/prefer-import-from-vue.html'
     },
     fixable: 'code',
diff --git a/lib/rules/return-in-emits-validator.js b/lib/rules/return-in-emits-validator.js
index a3c2e257..91c9e667 100644
--- a/lib/rules/return-in-emits-validator.js
+++ b/lib/rules/return-in-emits-validator.js
@@ -41,7 +41,7 @@ module.exports = {
     docs: {
       description:
         'enforce that a return statement is present in emits validator',
-      categories: ['vue3-essential'],
+      categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/return-in-emits-validator.html'
     },
     fixable: null, // or "code" or "whitespace"
diff --git a/lib/rules/script-setup-uses-vars.js b/lib/rules/script-setup-uses-vars.js
index 3cb5eef3..40b8e54a 100644
--- a/lib/rules/script-setup-uses-vars.js
+++ b/lib/rules/script-setup-uses-vars.js
@@ -32,9 +32,10 @@ module.exports = {
     docs: {
       description:
         'prevent `<script setup>` variables used in `<template>` to be marked as unused', // eslint-disable-line eslint-plugin/require-meta-docs-description
-      categories: ['base'],
+      categories: undefined,
       url: 'https://eslint.vuejs.org/rules/script-setup-uses-vars.html'
     },
+    deprecated: true,
     schema: []
   },
   /**
diff --git a/lib/rules/valid-define-emits.js b/lib/rules/valid-define-emits.js
index dc23ac3d..f225c7af 100644
--- a/lib/rules/valid-define-emits.js
+++ b/lib/rules/valid-define-emits.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `defineEmits` compiler macro',
-      categories: ['vue3-essential'],
+      categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/valid-define-emits.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-define-props.js b/lib/rules/valid-define-props.js
index 8521bd51..60101f49 100644
--- a/lib/rules/valid-define-props.js
+++ b/lib/rules/valid-define-props.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `defineProps` compiler macro',
-      categories: ['vue3-essential'],
+      categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/valid-define-props.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-model-definition.js b/lib/rules/valid-model-definition.js
new file mode 100644
index 00000000..5b7435f4
--- /dev/null
+++ b/lib/rules/valid-model-definition.js
@@ -0,0 +1,57 @@
+/**
+ * @fileoverview Requires valid keys in model option.
+ * @author Alex Sokolov
+ */
+'use strict'
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+const VALID_MODEL_KEYS = new Set(['prop', 'event'])
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'require valid keys in model option',
+      categories: ['essential'],
+      url: 'https://eslint.vuejs.org/rules/valid-model-definition.html'
+    },
+    fixable: null,
+    schema: []
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    // ----------------------------------------------------------------------
+    // Public
+    // ----------------------------------------------------------------------
+
+    return utils.executeOnVue(context, (obj) => {
+      const modelProperty = utils.findProperty(obj, 'model')
+      if (!modelProperty || modelProperty.value.type !== 'ObjectExpression') {
+        return
+      }
+
+      for (const p of modelProperty.value.properties) {
+        if (p.type !== 'Property') {
+          continue
+        }
+        const name = utils.getStaticPropertyName(p)
+        if (!name) {
+          continue
+        }
+        if (!VALID_MODEL_KEYS.has(name)) {
+          context.report({
+            node: p,
+            message: "Invalid key '{{name}}' in model option.",
+            data: {
+              name
+            }
+          })
+        }
+      }
+    })
+  }
+}
diff --git a/lib/utils/index.js b/lib/utils/index.js
index fba712a1..63a838bc 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -895,24 +895,12 @@ module.exports = {
    * @return {(ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp)[]} Array of component props
    */
   getComponentPropsFromOptions,
-  // TODO Since `utils` is used in some popular packages, we will remove it in the major version.
-  /**
-   * For backward compatibility.
-   * @deprecated Use getComponentPropsFromOptions instead.
-   */
-  getComponentProps: getComponentPropsFromOptions,
   /**
    * Get all emits by looking at all component's properties
    * @param {ObjectExpression} componentObject Object with component definition
    * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentUnknownEmit)[]} Array of component emits
    */
   getComponentEmitsFromOptions,
-  // TODO Since `utils` is used in some popular packages, we will remove it in the major version.
-  /**
-   * For backward compatibility.
-   * @deprecated Use getComponentEmitsFromOptions instead.
-   */
-  getComponentEmits: getComponentEmitsFromOptions,
 
   /**
    * Get all computed properties by looking at all component's properties
diff --git a/package.json b/package.json
index 093aab99..632cc0bb 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
     "url": "https://github.com/vuejs/eslint-plugin-vue/issues"
   },
   "engines": {
-    "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+    "node": "^14.17.0 || >=16.0.0"
   },
   "peerDependencies": {
     "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
@@ -59,7 +59,7 @@
     "nth-check": "^2.0.1",
     "postcss-selector-parser": "^6.0.9",
     "semver": "^7.3.5",
-    "vue-eslint-parser": "^8.0.1"
+    "vue-eslint-parser": "^9.0.1"
   },
   "devDependencies": {
     "@types/eslint": "^7.28.1",
diff --git a/tools/update-lib-index.js b/tools/update-lib-index.js
index b9a5dcbd..f57ae1e5 100644
--- a/tools/update-lib-index.js
+++ b/tools/update-lib-index.js
@@ -39,6 +39,8 @@ module.exports = {
     '.vue': require('./processor')
   },
   environments: {
+    // TODO Remove in the next major version
+    /** @deprecated */
     'setup-compiler-macros': {
       globals: {
         defineProps: 'readonly',


Test patch:
diff --git a/tests/lib/rules/custom-event-name-casing.js b/tests/lib/rules/custom-event-name-casing.js
index 00b76e4e..8ad20377 100644
--- a/tests/lib/rules/custom-event-name-casing.js
+++ b/tests/lib/rules/custom-event-name-casing.js
@@ -41,7 +41,8 @@ tester.run('custom-event-name-casing', rule, {
         }
       }
       </script>
-      `
+      `,
+      options: ['kebab-case']
     },
     {
       filename: 'test.vue',
@@ -67,7 +68,8 @@ tester.run('custom-event-name-casing', rule, {
         }
       }
       </script>
-      `
+      `,
+      options: ['kebab-case']
     },
     {
       filename: 'test.vue',
@@ -92,7 +94,8 @@ tester.run('custom-event-name-casing', rule, {
         }
       }
       </script>
-      `
+      `,
+      options: ['kebab-case']
     },
     {
       filename: 'test.vue',
@@ -117,7 +120,8 @@ tester.run('custom-event-name-casing', rule, {
         }
       }
       </script>
-      `
+      `,
+      options: ['kebab-case']
     },
     {
       filename: 'test.vue',
@@ -149,7 +153,8 @@ tester.run('custom-event-name-casing', rule, {
         },
       }
       </script>
-      `
+      `,
+      options: ['kebab-case']
     },
     {
       filename: 'test.vue',
@@ -165,7 +170,8 @@ tester.run('custom-event-name-casing', rule, {
         },
       }
       </script>
-      `
+      `,
+      options: ['kebab-case']
     },
     {
       filename: 'test.vue',
@@ -269,6 +275,60 @@ tester.run('custom-event-name-casing', rule, {
       </script>
       `,
       options: ['camelCase']
+    },
+    // Default
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('fooBar')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('barBaz')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('bazQux')
+          }
+        }
+      }
+      </script>
+      `
+    },
+
+    // kebab-case
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('foo-bar')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('bar-baz')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('baz-qux')
+          }
+        }
+      }
+      </script>
+      `,
+      options: ['kebab-case']
     }
   ],
   invalid: [
@@ -296,6 +356,7 @@ tester.run('custom-event-name-casing', rule, {
       }
       </script>
       `,
+      options: ['kebab-case'],
       errors: [
         {
           message: "Custom event name 'fooBar' must be kebab-case.",
@@ -344,6 +405,7 @@ tester.run('custom-event-name-casing', rule, {
       }
       </script>
       `,
+      options: ['kebab-case'],
       errors: [
         "Custom event name 'fooBar' must be kebab-case.",
         "Custom event name 'barBaz' must be kebab-case.",
@@ -374,6 +436,7 @@ tester.run('custom-event-name-casing', rule, {
       }
       </script>
       `,
+      options: ['kebab-case'],
       errors: [
         "Custom event name 'fooBar' must be kebab-case.",
         "Custom event name 'barBaz' must be kebab-case.",
@@ -448,6 +511,7 @@ tester.run('custom-event-name-casing', rule, {
         "Custom event name 'click/row' must be kebab-case."
       ]
     },
+    // camelCase
     {
       filename: 'test.vue',
       code: `
@@ -479,6 +543,69 @@ tester.run('custom-event-name-casing', rule, {
         "Custom event name 'baz-qux' must be camelCase."
       ]
     },
+    // Default
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('foo-bar')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('bar-baz')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('baz-qux')
+          }
+        }
+      }
+      </script>
+      `,
+      errors: [
+        "Custom event name 'foo-bar' must be camelCase.",
+        "Custom event name 'bar-baz' must be camelCase.",
+        "Custom event name 'baz-qux' must be camelCase."
+      ]
+    },
+    // kebab-case
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <input
+          @click="$emit('fooBar')">
+      </template>
+      <script>
+      export default {
+        setup(props, context) {
+          return {
+            onInput(value) {
+              context.emit('barBaz')
+            }
+          }
+        },
+        methods: {
+          onClick() {
+            this.$emit('bazQux')
+          }
+        }
+      }
+      </script>
+      `,
+      options: ['kebab-case'],
+      errors: [
+        "Custom event name 'fooBar' must be kebab-case.",
+        "Custom event name 'barBaz' must be kebab-case.",
+        "Custom event name 'bazQux' must be kebab-case."
+      ]
+    },
     {
       filename: 'test.vue',
       code: `
@@ -490,8 +617,8 @@ tester.run('custom-event-name-casing', rule, {
       `,
       errors: [
         {
-          message: "Custom event name 'fooBar' must be kebab-case.",
-          line: 4
+          message: "Custom event name 'foo-bar' must be camelCase.",
+          line: 5
         }
       ]
     }
diff --git a/tests/lib/rules/valid-model-definition.js b/tests/lib/rules/valid-model-definition.js
new file mode 100644
index 00000000..d9d8263a
--- /dev/null
+++ b/tests/lib/rules/valid-model-definition.js
@@ -0,0 +1,151 @@
+/**
+ * @fileoverview Prevents invalid keys in model option.
+ * @author Alex Sokolov
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/valid-model-definition')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+  parserOptions: {
+    ecmaVersion: 2018,
+    sourceType: 'module'
+  }
+})
+ruleTester.run('valid-model-definition', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            prop: 'list'
+          }
+        }
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            event: 'update'
+          }
+        }
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            prop: 'list',
+            event: 'update'
+          }
+        }
+      `
+    }
+  ],
+
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            props: 'list'
+          }
+        }
+      `,
+      errors: ["Invalid key 'props' in model option."]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            events: 'update'
+          }
+        }
+      `,
+      errors: ["Invalid key 'events' in model option."]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            props: 'list',
+            event: 'update'
+          }
+        }
+      `,
+      errors: ["Invalid key 'props' in model option."]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            prop: 'list',
+            events: 'update'
+          }
+        }
+      `,
+      errors: ["Invalid key 'events' in model option."]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            props: 'list',
+            events: 'update'
+          }
+        }
+      `,
+      errors: [
+        "Invalid key 'props' in model option.",
+        "Invalid key 'events' in model option."
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            prop: 'checked',
+            props: 'list',
+            event: 'update'
+          }
+        }
+      `,
+      errors: ["Invalid key 'props' in model option."]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        export default {
+          model: {
+            name: 'checked',
+            props: 'list',
+            event: 'update'
+          }
+        }
+      `,
+      errors: [
+        "Invalid key 'name' in model option.",
+        "Invalid key 'props' in model option."
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/script-setup-vars.js b/tests/lib/script-setup-vars.js
new file mode 100644
index 00000000..90921984
--- /dev/null
+++ b/tests/lib/script-setup-vars.js
@@ -0,0 +1,384 @@
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const eslint = require('eslint')
+const rules = new eslint.Linter().getRules()
+const ruleNoUnusedVars = rules.get('no-unused-vars')
+const ruleNoUndef = rules.get('no-undef')
+
+const RuleTester = eslint.RuleTester
+const ruleTester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: {
+    ecmaVersion: 6,
+    sourceType: 'module'
+  }
+})
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+describe('vue-eslint-parser should properly mark the variables used in the template', () => {
+  ruleTester.run('no-unused-vars', ruleNoUnusedVars, {
+    valid: [
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          // imported components are also directly usable in template
+          import Foo from './Foo.vue'
+          import { ref } from 'vue'
+
+          // write Composition API code just like in a normal setup()
+          // but no need to manually return everything
+          const count = ref(0)
+          const inc = () => {
+            count.value++
+          }
+        </script>
+
+        <template>
+          <Foo :count="count" @click="inc" />
+        </template>
+        `
+      },
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          const msg = 'Hello!'
+        </script>
+
+        <template>
+          <div>{{ msg }}</div>
+        </template>
+        `
+      },
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          import Foo from './Foo.vue'
+          import MyComponent from './MyComponent.vue'
+        </script>
+
+        <template>
+          <Foo />
+          <!-- kebab-case also works -->
+          <my-component />
+        </template>
+        `
+      },
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          import Foo from './Foo.vue'
+          import Bar from './Bar.vue'
+        </script>
+
+        <template>
+          <component :is="Foo" />
+          <component :is="someCondition ? Foo : Bar" />
+        </template>
+        `
+      },
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          import { directive as vClickOutside } from 'v-click-outside'
+        </script>
+
+        <template>
+          <div v-click-outside />
+        </template>
+        `
+      },
+
+      // Resolve component name
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          import FooPascalCase from './component.vue'
+          import BarPascalCase from './component.vue'
+          import BazPascalCase from './component.vue'
+        </script>
+
+        <template>
+          <FooPascalCase />
+          <bar-pascal-case />
+          <bazPascalCase />
+        </template>
+        `
+      },
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          import fooCamelCase from './component.vue'
+          import barCamelCase from './component.vue'
+        </script>
+
+        <template>
+          <fooCamelCase />
+          <bar-camel-case />
+        </template>
+        `
+      },
+
+      // TopLevel await
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          const post = await fetch(\`/api/post/1\`).then((r) => r.json())
+        </script>
+
+        <template>
+          {{post}}
+        </template>
+        `,
+        parserOptions: {
+          ecmaVersion: 2022,
+          sourceType: 'module'
+        }
+      },
+
+      // ref
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          import {ref} from 'vue'
+          const v = ref(null)
+        </script>
+
+        <template>
+          <div ref="v"/>
+        </template>
+        `
+      },
+
+      //style vars
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          const color = 'red'
+          const font = { size: '2em' }
+        </script>
+
+        <style>
+          * {
+            color: v-bind(color);
+            font-size: v-bind('font.size');
+          }
+        </style>
+        `
+      },
+      // ns
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+        import * as Form from './form-components'
+        </script>
+
+        <template>
+          <Form.Input>
+            <Form.Label>label</Form.Label>
+          </Form.Input>
+        </template>
+        `
+      }
+    ],
+
+    invalid: [
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          // imported components are also directly usable in template
+          import Foo from './Foo.vue'
+          import Bar from './Bar.vue'
+          import { ref } from 'vue'
+
+          // write Composition API code just like in a normal setup()
+          // but no need to manually return everything
+          const count = ref(0)
+          const inc = () => {
+            count.value++
+          }
+          const foo = ref(42)
+          console.log(foo.value)
+          const bar = ref(42)
+          bar.value++
+          const baz = ref(42)
+        </script>
+
+        <template>
+          <Foo :count="count" @click="inc" />
+        </template>
+        `,
+        errors: [
+          {
+            message: "'Bar' is defined but never used.",
+            line: 5
+          },
+          {
+            message: "'baz' is assigned a value but never used.",
+            line: 18
+          }
+        ]
+      },
+
+      // Resolve component name
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          import camelCase from './component.vue'
+        </script>
+
+        <template>
+          <CamelCase />
+        </template>
+        `,
+        errors: [
+          {
+            message: "'camelCase' is defined but never used.",
+            line: 3
+          }
+        ]
+      },
+
+      // Scope tests
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          if (a) {
+            const msg = 'Hello!'
+          }
+        </script>
+
+        <template>
+          <div>{{ msg }}</div>
+        </template>
+        `,
+        errors: [
+          {
+            message: "'msg' is assigned a value but never used.",
+            line: 4
+          }
+        ]
+      },
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          const i = 42
+          const list = [1,2,3]
+        </script>
+
+        <template>
+          <div v-for="i in list">{{ i }}</div>
+        </template>
+      `,
+        errors: [
+          {
+            message: "'i' is assigned a value but never used.",
+            line: 3
+          }
+        ]
+      },
+
+      // Not `<script setup>`
+      {
+        filename: 'test.vue',
+        code: `
+        <script>
+          const msg = 'Hello!'
+        </script>
+
+        <template>
+          <div>{{ msg }}</div>
+        </template>
+        `,
+        errors: [
+          {
+            message: "'msg' is assigned a value but never used.",
+            line: 3
+          }
+        ]
+      },
+
+      //style vars
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+          const color = 'red'
+        </script>
+
+        <style lang="scss">
+          .v-bind .color {
+            color: 'v-bind(color)';
+            background-color: 'v-bind(color)';
+          }
+          /* v-bind(color) */
+          // v-bind(color)
+        </style>
+        `,
+        errors: ["'color' is assigned a value but never used."]
+      }
+    ]
+  })
+
+  ruleTester.run('no-undef', ruleNoUndef, {
+    valid: [
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+        const props = defineProps({
+          foo: String
+        })
+
+        const emit = defineEmits(['change', 'delete'])
+        </script>
+        `
+      }
+    ],
+    invalid: [
+      {
+        filename: 'test.vue',
+        code: `
+        <script setup>
+        const props = defineUnknown({
+          foo: String
+        })
+
+        const emit = defineUnknown(['change', 'delete'])
+        </script>
+        `,
+        errors: [
+          {
+            message: "'defineUnknown' is not defined.",
+            line: 3
+          },
+          {
+            message: "'defineUnknown' is not defined.",
+            line: 7
+          }
+        ]
+      }
+    ]
+  })
+})