diff --git a/docs/rules/index.md b/docs/rules/index.md
index 7828b58bb..e9be6d753 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -546,6 +546,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
[vue/padding-line-between-blocks]: ./padding-line-between-blocks.md
[vue/padding-line-between-tags]: ./padding-line-between-tags.md
[vue/padding-lines-in-component-definition]: ./padding-lines-in-component-definition.md
+[vue/prefer-define-component]: ./prefer-define-component.md
[vue/prefer-define-options]: ./prefer-define-options.md
[vue/prefer-import-from-vue]: ./prefer-import-from-vue.md
[vue/prefer-prop-type-boolean-first]: ./prefer-prop-type-boolean-first.md
@@ -644,4 +645,4 @@ The following rules extend the rules provided by ESLint itself and apply them to
[v9.0.0]: https://github.com/vuejs/eslint-plugin-vue/releases/tag/v9.0.0
[v9.16.0]: https://github.com/vuejs/eslint-plugin-vue/releases/tag/v9.16.0
[v9.17.0]: https://github.com/vuejs/eslint-plugin-vue/releases/tag/v9.17.0
-[v9.7.0]: https://github.com/vuejs/eslint-plugin-vue/releases/tag/v9.7.0
+[v9.7.0]: https://github.com/vuejs/eslint-plugin-vue/releases/tag/v9.7.0
\ No newline at end of file
diff --git a/docs/rules/prefer-define-component.md b/docs/rules/prefer-define-component.md
new file mode 100644
index 000000000..03f02bf41
--- /dev/null
+++ b/docs/rules/prefer-define-component.md
@@ -0,0 +1,96 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/prefer-define-component
+description: require components to be defined using `defineComponent`
+---
+
+# vue/prefer-define-component
+
+> require components to be defined using `defineComponent`
+
+## :book: Rule Details
+
+This rule enforces the use of `defineComponent` when defining Vue components. Using `defineComponent` provides proper typing in Vue 3 and IDE support for object properties.
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
+
+This rule doesn't report components using `
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :couple: Related Rules
+
+- [vue/require-default-export](./require-default-export.md)
+- [vue/require-direct-export](./require-direct-export.md)
+
+## :books: Further Reading
+
+- [Vue.js Guide - TypeScript with Composition API](https://vuejs.org/guide/typescript/composition-api.html#typing-component-props)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-define-component.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-define-component.js)
diff --git a/lib/index.js b/lib/index.js
index e511536fa..0ce94365c 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -205,6 +205,7 @@ const plugin = {
'padding-line-between-blocks': require('./rules/padding-line-between-blocks'),
'padding-line-between-tags': require('./rules/padding-line-between-tags'),
'padding-lines-in-component-definition': require('./rules/padding-lines-in-component-definition'),
+ 'prefer-define-component': require('./rules/prefer-define-component'),
'prefer-define-options': require('./rules/prefer-define-options'),
'prefer-import-from-vue': require('./rules/prefer-import-from-vue'),
'prefer-prop-type-boolean-first': require('./rules/prefer-prop-type-boolean-first'),
diff --git a/lib/rules/prefer-define-component.js b/lib/rules/prefer-define-component.js
new file mode 100644
index 000000000..2ef6246db
--- /dev/null
+++ b/lib/rules/prefer-define-component.js
@@ -0,0 +1,114 @@
+/**
+ * @author Kamogelo Moalusi
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// @ts-nocheck
+const utils = require('../utils')
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce components to be defined using `defineComponent`',
+ categories: ['vue3-recommended', 'vue2-recommended'],
+ url: 'https://eslint.vuejs.org/rules/prefer-define-component.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ 'prefer-define-component': 'Use `defineComponent` to define a component.'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const filePath = context.getFilename()
+ if (!utils.isVueFile(filePath)) return {}
+
+ const sourceCode = context.getSourceCode()
+ const documentFragment = sourceCode.parserServices.getDocumentFragment?.()
+
+ // Check if there's a non-setup script tag
+ const hasNormalScript =
+ documentFragment &&
+ documentFragment.children.some(
+ (e) =>
+ utils.isVElement(e) &&
+ e.name === 'script' &&
+ (!e.startTag.attributes ||
+ !e.startTag.attributes.some((attr) => attr.key.name === 'setup'))
+ )
+
+ // If no regular script tag, we don't need to check
+ if (!hasNormalScript) return {}
+
+ // Skip checking if there's only a setup script (no normal script)
+ if (utils.isScriptSetup(context) && !hasNormalScript) return {}
+
+ let hasDefineComponent = false
+ /** @type {ExportDefaultDeclaration | null} */
+ let exportDefaultNode = null
+ let hasVueExtend = false
+
+ return utils.compositingVisitors(utils.defineVueVisitor(context, {}), {
+ /** @param {ExportDefaultDeclaration} node */
+ 'Program > ExportDefaultDeclaration'(node) {
+ exportDefaultNode = node
+ },
+
+ /** @param {CallExpression} node */
+ 'Program > ExportDefaultDeclaration > CallExpression'(node) {
+ if (
+ node.callee.type === 'Identifier' &&
+ node.callee.name === 'defineComponent'
+ ) {
+ hasDefineComponent = true
+ return
+ }
+
+ // Support aliased imports
+ if (node.callee.type === 'Identifier') {
+ const variable = utils.findVariableByIdentifier(context, node.callee)
+ if (
+ variable &&
+ variable.defs &&
+ variable.defs.length > 0 &&
+ variable.defs[0].node.type === 'ImportSpecifier' &&
+ variable.defs[0].node.imported &&
+ variable.defs[0].node.imported.name === 'defineComponent'
+ ) {
+ hasDefineComponent = true
+ return
+ }
+ }
+
+ // Check for Vue.extend case
+ if (
+ node.callee.type === 'MemberExpression' &&
+ node.callee.object &&
+ node.callee.object.type === 'Identifier' &&
+ node.callee.object.name === 'Vue' &&
+ node.callee.property &&
+ node.callee.property.type === 'Identifier' &&
+ node.callee.property.name === 'extend'
+ ) {
+ hasVueExtend = true
+ }
+ },
+
+ 'Program > ExportDefaultDeclaration > ObjectExpression'() {
+ hasDefineComponent = false
+ },
+
+ 'Program:exit'() {
+ if (exportDefaultNode && (hasVueExtend || !hasDefineComponent)) {
+ context.report({
+ node: exportDefaultNode,
+ messageId: 'prefer-define-component'
+ })
+ }
+ }
+ })
+ }
+}
diff --git a/tests/lib/rules/prefer-define-component.js b/tests/lib/rules/prefer-define-component.js
new file mode 100644
index 000000000..800c561c8
--- /dev/null
+++ b/tests/lib/rules/prefer-define-component.js
@@ -0,0 +1,229 @@
+/**
+ * @author Kamogelo Moalusi
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('../../eslint-compat').RuleTester
+const rule = require('../../../lib/rules/prefer-define-component')
+
+const tester = new RuleTester({
+ languageOptions: {
+ parser: require('vue-eslint-parser'),
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('prefer-define-component', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Use `defineComponent` to define a component.',
+ line: 3,
+ column: 7
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Use `defineComponent` to define a component.',
+ line: 3,
+ column: 7
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Use `defineComponent` to define a component.',
+ line: 3,
+ column: 7
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `,
+ errors: [
+ {
+ message: 'Use `defineComponent` to define a component.',
+ line: 3,
+ column: 7
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Use `defineComponent` to define a component.',
+ line: 7,
+ column: 7
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+
+ `,
+ errors: [
+ {
+ message: 'Use `defineComponent` to define a component.',
+ line: 3,
+ column: 7
+ }
+ ]
+ }
+ ]
+})