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

Issue:
Require default export on `<script>`
<!--
  Before proposing new rule, please make sure it hasn't been posted already.
  You can see all open propositions here:
  https://github.com/vuejs/eslint-plugin-vue/issues?q=is%3Aopen+is%3Aissue+label%3A%22new+rule+proposition%22
-->

**Please describe what the rule should do:**
<!-- A clear and concise description of what the rule should do. -->
To warn when there's a `<script>` tag with no default export.

With the new `<script setup>` syntax, I've noticed cases where devs forget or miss the `setup` and can't figure why their code is not working correctly.


**What category should the rule belong to?**
<!-- (place an "X" next to just one item) -->
[ ] Enforces code style (layout)
[x] Warns about a potential error (problem)
[ ] Suggests an alternate way of doing something (suggestion)
[ ] Other (please specify:)

**Provide 2-3 code examples that this rule should warn about:**

```vue
<!-- Forgetting to add `setup` -->
<script>
const value = 1;
</script>
```

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

From: https://github.com/vuejs/eslint-plugin-vue/issues/2328

Pull request:
Add `vue/require-default-export` rule
closes #2383 

Patch:
diff --git a/docs/rules/index.md b/docs/rules/index.md
index e718603f..113ff416 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -45,7 +45,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
 | [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher |  | :three::two::warning: |
 | [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties |  | :three::two::warning: |
 | [vue/no-child-content](./no-child-content.md) | disallow element's child contents which would be overwritten by a directive like `v-html` or `v-text` | :bulb: | :three::two::warning: |
-| [vue/no-computed-properties-in-data](./no-computed-properties-in-data.md) | disallow accessing computed properties in `data`. |  | :three::two::warning: |
+| [vue/no-computed-properties-in-data](./no-computed-properties-in-data.md) | disallow accessing computed properties in `data` |  | :three::two::warning: |
 | [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component |  | :two::warning: |
 | [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: | :three::warning: |
 | [vue/no-deprecated-destroyed-lifecycle](./no-deprecated-destroyed-lifecycle.md) | disallow using deprecated `destroyed` and `beforeDestroy` lifecycle hooks (in Vue.js 3.0.0+) | :wrench: | :three::warning: |
@@ -223,6 +223,7 @@ For example:
 | [vue/match-component-file-name](./match-component-file-name.md) | require component name property to match its file name | :bulb: | :hammer: |
 | [vue/match-component-import-name](./match-component-import-name.md) | require the registered component name to match the imported component name |  | :warning: |
 | [vue/max-lines-per-block](./max-lines-per-block.md) | enforce maximum number of lines in Vue SFC blocks |  | :warning: |
+| [vue/max-props](./max-props.md) | enforce maximum number of props in Vue component |  | :warning: |
 | [vue/new-line-between-multi-line-property](./new-line-between-multi-line-property.md) | enforce new lines between multi-line properties in Vue components | :wrench: | :lipstick: |
 | [vue/next-tick-style](./next-tick-style.md) | enforce Promise or callback style in `nextTick` | :wrench: | :hammer: |
 | [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in `<template>` |  | :hammer: |
@@ -263,10 +264,11 @@ For example:
 | [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: | :lipstick: |
 | [vue/padding-line-between-tags](./padding-line-between-tags.md) | require or disallow newlines between sibling tags in template | :wrench: | :lipstick: |
 | [vue/padding-lines-in-component-definition](./padding-lines-in-component-definition.md) | require or disallow padding lines in component definition | :wrench: | :lipstick: |
-| [vue/prefer-define-options](./prefer-define-options.md) | enforce use of `defineOptions` instead of default export. | :wrench: | :hammer: |
+| [vue/prefer-define-options](./prefer-define-options.md) | enforce use of `defineOptions` instead of default export | :wrench: | :hammer: |
 | [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md) | enforce `Boolean` comes first in component prop types | :bulb: | :warning: |
 | [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: | :hammer: |
 | [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: | :hammer: |
+| [vue/require-default-export](./require-default-export.md) | require components to be the default export |  | :warning: |
 | [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported |  | :hammer: |
 | [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | :bulb: | :hammer: |
 | [vue/require-explicit-slots](./require-explicit-slots.md) | require slots to be explicitly defined |  | :warning: |
diff --git a/docs/rules/max-props.md b/docs/rules/max-props.md
new file mode 100644
index 00000000..b6979660
--- /dev/null
+++ b/docs/rules/max-props.md
@@ -0,0 +1,62 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/max-props
+description: enforce maximum number of props in Vue component
+---
+
+# vue/max-props
+
+> enforce maximum number of props in Vue component
+
+- :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 enforces a maximum number of props in a Vue SFC, in order to aid in maintainability and reduce complexity.
+
+## :wrench: Options
+
+This rule takes an object, where you can specify the maximum number of props allowed in a Vue SFC.
+There is one property that can be specified for the object.
+
+- `maxProps` ... Specify the maximum number of props in the `script` block.
+
+### `{ maxProps: 1 }`
+
+<eslint-code-block :rules="{'vue/max-props': ['error', { maxProps: 1 }]}">
+
+```vue
+<!-- ✗ BAD -->
+<template>
+</template>
+
+<script setup>
+defineProps({ 
+  prop1: String,
+  prop2: String,
+})
+</script>
+```
+
+</eslint-code-block>
+
+### `{ maxProps: 5 }`
+
+<eslint-code-block :rules="{'vue/max-props': ['error', { maxProps: 5 }]}">
+
+```vue
+<!-- ✓ GOOD -->
+<script>
+defineProps({
+  prop1: String
+})
+</script>
+```
+
+</eslint-code-block>
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/max-props.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/max-props.js)
diff --git a/docs/rules/no-computed-properties-in-data.md b/docs/rules/no-computed-properties-in-data.md
index 35bf62f1..26c49cbb 100644
--- a/docs/rules/no-computed-properties-in-data.md
+++ b/docs/rules/no-computed-properties-in-data.md
@@ -2,13 +2,13 @@
 pageClass: rule-details
 sidebarDepth: 0
 title: vue/no-computed-properties-in-data
-description: disallow accessing computed properties in `data`.
+description: disallow accessing computed properties in `data`
 since: v7.20.0
 ---
 
 # vue/no-computed-properties-in-data
 
-> disallow accessing computed properties in `data`.
+> disallow accessing computed properties in `data`
 
 - :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `*.configs["flat/essential"]`, `"plugin:vue/essential"`, `*.configs["flat/vue2-essential"]`, `"plugin:vue/vue3-strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/vue2-strongly-recommended"]`, `"plugin:vue/vue3-recommended"`, `*.configs["flat/recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/vue2-recommended"]`.
 
diff --git a/docs/rules/one-component-per-file.md b/docs/rules/one-component-per-file.md
index 6fbede33..5e6b3751 100644
--- a/docs/rules/one-component-per-file.md
+++ b/docs/rules/one-component-per-file.md
@@ -49,6 +49,10 @@ export default {
 
 Nothing.
 
+## :couple: Related Rules
+
+- [vue/require-default-export](./require-default-export.md)
+
 ## :books: Further Reading
 
 - [Style guide - Component files](https://vuejs.org/style-guide/rules-strongly-recommended.html#component-files)
diff --git a/docs/rules/prefer-define-options.md b/docs/rules/prefer-define-options.md
index b6320795..5653cb01 100644
--- a/docs/rules/prefer-define-options.md
+++ b/docs/rules/prefer-define-options.md
@@ -2,13 +2,13 @@
 pageClass: rule-details
 sidebarDepth: 0
 title: vue/prefer-define-options
-description: enforce use of `defineOptions` instead of default export.
+description: enforce use of `defineOptions` instead of default export
 since: v9.13.0
 ---
 
 # vue/prefer-define-options
 
-> enforce use of `defineOptions` instead of default export.
+> enforce use of `defineOptions` instead of default export
 
 - :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
 
diff --git a/docs/rules/require-default-export.md b/docs/rules/require-default-export.md
new file mode 100644
index 00000000..1760c25f
--- /dev/null
+++ b/docs/rules/require-default-export.md
@@ -0,0 +1,57 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-default-export
+description: require components to be the default export
+---
+
+# vue/require-default-export
+
+> require components to be the default export
+
+- :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 when a Vue component does not have a default export, if the component is not defined as `<script setup>`.
+
+<eslint-code-block :rules="{'vue/require-default-export': ['error']}">
+
+```vue
+<!-- ✗ BAD -->
+<script>
+const foo = 'foo';
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/require-default-export': ['error']}">
+
+```vue
+<!-- ✓ GOOD -->
+<script>
+export default {
+  data() {
+    return {
+      foo: 'foo'
+    };
+  }
+};
+</script>
+```
+
+</eslint-code-block>
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/one-component-per-file](./one-component-per-file.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-default-export.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-default-export.js)
diff --git a/eslint.config.js b/eslint.config.js
index deff4615..5ddca617 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -139,6 +139,10 @@ module.exports = [
       'prefer-const': 2,
 
       'prettier/prettier': 'error',
+      'eslint-plugin/require-meta-docs-description': [
+        'error',
+        { pattern: '^(enforce|require|disallow).*[^.]$' }
+      ],
       'eslint-plugin/require-meta-docs-recommended': 'off', // use `categories` instead
       'eslint-plugin/require-meta-fixable': [
         'error',
diff --git a/lib/configs/flat/base.js b/lib/configs/flat/base.js
index 6f745c33..eb2393ed 100644
--- a/lib/configs/flat/base.js
+++ b/lib/configs/flat/base.js
@@ -6,7 +6,7 @@
 const globals = require('globals')
 module.exports = [
   {
-    name: 'vue:base:setup',
+    name: 'vue/base/setup',
     plugins: {
       get vue() {
         return require('../../index')
@@ -18,7 +18,7 @@ module.exports = [
     }
   },
   {
-    name: 'vue:base:setup-for-vue',
+    name: 'vue/base/setup-for-vue',
     files: ['*.vue', '**/*.vue'],
     plugins: {
       get vue() {
diff --git a/lib/configs/flat/vue2-essential.js b/lib/configs/flat/vue2-essential.js
index 1de89dbc..0d092041 100644
--- a/lib/configs/flat/vue2-essential.js
+++ b/lib/configs/flat/vue2-essential.js
@@ -9,7 +9,7 @@ const config = require('./base.js')
 module.exports = [
   ...config,
   {
-    name: 'vue:vue2-essential:rules',
+    name: 'vue/vue2-essential/rules',
     rules: {
       'vue/multi-word-component-names': 'error',
       'vue/no-arrow-functions-in-watch': 'error',
diff --git a/lib/configs/flat/vue2-recommended.js b/lib/configs/flat/vue2-recommended.js
index 6ccde545..0d98a4dd 100644
--- a/lib/configs/flat/vue2-recommended.js
+++ b/lib/configs/flat/vue2-recommended.js
@@ -9,7 +9,7 @@ const config = require('./vue2-strongly-recommended.js')
 module.exports = [
   ...config,
   {
-    name: 'vue:vue2-recommended:rules',
+    name: 'vue/vue2-recommended/rules',
     rules: {
       'vue/attributes-order': 'warn',
       'vue/component-tags-order': 'warn',
diff --git a/lib/configs/flat/vue2-strongly-recommended.js b/lib/configs/flat/vue2-strongly-recommended.js
index dc45fea3..e4913f40 100644
--- a/lib/configs/flat/vue2-strongly-recommended.js
+++ b/lib/configs/flat/vue2-strongly-recommended.js
@@ -9,7 +9,7 @@ const config = require('./vue2-essential.js')
 module.exports = [
   ...config,
   {
-    name: 'vue:vue2-strongly-recommended:rules',
+    name: 'vue/vue2-strongly-recommended/rules',
     rules: {
       'vue/attribute-hyphenation': 'warn',
       'vue/component-definition-name-casing': 'warn',
diff --git a/lib/configs/flat/vue3-essential.js b/lib/configs/flat/vue3-essential.js
index 595af5f1..18d8c857 100644
--- a/lib/configs/flat/vue3-essential.js
+++ b/lib/configs/flat/vue3-essential.js
@@ -9,7 +9,7 @@ const config = require('./base.js')
 module.exports = [
   ...config,
   {
-    name: 'vue:essential:rules',
+    name: 'vue/essential/rules',
     rules: {
       'vue/multi-word-component-names': 'error',
       'vue/no-arrow-functions-in-watch': 'error',
diff --git a/lib/configs/flat/vue3-recommended.js b/lib/configs/flat/vue3-recommended.js
index 8ab8aa46..523718f3 100644
--- a/lib/configs/flat/vue3-recommended.js
+++ b/lib/configs/flat/vue3-recommended.js
@@ -9,7 +9,7 @@ const config = require('./vue3-strongly-recommended.js')
 module.exports = [
   ...config,
   {
-    name: 'vue:recommended:rules',
+    name: 'vue/recommended/rules',
     rules: {
       'vue/attributes-order': 'warn',
       'vue/component-tags-order': 'warn',
diff --git a/lib/configs/flat/vue3-strongly-recommended.js b/lib/configs/flat/vue3-strongly-recommended.js
index 51134ebc..fee1d3bb 100644
--- a/lib/configs/flat/vue3-strongly-recommended.js
+++ b/lib/configs/flat/vue3-strongly-recommended.js
@@ -9,7 +9,7 @@ const config = require('./vue3-essential.js')
 module.exports = [
   ...config,
   {
-    name: 'vue:strongly-recommended:rules',
+    name: 'vue/strongly-recommended/rules',
     rules: {
       'vue/attribute-hyphenation': 'warn',
       'vue/component-definition-name-casing': 'warn',
diff --git a/lib/index.js b/lib/index.js
index 956eeaa4..ff3f69c6 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -79,6 +79,7 @@ const plugin = {
     'max-attributes-per-line': require('./rules/max-attributes-per-line'),
     'max-len': require('./rules/max-len'),
     'max-lines-per-block': require('./rules/max-lines-per-block'),
+    'max-props': require('./rules/max-props'),
     'multi-word-component-names': require('./rules/multi-word-component-names'),
     'multiline-html-element-content-newline': require('./rules/multiline-html-element-content-newline'),
     'multiline-ternary': require('./rules/multiline-ternary'),
@@ -208,6 +209,7 @@ const plugin = {
     'prop-name-casing': require('./rules/prop-name-casing'),
     'quote-props': require('./rules/quote-props'),
     'require-component-is': require('./rules/require-component-is'),
+    'require-default-export': require('./rules/require-default-export'),
     'require-default-prop': require('./rules/require-default-prop'),
     'require-direct-export': require('./rules/require-direct-export'),
     'require-emit-validator': require('./rules/require-emit-validator'),
diff --git a/lib/rules/max-props.js b/lib/rules/max-props.js
new file mode 100644
index 00000000..a7f44b8e
--- /dev/null
+++ b/lib/rules/max-props.js
@@ -0,0 +1,67 @@
+/**
+ * @author kevsommer Kevin Sommer
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+const utils = require('../utils')
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'enforce maximum number of props in Vue component',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/max-props.html'
+    },
+    fixable: null,
+    schema: [
+      {
+        type: 'object',
+        properties: {
+          maxProps: {
+            type: 'integer',
+            minimum: 1
+          }
+        },
+        additionalProperties: false,
+        minProperties: 1
+      }
+    ],
+    messages: {
+      tooManyProps:
+        'Component has too many props ({{propCount}}). Maximum allowed is {{limit}}.'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    /** @type {Record<string, number>} */
+    const option = context.options[0] || {}
+
+    /**
+     * @param {import('../utils').ComponentProp[]} props
+     */
+    function checkMaxNumberOfProps(props) {
+      if (props.length > option.maxProps && props[0].node) {
+        context.report({
+          node: props[0].node,
+          messageId: 'tooManyProps',
+          data: {
+            propCount: props.length,
+            limit: option.maxProps
+          }
+        })
+      }
+    }
+
+    return utils.compositingVisitors(
+      utils.executeOnVue(context, (obj) => {
+        checkMaxNumberOfProps(utils.getComponentPropsFromOptions(obj))
+      }),
+      utils.defineScriptSetupVisitor(context, {
+        onDefinePropsEnter(_, props) {
+          checkMaxNumberOfProps(props)
+        }
+      })
+    )
+  }
+}
diff --git a/lib/rules/no-computed-properties-in-data.js b/lib/rules/no-computed-properties-in-data.js
index a2273947..472d45f4 100644
--- a/lib/rules/no-computed-properties-in-data.js
+++ b/lib/rules/no-computed-properties-in-data.js
@@ -14,7 +14,7 @@ module.exports = {
   meta: {
     type: 'problem',
     docs: {
-      description: 'disallow accessing computed properties in `data`.',
+      description: 'disallow accessing computed properties in `data`',
       categories: ['vue3-essential', 'vue2-essential'],
       url: 'https://eslint.vuejs.org/rules/no-computed-properties-in-data.html'
     },
diff --git a/lib/rules/prefer-define-options.js b/lib/rules/prefer-define-options.js
index 90c83fa9..e5fabb0c 100644
--- a/lib/rules/prefer-define-options.js
+++ b/lib/rules/prefer-define-options.js
@@ -10,7 +10,7 @@ module.exports = {
   meta: {
     type: 'suggestion',
     docs: {
-      description: 'enforce use of `defineOptions` instead of default export.',
+      description: 'enforce use of `defineOptions` instead of default export',
       categories: undefined,
       url: 'https://eslint.vuejs.org/rules/prefer-define-options.html'
     },
diff --git a/lib/rules/require-default-export.js b/lib/rules/require-default-export.js
new file mode 100644
index 00000000..5c540200
--- /dev/null
+++ b/lib/rules/require-default-export.js
@@ -0,0 +1,68 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+module.exports = {
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'require components to be the default export',
+      categories: undefined,
+      url: 'https://eslint.vuejs.org/rules/require-default-export.html'
+    },
+    fixable: null,
+    schema: [],
+    messages: {
+      missing: 'Missing default export.',
+      mustBeDefaultExport: 'Component must be the default export.'
+    }
+  },
+  /** @param {RuleContext} context */
+  create(context) {
+    const sourceCode = context.getSourceCode()
+    const documentFragment = sourceCode.parserServices.getDocumentFragment?.()
+
+    const hasScript =
+      documentFragment &&
+      documentFragment.children.some(
+        (e) => utils.isVElement(e) && e.name === 'script'
+      )
+
+    if (utils.isScriptSetup(context) || !hasScript) {
+      return {}
+    }
+
+    let hasDefaultExport = false
+    let hasDefinedComponent = false
+
+    return utils.compositingVisitors(
+      utils.defineVueVisitor(context, {
+        onVueObjectExit() {
+          hasDefinedComponent = true
+        }
+      }),
+
+      {
+        'Program > ExportDefaultDeclaration'() {
+          hasDefaultExport = true
+        },
+
+        /**
+         * @param {Program} node
+         */
+        'Program:exit'(node) {
+          if (!hasDefaultExport && node.body.length > 0) {
+            context.report({
+              loc: node.tokens[node.tokens.length - 1].loc,
+              messageId: hasDefinedComponent ? 'mustBeDefaultExport' : 'missing'
+            })
+          }
+        }
+      }
+    )
+  }
+}
diff --git a/lib/rules/require-typed-object-prop.js b/lib/rules/require-typed-object-prop.js
index ee88a0f3..eb7a278a 100644
--- a/lib/rules/require-typed-object-prop.js
+++ b/lib/rules/require-typed-object-prop.js
@@ -129,6 +129,30 @@ module.exports = {
   },
   /** @param {RuleContext} context */
   create(context) {
+    const filename = context.getFilename()
+    if (!utils.isVueFile(filename) && !utils.isTypeScriptFile(filename)) {
+      return {}
+    }
+
+    if (utils.isVueFile(filename)) {
+      const sourceCode = context.getSourceCode()
+      const documentFragment =
+        sourceCode.parserServices.getDocumentFragment &&
+        sourceCode.parserServices.getDocumentFragment()
+      if (!documentFragment) {
+        return {}
+      }
+      const scripts = documentFragment.children.filter(
+        /** @returns {element is VElement} */
+        (element) => utils.isVElement(element) && element.name === 'script'
+      )
+      if (
+        scripts.every((script) => !utils.hasAttribute(script, 'lang', 'ts'))
+      ) {
+        return {}
+      }
+    }
+
     return utils.compositingVisitors(
       utils.defineScriptSetupVisitor(context, {
         onDefinePropsEnter(_node, props) {
diff --git a/lib/rules/v-if-else-key.js b/lib/rules/v-if-else-key.js
index 75add9ca..a83d436b 100644
--- a/lib/rules/v-if-else-key.js
+++ b/lib/rules/v-if-else-key.js
@@ -25,6 +25,28 @@ const casing = require('../utils/casing')
  * @property {VElement | null} else - The node associated with the 'v-else' directive, or null if there isn't one.
  */
 
+/**
+ * Checks if a given node has sibling nodes of the same type that are also conditionally rendered.
+ * This is used to determine if multiple instances of the same component are being conditionally
+ * rendered within the same parent scope.
+ *
+ * @param {VElement} node - The Vue component node to check for conditional rendering siblings.
+ * @param {string} componentName - The name of the component to check for sibling instances.
+ * @returns {boolean} True if there are sibling nodes of the same type and conditionally rendered, false otherwise.
+ */
+const hasConditionalRenderedSiblings = (node, componentName) => {
+  if (!node.parent || node.parent.type !== 'VElement') {
+    return false
+  }
+  return node.parent.children.some(
+    (sibling) =>
+      sibling !== node &&
+      sibling.type === 'VElement' &&
+      sibling.rawName === componentName &&
+      hasConditionalDirective(sibling)
+  )
+}
+
 /**
  * Checks for the presence of a 'key' attribute in the given node. If the 'key' attribute is missing
  * and the node is part of a conditional family a report is generated.
@@ -44,26 +66,31 @@ const checkForKey = (
   uniqueKey,
   conditionalFamilies
 ) => {
-  if (node.parent && node.parent.type === 'VElement') {
-    const conditionalFamily = conditionalFamilies.get(node.parent)
+  if (
+    !node.parent ||
+    node.parent.type !== 'VElement' ||
+    !hasConditionalRenderedSiblings(node, componentName)
+  ) {
+    return
+  }
 
-    if (
-      conditionalFamily &&
-      (utils.hasDirective(node, 'bind', 'key') ||
-        utils.hasAttribute(node, 'key') ||
-        !hasConditionalDirective(node) ||
-        !(conditionalFamily.else || conditionalFamily.elseIf.length > 0))
-    ) {
-      return
-    }
+  const conditionalFamily = conditionalFamilies.get(node.parent)
+
+  if (!conditionalFamily || utils.hasAttribute(node, 'key')) {
+    return
+  }
+
+  const needsKey =
+    conditionalFamily.if === node ||
+    conditionalFamily.else === node ||
+    conditionalFamily.elseIf.includes(node)
 
+  if (needsKey) {
     context.report({
       node: node.startTag,
       loc: node.startTag.loc,
       messageId: 'requireKey',
-      data: {
-        componentName
-      },
+      data: { componentName },
       fix(fixer) {
         const afterComponentNamePosition =
           node.startTag.range[0] + componentName.length + 1
@@ -190,13 +217,18 @@ module.exports = {
           if (node.parent && node.parent.type === 'VElement') {
             let conditionalFamily = conditionalFamilies.get(node.parent)
 
-            if (conditionType === 'if' && !conditionalFamily) {
+            if (!conditionalFamily) {
               conditionalFamily = createConditionalFamily(node)
               conditionalFamilies.set(node.parent, conditionalFamily)
             }
 
             if (conditionalFamily) {
               switch (conditionType) {
+                case 'if': {
+                  conditionalFamily = createConditionalFamily(node)
+                  conditionalFamilies.set(node.parent, conditionalFamily)
+                  break
+                }
                 case 'else-if': {
                   conditionalFamily.elseIf.push(node)
                   break
diff --git a/tools/update-lib-flat-configs.js b/tools/update-lib-flat-configs.js
index 9d1671b9..d25ba281 100644
--- a/tools/update-lib-flat-configs.js
+++ b/tools/update-lib-flat-configs.js
@@ -58,7 +58,7 @@ function formatCategory(category) {
 const globals = require('globals')
 module.exports = [
   {
-    name: 'vue:base:setup',
+    name: 'vue/base/setup',
     plugins: {
       get vue() {
         return require('../../index')
@@ -70,7 +70,7 @@ module.exports = [
     }
   },
   {
-    name: 'vue:base:setup-for-vue',
+    name: 'vue/base/setup-for-vue',
     files: ['*.vue', '**/*.vue'],
     plugins: {
       get vue() {
@@ -99,7 +99,7 @@ const config = require('./${extendsCategoryId}.js')
 module.exports = [
   ...config,
   {
-    name: 'vue:${category.categoryId.replace(/^vue3-/u, '')}:rules',
+    name: 'vue/${category.categoryId.replace(/^vue3-/u, '')}/rules',
     rules: ${formatRules(category.rules, category.categoryId)},
   }
 ]


Test patch:
diff --git a/tests/lib/rules/max-props.js b/tests/lib/rules/max-props.js
new file mode 100644
index 00000000..1dd42a03
--- /dev/null
+++ b/tests/lib/rules/max-props.js
@@ -0,0 +1,162 @@
+/**
+ * @author kevsommer Kevin Sommer
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('../../eslint-compat').RuleTester
+const rule = require('../../../lib/rules/max-props')
+
+const tester = new RuleTester({
+  languageOptions: {
+    parser: require('vue-eslint-parser'),
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('max-props', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      defineProps({ prop1: '', prop2: '' })
+      </script>
+      `,
+      options: [{ maxProps: 5 }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        props: {
+          prop1: String,
+          prop2: String
+        }
+      }
+      </script>
+      `,
+      options: [{ maxProps: 5 }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        props: {
+          prop1: String,
+          prop2: String,
+          prop3: String,
+          prop4: String,
+          prop5: String
+        }
+      }
+      </script>
+      `,
+      options: [{ maxProps: 5 }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        props: {}
+      }
+      </script>
+      `,
+      options: [{ maxProps: 5 }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      defineProps({})
+      </script>
+      `,
+      options: [{ maxProps: 5 }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      </script>
+      `,
+      options: [{ maxProps: 5 }]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      defineProps<{ prop1: string, prop2: string }>();
+      </script>
+      `,
+      options: [{ maxProps: 5 }],
+      languageOptions: {
+        parser: require('vue-eslint-parser'),
+        parserOptions: {
+          parser: require.resolve('@typescript-eslint/parser')
+        }
+      }
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      defineProps({ prop1: '', prop2: '' })
+      </script>
+      `,
+      options: [{ maxProps: 1 }],
+      errors: [
+        {
+          message: 'Component has too many props (2). Maximum allowed is 1.',
+          line: 3
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default {
+        props: {
+          prop1: String,
+          prop2: String
+        }
+      }
+      </script>
+      `,
+      options: [{ maxProps: 1 }],
+      errors: [
+        {
+          message: 'Component has too many props (2). Maximum allowed is 1.',
+          line: 5
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup lang="ts">
+      defineProps<{ prop1: string, prop2: string, prop3: string }>();
+      </script>
+      `,
+      options: [{ maxProps: 2 }],
+      languageOptions: {
+        parser: require('vue-eslint-parser'),
+        parserOptions: {
+          parser: require.resolve('@typescript-eslint/parser')
+        }
+      },
+      errors: [
+        {
+          message: 'Component has too many props (3). Maximum allowed is 2.',
+          line: 3
+        }
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/require-default-export.js b/tests/lib/rules/require-default-export.js
new file mode 100644
index 00000000..279af4a3
--- /dev/null
+++ b/tests/lib/rules/require-default-export.js
@@ -0,0 +1,190 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('../../eslint-compat').RuleTester
+const rule = require('../../../lib/rules/require-default-export')
+
+const tester = new RuleTester({
+  languageOptions: {
+    parser: require('vue-eslint-parser'),
+    ecmaVersion: 2020,
+    sourceType: 'module'
+  }
+})
+
+tester.run('require-default-export', rule, {
+  valid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <template>Without script</template>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+        import { ref } from 'vue';
+        
+        export default {}
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+        const foo = 'foo';
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      const component = {};
+
+      export default component;
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      import {defineComponent} from 'vue';
+
+      export default defineComponent({});
+      </script>
+      `
+    },
+    {
+      filename: 'test.js',
+      code: `
+      const foo = 'foo';
+      export const bar = 'bar';
+      `
+    },
+    {
+      filename: 'test.js',
+      code: `
+      import {defineComponent} from 'vue';
+      defineComponent({});
+      `
+    }
+  ],
+  invalid: [
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      const foo = 'foo';
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'missing',
+          line: 4,
+          endLine: 4,
+          column: 7,
+          endColumn: 16
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export const foo = 'foo';
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'missing',
+          line: 4,
+          endLine: 4,
+          column: 7,
+          endColumn: 16
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      const foo = 'foo';
+
+      export { foo };
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'missing',
+          line: 6,
+          endLine: 6,
+          column: 7,
+          endColumn: 16
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export const foo = 'foo';
+      export const bar = 'bar';
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'missing',
+          line: 5,
+          endLine: 5,
+          column: 7,
+          endColumn: 16
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      import { defineComponent } from 'vue';
+
+      export const component = defineComponent({});
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'mustBeDefaultExport',
+          line: 6,
+          endLine: 6,
+          column: 7,
+          endColumn: 16
+        }
+      ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      import Vue from 'vue';
+
+      const component = Vue.component('foo', {});
+      </script>
+      `,
+      errors: [
+        {
+          messageId: 'mustBeDefaultExport',
+          line: 6,
+          endLine: 6,
+          column: 7,
+          endColumn: 16
+        }
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/require-typed-object-prop.js b/tests/lib/rules/require-typed-object-prop.js
index 9f5f000a..8a16fb56 100644
--- a/tests/lib/rules/require-typed-object-prop.js
+++ b/tests/lib/rules/require-typed-object-prop.js
@@ -9,7 +9,12 @@ const rule = require('../../../lib/rules/require-typed-object-prop')
 
 const ruleTester = new RuleTester({
   languageOptions: {
-    parser: require('@typescript-eslint/parser')
+    parser: require('vue-eslint-parser'),
+    ecmaVersion: 6,
+    sourceType: 'module',
+    parserOptions: {
+      parser: require.resolve('@typescript-eslint/parser')
+    }
   }
 })
 
@@ -19,20 +24,22 @@ ruleTester.run('require-typed-object-prop', rule, {
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: {}
       }
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default Vue.extend({
         props: {}
       });
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
@@ -40,31 +47,28 @@ ruleTester.run('require-typed-object-prop', rule, {
       <script setup lang="ts">
       defineProps({});
       </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module'
-      }
+      `
     },
     // array props
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: ['foo']
       }
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default Vue.extend({
         props: ['foo']
       });
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
@@ -72,31 +76,28 @@ ruleTester.run('require-typed-object-prop', rule, {
       <script setup lang="ts">
       defineProps(['foo']);
       </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module'
-      }
+      `
     },
     // primitive props
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: { foo: String }
       }
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: String }
       });
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
@@ -104,31 +105,28 @@ ruleTester.run('require-typed-object-prop', rule, {
       <script setup lang="ts">
       defineProps({ foo: String });
       </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module'
-      }
+      `
     },
     // union
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: { foo: [Number, String, Boolean] }
       }
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: [Number, String, Boolean] }
       });
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
@@ -136,31 +134,28 @@ ruleTester.run('require-typed-object-prop', rule, {
       <script setup lang="ts">
       defineProps({ foo: [Number, String, Boolean] });
       </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module'
-      }
+      `
     },
     // function
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: { foo: someFunction() }
       }
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: someFunction() }
       });
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
@@ -168,40 +163,38 @@ ruleTester.run('require-typed-object-prop', rule, {
       <script setup lang="ts">
       defineProps({ foo: someFunction() });
       </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module'
-      }
+      `
     },
     // typed object
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: { foo: Object as PropType<User> }
       }
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: { foo: Array as PropType<User[]> }
       }
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: Object as PropType<User> }
       });
-      `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
@@ -209,42 +202,28 @@ ruleTester.run('require-typed-object-prop', rule, {
       <script setup lang="ts">
       defineProps({ foo: Object as PropType<User> });
       </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: {
-          parser: require.resolve('@typescript-eslint/parser')
-        }
-      }
+      `
     },
 
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: { foo: Object as () => User }
       }
-      `,
-      languageOptions: {
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parser: require('@typescript-eslint/parser')
-      }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: Object as () => User }
       });
-      `,
-      languageOptions: {
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parser: require('@typescript-eslint/parser')
-      }
+      </script>
+      `
     },
     {
       filename: 'test.vue',
@@ -252,15 +231,7 @@ ruleTester.run('require-typed-object-prop', rule, {
       <script setup lang="ts">
       defineProps({ foo: Object as () => User });
       </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: {
-          parser: require.resolve('@typescript-eslint/parser')
-        }
-      }
+      `
     },
     // any
     {
@@ -269,15 +240,7 @@ ruleTester.run('require-typed-object-prop', rule, {
       <script setup lang="ts">
       defineProps({ foo: { type: Object as any } });
       </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: {
-          parser: require.resolve('@typescript-eslint/parser')
-        }
-      }
+      `
     },
     {
       filename: 'test.vue',
@@ -289,15 +252,7 @@ ruleTester.run('require-typed-object-prop', rule, {
           }
         };
         </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: {
-          parser: require.resolve('@typescript-eslint/parser')
-        }
-      }
+      `
     },
     {
       filename: 'test.vue',
@@ -309,13 +264,7 @@ ruleTester.run('require-typed-object-prop', rule, {
           }
         });
         </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
-      }
+      `
     },
     // unknown
     {
@@ -324,13 +273,7 @@ ruleTester.run('require-typed-object-prop', rule, {
       <script setup lang="ts">
       defineProps({ foo: { type: Object as unknown } });
       </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
-      }
+      `
     },
     {
       filename: 'test.vue',
@@ -342,13 +285,7 @@ ruleTester.run('require-typed-object-prop', rule, {
           }
         };
         </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
-      }
+      `
     },
     {
       filename: 'test.vue',
@@ -360,13 +297,34 @@ ruleTester.run('require-typed-object-prop', rule, {
           }
         });
         </script>
-      `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: { parser: require.resolve('@typescript-eslint/parser') }
-      }
+      `
+    },
+    // JavaScript components
+    {
+      filename: 'test.vue',
+      code: `
+      <script setup>
+      defineProps({ foo: Object });
+      </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <script>
+      export default Vue.extend({
+        props: { foo: { type: Object } }
+      });
+      </script>
+      `
+    },
+    {
+      filename: 'test.js',
+      code: `
+      export default Vue.extend({
+        props: { foo: Object }
+      });
+      `
     }
   ],
   invalid: [
@@ -377,14 +335,6 @@ ruleTester.run('require-typed-object-prop', rule, {
       defineProps({ foo: Object });
       </script>
       `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: {
-          parser: require.resolve('@typescript-eslint/parser')
-        }
-      },
       errors: [
         {
           messageId: 'expectedTypeAnnotation',
@@ -422,14 +372,6 @@ ruleTester.run('require-typed-object-prop', rule, {
       defineProps({ foo: Array });
       </script>
       `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: {
-          parser: require.resolve('@typescript-eslint/parser')
-        }
-      },
       errors: [
         {
           messageId: 'expectedTypeAnnotation',
@@ -463,35 +405,40 @@ ruleTester.run('require-typed-object-prop', rule, {
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: { foo: Object }
       }
+      </script>
       `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' },
       errors: [
         {
           messageId: 'expectedTypeAnnotation',
-          line: 3,
+          line: 4,
           column: 23,
-          endLine: 3,
+          endLine: 4,
           endColumn: 29,
           suggestions: [
             {
               messageId: 'addTypeAnnotation',
               data: { type: 'any' },
               output: `
+      <script lang="ts">
       export default {
         props: { foo: Object as PropType<any> }
       }
+      </script>
       `
             },
             {
               messageId: 'addTypeAnnotation',
               data: { type: 'unknown' },
               output: `
+      <script lang="ts">
       export default {
         props: { foo: Object as PropType<unknown> }
       }
+      </script>
       `
             }
           ]
@@ -501,35 +448,40 @@ ruleTester.run('require-typed-object-prop', rule, {
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: Object }
       });
+      </script>
       `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' },
       errors: [
         {
           messageId: 'expectedTypeAnnotation',
-          line: 3,
+          line: 4,
           column: 23,
-          endLine: 3,
+          endLine: 4,
           endColumn: 29,
           suggestions: [
             {
               messageId: 'addTypeAnnotation',
               data: { type: 'any' },
               output: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: Object as PropType<any> }
       });
+      </script>
       `
             },
             {
               messageId: 'addTypeAnnotation',
               data: { type: 'unknown' },
               output: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: Object as PropType<unknown> }
       });
+      </script>
       `
             }
           ]
@@ -539,35 +491,40 @@ ruleTester.run('require-typed-object-prop', rule, {
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: { type: Object } }
       });
+      </script>
       `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' },
       errors: [
         {
           messageId: 'expectedTypeAnnotation',
-          line: 3,
+          line: 4,
           column: 31,
-          endLine: 3,
+          endLine: 4,
           endColumn: 37,
           suggestions: [
             {
               messageId: 'addTypeAnnotation',
               data: { type: 'any' },
               output: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: { type: Object as PropType<any> } }
       });
+      </script>
       `
             },
             {
               messageId: 'addTypeAnnotation',
               data: { type: 'unknown' },
               output: `
+      <script lang="ts">
       export default Vue.extend({
         props: { foo: { type: Object as PropType<unknown> } }
       });
+      </script>
       `
             }
           ]
@@ -577,35 +534,40 @@ ruleTester.run('require-typed-object-prop', rule, {
     {
       filename: 'test.vue',
       code: `
+      <script lang="ts">
       export default {
         props: { foo: { type: Object } }
       }
+      </script>
       `,
-      languageOptions: { ecmaVersion: 6, sourceType: 'module' },
       errors: [
         {
           messageId: 'expectedTypeAnnotation',
-          line: 3,
+          line: 4,
           column: 31,
-          endLine: 3,
+          endLine: 4,
           endColumn: 37,
           suggestions: [
             {
               messageId: 'addTypeAnnotation',
               data: { type: 'any' },
               output: `
+      <script lang="ts">
       export default {
         props: { foo: { type: Object as PropType<any> } }
       }
+      </script>
       `
             },
             {
               messageId: 'addTypeAnnotation',
               data: { type: 'unknown' },
               output: `
+      <script lang="ts">
       export default {
         props: { foo: { type: Object as PropType<unknown> } }
       }
+      </script>
       `
             }
           ]
@@ -619,14 +581,6 @@ ruleTester.run('require-typed-object-prop', rule, {
       defineProps({ foo: { type: Object } });
       </script>
       `,
-      languageOptions: {
-        parser: require('vue-eslint-parser'),
-        ecmaVersion: 6,
-        sourceType: 'module',
-        parserOptions: {
-          parser: require.resolve('@typescript-eslint/parser')
-        }
-      },
       errors: [
         {
           messageId: 'expectedTypeAnnotation',
@@ -656,6 +610,44 @@ ruleTester.run('require-typed-object-prop', rule, {
           ]
         }
       ]
+    },
+    {
+      // `.ts` file
+      filename: 'test.ts',
+      code: `
+      export default Vue.extend({
+        props: { foo: Object }
+      });
+      `,
+      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> }
+      });
+      `
+            }
+          ]
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/v-if-else-key.js b/tests/lib/rules/v-if-else-key.js
index 16d975a0..46c42f52 100644
--- a/tests/lib/rules/v-if-else-key.js
+++ b/tests/lib/rules/v-if-else-key.js
@@ -127,6 +127,90 @@ tester.run('v-if-else-key', rule, {
         }
         </script>
         `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <div>
+              <ComponentA v-if="foo" /> 
+              <ComponentB v-else-if="bar" /> 
+            </div>
+            <div>
+              <div>
+                <ComponentA v-if="foo" />
+                <ComponentB v-else-if="baz" /> 
+              </div>
+              <div>
+                <ComponentA v-if="foo" />
+                <ComponentB v-else-if="baz" />
+              </div>
+            </div>
+            <div>
+              <ComponentA v-if="foo" />
+              <ComponentB v-else /> 
+            </div>
+            <div>
+              <div v-if="foo" />
+              <ComponentB v-else />
+            </div>
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            ComponentA,
+            ComponentB
+          }
+        }
+        </script>
+      `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <div>
+              <CustomComponent v-if="foo" />
+              <div v-else />
+            </div>
+      
+            <CustomComponent v-if="bar" />
+          </div>
+        </template>
+        <script>
+        export default {
+            components: {
+                CustomComponent
+            }
+        }
+        </script>
+        `
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <tile>
+            <template v-if="state === 'foo'">
+                <ComponentA>…</ComponentA>
+                <ComponentB>…</ComponentB>
+            </template>
+            <ComponentA v-else-if="state === 'bar'" key="a">…</ComponentA>
+            <ComponentA v-else-if="state === 'bar'" key="b">…</ComponentA>
+          </tile>
+        </template>
+        <script>
+        export default {
+            components: {
+                ComponentA,
+                ComponentB
+            }
+        }
+        </script>
+        `
     }
   ],
   invalid: [
@@ -424,6 +508,74 @@ tester.run('v-if-else-key', rule, {
           line: 6
         }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: `
+        <template>
+          <div>
+            <ComponentA v-if="foo" />
+            <ComponentA v-else />
+
+            <ComponentA v-if="bar" />
+            <ComponentA v-else-if="baz" />
+            <ComponentA v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            ComponentA
+          }
+        }
+        </script>
+        `,
+      output: `
+        <template>
+          <div>
+            <ComponentA key="component-a-1" v-if="foo" />
+            <ComponentA key="component-a-2" v-else />
+
+            <ComponentA key="component-a-3" v-if="bar" />
+            <ComponentA key="component-a-4" v-else-if="baz" />
+            <ComponentA key="component-a-5" v-else />
+          </div>
+        </template>
+        <script>
+        export default {
+          components: {
+            ComponentA
+          }
+        }
+        </script>
+        `,
+      errors: [
+        {
+          message:
+            "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+          line: 4
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+          line: 5
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+          line: 7
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+          line: 8
+        },
+        {
+          message:
+            "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.",
+          line: 9
+        }
+      ]
     }
   ]
 })