Repository: vuejs/eslint-plugin-vue
Pull request #2407 (2024-02-19 08:45:29)

feat: support flat configs
the main changes:
* flat configs are exported as `flat/*`.
* ~~flat configs are exported as arrays.~~
* configs naming: 
  * `flat/recommended`is for vue 3. 
  * `flat/vue2-recommended` is for vue 2.


resubmit https://github.com/vuejs/eslint-plugin-vue/pull/2319.
fixes https://github.com/vuejs/eslint-plugin-vue/issues/1291

ganondev: Hi, just wanted to share some of the work challenges I had getting this to work in the context of my vue3+typescript project.
The work I did was from the original @conradhale fork, but I checked out @aladdin-add as well and it seems to be missing one thing I had to add:

* Setting `plugins: { vue: module },` for every flat config option wasn't working out. I ended up changing the generator to set a recursive reference for each flat config at the bottom of `index.js` after declaring the export object. Here is an example:

```js
module.exports.configs['flat/base'].plugins.vue = module.exports;
```

https://github.com/ganondev/eslint-plugin-vue/blob/master/tools/update-lib-index.js#L62

This is what the `eslint.config.js` for the project using this looks like with that update:

```js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

import vue from 'eslint-plugin-vue';


export default [
  {
    ignores: [
      'api/',
      'node_modules/',
      'dist/',
      '.yarn/',
      '.pnp.cjs',
      '.pnp.loader.mjs'
    ],
  },
  {
    ...eslint.configs.recommended,
    languageOptions: {
      ...eslint.configs.recommended.languageOptions,
      globals: {
        require: 'readonly',
        console: 'readonly',
      }
    },
  },
  ...tseslint.configs.recommended,
  {
    files: ['**/*.vue'],
    ...vue.configs["flat/vue3-recommended"],
    languageOptions: {
      ...vue.configs["flat/vue3-recommended"].languageOptions,
      parserOptions: {
        ...vue.configs["flat/vue3-recommended"].languageOptions.parserOptions,
        parser: '@typescript-eslint/parser'
      }
    },
  },
  {
    rules: {
      indent: ['error', 2],
    }
  },

];
```

Please let me know if this is how this is intended to look. It would be nice if I could parameterize the typescript parser option in, instead of flatting out down to the shared `parserOptions`.
Also, it's worth noting that with the other shared configs, the vue one **must** be the last one, otherwise the typescript or eslint config will attempt to process `.vue` and throw a fit. I think this should be documented, not sure how much control the vue plugin would have to be able to soften that.

Unfortunately, I can't put export change up against the active fork here since it's a different upstream, but I wanted to make sure this at least gets shared.
aladdin-add: @ganondev thanks for trying it - it seems a bug, I've pushed a new commit, should be fixed now.
aladdin-add: friendly ping @ota-meshi 

Patch:
diff --git a/lib/configs/flat/base.js b/lib/configs/flat/base.js
new file mode 100644
index 00000000..e36ab4b7
--- /dev/null
+++ b/lib/configs/flat/base.js
@@ -0,0 +1,17 @@
+/*
+ * IMPORTANT!
+ * This file has been automatically generated,
+ * in order to update its content execute "npm run update"
+ */
+const globals = require('globals')
+module.exports = {
+  languageOptions: {
+    parser: require('vue-eslint-parser'),
+    sourceType: 'module',
+    globals: globals.browser
+  },
+  rules: {
+    'vue/comment-directive': 'error',
+    'vue/jsx-uses-vars': 'error'
+  }
+}
diff --git a/lib/configs/flat/vue2-essential.js b/lib/configs/flat/vue2-essential.js
new file mode 100644
index 00000000..ce253352
--- /dev/null
+++ b/lib/configs/flat/vue2-essential.js
@@ -0,0 +1,76 @@
+/*
+ * IMPORTANT!
+ * This file has been automatically generated,
+ * in order to update its content execute "npm run update"
+ */
+'use strict'
+const config = require('./base.js')
+const { extendRules } = require('../../utils/config-helpers.js')
+
+const rules = {
+  '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',
+    {
+      vueVersion: 2
+    }
+  ],
+  '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-attribute-name': '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',
+  'vue/valid-v-bind': 'error',
+  'vue/valid-v-cloak': 'error',
+  'vue/valid-v-else-if': 'error',
+  'vue/valid-v-else': 'error',
+  'vue/valid-v-for': 'error',
+  'vue/valid-v-html': 'error',
+  'vue/valid-v-if': 'error',
+  'vue/valid-v-model': 'error',
+  'vue/valid-v-on': 'error',
+  'vue/valid-v-once': 'error',
+  'vue/valid-v-pre': 'error',
+  'vue/valid-v-show': 'error',
+  'vue/valid-v-slot': 'error',
+  'vue/valid-v-text': 'error'
+}
+
+module.exports = extendRules(config, rules)
diff --git a/lib/configs/flat/vue2-recommended.js b/lib/configs/flat/vue2-recommended.js
new file mode 100644
index 00000000..e15b0207
--- /dev/null
+++ b/lib/configs/flat/vue2-recommended.js
@@ -0,0 +1,20 @@
+/*
+ * IMPORTANT!
+ * This file has been automatically generated,
+ * in order to update its content execute "npm run update"
+ */
+'use strict'
+const config = require('./vue2-strongly-recommended.js')
+const { extendRules } = require('../../utils/config-helpers.js')
+
+const rules = {
+  'vue/attributes-order': 'warn',
+  'vue/component-tags-order': 'warn',
+  'vue/no-lone-template': 'warn',
+  'vue/no-multiple-slot-args': 'warn',
+  'vue/no-v-html': 'warn',
+  'vue/order-in-components': 'warn',
+  'vue/this-in-template': 'warn'
+}
+
+module.exports = extendRules(config, rules)
diff --git a/lib/configs/flat/vue2-strongly-recommended.js b/lib/configs/flat/vue2-strongly-recommended.js
new file mode 100644
index 00000000..a1c870b9
--- /dev/null
+++ b/lib/configs/flat/vue2-strongly-recommended.js
@@ -0,0 +1,36 @@
+/*
+ * IMPORTANT!
+ * This file has been automatically generated,
+ * in order to update its content execute "npm run update"
+ */
+'use strict'
+const config = require('./vue2-essential.js')
+const { extendRules } = require('../../utils/config-helpers.js')
+
+const rules = {
+  'vue/attribute-hyphenation': 'warn',
+  'vue/component-definition-name-casing': 'warn',
+  'vue/first-attribute-linebreak': 'warn',
+  'vue/html-closing-bracket-newline': 'warn',
+  'vue/html-closing-bracket-spacing': 'warn',
+  'vue/html-end-tags': 'warn',
+  'vue/html-indent': 'warn',
+  'vue/html-quotes': 'warn',
+  'vue/html-self-closing': 'warn',
+  'vue/max-attributes-per-line': 'warn',
+  'vue/multiline-html-element-content-newline': 'warn',
+  'vue/mustache-interpolation-spacing': 'warn',
+  'vue/no-multi-spaces': 'warn',
+  'vue/no-spaces-around-equal-signs-in-attribute': 'warn',
+  'vue/no-template-shadow': 'warn',
+  'vue/one-component-per-file': 'warn',
+  'vue/prop-name-casing': 'warn',
+  'vue/require-default-prop': 'warn',
+  'vue/require-prop-types': 'warn',
+  'vue/singleline-html-element-content-newline': 'warn',
+  'vue/v-bind-style': 'warn',
+  'vue/v-on-style': 'warn',
+  'vue/v-slot-style': 'warn'
+}
+
+module.exports = extendRules(config, rules)
diff --git a/lib/configs/flat/vue3-essential.js b/lib/configs/flat/vue3-essential.js
new file mode 100644
index 00000000..e8450635
--- /dev/null
+++ b/lib/configs/flat/vue3-essential.js
@@ -0,0 +1,93 @@
+/*
+ * IMPORTANT!
+ * This file has been automatically generated,
+ * in order to update its content execute "npm run update"
+ */
+'use strict'
+const config = require('./base.js')
+const { extendRules } = require('../../utils/config-helpers.js')
+
+const rules = {
+  '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',
+  'vue/no-deprecated-dollar-listeners-api': 'error',
+  'vue/no-deprecated-dollar-scopedslots-api': 'error',
+  'vue/no-deprecated-events-api': 'error',
+  'vue/no-deprecated-filter': 'error',
+  'vue/no-deprecated-functional-template': 'error',
+  'vue/no-deprecated-html-element-is': 'error',
+  'vue/no-deprecated-inline-template': 'error',
+  'vue/no-deprecated-props-default-this': 'error',
+  'vue/no-deprecated-router-link-tag-prop': 'error',
+  'vue/no-deprecated-scope-attribute': 'error',
+  'vue/no-deprecated-slot-attribute': 'error',
+  'vue/no-deprecated-slot-scope-attribute': 'error',
+  'vue/no-deprecated-v-bind-sync': 'error',
+  'vue/no-deprecated-v-is': 'error',
+  'vue/no-deprecated-v-on-native-modifier': 'error',
+  'vue/no-deprecated-v-on-number-modifiers': 'error',
+  'vue/no-deprecated-vue-config-keycodes': '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-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-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-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',
+  'vue/require-slots-as-functions': 'error',
+  'vue/require-toggle-inside-transition': '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-attribute-name': 'error',
+  'vue/valid-define-emits': 'error',
+  'vue/valid-define-props': 'error',
+  'vue/valid-next-tick': 'error',
+  'vue/valid-template-root': 'error',
+  'vue/valid-v-bind': 'error',
+  'vue/valid-v-cloak': 'error',
+  'vue/valid-v-else-if': 'error',
+  'vue/valid-v-else': 'error',
+  'vue/valid-v-for': 'error',
+  'vue/valid-v-html': 'error',
+  'vue/valid-v-if': 'error',
+  'vue/valid-v-is': 'error',
+  'vue/valid-v-memo': 'error',
+  'vue/valid-v-model': 'error',
+  'vue/valid-v-on': 'error',
+  'vue/valid-v-once': 'error',
+  'vue/valid-v-pre': 'error',
+  'vue/valid-v-show': 'error',
+  'vue/valid-v-slot': 'error',
+  'vue/valid-v-text': 'error'
+}
+
+module.exports = extendRules(config, rules)
diff --git a/lib/configs/flat/vue3-recommended.js b/lib/configs/flat/vue3-recommended.js
new file mode 100644
index 00000000..e39b86bc
--- /dev/null
+++ b/lib/configs/flat/vue3-recommended.js
@@ -0,0 +1,20 @@
+/*
+ * IMPORTANT!
+ * This file has been automatically generated,
+ * in order to update its content execute "npm run update"
+ */
+'use strict'
+const config = require('./vue3-strongly-recommended.js')
+const { extendRules } = require('../../utils/config-helpers.js')
+
+const rules = {
+  'vue/attributes-order': 'warn',
+  'vue/component-tags-order': 'warn',
+  'vue/no-lone-template': 'warn',
+  'vue/no-multiple-slot-args': 'warn',
+  'vue/no-v-html': 'warn',
+  'vue/order-in-components': 'warn',
+  'vue/this-in-template': 'warn'
+}
+
+module.exports = extendRules(config, rules)
diff --git a/lib/configs/flat/vue3-strongly-recommended.js b/lib/configs/flat/vue3-strongly-recommended.js
new file mode 100644
index 00000000..ec71ac46
--- /dev/null
+++ b/lib/configs/flat/vue3-strongly-recommended.js
@@ -0,0 +1,44 @@
+/*
+ * IMPORTANT!
+ * This file has been automatically generated,
+ * in order to update its content execute "npm run update"
+ */
+'use strict'
+const config = require('./vue3-essential.js')
+const { extendRules } = require('../../utils/config-helpers.js')
+
+const rules = {
+  'vue/attribute-hyphenation': 'warn',
+  'vue/component-definition-name-casing': 'warn',
+  'vue/first-attribute-linebreak': 'warn',
+  'vue/html-closing-bracket-newline': 'warn',
+  'vue/html-closing-bracket-spacing': 'warn',
+  'vue/html-end-tags': 'warn',
+  'vue/html-indent': 'warn',
+  'vue/html-quotes': 'warn',
+  'vue/html-self-closing': 'warn',
+  'vue/max-attributes-per-line': 'warn',
+  'vue/multiline-html-element-content-newline': 'warn',
+  'vue/mustache-interpolation-spacing': 'warn',
+  'vue/no-multi-spaces': 'warn',
+  'vue/no-spaces-around-equal-signs-in-attribute': 'warn',
+  'vue/no-template-shadow': 'warn',
+  'vue/one-component-per-file': 'warn',
+  'vue/prop-name-casing': 'warn',
+  'vue/require-default-prop': 'warn',
+  'vue/require-explicit-emits': 'warn',
+  'vue/require-prop-types': 'warn',
+  'vue/singleline-html-element-content-newline': 'warn',
+  'vue/v-bind-style': 'warn',
+  'vue/v-on-event-hyphenation': [
+    'warn',
+    'always',
+    {
+      autofix: true
+    }
+  ],
+  'vue/v-on-style': 'warn',
+  'vue/v-slot-style': 'warn'
+}
+
+module.exports = extendRules(config, rules)
diff --git a/lib/configs/essential.js b/lib/configs/vue2-essential.js
similarity index 100%
rename from lib/configs/essential.js
rename to lib/configs/vue2-essential.js
diff --git a/lib/configs/recommended.js b/lib/configs/vue2-recommended.js
similarity index 87%
rename from lib/configs/recommended.js
rename to lib/configs/vue2-recommended.js
index 3db36d8c..b0418099 100644
--- a/lib/configs/recommended.js
+++ b/lib/configs/vue2-recommended.js
@@ -4,7 +4,7 @@
  * in order to update its content execute "npm run update"
  */
 module.exports = {
-  extends: require.resolve('./strongly-recommended'),
+  extends: require.resolve('./vue2-strongly-recommended'),
   rules: {
     'vue/attributes-order': 'warn',
     'vue/component-tags-order': 'warn',
diff --git a/lib/configs/strongly-recommended.js b/lib/configs/vue2-strongly-recommended.js
similarity index 95%
rename from lib/configs/strongly-recommended.js
rename to lib/configs/vue2-strongly-recommended.js
index 02daf9a3..e7b0f3dc 100644
--- a/lib/configs/strongly-recommended.js
+++ b/lib/configs/vue2-strongly-recommended.js
@@ -4,7 +4,7 @@
  * in order to update its content execute "npm run update"
  */
 module.exports = {
-  extends: require.resolve('./essential'),
+  extends: require.resolve('./vue2-essential'),
   rules: {
     'vue/attribute-hyphenation': 'warn',
     'vue/component-definition-name-casing': 'warn',
diff --git a/lib/index.js b/lib/index.js
index 99421f24..bc6c9a46 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -5,7 +5,7 @@
  */
 'use strict'
 
-module.exports = {
+const plugin = {
   meta: require('./meta'),
   rules: {
     'array-bracket-newline': require('./rules/array-bracket-newline'),
@@ -250,16 +250,6 @@ module.exports = {
     'valid-v-slot': require('./rules/valid-v-slot'),
     'valid-v-text': require('./rules/valid-v-text')
   },
-  configs: {
-    base: require('./configs/base'),
-    essential: require('./configs/essential'),
-    'no-layout-rules': require('./configs/no-layout-rules'),
-    recommended: require('./configs/recommended'),
-    'strongly-recommended': require('./configs/strongly-recommended'),
-    'vue3-essential': require('./configs/vue3-essential'),
-    'vue3-recommended': require('./configs/vue3-recommended'),
-    'vue3-strongly-recommended': require('./configs/vue3-strongly-recommended')
-  },
   processors: {
     '.vue': require('./processor')
   },
@@ -276,3 +266,54 @@ module.exports = {
     }
   }
 }
+
+const baseConfig = { plugins: { vue: plugin } }
+
+plugin.configs = {
+  // eslintrc configs
+  base: require('./configs/base'),
+  essential: require('./configs/vue2-essential'),
+  'no-layout-rules': require('./configs/no-layout-rules'),
+  recommended: require('./configs/vue2-recommended'),
+  'strongly-recommended': require('./configs/vue2-strongly-recommended'),
+  'vue3-essential': require('./configs/vue3-essential'),
+  'vue3-recommended': require('./configs/vue3-recommended'),
+  'vue3-strongly-recommended': require('./configs/vue3-strongly-recommended'),
+
+  // flat configs
+  'flat/base': Object.assign({}, baseConfig, require('./configs/flat/base.js')),
+  'flat/vue2-essential': Object.assign(
+    {},
+    baseConfig,
+    require('./configs/flat/vue2-essential.js')
+  ),
+  'flat/vue2-recommended': Object.assign(
+    {},
+    baseConfig,
+    require('./configs/flat/vue2-recommended.js')
+  ),
+  'flat/vue2-strongly-recommended': Object.assign(
+    {},
+    baseConfig,
+    require('./configs/flat/vue2-strongly-recommended.js')
+  ),
+
+  // in flat configs, non-prefixed config is for Vue 3 (unlike eslintrc configs)
+  'flat/essential': Object.assign(
+    {},
+    baseConfig,
+    require('./configs/flat/vue3-essential.js')
+  ),
+  'flat/recommended': Object.assign(
+    {},
+    baseConfig,
+    require('./configs/flat/vue3-recommended.js')
+  ),
+  'flat/strongly-recommended': Object.assign(
+    {},
+    baseConfig,
+    require('./configs/flat/vue3-strongly-recommended.js')
+  )
+}
+
+module.exports = plugin
diff --git a/lib/rules/attribute-hyphenation.js b/lib/rules/attribute-hyphenation.js
index 11f681ef..3de0af1b 100644
--- a/lib/rules/attribute-hyphenation.js
+++ b/lib/rules/attribute-hyphenation.js
@@ -34,7 +34,7 @@ module.exports = {
     docs: {
       description:
         'enforce attribute naming style on custom components in template',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/attribute-hyphenation.html'
     },
     fixable: 'code',
diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js
index 44404f05..30dc75c2 100644
--- a/lib/rules/attributes-order.js
+++ b/lib/rules/attributes-order.js
@@ -424,7 +424,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce order of attributes',
-      categories: ['vue3-recommended', 'recommended'],
+      categories: ['vue3-recommended', 'vue2-recommended'],
       url: 'https://eslint.vuejs.org/rules/attributes-order.html'
     },
     fixable: 'code',
diff --git a/lib/rules/component-definition-name-casing.js b/lib/rules/component-definition-name-casing.js
index 99ba619c..840a89e5 100644
--- a/lib/rules/component-definition-name-casing.js
+++ b/lib/rules/component-definition-name-casing.js
@@ -26,7 +26,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce specific casing for component definition name',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/component-definition-name-casing.html'
     },
     fixable: 'code',
diff --git a/lib/rules/component-tags-order.js b/lib/rules/component-tags-order.js
index a2a5cd65..132aab89 100644
--- a/lib/rules/component-tags-order.js
+++ b/lib/rules/component-tags-order.js
@@ -10,7 +10,7 @@ module.exports = {
     type: baseRule.meta.type,
     docs: {
       description: baseRule.meta.docs.description,
-      categories: ['vue3-recommended', 'recommended'],
+      categories: ['vue3-recommended', 'vue2-recommended'],
       url: 'https://eslint.vuejs.org/rules/component-tags-order.html'
     },
     deprecated: true,
diff --git a/lib/rules/first-attribute-linebreak.js b/lib/rules/first-attribute-linebreak.js
index c023762d..b2e6aefc 100644
--- a/lib/rules/first-attribute-linebreak.js
+++ b/lib/rules/first-attribute-linebreak.js
@@ -11,7 +11,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description: 'enforce the location of first attribute',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/first-attribute-linebreak.html'
     },
     fixable: 'whitespace',
diff --git a/lib/rules/html-closing-bracket-newline.js b/lib/rules/html-closing-bracket-newline.js
index 58f7fc8e..5787bf97 100644
--- a/lib/rules/html-closing-bracket-newline.js
+++ b/lib/rules/html-closing-bracket-newline.js
@@ -69,7 +69,7 @@ module.exports = {
     docs: {
       description:
         "require or disallow a line break before tag's closing brackets",
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/html-closing-bracket-newline.html'
     },
     fixable: 'whitespace',
diff --git a/lib/rules/html-closing-bracket-spacing.js b/lib/rules/html-closing-bracket-spacing.js
index daf42d54..6c0da838 100644
--- a/lib/rules/html-closing-bracket-spacing.js
+++ b/lib/rules/html-closing-bracket-spacing.js
@@ -56,7 +56,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description: "require or disallow a space before tag's closing brackets",
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/html-closing-bracket-spacing.html'
     },
     fixable: 'whitespace',
diff --git a/lib/rules/html-end-tags.js b/lib/rules/html-end-tags.js
index 58c1142e..a0d313cf 100644
--- a/lib/rules/html-end-tags.js
+++ b/lib/rules/html-end-tags.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce end tag style',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/html-end-tags.html'
     },
     fixable: 'code',
diff --git a/lib/rules/html-indent.js b/lib/rules/html-indent.js
index 397cdc52..96a8a445 100644
--- a/lib/rules/html-indent.js
+++ b/lib/rules/html-indent.js
@@ -26,7 +26,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description: 'enforce consistent indentation in `<template>`',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/html-indent.html'
     },
     // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
diff --git a/lib/rules/html-quotes.js b/lib/rules/html-quotes.js
index 2be837c0..f4f6d1c1 100644
--- a/lib/rules/html-quotes.js
+++ b/lib/rules/html-quotes.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description: 'enforce quotes style of HTML attributes',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/html-quotes.html'
     },
     fixable: 'code',
diff --git a/lib/rules/html-self-closing.js b/lib/rules/html-self-closing.js
index 9e626f41..531ddfc2 100644
--- a/lib/rules/html-self-closing.js
+++ b/lib/rules/html-self-closing.js
@@ -88,7 +88,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description: 'enforce self-closing style',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/html-self-closing.html'
     },
     fixable: 'code',
diff --git a/lib/rules/max-attributes-per-line.js b/lib/rules/max-attributes-per-line.js
index 443826fe..f5c96c12 100644
--- a/lib/rules/max-attributes-per-line.js
+++ b/lib/rules/max-attributes-per-line.js
@@ -65,7 +65,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description: 'enforce the maximum number of attributes per line',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/max-attributes-per-line.html'
     },
     fixable: 'whitespace',
diff --git a/lib/rules/multi-word-component-names.js b/lib/rules/multi-word-component-names.js
index 7f8e7bb6..60f70a99 100644
--- a/lib/rules/multi-word-component-names.js
+++ b/lib/rules/multi-word-component-names.js
@@ -17,7 +17,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'require component names to be always multi-word',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/multi-word-component-names.html'
     },
     schema: [
diff --git a/lib/rules/multiline-html-element-content-newline.js b/lib/rules/multiline-html-element-content-newline.js
index bfe0c79f..afe89bd2 100644
--- a/lib/rules/multiline-html-element-content-newline.js
+++ b/lib/rules/multiline-html-element-content-newline.js
@@ -61,7 +61,7 @@ module.exports = {
     docs: {
       description:
         'require a line break before and after the contents of a multiline element',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/multiline-html-element-content-newline.html'
     },
     fixable: 'whitespace',
diff --git a/lib/rules/mustache-interpolation-spacing.js b/lib/rules/mustache-interpolation-spacing.js
index 79bae054..304b50c7 100644
--- a/lib/rules/mustache-interpolation-spacing.js
+++ b/lib/rules/mustache-interpolation-spacing.js
@@ -11,7 +11,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description: 'enforce unified spacing in mustache interpolations',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/mustache-interpolation-spacing.html'
     },
     fixable: 'whitespace',
diff --git a/lib/rules/no-arrow-functions-in-watch.js b/lib/rules/no-arrow-functions-in-watch.js
index 3b1fcd6d..b16b267f 100644
--- a/lib/rules/no-arrow-functions-in-watch.js
+++ b/lib/rules/no-arrow-functions-in-watch.js
@@ -10,7 +10,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow using arrow functions to define watcher',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-arrow-functions-in-watch.html'
     },
     fixable: null,
diff --git a/lib/rules/no-async-in-computed-properties.js b/lib/rules/no-async-in-computed-properties.js
index afe9cd35..e8ed02f0 100644
--- a/lib/rules/no-async-in-computed-properties.js
+++ b/lib/rules/no-async-in-computed-properties.js
@@ -81,7 +81,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow asynchronous actions in computed properties',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-async-in-computed-properties.html'
     },
     fixable: null,
diff --git a/lib/rules/no-child-content.js b/lib/rules/no-child-content.js
index 62de865b..df39c758 100644
--- a/lib/rules/no-child-content.js
+++ b/lib/rules/no-child-content.js
@@ -74,7 +74,7 @@ module.exports = {
     docs: {
       description:
         "disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text`",
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-child-content.html'
     },
     fixable: null,
diff --git a/lib/rules/no-computed-properties-in-data.js b/lib/rules/no-computed-properties-in-data.js
index 58aa0070..a2273947 100644
--- a/lib/rules/no-computed-properties-in-data.js
+++ b/lib/rules/no-computed-properties-in-data.js
@@ -15,7 +15,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow accessing computed properties in `data`.',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-computed-properties-in-data.html'
     },
     fixable: null,
diff --git a/lib/rules/no-custom-modifiers-on-v-model.js b/lib/rules/no-custom-modifiers-on-v-model.js
index 4cbbfdfe..cef3dfae 100644
--- a/lib/rules/no-custom-modifiers-on-v-model.js
+++ b/lib/rules/no-custom-modifiers-on-v-model.js
@@ -13,7 +13,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow custom modifiers on v-model used on the component',
-      categories: ['essential'],
+      categories: ['vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-custom-modifiers-on-v-model.html'
     },
     fixable: null,
diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js
index dd8b51c6..01b85d9f 100644
--- a/lib/rules/no-dupe-keys.js
+++ b/lib/rules/no-dupe-keys.js
@@ -63,7 +63,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow duplication of field names',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-dupe-keys.html'
     },
     fixable: null,
diff --git a/lib/rules/no-dupe-v-else-if.js b/lib/rules/no-dupe-v-else-if.js
index dc7c18c0..a20d5256 100644
--- a/lib/rules/no-dupe-v-else-if.js
+++ b/lib/rules/no-dupe-v-else-if.js
@@ -71,7 +71,7 @@ module.exports = {
     docs: {
       description:
         'disallow duplicate conditions in `v-if` / `v-else-if` chains',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-dupe-v-else-if.html'
     },
     fixable: null,
diff --git a/lib/rules/no-duplicate-attributes.js b/lib/rules/no-duplicate-attributes.js
index 39a3a0bb..0c7d3eb5 100644
--- a/lib/rules/no-duplicate-attributes.js
+++ b/lib/rules/no-duplicate-attributes.js
@@ -32,7 +32,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow duplication of attributes',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-duplicate-attributes.html'
     },
     fixable: null,
diff --git a/lib/rules/no-export-in-script-setup.js b/lib/rules/no-export-in-script-setup.js
index 0105d52f..66286375 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', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-export-in-script-setup.html'
     },
     fixable: null,
diff --git a/lib/rules/no-lone-template.js b/lib/rules/no-lone-template.js
index d9537513..765b2490 100644
--- a/lib/rules/no-lone-template.js
+++ b/lib/rules/no-lone-template.js
@@ -41,7 +41,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow unnecessary `<template>`',
-      categories: ['vue3-recommended', 'recommended'],
+      categories: ['vue3-recommended', 'vue2-recommended'],
       url: 'https://eslint.vuejs.org/rules/no-lone-template.html'
     },
     fixable: null,
diff --git a/lib/rules/no-multi-spaces.js b/lib/rules/no-multi-spaces.js
index 7e8f192a..8f205453 100644
--- a/lib/rules/no-multi-spaces.js
+++ b/lib/rules/no-multi-spaces.js
@@ -20,7 +20,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description: 'disallow multiple spaces',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/no-multi-spaces.html'
     },
     fixable: 'whitespace',
diff --git a/lib/rules/no-multiple-slot-args.js b/lib/rules/no-multiple-slot-args.js
index 86bd6303..1ee4ca0d 100644
--- a/lib/rules/no-multiple-slot-args.js
+++ b/lib/rules/no-multiple-slot-args.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow to pass multiple arguments to scoped slots',
-      categories: ['vue3-recommended', 'recommended'],
+      categories: ['vue3-recommended', 'vue2-recommended'],
       url: 'https://eslint.vuejs.org/rules/no-multiple-slot-args.html'
     },
     fixable: null,
diff --git a/lib/rules/no-multiple-template-root.js b/lib/rules/no-multiple-template-root.js
index f7948ecd..45c22389 100644
--- a/lib/rules/no-multiple-template-root.js
+++ b/lib/rules/no-multiple-template-root.js
@@ -11,7 +11,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow adding multiple root nodes to the template',
-      categories: ['essential'],
+      categories: ['vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-multiple-template-root.html'
     },
     fixable: null,
diff --git a/lib/rules/no-mutating-props.js b/lib/rules/no-mutating-props.js
index d5d28303..3689d386 100644
--- a/lib/rules/no-mutating-props.js
+++ b/lib/rules/no-mutating-props.js
@@ -106,7 +106,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'disallow mutation of component props',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-mutating-props.html'
     },
     fixable: null,
diff --git a/lib/rules/no-parsing-error.js b/lib/rules/no-parsing-error.js
index f4e2f7e2..8b7ab837 100644
--- a/lib/rules/no-parsing-error.js
+++ b/lib/rules/no-parsing-error.js
@@ -52,7 +52,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow parsing errors in `<template>`',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-parsing-error.html'
     },
     fixable: null,
diff --git a/lib/rules/no-ref-as-operand.js b/lib/rules/no-ref-as-operand.js
index 9661b2b6..02b43863 100644
--- a/lib/rules/no-ref-as-operand.js
+++ b/lib/rules/no-ref-as-operand.js
@@ -30,7 +30,7 @@ module.exports = {
     docs: {
       description:
         'disallow use of value wrapped by `ref()` (Composition API) as an operand',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-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 835fb732..90e53604 100644
--- a/lib/rules/no-reserved-component-names.js
+++ b/lib/rules/no-reserved-component-names.js
@@ -77,7 +77,7 @@ module.exports = {
     docs: {
       description:
         'disallow the use of reserved names in component definitions',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-reserved-component-names.html'
     },
     fixable: null,
diff --git a/lib/rules/no-reserved-keys.js b/lib/rules/no-reserved-keys.js
index 20bca97c..c13ce1e4 100644
--- a/lib/rules/no-reserved-keys.js
+++ b/lib/rules/no-reserved-keys.js
@@ -26,7 +26,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'disallow overwriting reserved keys',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-reserved-keys.html'
     },
     fixable: null,
diff --git a/lib/rules/no-reserved-props.js b/lib/rules/no-reserved-props.js
index f493212c..bafc6ef1 100644
--- a/lib/rules/no-reserved-props.js
+++ b/lib/rules/no-reserved-props.js
@@ -21,7 +21,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow reserved names in props',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-reserved-props.html',
       defaultOptions: {
         vue2: [{ vueVersion: 2 }]
diff --git a/lib/rules/no-shared-component-data.js b/lib/rules/no-shared-component-data.js
index b91602b1..dc7b509a 100644
--- a/lib/rules/no-shared-component-data.js
+++ b/lib/rules/no-shared-component-data.js
@@ -42,7 +42,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: "enforce component's data property to be a function",
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-shared-component-data.html'
     },
     fixable: 'code',
diff --git a/lib/rules/no-side-effects-in-computed-properties.js b/lib/rules/no-side-effects-in-computed-properties.js
index b0fbfc3d..36b0e877 100644
--- a/lib/rules/no-side-effects-in-computed-properties.js
+++ b/lib/rules/no-side-effects-in-computed-properties.js
@@ -20,7 +20,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow side effects in computed properties',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-side-effects-in-computed-properties.html'
     },
     fixable: null,
diff --git a/lib/rules/no-spaces-around-equal-signs-in-attribute.js b/lib/rules/no-spaces-around-equal-signs-in-attribute.js
index 9ee153a7..db8cbaab 100644
--- a/lib/rules/no-spaces-around-equal-signs-in-attribute.js
+++ b/lib/rules/no-spaces-around-equal-signs-in-attribute.js
@@ -11,7 +11,7 @@ module.exports = {
     type: 'layout',
     docs: {
       description: 'disallow spaces around equal signs in attribute',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/no-spaces-around-equal-signs-in-attribute.html'
     },
     fixable: 'whitespace',
diff --git a/lib/rules/no-template-key.js b/lib/rules/no-template-key.js
index 686c2de1..5530a19b 100644
--- a/lib/rules/no-template-key.js
+++ b/lib/rules/no-template-key.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow `key` attribute on `<template>`',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-template-key.html'
     },
     fixable: null,
diff --git a/lib/rules/no-template-shadow.js b/lib/rules/no-template-shadow.js
index e00da50e..8d9c40a7 100644
--- a/lib/rules/no-template-shadow.js
+++ b/lib/rules/no-template-shadow.js
@@ -37,7 +37,7 @@ module.exports = {
     docs: {
       description:
         'disallow variable declarations from shadowing variables declared in the outer scope',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/no-template-shadow.html'
     },
     fixable: null,
diff --git a/lib/rules/no-textarea-mustache.js b/lib/rules/no-textarea-mustache.js
index b5ad8d96..b4b5a9f7 100644
--- a/lib/rules/no-textarea-mustache.js
+++ b/lib/rules/no-textarea-mustache.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow mustaches in `<textarea>`',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-textarea-mustache.html'
     },
     fixable: null,
diff --git a/lib/rules/no-unused-components.js b/lib/rules/no-unused-components.js
index 603cce85..75b08fa1 100644
--- a/lib/rules/no-unused-components.js
+++ b/lib/rules/no-unused-components.js
@@ -13,7 +13,7 @@ module.exports = {
     docs: {
       description:
         'disallow registering components that are not used inside templates',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-unused-components.html'
     },
     fixable: null,
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index 454b1a18..b0226bba 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -55,7 +55,7 @@ module.exports = {
     docs: {
       description:
         'disallow unused variable definitions of v-for directives or scope attributes',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-unused-vars.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 242e6f18..5d5bd8b4 100644
--- a/lib/rules/no-use-computed-property-like-method.js
+++ b/lib/rules/no-use-computed-property-like-method.js
@@ -492,7 +492,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow use computed property like method',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-use-computed-property-like-method.html'
     },
     fixable: null,
diff --git a/lib/rules/no-use-v-if-with-v-for.js b/lib/rules/no-use-v-if-with-v-for.js
index dccda39b..e733c0f5 100644
--- a/lib/rules/no-use-v-if-with-v-for.js
+++ b/lib/rules/no-use-v-if-with-v-for.js
@@ -39,7 +39,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'disallow using `v-if` on the same element as `v-for`',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-use-v-if-with-v-for.html'
     },
     fixable: null,
diff --git a/lib/rules/no-useless-template-attributes.js b/lib/rules/no-useless-template-attributes.js
index 69114a3f..27c58351 100644
--- a/lib/rules/no-useless-template-attributes.js
+++ b/lib/rules/no-useless-template-attributes.js
@@ -69,7 +69,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow useless attribute on `<template>`',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-useless-template-attributes.html'
     },
     fixable: null,
diff --git a/lib/rules/no-v-for-template-key.js b/lib/rules/no-v-for-template-key.js
index 9549fde8..e26716b2 100644
--- a/lib/rules/no-v-for-template-key.js
+++ b/lib/rules/no-v-for-template-key.js
@@ -10,7 +10,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow `key` attribute on `<template v-for>`',
-      categories: ['essential'],
+      categories: ['vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-v-for-template-key.html'
     },
     fixable: null,
diff --git a/lib/rules/no-v-html.js b/lib/rules/no-v-html.js
index b135b954..355e86b2 100644
--- a/lib/rules/no-v-html.js
+++ b/lib/rules/no-v-html.js
@@ -10,7 +10,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'disallow use of v-html to prevent XSS attack',
-      categories: ['vue3-recommended', 'recommended'],
+      categories: ['vue3-recommended', 'vue2-recommended'],
       url: 'https://eslint.vuejs.org/rules/no-v-html.html'
     },
     fixable: null,
diff --git a/lib/rules/no-v-model-argument.js b/lib/rules/no-v-model-argument.js
index f69fa101..02e72ad7 100644
--- a/lib/rules/no-v-model-argument.js
+++ b/lib/rules/no-v-model-argument.js
@@ -12,7 +12,7 @@ module.exports = {
     docs: {
       description:
         'disallow adding an argument to `v-model` used in custom component',
-      categories: ['essential'],
+      categories: ['vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-v-model-argument.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 92289f6b..50ef9c76 100644
--- a/lib/rules/no-v-text-v-html-on-component.js
+++ b/lib/rules/no-v-text-v-html-on-component.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'disallow v-text / v-html on component',
-      categories: ['essential', 'vue3-essential'],
+      categories: ['vue2-essential', 'vue3-essential'],
       url: 'https://eslint.vuejs.org/rules/no-v-text-v-html-on-component.html'
     },
     fixable: null,
diff --git a/lib/rules/one-component-per-file.js b/lib/rules/one-component-per-file.js
index b0cd795f..3cbb25ba 100644
--- a/lib/rules/one-component-per-file.js
+++ b/lib/rules/one-component-per-file.js
@@ -11,7 +11,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce that each component should be in its own file',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/one-component-per-file.html'
     },
     fixable: null,
diff --git a/lib/rules/order-in-components.js b/lib/rules/order-in-components.js
index 06ff78e4..38624d00 100644
--- a/lib/rules/order-in-components.js
+++ b/lib/rules/order-in-components.js
@@ -214,7 +214,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce order of properties in components',
-      categories: ['vue3-recommended', 'recommended'],
+      categories: ['vue3-recommended', 'vue2-recommended'],
       url: 'https://eslint.vuejs.org/rules/order-in-components.html'
     },
     fixable: 'code', // null or "code" or "whitespace"
diff --git a/lib/rules/prop-name-casing.js b/lib/rules/prop-name-casing.js
index 09442151..ba8fcc1b 100644
--- a/lib/rules/prop-name-casing.js
+++ b/lib/rules/prop-name-casing.js
@@ -57,7 +57,7 @@ module.exports = {
     docs: {
       description:
         'enforce specific casing for the Prop name in Vue components',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/prop-name-casing.html'
     },
     fixable: null, // null or "code" or "whitespace"
diff --git a/lib/rules/require-component-is.js b/lib/rules/require-component-is.js
index 13e16224..899ac5e4 100644
--- a/lib/rules/require-component-is.js
+++ b/lib/rules/require-component-is.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'require `v-bind:is` of `<component>` elements',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/require-component-is.html'
     },
     fixable: null,
diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js
index 194e4bb8..313574a1 100644
--- a/lib/rules/require-default-prop.js
+++ b/lib/rules/require-default-prop.js
@@ -48,7 +48,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'require default value for props',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/require-default-prop.html'
     },
     fixable: null,
diff --git a/lib/rules/require-prop-type-constructor.js b/lib/rules/require-prop-type-constructor.js
index c23ba808..d4c34484 100644
--- a/lib/rules/require-prop-type-constructor.js
+++ b/lib/rules/require-prop-type-constructor.js
@@ -33,7 +33,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'require prop type to be a constructor',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/require-prop-type-constructor.html'
     },
     fixable: 'code',
diff --git a/lib/rules/require-prop-types.js b/lib/rules/require-prop-types.js
index b658ab91..4102ad03 100644
--- a/lib/rules/require-prop-types.js
+++ b/lib/rules/require-prop-types.js
@@ -15,7 +15,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'require type definitions in props',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/require-prop-types.html'
     },
     fixable: null,
diff --git a/lib/rules/require-render-return.js b/lib/rules/require-render-return.js
index 1487bfc3..084e63e5 100644
--- a/lib/rules/require-render-return.js
+++ b/lib/rules/require-render-return.js
@@ -11,7 +11,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce render function to always return value',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/require-render-return.html'
     },
     fixable: null,
diff --git a/lib/rules/require-v-for-key.js b/lib/rules/require-v-for-key.js
index e4b1f2e4..3ca2c68a 100644
--- a/lib/rules/require-v-for-key.js
+++ b/lib/rules/require-v-for-key.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'require `v-bind:key` with `v-for` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/require-v-for-key.html'
     },
     fixable: null,
diff --git a/lib/rules/require-valid-default-prop.js b/lib/rules/require-valid-default-prop.js
index ab6e9b14..ddae351e 100644
--- a/lib/rules/require-valid-default-prop.js
+++ b/lib/rules/require-valid-default-prop.js
@@ -75,7 +75,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce props default values to be valid',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/require-valid-default-prop.html'
     },
     fixable: null,
diff --git a/lib/rules/return-in-computed-property.js b/lib/rules/return-in-computed-property.js
index 74fff9d1..0728d0c5 100644
--- a/lib/rules/return-in-computed-property.js
+++ b/lib/rules/return-in-computed-property.js
@@ -16,7 +16,7 @@ module.exports = {
     docs: {
       description:
         'enforce that a return statement is present in computed property',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/return-in-computed-property.html'
     },
     fixable: null,
diff --git a/lib/rules/return-in-emits-validator.js b/lib/rules/return-in-emits-validator.js
index 141aec23..8989753b 100644
--- a/lib/rules/return-in-emits-validator.js
+++ b/lib/rules/return-in-emits-validator.js
@@ -37,7 +37,7 @@ module.exports = {
     docs: {
       description:
         'enforce that a return statement is present in emits validator',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/return-in-emits-validator.html'
     },
     fixable: null,
diff --git a/lib/rules/singleline-html-element-content-newline.js b/lib/rules/singleline-html-element-content-newline.js
index 69d50734..bdccf92b 100644
--- a/lib/rules/singleline-html-element-content-newline.js
+++ b/lib/rules/singleline-html-element-content-newline.js
@@ -49,7 +49,7 @@ module.exports = {
     docs: {
       description:
         'require a line break before and after the contents of a singleline element',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/singleline-html-element-content-newline.html'
     },
     fixable: 'whitespace',
diff --git a/lib/rules/this-in-template.js b/lib/rules/this-in-template.js
index 0652902f..a664a8ed 100644
--- a/lib/rules/this-in-template.js
+++ b/lib/rules/this-in-template.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'disallow usage of `this` in template',
-      categories: ['vue3-recommended', 'recommended'],
+      categories: ['vue3-recommended', 'vue2-recommended'],
       url: 'https://eslint.vuejs.org/rules/this-in-template.html'
     },
     fixable: 'code',
diff --git a/lib/rules/use-v-on-exact.js b/lib/rules/use-v-on-exact.js
index 5aaa1713..f048b70e 100644
--- a/lib/rules/use-v-on-exact.js
+++ b/lib/rules/use-v-on-exact.js
@@ -164,7 +164,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce usage of `exact` modifier on `v-on`',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/use-v-on-exact.html'
     },
     fixable: null,
diff --git a/lib/rules/v-bind-style.js b/lib/rules/v-bind-style.js
index 637ded0c..752a8fdf 100644
--- a/lib/rules/v-bind-style.js
+++ b/lib/rules/v-bind-style.js
@@ -54,7 +54,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce `v-bind` directive style',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/v-bind-style.html'
     },
     fixable: 'code',
diff --git a/lib/rules/v-on-style.js b/lib/rules/v-on-style.js
index b2ff507f..eb595002 100644
--- a/lib/rules/v-on-style.js
+++ b/lib/rules/v-on-style.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce `v-on` directive style',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/v-on-style.html'
     },
     fixable: 'code',
diff --git a/lib/rules/v-slot-style.js b/lib/rules/v-slot-style.js
index daa3d96d..d932aa9d 100644
--- a/lib/rules/v-slot-style.js
+++ b/lib/rules/v-slot-style.js
@@ -86,7 +86,7 @@ module.exports = {
     type: 'suggestion',
     docs: {
       description: 'enforce `v-slot` directive style',
-      categories: ['vue3-strongly-recommended', 'strongly-recommended'],
+      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
       url: 'https://eslint.vuejs.org/rules/v-slot-style.html'
     },
     fixable: 'code',
diff --git a/lib/rules/valid-attribute-name.js b/lib/rules/valid-attribute-name.js
index 6da399f3..51d52a1b 100644
--- a/lib/rules/valid-attribute-name.js
+++ b/lib/rules/valid-attribute-name.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'require valid attribute names',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-attribute-name.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-define-emits.js b/lib/rules/valid-define-emits.js
index 6f6085cb..8d2306c2 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', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-define-emits.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-define-options.js b/lib/rules/valid-define-options.js
index 71dff325..52f1b847 100644
--- a/lib/rules/valid-define-options.js
+++ b/lib/rules/valid-define-options.js
@@ -13,7 +13,7 @@ module.exports = {
     docs: {
       description: 'enforce valid `defineOptions` compiler macro',
       // TODO Switch in the next major version
-      // categories: ['vue3-essential', 'essential'],
+      // categories: ['vue3-essential', 'vue2-essential'],
       categories: undefined,
       url: 'https://eslint.vuejs.org/rules/valid-define-options.html'
     },
diff --git a/lib/rules/valid-define-props.js b/lib/rules/valid-define-props.js
index bd1e41bd..2ba81b18 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', 'essential'],
+      categories: ['vue3-essential', 'vue2-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
index c1ba6918..00fe9761 100644
--- a/lib/rules/valid-model-definition.js
+++ b/lib/rules/valid-model-definition.js
@@ -13,7 +13,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'require valid keys in model option',
-      categories: ['essential'],
+      categories: ['vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-model-definition.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-next-tick.js b/lib/rules/valid-next-tick.js
index 969488cb..c384f6ac 100644
--- a/lib/rules/valid-next-tick.js
+++ b/lib/rules/valid-next-tick.js
@@ -113,7 +113,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `nextTick` function calls',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-next-tick.html'
     },
     fixable: 'code',
diff --git a/lib/rules/valid-template-root.js b/lib/rules/valid-template-root.js
index 214a5bfc..c604a43a 100644
--- a/lib/rules/valid-template-root.js
+++ b/lib/rules/valid-template-root.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid template root',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-template-root.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-bind-sync.js b/lib/rules/valid-v-bind-sync.js
index d01b1b8c..141a313c 100644
--- a/lib/rules/valid-v-bind-sync.js
+++ b/lib/rules/valid-v-bind-sync.js
@@ -75,7 +75,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `.sync` modifier on `v-bind` directives',
-      categories: ['essential'],
+      categories: ['vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-bind-sync.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-bind.js b/lib/rules/valid-v-bind.js
index 361db789..0f44dad2 100644
--- a/lib/rules/valid-v-bind.js
+++ b/lib/rules/valid-v-bind.js
@@ -14,7 +14,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-bind` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-bind.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-cloak.js b/lib/rules/valid-v-cloak.js
index 8fe19504..5ff95681 100644
--- a/lib/rules/valid-v-cloak.js
+++ b/lib/rules/valid-v-cloak.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-cloak` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-cloak.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-else-if.js b/lib/rules/valid-v-else-if.js
index d8888534..b9443746 100644
--- a/lib/rules/valid-v-else-if.js
+++ b/lib/rules/valid-v-else-if.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-else-if` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-else-if.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-else.js b/lib/rules/valid-v-else.js
index 8c7f8e49..f35ac661 100644
--- a/lib/rules/valid-v-else.js
+++ b/lib/rules/valid-v-else.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-else` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-else.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-for.js b/lib/rules/valid-v-for.js
index 9dcf7635..05183dd7 100644
--- a/lib/rules/valid-v-for.js
+++ b/lib/rules/valid-v-for.js
@@ -92,7 +92,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-for` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-for.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-html.js b/lib/rules/valid-v-html.js
index af4636a5..1df25403 100644
--- a/lib/rules/valid-v-html.js
+++ b/lib/rules/valid-v-html.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-html` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-html.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-if.js b/lib/rules/valid-v-if.js
index b728a829..2e8651b4 100644
--- a/lib/rules/valid-v-if.js
+++ b/lib/rules/valid-v-if.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-if` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-if.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-model.js b/lib/rules/valid-v-model.js
index d818439b..ca2830dc 100644
--- a/lib/rules/valid-v-model.js
+++ b/lib/rules/valid-v-model.js
@@ -116,7 +116,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-model` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-model.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-on.js b/lib/rules/valid-v-on.js
index 7c5471f2..82612d49 100644
--- a/lib/rules/valid-v-on.js
+++ b/lib/rules/valid-v-on.js
@@ -63,7 +63,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-on` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-on.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-once.js b/lib/rules/valid-v-once.js
index 98721858..9d3d8bf8 100644
--- a/lib/rules/valid-v-once.js
+++ b/lib/rules/valid-v-once.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-once` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-once.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-pre.js b/lib/rules/valid-v-pre.js
index 7cd9e30e..0505b9cb 100644
--- a/lib/rules/valid-v-pre.js
+++ b/lib/rules/valid-v-pre.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-pre` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-pre.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-show.js b/lib/rules/valid-v-show.js
index c547b8c3..1da61780 100644
--- a/lib/rules/valid-v-show.js
+++ b/lib/rules/valid-v-show.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-show` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-show.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-slot.js b/lib/rules/valid-v-slot.js
index d1608472..d5c0efd1 100644
--- a/lib/rules/valid-v-slot.js
+++ b/lib/rules/valid-v-slot.js
@@ -241,7 +241,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-slot` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-slot.html'
     },
     fixable: null,
diff --git a/lib/rules/valid-v-text.js b/lib/rules/valid-v-text.js
index c6ef676d..b41e7f2b 100644
--- a/lib/rules/valid-v-text.js
+++ b/lib/rules/valid-v-text.js
@@ -12,7 +12,7 @@ module.exports = {
     type: 'problem',
     docs: {
       description: 'enforce valid `v-text` directives',
-      categories: ['vue3-essential', 'essential'],
+      categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/valid-v-text.html'
     },
     fixable: null,
diff --git a/lib/utils/config-helpers.js b/lib/utils/config-helpers.js
new file mode 100644
index 00000000..cfee2338
--- /dev/null
+++ b/lib/utils/config-helpers.js
@@ -0,0 +1,19 @@
+/**
+ * @fileoverview eslint config helpers
+ * @author 唯然<weiran.zsd@outlook.com>
+ */
+
+function extendRules(config, rules) {
+  const { rules: extendedRules = {}, ...rest } = config
+  return {
+    ...rest,
+    rules: {
+      ...extendedRules,
+      ...rules
+    }
+  }
+}
+
+module.exports = {
+  extendRules
+}
diff --git a/package.json b/package.json
index e1be4ea2..23ca9bb7 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,7 @@
   },
   "dependencies": {
     "@eslint-community/eslint-utils": "^4.4.0",
+    "globals": "^13.24.0",
     "natural-compare": "^1.4.0",
     "nth-check": "^2.1.1",
     "postcss-selector-parser": "^6.0.15",
diff --git a/tools/lib/categories.js b/tools/lib/categories.js
index e5979016..5e340d61 100644
--- a/tools/lib/categories.js
+++ b/tools/lib/categories.js
@@ -23,16 +23,16 @@ const categoryTitles = {
   'vue3-use-with-caution': {
     text: 'Priority D: Use with Caution (Potentially Dangerous Patterns) for Vue.js 3.x'
   },
-  essential: {
+  'vue2-essential': {
     text: 'Priority A: Essential (Error Prevention) for Vue.js 2.x'
   },
-  'strongly-recommended': {
+  'vue2-strongly-recommended': {
     text: 'Priority B: Strongly Recommended (Improving Readability) for Vue.js 2.x'
   },
-  recommended: {
+  'vue2-recommended': {
     text: 'Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) for Vue.js 2.x'
   },
-  'use-with-caution': {
+  'vue2-use-with-caution': {
     text: 'Priority D: Use with Caution (Potentially Dangerous Patterns) for Vue.js 2.x'
   }
 }
diff --git a/tools/lib/configs.js b/tools/lib/configs.js
deleted file mode 100644
index 031f41e6..00000000
--- a/tools/lib/configs.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * @author Michał Sajnóg <https://github.com/michalsnik>
- * See LICENSE file in root directory for full license.
- */
-
-'use strict'
-
-const fs = require('fs')
-const path = require('path')
-const ROOT = path.resolve(__dirname, '../../lib/configs')
-
-module.exports = fs
-  .readdirSync(ROOT)
-  .filter((file) => path.extname(file) === '.js')
-  .map((file) => path.basename(file, '.js'))
diff --git a/tools/lib/utils.js b/tools/lib/utils.js
index eb3f53fd..937288ec 100644
--- a/tools/lib/utils.js
+++ b/tools/lib/utils.js
@@ -2,11 +2,11 @@ module.exports = { getPresetIds, formatItems }
 
 const presetCategories = {
   base: null,
-  essential: 'base',
+  'vue2-essential': 'base',
   'vue3-essential': 'base',
-  'strongly-recommended': 'essential',
+  'vue2-strongly-recommended': 'vue2-essential',
   'vue3-strongly-recommended': 'vue3-essential',
-  recommended: 'strongly-recommended',
+  'vue2-recommended': 'vue2-strongly-recommended',
   'vue3-recommended': 'vue3-strongly-recommended'
   // 'use-with-caution': 'recommended',
   // 'vue3-use-with-caution': 'vue3-recommended'
diff --git a/tools/update-docs-rules-index.js b/tools/update-docs-rules-index.js
index 216cc4e2..017ffe17 100644
--- a/tools/update-docs-rules-index.js
+++ b/tools/update-docs-rules-index.js
@@ -88,19 +88,19 @@ const categoryGroups = [
   {
     title: 'Priority A: Essential (Error Prevention)',
     categoryIdForVue3: 'vue3-essential',
-    categoryIdForVue2: 'essential',
+    categoryIdForVue2: 'vue2-essential',
     useMark: true
   },
   {
     title: 'Priority B: Strongly Recommended (Improving Readability)',
     categoryIdForVue3: 'vue3-strongly-recommended',
-    categoryIdForVue2: 'strongly-recommended',
+    categoryIdForVue2: 'vue2-strongly-recommended',
     useMark: true
   },
   {
     title: 'Priority C: Recommended (Potentially Dangerous Patterns)',
     categoryIdForVue3: 'vue3-recommended',
-    categoryIdForVue2: 'recommended',
+    categoryIdForVue2: 'vue2-recommended',
     useMark: true
   }
 ]
@@ -127,7 +127,7 @@ ${group.description}
         (categoryId) => `\`"plugin:vue/${categoryId}"\``
       )
       const presetsForVue2 = getPresetIds([group.categoryIdForVue2]).map(
-        (categoryId) => `\`"plugin:vue/${categoryId}"\``
+        (categoryId) => `\`"plugin:vue/${categoryId.replace(/^vue2-/, '')}"\`` // 'vue2-recommended' ->  'recommended'
       )
       content += `
 - ${VUE3_EMOJI} Indicates that the rule is for Vue 3 and is included in ${formatItems(
diff --git a/tools/update-docs.js b/tools/update-docs.js
index d64293ae..e32d5a1d 100644
--- a/tools/update-docs.js
+++ b/tools/update-docs.js
@@ -123,7 +123,9 @@ class DocFile {
     }
     if (meta.docs?.categories) {
       const presets = getPresetIds(meta.docs.categories).map(
-        (categoryId) => `\`"plugin:vue/${categoryId}"\``
+        // non-prefixed configs are for vue2
+        // e.g. 'vue2-recommended' ->  'recommended'
+        (categoryId) => `\`"plugin:vue/${categoryId.replace(/^vue2-/, '')}"\``
       )
 
       notes.push(`- :gear: This rule is included in ${formatItems(presets)}.`)
diff --git a/tools/update-lib-configs.js b/tools/update-lib-configs.js
index b8622e4b..a5b8ced7 100644
--- a/tools/update-lib-configs.js
+++ b/tools/update-lib-configs.js
@@ -14,17 +14,17 @@ const path = require('path')
 const { FlatESLint } = require('eslint/use-at-your-own-risk')
 const categories = require('./lib/categories')
 
-const errorCategories = new Set(['base', 'essential', 'vue3-essential'])
+const errorCategories = new Set(['base', 'vue2-essential', 'vue3-essential'])
 
 const extendsCategories = {
   base: null,
-  essential: 'base',
+  'vue2-essential': 'base',
   'vue3-essential': 'base',
-  'strongly-recommended': 'essential',
+  'vue2-strongly-recommended': 'vue2-essential',
   'vue3-strongly-recommended': 'vue3-essential',
-  recommended: 'strongly-recommended',
+  'vue2-recommended': 'vue2-strongly-recommended',
   'vue3-recommended': 'vue3-strongly-recommended',
-  'use-with-caution': 'recommended',
+  'vue2-use-with-caution': 'vue2-recommended',
   'vue3-use-with-caution': 'vue3-recommended'
 }
 
diff --git a/tools/update-lib-flat-configs.js b/tools/update-lib-flat-configs.js
new file mode 100644
index 00000000..1266b787
--- /dev/null
+++ b/tools/update-lib-flat-configs.js
@@ -0,0 +1,101 @@
+/**
+ * @author 唯然<weiran.zsd@outlook.com>
+ * @copyright 2023- 唯然. All rights reserved.
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+/*
+This script updates `lib/configs/flat/*.js` files from rule's meta data.
+*/
+
+const fs = require('fs')
+const path = require('path')
+const { FlatESLint } = require('eslint/use-at-your-own-risk')
+const categories = require('./lib/categories')
+
+const errorCategories = new Set(['base', 'vue2-essential', 'vue3-essential'])
+
+const extendsCategories = {
+  base: null,
+  'vue2-essential': 'base',
+  'vue3-essential': 'base',
+  'vue2-strongly-recommended': 'vue2-essential',
+  'vue3-strongly-recommended': 'vue3-essential',
+  'vue2-recommended': 'vue2-strongly-recommended',
+  'vue3-recommended': 'vue3-strongly-recommended',
+  'vue2-use-with-caution': 'vue2-recommended',
+  'vue3-use-with-caution': 'vue3-recommended'
+}
+
+function formatRules(rules, categoryId) {
+  const obj = Object.fromEntries(
+    rules.map((rule) => {
+      let options = errorCategories.has(categoryId) ? 'error' : 'warn'
+      const defaultOptions =
+        rule.meta && rule.meta.docs && rule.meta.docs.defaultOptions
+      if (defaultOptions) {
+        const v = categoryId.startsWith('vue3') ? 3 : 2
+        const defaultOption = defaultOptions[`vue${v}`]
+        if (defaultOption) {
+          options = [options, ...defaultOption]
+        }
+      }
+      return [rule.ruleId, options]
+    })
+  )
+  return JSON.stringify(obj, null, 2)
+}
+
+function formatCategory(category) {
+  const extendsCategoryId = extendsCategories[category.categoryId]
+  if (extendsCategoryId == null) {
+    return `/*
+ * IMPORTANT!
+ * This file has been automatically generated,
+ * in order to update its content execute "npm run update"
+ */
+const globals = require('globals')
+module.exports = {
+  languageOptions: {
+    parser: require('vue-eslint-parser'),
+    sourceType: 'module',
+    globals: globals.browser
+  },
+  rules: ${formatRules(category.rules, category.categoryId)}
+}
+
+`
+  }
+  return `/*
+ * IMPORTANT!
+ * This file has been automatically generated,
+ * in order to update its content execute "npm run update"
+ */
+'use strict'
+const config = require('./${extendsCategoryId}.js')
+const { extendRules } = require('../../utils/config-helpers.js')
+
+const rules = ${formatRules(category.rules, category.categoryId)}
+
+module.exports = extendRules(config, rules)
+`
+}
+
+// Update files.
+const ROOT = path.resolve(__dirname, '../lib/configs/flat/')
+for (const category of categories) {
+  const filePath = path.join(ROOT, `${category.categoryId}.js`)
+  const content = formatCategory(category)
+
+  fs.writeFileSync(filePath, content)
+}
+
+// Format files.
+async function format() {
+  const linter = new FlatESLint({ fix: true })
+  const report = await linter.lintFiles([ROOT])
+  FlatESLint.outputFixes(report)
+}
+
+format()
diff --git a/tools/update-lib-index.js b/tools/update-lib-index.js
index d1283072..399da68f 100644
--- a/tools/update-lib-index.js
+++ b/tools/update-lib-index.js
@@ -13,7 +13,6 @@ const fs = require('fs')
 const path = require('path')
 const { FlatESLint } = require('eslint/use-at-your-own-risk')
 const rules = require('./lib/rules')
-const configs = require('./lib/configs')
 
 // Update files.
 const filePath = path.resolve(__dirname, '../lib/index.js')
@@ -24,18 +23,13 @@ const content = `/*
  */
 'use strict'
 
-module.exports = {
+const plugin = {
   meta: require('./meta'),
   rules: {
     ${rules
       .map((rule) => `'${rule.name}': require('./rules/${rule.name}')`)
       .join(',\n')}
   },
-  configs: {
-    ${configs
-      .map((config) => `'${config}': require('./configs/${config}')`)
-      .join(',\n')}
-  },
   processors: {
     '.vue': require('./processor')
   },
@@ -52,6 +46,33 @@ module.exports = {
     }
   }
 }
+
+const baseConfig = {plugins: {vue: plugin}}
+
+plugin.configs = {
+  // eslintrc configs
+  base: require('./configs/base'),
+  essential: require('./configs/vue2-essential'),
+  'no-layout-rules': require('./configs/no-layout-rules'),
+  recommended: require('./configs/vue2-recommended'),
+  'strongly-recommended': require('./configs/vue2-strongly-recommended'),
+  'vue3-essential': require('./configs/vue3-essential'),
+  'vue3-recommended': require('./configs/vue3-recommended'),
+  'vue3-strongly-recommended': require('./configs/vue3-strongly-recommended'),
+
+  // flat configs
+  'flat/base': Object.assign({}, baseConfig, require('./configs/flat/base.js')),
+  'flat/vue2-essential': Object.assign({}, baseConfig, require('./configs/flat/vue2-essential.js')),
+  'flat/vue2-recommended': Object.assign({}, baseConfig, require('./configs/flat/vue2-recommended.js')),
+  'flat/vue2-strongly-recommended': Object.assign({}, baseConfig, require('./configs/flat/vue2-strongly-recommended.js')),
+
+  // in flat configs, non-prefixed config is for Vue 3 (unlike eslintrc configs)
+  'flat/essential': Object.assign({}, baseConfig, require('./configs/flat/vue3-essential.js')),
+  'flat/recommended': Object.assign({}, baseConfig, require('./configs/flat/vue3-recommended.js')),
+  'flat/strongly-recommended': Object.assign({}, baseConfig, require('./configs/flat/vue3-strongly-recommended.js')),
+}
+
+module.exports = plugin
 `
 fs.writeFileSync(filePath, content)
 
diff --git a/tools/update.js b/tools/update.js
index 266aedbc..de792d50 100644
--- a/tools/update.js
+++ b/tools/update.js
@@ -7,6 +7,7 @@
 
 require('./update-no-layout-rules-config')
 require('./update-lib-configs')
+require('./update-lib-flat-configs')
 require('./update-lib-index')
 require('./update-docs')
 require('./update-docs-rules-index')


Test patch:
diff --git a/tests/lib/configs/configs.js b/tests/lib/configs/eslintrc.js
similarity index 85%
rename from tests/lib/configs/configs.js
rename to tests/lib/configs/eslintrc.js
index 926fed42..a9febaee 100644
--- a/tests/lib/configs/configs.js
+++ b/tests/lib/configs/eslintrc.js
@@ -3,8 +3,12 @@
 const { ESLint } = require('../../eslint-compat')
 const plugin = require('../../../lib/index')
 
-describe('configs', () => {
+describe('eslintrc configs', () => {
   for (const name of Object.keys(plugin.configs)) {
+    if (name.startsWith('flat/')) {
+      continue
+    }
+
     const configName = `plugin:vue/${name}`
     const eslint = new ESLint({
       overrideConfigFile: true,
diff --git a/tests/lib/configs/flat.js b/tests/lib/configs/flat.js
new file mode 100644
index 00000000..7d9a97d2
--- /dev/null
+++ b/tests/lib/configs/flat.js
@@ -0,0 +1,99 @@
+/**
+ * @fileoverview flat configs test
+ * @author 唯然<weiran.zsd@outlook.com>
+ */
+
+'use strict'
+
+const plugin = require('../../../lib/index')
+const { strict: assert } = require('assert') // node v14 does not support 'assert/strict'
+
+describe('flat configs', () => {
+  it('should export base config', () => {
+    const base = plugin.configs['flat/base']
+    assert.ok(base)
+    assert.equal(typeof base, 'object')
+    assert.strictEqual(base.plugins.vue, plugin)
+    assert.strictEqual(base.rules['vue/comment-directive'], 'error')
+  })
+
+  it('should export essential config', () => {
+    const essential = plugin.configs['flat/essential']
+    assert.ok(essential)
+    assert.equal(typeof essential, 'object')
+    assert.strictEqual(essential.plugins.vue, plugin)
+    assert.strictEqual(essential.rules['vue/comment-directive'], 'error')
+    assert.strictEqual(
+      essential.rules['vue/multi-word-component-names'],
+      'error'
+    )
+  })
+
+  it('should export strongly-recommended config', () => {
+    const stronglyRecommended = plugin.configs['flat/vue2-strongly-recommended']
+    assert.ok(stronglyRecommended)
+    assert.equal(typeof stronglyRecommended, 'object')
+    assert.strictEqual(stronglyRecommended.plugins.vue, plugin)
+    assert.strictEqual(
+      stronglyRecommended.rules['vue/comment-directive'],
+      'error'
+    )
+    assert.strictEqual(
+      stronglyRecommended.rules['vue/multi-word-component-names'],
+      'error'
+    )
+  })
+
+  it('should export recommended config', () => {
+    const recommended = plugin.configs['flat/recommended']
+    assert.ok(recommended)
+    assert.equal(typeof recommended, 'object')
+    assert.strictEqual(recommended.plugins.vue, plugin)
+    assert.strictEqual(recommended.rules['vue/comment-directive'], 'error')
+    assert.strictEqual(
+      recommended.rules['vue/multi-word-component-names'],
+      'error'
+    )
+    assert.strictEqual(recommended.rules['vue/attributes-order'], 'warn')
+  })
+
+  it('should export vue2-essential config', () => {
+    const essential = plugin.configs['flat/vue2-essential']
+    assert.ok(essential)
+    assert.equal(typeof essential, 'object')
+    assert.strictEqual(essential.plugins.vue, plugin)
+    assert.strictEqual(essential.rules['vue/comment-directive'], 'error')
+    assert.strictEqual(
+      essential.rules['vue/multi-word-component-names'],
+      'error'
+    )
+  })
+
+  it('should export vue2-strongly-recommended config', () => {
+    const stronglyRecommended = plugin.configs['flat/vue2-strongly-recommended']
+    assert.ok(stronglyRecommended)
+    assert.equal(typeof stronglyRecommended, 'object')
+    assert.strictEqual(stronglyRecommended.plugins.vue, plugin)
+    assert.strictEqual(
+      stronglyRecommended.rules['vue/comment-directive'],
+      'error'
+    )
+    assert.strictEqual(
+      stronglyRecommended.rules['vue/multi-word-component-names'],
+      'error'
+    )
+  })
+
+  it('should export vue2-recommended config', () => {
+    const recommended = plugin.configs['flat/vue2-recommended']
+    assert.ok(recommended)
+    assert.equal(typeof recommended, 'object')
+    assert.strictEqual(recommended.plugins.vue, plugin)
+    assert.strictEqual(recommended.rules['vue/comment-directive'], 'error')
+    assert.strictEqual(
+      recommended.rules['vue/multi-word-component-names'],
+      'error'
+    )
+    assert.strictEqual(recommended.rules['vue/attributes-order'], 'warn')
+  })
+})