Repository: vuejs/eslint-plugin-vue
Pull request #1983 (2022-09-20 08:52:01)

Add `vue/require-typed-object-prop` rule

FloEdelmann: @przemyslawjanpietrzak Could you please have a look into the CI failures? Also, is this ready to review?
przemyslawjanpietrzak: > 

I saw it, I'm still working on that, but I need a little more time. Is not ready for review yet
przemyslawjanpietrzak: @FloEdelmann sorry for the delay, life happened.

I can not fix lint error `/home/runner/work/eslint-plugin-vue/eslint-plugin-vue/lib/index.js
Warning:   247:5  warning  Unexpected 'todo' comment: 'TODO Remove in the next major version'  no-warning-comments`

I didn't change this file, also I didn't add any todo comments and the branch is synced with the master branch. Can you help me with that
FloEdelmann: No worries. The warning you mentioned is not failing the lint task, but rather the Markdownlint error below:

```
docs/rules/force-types-on-object-props.md:77 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2]
```

Patch:
diff --git a/.eslintignore b/.eslintignore
deleted file mode 100644
index 2353241c..00000000
--- a/.eslintignore
+++ /dev/null
@@ -1,12 +0,0 @@
-/.nyc_output
-/coverage
-/node_modules
-/tests/fixtures
-/tests/integrations/eslint-plugin-import
-
-!.vitepress
-/docs/.vitepress/dist
-/docs/.vitepress/build-system/shim/eslint.mjs
-/docs/.vitepress/build-system/shim/assert.mjs
-/docs/.vitepress/.temp
-/docs/.vitepress/cache
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index e7f17d88..00000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,181 +0,0 @@
-'use strict'
-
-module.exports = {
-  root: true,
-  parserOptions: {
-    ecmaVersion: 'latest'
-  },
-  env: {
-    es6: true,
-    node: true,
-    mocha: true
-  },
-  extends: [
-    'plugin:eslint-plugin/recommended',
-    'prettier',
-    'plugin:node-dependencies/recommended',
-    'plugin:jsonc/recommended-with-jsonc',
-    'plugin:unicorn/recommended'
-  ],
-  plugins: ['eslint-plugin', 'prettier', 'unicorn'],
-  rules: {
-    'accessor-pairs': 2,
-    camelcase: [2, { properties: 'never' }],
-    'constructor-super': 2,
-    eqeqeq: [2, 'allow-null'],
-    'handle-callback-err': [2, '^(err|error)$'],
-    'jsx-quotes': [2, 'prefer-single'],
-    'new-cap': [2, { newIsCap: true, capIsNew: false }],
-    'new-parens': 2,
-    'no-array-constructor': 2,
-    'no-caller': 2,
-    'no-class-assign': 2,
-    'no-cond-assign': 2,
-    'no-const-assign': 2,
-    'no-control-regex': 2,
-    'no-delete-var': 2,
-    'no-dupe-args': 2,
-    'no-dupe-class-members': 2,
-    'no-dupe-keys': 2,
-    'no-duplicate-case': 2,
-    'no-empty-character-class': 2,
-    'no-empty-pattern': 2,
-    'no-eval': 2,
-    'no-ex-assign': 2,
-    'no-extend-native': 2,
-    'no-extra-bind': 2,
-    'no-extra-boolean-cast': 2,
-    'no-extra-parens': [2, 'functions'],
-    'no-fallthrough': 2,
-    'no-floating-decimal': 2,
-    'no-func-assign': 2,
-    'no-implied-eval': 2,
-    'no-inner-declarations': [2, 'functions'],
-    'no-invalid-regexp': 2,
-    'no-irregular-whitespace': 2,
-    'no-iterator': 2,
-    'no-label-var': 2,
-    'no-labels': [2, { allowLoop: false, allowSwitch: false }],
-    'no-lone-blocks': 2,
-    'no-multi-spaces': [2, { ignoreEOLComments: true }],
-    'no-multi-str': 2,
-    'no-native-reassign': 2,
-    'no-negated-in-lhs': 2,
-    'no-new-object': 2,
-    'no-new-require': 2,
-    'no-new-symbol': 2,
-    'no-new-wrappers': 2,
-    'no-obj-calls': 2,
-    'no-octal': 2,
-    'no-octal-escape': 2,
-    'no-path-concat': 2,
-    'no-proto': 2,
-    'no-redeclare': 2,
-    'no-regex-spaces': 2,
-    'no-return-assign': [2, 'except-parens'],
-    'no-self-assign': 2,
-    'no-self-compare': 2,
-    'no-sequences': 2,
-    'no-shadow-restricted-names': 2,
-    'no-sparse-arrays': 2,
-    'no-this-before-super': 2,
-    'no-throw-literal': 2,
-    'no-undef': 2,
-    'no-undef-init': 2,
-    'no-unexpected-multiline': 2,
-    'no-unmodified-loop-condition': 2,
-    'no-unneeded-ternary': [2, { defaultAssignment: false }],
-    'no-unreachable': 2,
-    'no-unsafe-finally': 2,
-    'no-unused-vars': [2, { vars: 'all', args: 'none' }],
-    'no-useless-call': 2,
-    'no-useless-computed-key': 2,
-    'no-useless-constructor': 2,
-    'no-useless-escape': 0,
-    'no-with': 2,
-    'one-var': [2, { initialized: 'never' }],
-    'use-isnan': 2,
-    'valid-typeof': 2,
-    'wrap-iife': [2, 'any'],
-    yoda: [2, 'never'],
-    'prefer-const': 2,
-
-    'prettier/prettier': 'error',
-    'eslint-plugin/report-message-format': ['error', "^[A-Z`'{].*\\.$"],
-    'eslint-plugin/prefer-placeholders': 'error',
-    'eslint-plugin/consistent-output': 'error',
-
-    'no-debugger': 'error',
-    'no-console': 'error',
-    'no-alert': 'error',
-    'no-void': 'error',
-
-    'no-warning-comments': 'warn',
-    'no-var': 'error',
-    'prefer-template': 'error',
-    'object-shorthand': 'error',
-    'prefer-rest-params': 'error',
-    'prefer-arrow-callback': 'error',
-    'prefer-spread': 'error',
-
-    'dot-notation': 'error',
-    'arrow-body-style': 'error',
-
-    'unicorn/consistent-function-scoping': [
-      'error',
-      { checkArrowFunctions: false }
-    ],
-    'unicorn/filename-case': 'off',
-    'unicorn/no-null': 'off',
-    'unicorn/no-array-callback-reference': 'off', // doesn't work well with TypeScript's custom type guards
-    'unicorn/no-negated-condition': 'off', // remove when there are few pull requests (ref: #2146)
-    'unicorn/no-useless-undefined': 'off',
-    'unicorn/prefer-optional-catch-binding': 'off', // not supported by current ESLint parser version
-    'unicorn/prefer-module': 'off',
-    'unicorn/prevent-abbreviations': 'off',
-    'unicorn/prefer-at': 'off', //                 turn off to prevent make breaking changes (ref: #2146)
-    'unicorn/prefer-node-protocol': 'off', //      turn off to prevent make breaking changes (ref: #2146)
-    'unicorn/prefer-string-replace-all': 'off', // turn off to prevent make breaking changes (ref: #2146)
-    'unicorn/prefer-ternary': 'off', //            remove when there are few pull requests (ref: #2146)
-    'unicorn/prefer-top-level-await': 'off', //    turn off to prevent make breaking changes (ref: #2146)
-    'unicorn/switch-case-braces': 'off', //        remove when there are few pull requests (ref: #2146)
-
-    'require-eslint-community': ['error']
-  },
-  overrides: [
-    {
-      files: ['./**/*.vue'],
-      parser: require.resolve('vue-eslint-parser'),
-      parserOptions: {
-        ecmaVersion: 'latest',
-        sourceType: 'module'
-      }
-    },
-    {
-      files: ['lib/rules/*.js'],
-      rules: {
-        'eslint-plugin/no-deprecated-context-methods': 'error',
-        'eslint-plugin/no-only-tests': 'error',
-        'eslint-plugin/prefer-object-rule': 'error',
-        'eslint-plugin/require-meta-docs-description': 'error',
-        'eslint-plugin/require-meta-docs-url': [
-          'error',
-          {
-            pattern: `https://eslint.vuejs.org/rules/{{name}}.html`
-          }
-        ],
-        'eslint-plugin/require-meta-has-suggestions': 'error',
-        'eslint-plugin/require-meta-schema': 'error',
-        'eslint-plugin/require-meta-type': 'error',
-        'no-invalid-meta': 'error',
-        'no-invalid-meta-docs-categories': 'error'
-      }
-    },
-    {
-      files: ['*.json'],
-      rules: {
-        'prettier/prettier': 'off'
-      }
-    }
-  ]
-}
diff --git a/docs/rules/index.md b/docs/rules/index.md
index c2093a8c..1ef0ef39 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -87,7 +87,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
 | [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-use-v-if-with-v-for](./no-use-v-if-with-v-for.md) | disallow using `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: |
@@ -251,6 +251,7 @@ 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-v-else-with-v-for](./no-use-v-else-with-v-for.md) | disallow using `v-else-if`/`v-else` on the same element as `v-for` |  | :hammer: |
 | [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](./no-v-text.md) | disallow use of v-text |  | :hammer: |
@@ -267,6 +268,7 @@ For example:
 | [vue/require-macro-variable-name](./require-macro-variable-name.md) | require a certain macro variable name | :bulb: | :hammer: |
 | [vue/require-name-property](./require-name-property.md) | require a name property in Vue components | :bulb: | :hammer: |
 | [vue/require-prop-comment](./require-prop-comment.md) | require props to have a comment |  | :hammer: |
+| [vue/require-typed-object-prop](./require-typed-object-prop.md) | enforce adding type declarations to object props | :bulb: | :hammer: |
 | [vue/require-typed-ref](./require-typed-ref.md) | require `ref` and `shallowRef` functions to be strongly typed |  | :hammer: |
 | [vue/script-indent](./script-indent.md) | enforce consistent indentation in `<script>` | :wrench: | :lipstick: |
 | [vue/sort-keys](./sort-keys.md) | enforce sort-keys in a manner that is compatible with order-in-components |  | :hammer: |
diff --git a/docs/rules/no-use-v-else-with-v-for.md b/docs/rules/no-use-v-else-with-v-for.md
new file mode 100644
index 00000000..efdb8b29
--- /dev/null
+++ b/docs/rules/no-use-v-else-with-v-for.md
@@ -0,0 +1,54 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-use-v-else-with-v-for
+description: disallow using `v-else-if`/`v-else` on the same element as `v-for`
+---
+# vue/no-use-v-else-with-v-for
+
+> disallow using `v-else-if`/`v-else` on the same element as `v-for`
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+
+## :book: Rule Details
+
+This rule reports elements that have both `v-else-if`/`v-else` and `v-for` directives. That is valid in Vue (`v-else-if`/`v-else` will take precedence), but is confusing to read.
+
+<eslint-code-block :rules="{'vue/no-use-v-else-with-v-for': ['error']}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <div v-if="foo">foo</div>
+  <template v-else-if="bar">
+    <div v-for="x in xs">{{ x }}</div>
+  </template>
+  <template v-else>
+    <div v-for="x in xs">{{ x }}</div>
+  </template>
+
+  <!-- ✗ BAD -->
+  <div v-if="foo">foo</div>
+  <div v-else-if="bar" v-for="x in xs">{{ x }}</div>
+  <div v-else v-for="x in xs">{{ x }}</div>
+</template>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :mute: When Not To Use It
+
+If you don't find using `v-else-if`/`v-else` together with `v-for` confusing to read, you can safely disable this rule.
+
+## :couple: Related Rules
+
+- [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-use-v-else-with-v-for.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-use-v-else-with-v-for.js)
diff --git a/docs/rules/no-use-v-if-with-v-for.md b/docs/rules/no-use-v-if-with-v-for.md
index 4e93ea46..49535f2c 100644
--- a/docs/rules/no-use-v-if-with-v-for.md
+++ b/docs/rules/no-use-v-if-with-v-for.md
@@ -2,12 +2,12 @@
 pageClass: rule-details
 sidebarDepth: 0
 title: vue/no-use-v-if-with-v-for
-description: disallow use v-if on the same element as v-for
+description: disallow using `v-if` on the same element as `v-for`
 since: v4.6.0
 ---
 # vue/no-use-v-if-with-v-for
 
-> disallow use v-if on the same element as v-for
+> disallow using `v-if` on the same element as `v-for`
 
 - :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"`.
 
@@ -88,6 +88,10 @@ There are two common cases where this can be tempting:
 
 </eslint-code-block>
 
+## :couple: Related Rules
+
+- [vue/no-use-v-else-with-v-for](./no-use-v-else-with-v-for.md)
+
 ## :books: Further Reading
 
 - [Style guide - Avoid v-if with v-for](https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for)
diff --git a/docs/rules/require-typed-object-prop.md b/docs/rules/require-typed-object-prop.md
new file mode 100644
index 00000000..3c9fbaac
--- /dev/null
+++ b/docs/rules/require-typed-object-prop.md
@@ -0,0 +1,50 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-typed-object-prop
+description: enforce adding type declarations to object props
+---
+# vue/require-typed-object-prop
+
+> enforce adding type declarations to object props
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- :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
+
+Prevent missing type declarations for non-primitive object props in TypeScript projects.
+
+<eslint-code-block :rules="{'vue/require-typed-object-prop': ['error']}">
+
+```vue
+<script lang="ts">
+export default {
+  props: {
+    // ✗ BAD
+    bad1: Object,
+    bad2: { type: Array },
+      
+    // ✓ GOOD
+    good1: Object as PropType<Anything>,
+    good2: { type: Array as PropType<Anything[]> },
+    good3: [String, Boolean], // or any other primitive type
+  }
+}
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :mute: When Not To Use It
+
+When you're not using TypeScript in the project.
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-typed-object-prop.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-typed-object-prop.js)
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 00000000..78cecffe
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,221 @@
+'use strict'
+
+const globals = require('globals')
+const { FlatCompat } = require('@eslint/eslintrc')
+
+const eslintrc = new FlatCompat({
+  baseDirectory: __dirname
+})
+
+module.exports = [
+  {
+    ignores: [
+      '.nyc_output',
+      'coverage',
+      'node_modules',
+      'tests/fixtures',
+      'tests/integrations/eslint-plugin-import',
+
+      '!.vitepress',
+      'docs/.vitepress/dist',
+      'docs/.vitepress/build-system/shim/eslint.mjs',
+      'docs/.vitepress/build-system/shim/assert.mjs',
+      'docs/.vitepress/build-system/shim/path.mjs',
+      'docs/.vitepress/.temp',
+      'docs/.vitepress/cache'
+    ]
+  },
+  ...eslintrc.plugins('eslint-plugin', 'prettier', 'unicorn'),
+  ...eslintrc.extends(
+    'plugin:eslint-plugin/recommended',
+    'prettier',
+    'plugin:node-dependencies/recommended',
+    'plugin:jsonc/recommended-with-jsonc',
+    'plugin:unicorn/recommended'
+  ),
+  {
+    plugins: {
+      internal: {
+        rules: {
+          'no-invalid-meta': require('./eslint-internal-rules/no-invalid-meta'),
+          'no-invalid-meta-docs-categories': require('./eslint-internal-rules/no-invalid-meta-docs-categories'),
+          'require-eslint-community': require('./eslint-internal-rules/require-eslint-community')
+        }
+      }
+    }
+  },
+
+  {
+    files: ['**/*.js'],
+    languageOptions: {
+      ecmaVersion: 'latest',
+      globals: {
+        ...globals.es6,
+        ...globals.node,
+        ...globals.mocha
+      }
+    },
+    rules: {
+      'accessor-pairs': 2,
+      camelcase: [2, { properties: 'never' }],
+      'constructor-super': 2,
+      eqeqeq: [2, 'allow-null'],
+      'handle-callback-err': [2, '^(err|error)$'],
+      'jsx-quotes': [2, 'prefer-single'],
+      'new-cap': [2, { newIsCap: true, capIsNew: false }],
+      'new-parens': 2,
+      'no-array-constructor': 2,
+      'no-caller': 2,
+      'no-class-assign': 2,
+      'no-cond-assign': 2,
+      'no-const-assign': 2,
+      'no-control-regex': 2,
+      'no-delete-var': 2,
+      'no-dupe-args': 2,
+      'no-dupe-class-members': 2,
+      'no-dupe-keys': 2,
+      'no-duplicate-case': 2,
+      'no-empty-character-class': 2,
+      'no-empty-pattern': 2,
+      'no-eval': 2,
+      'no-ex-assign': 2,
+      'no-extend-native': 2,
+      'no-extra-bind': 2,
+      'no-extra-boolean-cast': 2,
+      'no-extra-parens': [2, 'functions'],
+      'no-fallthrough': 2,
+      'no-floating-decimal': 2,
+      'no-func-assign': 2,
+      'no-implied-eval': 2,
+      'no-inner-declarations': [2, 'functions'],
+      'no-invalid-regexp': 2,
+      'no-irregular-whitespace': 2,
+      'no-iterator': 2,
+      'no-label-var': 2,
+      'no-labels': [2, { allowLoop: false, allowSwitch: false }],
+      'no-lone-blocks': 2,
+      'no-multi-spaces': [2, { ignoreEOLComments: true }],
+      'no-multi-str': 2,
+      'no-native-reassign': 2,
+      'no-negated-in-lhs': 2,
+      'no-new-object': 2,
+      'no-new-require': 2,
+      'no-new-symbol': 2,
+      'no-new-wrappers': 2,
+      'no-obj-calls': 2,
+      'no-octal': 2,
+      'no-octal-escape': 2,
+      'no-path-concat': 2,
+      'no-proto': 2,
+      'no-redeclare': 2,
+      'no-regex-spaces': 2,
+      'no-return-assign': [2, 'except-parens'],
+      'no-self-assign': 2,
+      'no-self-compare': 2,
+      'no-sequences': 2,
+      'no-shadow-restricted-names': 2,
+      'no-sparse-arrays': 2,
+      'no-this-before-super': 2,
+      'no-throw-literal': 2,
+      'no-undef': 2,
+      'no-undef-init': 2,
+      'no-unexpected-multiline': 2,
+      'no-unmodified-loop-condition': 2,
+      'no-unneeded-ternary': [2, { defaultAssignment: false }],
+      'no-unreachable': 2,
+      'no-unsafe-finally': 2,
+      'no-unused-vars': [2, { vars: 'all', args: 'none' }],
+      'no-useless-call': 2,
+      'no-useless-computed-key': 2,
+      'no-useless-constructor': 2,
+      'no-useless-escape': 0,
+      'no-with': 2,
+      'one-var': [2, { initialized: 'never' }],
+      'use-isnan': 2,
+      'valid-typeof': 2,
+      'wrap-iife': [2, 'any'],
+      yoda: [2, 'never'],
+      'prefer-const': 2,
+
+      'prettier/prettier': 'error',
+      'eslint-plugin/report-message-format': ['error', "^[A-Z`'{].*\\.$"],
+      'eslint-plugin/prefer-placeholders': 'error',
+      'eslint-plugin/consistent-output': 'error',
+
+      'no-debugger': 'error',
+      'no-console': 'error',
+      'no-alert': 'error',
+      'no-void': 'error',
+
+      'no-warning-comments': 'warn',
+      'no-var': 'error',
+      'prefer-template': 'error',
+      'object-shorthand': 'error',
+      'prefer-rest-params': 'error',
+      'prefer-arrow-callback': 'error',
+      'prefer-spread': 'error',
+
+      'dot-notation': 'error',
+      'arrow-body-style': 'error',
+
+      'unicorn/consistent-function-scoping': [
+        'error',
+        { checkArrowFunctions: false }
+      ],
+      'unicorn/filename-case': 'off',
+      'unicorn/no-null': 'off',
+      'unicorn/no-array-callback-reference': 'off', // doesn't work well with TypeScript's custom type guards
+      'unicorn/no-negated-condition': 'off', // remove when there are few pull requests (ref: #2146)
+      'unicorn/no-useless-undefined': 'off',
+      'unicorn/prefer-optional-catch-binding': 'off', // not supported by current ESLint parser version
+      'unicorn/prefer-module': 'off',
+      'unicorn/prevent-abbreviations': 'off',
+      'unicorn/prefer-at': 'off', //                 turn off to prevent make breaking changes (ref: #2146)
+      'unicorn/prefer-node-protocol': 'off', //      turn off to prevent make breaking changes (ref: #2146)
+      'unicorn/prefer-string-replace-all': 'off', // turn off to prevent make breaking changes (ref: #2146)
+      'unicorn/prefer-ternary': 'off', //            remove when there are few pull requests (ref: #2146)
+      'unicorn/prefer-top-level-await': 'off', //    turn off to prevent make breaking changes (ref: #2146)
+      'unicorn/switch-case-braces': 'off', //        remove when there are few pull requests (ref: #2146)
+
+      'internal/require-eslint-community': ['error']
+    }
+  },
+  {
+    files: ['./**/*.vue'],
+    languageOptions: {
+      ecmaVersion: 'latest',
+      sourceType: 'module',
+      parser: require('vue-eslint-parser')
+      // parserOptions: {
+      //   ecmaVersion: 'latest',
+      //   sourceType: 'module'
+      // }
+    }
+  },
+  {
+    files: ['lib/rules/*.js'],
+    rules: {
+      'eslint-plugin/no-deprecated-context-methods': 'error',
+      'eslint-plugin/no-only-tests': 'error',
+      'eslint-plugin/prefer-object-rule': 'error',
+      'eslint-plugin/require-meta-docs-description': 'error',
+      'eslint-plugin/require-meta-docs-url': [
+        'error',
+        {
+          pattern: `https://eslint.vuejs.org/rules/{{name}}.html`
+        }
+      ],
+      'eslint-plugin/require-meta-has-suggestions': 'error',
+      'eslint-plugin/require-meta-schema': 'error',
+      'eslint-plugin/require-meta-type': 'error',
+      'internal/no-invalid-meta': 'error',
+      'internal/no-invalid-meta-docs-categories': 'error'
+    }
+  },
+  {
+    files: ['**/*.json'],
+    rules: {
+      'prettier/prettier': 'off'
+    }
+  }
+]
diff --git a/lib/index.js b/lib/index.js
index 66ab217b..6c52515a 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -147,6 +147,7 @@ module.exports = {
     'no-unused-refs': require('./rules/no-unused-refs'),
     'no-unused-vars': require('./rules/no-unused-vars'),
     'no-use-computed-property-like-method': require('./rules/no-use-computed-property-like-method'),
+    'no-use-v-else-with-v-for': require('./rules/no-use-v-else-with-v-for'),
     'no-use-v-if-with-v-for': require('./rules/no-use-v-if-with-v-for'),
     'no-useless-concat': require('./rules/no-useless-concat'),
     'no-useless-mustaches': require('./rules/no-useless-mustaches'),
@@ -191,6 +192,7 @@ module.exports = {
     'require-render-return': require('./rules/require-render-return'),
     'require-slots-as-functions': require('./rules/require-slots-as-functions'),
     'require-toggle-inside-transition': require('./rules/require-toggle-inside-transition'),
+    'require-typed-object-prop': require('./rules/require-typed-object-prop'),
     'require-typed-ref': require('./rules/require-typed-ref'),
     'require-v-for-key': require('./rules/require-v-for-key'),
     'require-valid-default-prop': require('./rules/require-valid-default-prop'),
diff --git a/lib/rules/array-bracket-newline.js b/lib/rules/array-bracket-newline.js
index c667fc7e..f4612db5 100644
--- a/lib/rules/array-bracket-newline.js
+++ b/lib/rules/array-bracket-newline.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('array-bracket-newline', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/array-bracket-spacing.js b/lib/rules/array-bracket-spacing.js
index 0541d058..d32b0006 100644
--- a/lib/rules/array-bracket-spacing.js
+++ b/lib/rules/array-bracket-spacing.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('array-bracket-spacing', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/array-element-newline.js b/lib/rules/array-element-newline.js
index 251d72de..645ecc17 100644
--- a/lib/rules/array-element-newline.js
+++ b/lib/rules/array-element-newline.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('array-element-newline', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/arrow-spacing.js b/lib/rules/arrow-spacing.js
index f31ab3c3..a64d4967 100644
--- a/lib/rules/arrow-spacing.js
+++ b/lib/rules/arrow-spacing.js
@@ -5,5 +5,5 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('arrow-spacing')
diff --git a/lib/rules/block-spacing.js b/lib/rules/block-spacing.js
index 0634f2b6..f234d5e9 100644
--- a/lib/rules/block-spacing.js
+++ b/lib/rules/block-spacing.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('block-spacing', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/brace-style.js b/lib/rules/brace-style.js
index 5dad2c29..1672b936 100644
--- a/lib/rules/brace-style.js
+++ b/lib/rules/brace-style.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('brace-style', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js
index 4b0898fe..a37da86b 100644
--- a/lib/rules/camelcase.js
+++ b/lib/rules/camelcase.js
@@ -5,5 +5,5 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('camelcase')
diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js
index 6fb48b79..c338d678 100644
--- a/lib/rules/comma-dangle.js
+++ b/lib/rules/comma-dangle.js
@@ -5,5 +5,5 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('comma-dangle')
diff --git a/lib/rules/comma-spacing.js b/lib/rules/comma-spacing.js
index 6bad7e4b..0b5b7490 100644
--- a/lib/rules/comma-spacing.js
+++ b/lib/rules/comma-spacing.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('comma-spacing', {
   skipDynamicArguments: true,
   skipDynamicArgumentsReport: true,
diff --git a/lib/rules/comma-style.js b/lib/rules/comma-style.js
index 960e6d6d..9263e4fc 100644
--- a/lib/rules/comma-style.js
+++ b/lib/rules/comma-style.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('comma-style', {
   create(_context, { coreHandlers }) {
     return {
diff --git a/lib/rules/dot-location.js b/lib/rules/dot-location.js
index 08f96127..be002b6e 100644
--- a/lib/rules/dot-location.js
+++ b/lib/rules/dot-location.js
@@ -5,5 +5,5 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('dot-location')
diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js
index bd72bd82..e7a2b0f3 100644
--- a/lib/rules/dot-notation.js
+++ b/lib/rules/dot-notation.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('dot-notation', {
   applyDocument: true
 })
diff --git a/lib/rules/eqeqeq.js b/lib/rules/eqeqeq.js
index d918b4d8..29d88904 100644
--- a/lib/rules/eqeqeq.js
+++ b/lib/rules/eqeqeq.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('eqeqeq', {
   applyDocument: true
 })
diff --git a/lib/rules/func-call-spacing.js b/lib/rules/func-call-spacing.js
index 2054376c..1a4554df 100644
--- a/lib/rules/func-call-spacing.js
+++ b/lib/rules/func-call-spacing.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('func-call-spacing', {
   skipDynamicArguments: true,
   applyDocument: true
diff --git a/lib/rules/html-self-closing.js b/lib/rules/html-self-closing.js
index 19fc4b58..6cbbb7a6 100644
--- a/lib/rules/html-self-closing.js
+++ b/lib/rules/html-self-closing.js
@@ -131,7 +131,7 @@ module.exports = {
       context,
       {
         VElement(node) {
-          if (hasInvalidEOF) {
+          if (hasInvalidEOF || node.parent.type === 'VDocumentFragment') {
             return
           }
 
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
index a949c18a..3dfe4c9a 100644
--- a/lib/rules/key-spacing.js
+++ b/lib/rules/key-spacing.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('key-spacing', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js
index af1b2848..c1ce130f 100644
--- a/lib/rules/keyword-spacing.js
+++ b/lib/rules/keyword-spacing.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('keyword-spacing', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/multiline-ternary.js b/lib/rules/multiline-ternary.js
index 58415eb2..08e99fd2 100644
--- a/lib/rules/multiline-ternary.js
+++ b/lib/rules/multiline-ternary.js
@@ -6,7 +6,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('multiline-ternary', {
   skipDynamicArguments: true,
   applyDocument: true
diff --git a/lib/rules/no-console.js b/lib/rules/no-console.js
index cf425403..39698f2e 100644
--- a/lib/rules/no-console.js
+++ b/lib/rules/no-console.js
@@ -6,8 +6,9 @@
 
 const utils = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = utils.wrapCoreRule('no-console', {
+  skipCoreHandlers: true,
   create(context) {
     const options = context.options[0] || {}
     const allowed = options.allow || []
diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js
index a6b62431..e485f5df 100644
--- a/lib/rules/no-constant-condition.js
+++ b/lib/rules/no-constant-condition.js
@@ -7,7 +7,7 @@ const { wrapCoreRule } = require('../utils')
 
 const conditionalDirectiveNames = new Set(['v-show', 'v-if', 'v-else-if'])
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('no-constant-condition', {
   create(_context, { coreHandlers }) {
     return {
diff --git a/lib/rules/no-empty-pattern.js b/lib/rules/no-empty-pattern.js
index 91c7472d..200a278d 100644
--- a/lib/rules/no-empty-pattern.js
+++ b/lib/rules/no-empty-pattern.js
@@ -5,5 +5,5 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('no-empty-pattern')
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index bf1ff1e4..43a93392 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -7,7 +7,7 @@ const { isParenthesized } = require('@eslint-community/eslint-utils')
 const { wrapCoreRule } = require('../utils')
 const { getStyleVariablesContext } = require('../utils/style-variables')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('no-extra-parens', {
   skipDynamicArguments: true,
   applyDocument: true,
diff --git a/lib/rules/no-loss-of-precision.js b/lib/rules/no-loss-of-precision.js
index 5d18cb6b..f426acc5 100644
--- a/lib/rules/no-loss-of-precision.js
+++ b/lib/rules/no-loss-of-precision.js
@@ -6,7 +6,7 @@
 
 const utils = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = utils.wrapCoreRule('no-loss-of-precision', {
   applyDocument: true
 })
diff --git a/lib/rules/no-restricted-syntax.js b/lib/rules/no-restricted-syntax.js
index 19feb14e..2b3e594f 100644
--- a/lib/rules/no-restricted-syntax.js
+++ b/lib/rules/no-restricted-syntax.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('no-restricted-syntax', {
   applyDocument: true
 })
diff --git a/lib/rules/no-sparse-arrays.js b/lib/rules/no-sparse-arrays.js
index 48f7390e..d6e91db5 100644
--- a/lib/rules/no-sparse-arrays.js
+++ b/lib/rules/no-sparse-arrays.js
@@ -5,5 +5,5 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('no-sparse-arrays')
diff --git a/lib/rules/no-use-v-else-with-v-for.js b/lib/rules/no-use-v-else-with-v-for.js
new file mode 100644
index 00000000..dd6dec41
--- /dev/null
+++ b/lib/rules/no-use-v-else-with-v-for.js
@@ -0,0 +1,44 @@
+'use strict'
+
+const { defineTemplateBodyVisitor, hasDirective } = require('../utils')
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description:
+        'disallow using `v-else-if`/`v-else` on the same element as `v-for`',
+      categories: null,
+      url: 'https://eslint.vuejs.org/rules/no-use-v-else-with-v-for.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      unexpectedDirectiveWithVFor:
+        'Unexpected `{{ directiveName }}` and `v-for` on the same element. Move `{{ directiveName }}` to a wrapper element instead.'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    return defineTemplateBodyVisitor(context, {
+      /** @param {VDirective} node */
+      "VAttribute[directive=true][key.name.name='for']"(node) {
+        const element = node.parent.parent
+
+        if (hasDirective(element, 'else-if')) {
+          context.report({
+            node: element,
+            messageId: 'unexpectedDirectiveWithVFor',
+            data: { directiveName: 'v-else-if' }
+          })
+        } else if (hasDirective(element, 'else')) {
+          context.report({
+            node: element,
+            messageId: 'unexpectedDirectiveWithVFor',
+            data: { directiveName: 'v-else' }
+          })
+        }
+      }
+    })
+  }
+}
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 650eb0d9..e3e7cafc 100644
--- a/lib/rules/no-use-v-if-with-v-for.js
+++ b/lib/rules/no-use-v-if-with-v-for.js
@@ -38,7 +38,7 @@ module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
-      description: 'disallow use v-if on the same element as v-for',
+      description: 'disallow using `v-if` on the same element as `v-for`',
       categories: ['vue3-essential', 'essential'],
       url: 'https://eslint.vuejs.org/rules/no-use-v-if-with-v-for.html'
     },
diff --git a/lib/rules/no-useless-concat.js b/lib/rules/no-useless-concat.js
index f2c51b90..88191ae0 100644
--- a/lib/rules/no-useless-concat.js
+++ b/lib/rules/no-useless-concat.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('no-useless-concat', {
   applyDocument: true
 })
diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js
index 5aa99411..ce180dcd 100644
--- a/lib/rules/object-curly-newline.js
+++ b/lib/rules/object-curly-newline.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('object-curly-newline', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/object-curly-spacing.js b/lib/rules/object-curly-spacing.js
index b7c669c4..6a1ee221 100644
--- a/lib/rules/object-curly-spacing.js
+++ b/lib/rules/object-curly-spacing.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('object-curly-spacing', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/object-property-newline.js b/lib/rules/object-property-newline.js
index dacd8244..1e077bf6 100644
--- a/lib/rules/object-property-newline.js
+++ b/lib/rules/object-property-newline.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('object-property-newline', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/object-shorthand.js b/lib/rules/object-shorthand.js
index 79a3c8a3..d58b8b6a 100644
--- a/lib/rules/object-shorthand.js
+++ b/lib/rules/object-shorthand.js
@@ -6,7 +6,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('object-shorthand', {
   skipDynamicArguments: true
 })
diff --git a/lib/rules/operator-linebreak.js b/lib/rules/operator-linebreak.js
index ad92aa74..db752efb 100644
--- a/lib/rules/operator-linebreak.js
+++ b/lib/rules/operator-linebreak.js
@@ -5,5 +5,5 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('operator-linebreak')
diff --git a/lib/rules/prefer-template.js b/lib/rules/prefer-template.js
index 6fac1a05..e9e50da8 100644
--- a/lib/rules/prefer-template.js
+++ b/lib/rules/prefer-template.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('prefer-template', {
   applyDocument: true
 })
diff --git a/lib/rules/quote-props.js b/lib/rules/quote-props.js
index aabfc2bf..27e21077 100644
--- a/lib/rules/quote-props.js
+++ b/lib/rules/quote-props.js
@@ -6,7 +6,7 @@
 
 const { wrapCoreRule, flatten } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('quote-props', {
   skipDynamicArguments: true,
   preprocess(context, { wrapContextToOverrideProperties, defineVisitor }) {
diff --git a/lib/rules/require-typed-object-prop.js b/lib/rules/require-typed-object-prop.js
new file mode 100644
index 00000000..dda7b834
--- /dev/null
+++ b/lib/rules/require-typed-object-prop.js
@@ -0,0 +1,150 @@
+/**
+ * @author Przemysław Jan Beigert
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @param {RuleContext} context
+ * @param {Identifier} identifierNode
+ */
+const checkPropIdentifierType = (context, identifierNode) => {
+  if (identifierNode.name === 'Object' || identifierNode.name === 'Array') {
+    const arrayTypeSuggestion = identifierNode.name === 'Array' ? '[]' : ''
+    context.report({
+      node: identifierNode,
+      messageId: 'expectedTypeAnnotation',
+      suggest: [
+        {
+          messageId: 'addTypeAnnotation',
+          data: { type: `any${arrayTypeSuggestion}` },
+          fix(fixer) {
+            return fixer.insertTextAfter(
+              identifierNode,
+              ` as PropType<any${arrayTypeSuggestion}>`
+            )
+          }
+        },
+        {
+          messageId: 'addTypeAnnotation',
+          data: { type: `unknown${arrayTypeSuggestion}` },
+          fix(fixer) {
+            return fixer.insertTextAfter(
+              identifierNode,
+              ` as PropType<unknown${arrayTypeSuggestion}>`
+            )
+          }
+        }
+      ]
+    })
+  }
+}
+
+/**
+ * @param {RuleContext} context
+ * @param {ArrayExpression} arrayNode
+ */
+const checkPropArrayType = (context, arrayNode) => {
+  for (const elementNode of arrayNode.elements) {
+    if (elementNode?.type === 'Identifier') {
+      checkPropIdentifierType(context, elementNode)
+    }
+  }
+}
+
+/**
+ * @param {RuleContext} context
+ * @param {ObjectExpression} objectNode
+ */
+const checkPropObjectType = (context, objectNode) => {
+  const typeProperty = objectNode.properties.find(
+    (prop) =>
+      prop.type === 'Property' &&
+      prop.key.type === 'Identifier' &&
+      prop.key.name === 'type'
+  )
+  if (!typeProperty || typeProperty.type !== 'Property') {
+    return
+  }
+
+  if (typeProperty.value.type === 'Identifier') {
+    // `foo: { type: String }`
+    checkPropIdentifierType(context, typeProperty.value)
+  } else if (typeProperty.value.type === 'ArrayExpression') {
+    // `foo: { type: [String, Boolean] }`
+    checkPropArrayType(context, typeProperty.value)
+  }
+}
+
+/**
+ * @param {import('../utils').ComponentProp} prop
+ * @param {RuleContext} context
+ */
+const checkProp = (prop, context) => {
+  if (prop.type !== 'object') {
+    return
+  }
+
+  switch (prop.node.value.type) {
+    case 'Identifier': {
+      // e.g. `foo: String`
+      checkPropIdentifierType(context, prop.node.value)
+      break
+    }
+    case 'ArrayExpression': {
+      // e.g. `foo: [String, Boolean]`
+      checkPropArrayType(context, prop.node.value)
+      break
+    }
+    case 'ObjectExpression': {
+      // e.g. `foo: { type: … }`
+      checkPropObjectType(context, prop.node.value)
+      return
+    }
+  }
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'enforce adding type declarations to object props',
+      categories: undefined,
+      recommended: false,
+      url: 'https://eslint.vuejs.org/rules/require-typed-object-prop.html'
+    },
+    fixable: null,
+    schema: [],
+    // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- `context.report` with suggestion is not recognized in `checkPropIdentifierType`
+    hasSuggestions: true,
+    messages: {
+      expectedTypeAnnotation: 'Expected type annotation on object prop.',
+      addTypeAnnotation: 'Add `{{ type }}` type annotation.'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    return utils.compositingVisitors(
+      utils.defineScriptSetupVisitor(context, {
+        onDefinePropsEnter(_node, props) {
+          for (const prop of props) {
+            checkProp(prop, context)
+          }
+        }
+      }),
+      utils.executeOnVue(context, (obj) => {
+        const props = utils.getComponentPropsFromOptions(obj)
+
+        for (const prop of props) {
+          checkProp(prop, context)
+        }
+      })
+    )
+  }
+}
diff --git a/lib/rules/space-in-parens.js b/lib/rules/space-in-parens.js
index 3a9a7e0d..fff3ee17 100644
--- a/lib/rules/space-in-parens.js
+++ b/lib/rules/space-in-parens.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('space-in-parens', {
   skipDynamicArguments: true,
   skipDynamicArgumentsReport: true,
diff --git a/lib/rules/space-infix-ops.js b/lib/rules/space-infix-ops.js
index dd0a8af2..8f0083a9 100644
--- a/lib/rules/space-infix-ops.js
+++ b/lib/rules/space-infix-ops.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('space-infix-ops', {
   skipDynamicArguments: true,
   applyDocument: true
diff --git a/lib/rules/space-unary-ops.js b/lib/rules/space-unary-ops.js
index e3ece4c2..a5fd74e3 100644
--- a/lib/rules/space-unary-ops.js
+++ b/lib/rules/space-unary-ops.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('space-unary-ops', {
   skipDynamicArguments: true,
   applyDocument: true
diff --git a/lib/rules/template-curly-spacing.js b/lib/rules/template-curly-spacing.js
index 549c790b..1fb3fd20 100644
--- a/lib/rules/template-curly-spacing.js
+++ b/lib/rules/template-curly-spacing.js
@@ -5,7 +5,7 @@
 
 const { wrapCoreRule } = require('../utils')
 
-// eslint-disable-next-line no-invalid-meta, no-invalid-meta-docs-categories
+// eslint-disable-next-line internal/no-invalid-meta, internal/no-invalid-meta-docs-categories
 module.exports = wrapCoreRule('template-curly-spacing', {
   skipDynamicArguments: true,
   applyDocument: true
diff --git a/lib/utils/index.js b/lib/utils/index.js
index 1c54ef3f..337b31a8 100644
--- a/lib/utils/index.js
+++ b/lib/utils/index.js
@@ -382,6 +382,7 @@ module.exports = {
    * @param {boolean} [options.skipDynamicArguments] If `true`, skip validation within dynamic arguments.
    * @param {boolean} [options.skipDynamicArgumentsReport] If `true`, skip report within dynamic arguments.
    * @param {boolean} [options.applyDocument] If `true`, apply check to document fragment.
+   * @param {boolean} [options.skipCoreHandlers] If `true`, skip core handlers.
    * @param {WrapCoreRulePreprocess} [options.preprocess] Preprocess to calling create of core rule.
    * @param {WrapCoreRuleCreate} [options.create] If define, extend core rule.
    * @returns {RuleModule} The wrapped rule implementation.
@@ -418,6 +419,7 @@ module.exports = {
       categories,
       skipDynamicArguments,
       skipDynamicArgumentsReport,
+      skipCoreHandlers,
       applyDocument,
       preprocess,
       create
@@ -457,7 +459,9 @@ module.exports = {
         }
 
         const coreHandlers = coreRule.create(context)
-        compositingVisitors(handlers, coreHandlers)
+        if (!skipCoreHandlers) {
+          compositingVisitors(handlers, coreHandlers)
+        }
 
         // Move `Program` handlers to `VElement[parent.type!='VElement']`
         if (handlers.Program) {
diff --git a/package.json b/package.json
index d896680f..75e3ebb7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint-plugin-vue",
-  "version": "9.15.0",
+  "version": "9.15.1",
   "description": "Official ESLint plugin for Vue.js",
   "main": "lib/index.js",
   "scripts": {
@@ -12,8 +12,8 @@
     "cover": "npm run cover:test && npm run cover:report",
     "cover:test": "nyc npm run test:base -- --timeout 60000",
     "cover:report": "nyc report --reporter=html",
-    "lint": "eslint . --rulesdir eslint-internal-rules && markdownlint \"**/*.md\"",
-    "lint:fix": "eslint . --rulesdir eslint-internal-rules --fix && markdownlint \"**/*.md\" --fix",
+    "lint": "eslint . && markdownlint \"**/*.md\"",
+    "lint:fix": "eslint . --fix && markdownlint \"**/*.md\" --fix",
     "tsc": "tsc",
     "preversion": "npm test && git add .",
     "version": "env-cmd -e version npm run update && npm run lint -- --fix && git add .",
diff --git a/tools/update-lib-configs.js b/tools/update-lib-configs.js
index f75aff4f..b8622e4b 100644
--- a/tools/update-lib-configs.js
+++ b/tools/update-lib-configs.js
@@ -11,7 +11,7 @@ This script updates `lib/configs/*.js` files from rule's meta data.
 
 const fs = require('fs')
 const path = require('path')
-const eslint = require('eslint')
+const { FlatESLint } = require('eslint/use-at-your-own-risk')
 const categories = require('./lib/categories')
 
 const errorCategories = new Set(['base', 'essential', 'vue3-essential'])
@@ -95,9 +95,9 @@ for (const category of categories) {
 
 // Format files.
 async function format() {
-  const linter = new eslint.ESLint({ fix: true })
+  const linter = new FlatESLint({ fix: true })
   const report = await linter.lintFiles([ROOT])
-  eslint.ESLint.outputFixes(report)
+  FlatESLint.outputFixes(report)
 }
 
 format()
diff --git a/tools/update-lib-index.js b/tools/update-lib-index.js
index 5a53b52b..d1283072 100644
--- a/tools/update-lib-index.js
+++ b/tools/update-lib-index.js
@@ -11,7 +11,7 @@ This script updates `lib/index.js` file from rule's meta data.
 
 const fs = require('fs')
 const path = require('path')
-const eslint = require('eslint')
+const { FlatESLint } = require('eslint/use-at-your-own-risk')
 const rules = require('./lib/rules')
 const configs = require('./lib/configs')
 
@@ -57,9 +57,9 @@ fs.writeFileSync(filePath, content)
 
 // Format files.
 async function format() {
-  const linter = new eslint.ESLint({ fix: true })
+  const linter = new FlatESLint({ fix: true })
   const report = await linter.lintFiles([filePath])
-  eslint.ESLint.outputFixes(report)
+  FlatESLint.outputFixes(report)
 }
 
 format()


Test patch:
diff --git a/tests/lib/rules/html-self-closing.js b/tests/lib/rules/html-self-closing.js
index c600fdaf..5589c3da 100644
--- a/tests/lib/rules/html-self-closing.js
+++ b/tests/lib/rules/html-self-closing.js
@@ -64,6 +64,9 @@ tester.run('html-self-closing', rule, {
     '<template><div a=">test</div></template>',
     '<template><div><!--test</div></template>',
 
+    // Empty top-level tags
+    '<template></template><script></script><docs></docs>',
+
     // https://github.com/vuejs/eslint-plugin-vue/issues/1403
     {
       code: `
diff --git a/tests/lib/rules/no-console.js b/tests/lib/rules/no-console.js
index a0c27304..7b6acfea 100644
--- a/tests/lib/rules/no-console.js
+++ b/tests/lib/rules/no-console.js
@@ -53,6 +53,15 @@ tester.run('no-console', rule, {
       </template>
       `,
       options: [{ allow: ['log', 'warn', 'info'] }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template><div /></template>
+      <script setup>
+        console.log('test')
+      </script>
+      `
     }
   ],
   invalid: [
diff --git a/tests/lib/rules/no-use-v-else-with-v-for.js b/tests/lib/rules/no-use-v-else-with-v-for.js
new file mode 100644
index 00000000..8778cc12
--- /dev/null
+++ b/tests/lib/rules/no-use-v-else-with-v-for.js
@@ -0,0 +1,98 @@
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-use-v-else-with-v-for')
+
+const tester = new RuleTester({
+  parser: require.resolve('vue-eslint-parser'),
+  parserOptions: { ecmaVersion: 2015 }
+})
+
+tester.run('no-use-v-else-with-v-for', rule, {
+  valid: [
+    {
+      // caught by `vue/no-use-v-if-with-v-for`
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-if="foo" v-for="x in xs">{{ x }}</div>
+        </template>
+      `
+    },
+    {
+      // `v-if`/`v-else-if`/`v-else` only
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-if="foo">{{ x }}</div>
+          <div v-else-if="foo">{{ x }}</div>
+          <div v-else="foo">{{ x }}</div>
+        </template>
+      `
+    },
+    {
+      // `v-for` only
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-for="x in xs">{{ x }}</div>
+        </template>
+      `
+    },
+    {
+      // `v-else-if`/`v-else` in template + `v-for`
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-if="foo">foo</div>
+          <template v-else-if="bar">
+            <div v-for="x in xs">{{ x }}</div>
+          </template>
+          <template v-else>
+            <div v-for="x in xs">{{ x }}</div>
+          </template>
+        </template>
+      `
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-if="foo">foo</div>
+          <div v-else v-for="x in xs">{{ x }}</div>
+        </template>
+      `,
+      errors: [
+        {
+          message:
+            'Unexpected `v-else` and `v-for` on the same element. Move `v-else` to a wrapper element instead.',
+          line: 4,
+          endLine: 4,
+          column: 11,
+          endColumn: 52
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div v-if="foo">foo</div>
+          <div v-else-if="bar" v-for="x in xs">{{ x }}</div>
+        </template>
+      `,
+      errors: [
+        {
+          message:
+            'Unexpected `v-else-if` and `v-for` on the same element. Move `v-else-if` to a wrapper element instead.',
+          line: 4,
+          endLine: 4,
+          column: 11,
+          endColumn: 61
+        }
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/require-typed-object-prop.js b/tests/lib/rules/require-typed-object-prop.js
new file mode 100644
index 00000000..5d93965a
--- /dev/null
+++ b/tests/lib/rules/require-typed-object-prop.js
@@ -0,0 +1,621 @@
+/**
+ * @author Przemysław Jan Beigert
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/require-typed-object-prop')
+
+const ruleTester = new RuleTester()
+
+ruleTester.run('require-typed-object-prop', rule, {
+  valid: [
+    // empty
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default {
+        props: {}
+      }
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default Vue.extend({
+        props: {}
+      });
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      <script setup lang="ts">
+      defineProps({});
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser')
+    },
+    // array props
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default {
+        props: ['foo']
+      }
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default Vue.extend({
+        props: ['foo']
+      });
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      <script setup lang="ts">
+      defineProps(['foo']);
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser')
+    },
+    // primitive props
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default {
+        props: { foo: String }
+      }
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default Vue.extend({
+        props: { foo: String }
+      });
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: String });
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser')
+    },
+    // union
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default {
+        props: { foo: [Number, String, Boolean] }
+      }
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default Vue.extend({
+        props: { foo: [Number, String, Boolean] }
+      });
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: [Number, String, Boolean] });
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser')
+    },
+    // function
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default {
+        props: { foo: someFunction() }
+      }
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default Vue.extend({
+        props: { foo: someFunction() }
+      });
+      `
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: someFunction() });
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser')
+    },
+    // typed object
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default {
+        props: { foo: Object as PropType<User> }
+      }
+      `,
+      parser: require.resolve('@typescript-eslint/parser')
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default {
+        props: { foo: Array as PropType<User[]> }
+      }
+      `,
+      parser: require.resolve('@typescript-eslint/parser')
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      code: `
+      export default Vue.extend({
+        props: { foo: Object as PropType<User> }
+      });
+      `,
+      parser: require.resolve('@typescript-eslint/parser')
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: Object as PropType<User> });
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser')
+    },
+
+    {
+      filename: 'test.vue',
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      code: `
+      export default {
+        props: { foo: Object as () => User }
+      }
+      `,
+      parser: require.resolve('@typescript-eslint/parser')
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      code: `
+      export default Vue.extend({
+        props: { foo: Object as () => User }
+      });
+      `,
+      parser: require.resolve('@typescript-eslint/parser')
+    },
+    {
+      filename: 'test.vue',
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: Object as () => User });
+      </script>
+      `,
+      parser: require.resolve('vue-eslint-parser')
+    },
+    // any
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: { type: Object as any } });
+      </script>
+      `,
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      parser: require.resolve('vue-eslint-parser')
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script lang="ts">
+        export default {
+          props: {
+            foo: { type: Object as any }
+          }
+        };
+        </script>
+      `,
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      parser: require.resolve('vue-eslint-parser')
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script lang="ts">
+        export default Vue.extend({
+          props: {
+            foo: { type: Object as any }
+          }
+        });
+        </script>
+      `,
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      parser: require.resolve('vue-eslint-parser')
+    },
+    // unknown
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: { type: Object as unknown } });
+      </script>
+      `,
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      parser: require.resolve('vue-eslint-parser')
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script lang="ts">
+        export default {
+          props: {
+            foo: { type: Object as unknown }
+          }
+        };
+        </script>
+      `,
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      parser: require.resolve('vue-eslint-parser')
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script lang="ts">
+        export default Vue.extend({
+          props: {
+            foo: { type: Object as unknown }
+          }
+        });
+        </script>
+      `,
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        parser: require.resolve('@typescript-eslint/parser')
+      },
+      parser: require.resolve('vue-eslint-parser')
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: Object });
+      </script>
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      parser: require.resolve('vue-eslint-parser'),
+      errors: [
+        {
+          messageId: 'expectedTypeAnnotation',
+          line: 3,
+          column: 26,
+          endLine: 3,
+          endColumn: 32,
+          suggestions: [
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'any' },
+              output: `
+      <script setup lang="ts">
+      defineProps({ foo: Object as PropType<any> });
+      </script>
+      `
+            },
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'unknown' },
+              output: `
+      <script setup lang="ts">
+      defineProps({ foo: Object as PropType<unknown> });
+      </script>
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: Array });
+      </script>
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      parser: require.resolve('vue-eslint-parser'),
+      errors: [
+        {
+          messageId: 'expectedTypeAnnotation',
+          line: 3,
+          column: 26,
+          endLine: 3,
+          endColumn: 31,
+          suggestions: [
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'any[]' },
+              output: `
+      <script setup lang="ts">
+      defineProps({ foo: Array as PropType<any[]> });
+      </script>
+      `
+            },
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'unknown[]' },
+              output: `
+      <script setup lang="ts">
+      defineProps({ foo: Array as PropType<unknown[]> });
+      </script>
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      export default {
+        props: { foo: Object }
+      }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [
+        {
+          messageId: 'expectedTypeAnnotation',
+          line: 3,
+          column: 23,
+          endLine: 3,
+          endColumn: 29,
+          suggestions: [
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'any' },
+              output: `
+      export default {
+        props: { foo: Object as PropType<any> }
+      }
+      `
+            },
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'unknown' },
+              output: `
+      export default {
+        props: { foo: Object as PropType<unknown> }
+      }
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      export default Vue.extend({
+        props: { foo: Object }
+      });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [
+        {
+          messageId: 'expectedTypeAnnotation',
+          line: 3,
+          column: 23,
+          endLine: 3,
+          endColumn: 29,
+          suggestions: [
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'any' },
+              output: `
+      export default Vue.extend({
+        props: { foo: Object as PropType<any> }
+      });
+      `
+            },
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'unknown' },
+              output: `
+      export default Vue.extend({
+        props: { foo: Object as PropType<unknown> }
+      });
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      export default Vue.extend({
+        props: { foo: { type: Object } }
+      });
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [
+        {
+          messageId: 'expectedTypeAnnotation',
+          line: 3,
+          column: 31,
+          endLine: 3,
+          endColumn: 37,
+          suggestions: [
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'any' },
+              output: `
+      export default Vue.extend({
+        props: { foo: { type: Object as PropType<any> } }
+      });
+      `
+            },
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'unknown' },
+              output: `
+      export default Vue.extend({
+        props: { foo: { type: Object as PropType<unknown> } }
+      });
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      export default {
+        props: { foo: { type: Object } }
+      }
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      errors: [
+        {
+          messageId: 'expectedTypeAnnotation',
+          line: 3,
+          column: 31,
+          endLine: 3,
+          endColumn: 37,
+          suggestions: [
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'any' },
+              output: `
+      export default {
+        props: { foo: { type: Object as PropType<any> } }
+      }
+      `
+            },
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'unknown' },
+              output: `
+      export default {
+        props: { foo: { type: Object as PropType<unknown> } }
+      }
+      `
+            }
+          ]
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      defineProps({ foo: { type: Object } });
+      </script>
+      `,
+      parserOptions: { ecmaVersion: 6, sourceType: 'module' },
+      parser: require.resolve('vue-eslint-parser'),
+      errors: [
+        {
+          messageId: 'expectedTypeAnnotation',
+          line: 3,
+          column: 34,
+          endLine: 3,
+          endColumn: 40,
+          suggestions: [
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'any' },
+              output: `
+      <script setup lang="ts">
+      defineProps({ foo: { type: Object as PropType<any> } });
+      </script>
+      `
+            },
+            {
+              messageId: 'addTypeAnnotation',
+              data: { type: 'unknown' },
+              output: `
+      <script setup lang="ts">
+      defineProps({ foo: { type: Object as PropType<unknown> } });
+      </script>
+      `
+            }
+          ]
+        }
+      ]
+    }
+  ]
+})