diff --git a/docs/rules/README.md b/docs/rules/README.md
index 11a347738..d022dd67f 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -73,6 +73,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
| [vue/require-v-for-key](./require-v-for-key.md) | require `v-bind:key` with `v-for` directives | |
| [vue/require-valid-default-prop](./require-valid-default-prop.md) | enforce props default values to be valid | |
| [vue/return-in-computed-property](./return-in-computed-property.md) | enforce that a return statement is present in computed property | |
+| [vue/return-in-emits-validator](./return-in-emits-validator.md) | enforce that a return statement is present in emits validator | |
| [vue/use-v-on-exact](./use-v-on-exact.md) | enforce usage of `exact` modifier on `v-on` | |
| [vue/valid-template-root](./valid-template-root.md) | enforce valid template root | |
| [vue/valid-v-bind](./valid-v-bind.md) | enforce valid `v-bind` directives | |
diff --git a/docs/rules/return-in-emits-validator.md b/docs/rules/return-in-emits-validator.md
new file mode 100644
index 000000000..1122af378
--- /dev/null
+++ b/docs/rules/return-in-emits-validator.md
@@ -0,0 +1,63 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/return-in-emits-validator
+description: enforce that a return statement is present in emits validator
+---
+# vue/return-in-emits-validator
+> enforce that a return statement is present in emits validator
+
+- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/vue3-strongly-recommended"` and `"plugin:vue/vue3-recommended"`.
+
+## :book: Rule Details
+
+This rule enforces that a `return` statement is present in `emits` validators.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further reading
+
+- [Vue RFCs - 0030-emits-option](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0030-emits-option.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/return-in-emits-validator.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/return-in-emits-validator.js)
diff --git a/lib/configs/vue3-essential.js b/lib/configs/vue3-essential.js
index c76edfdc4..651ff096b 100644
--- a/lib/configs/vue3-essential.js
+++ b/lib/configs/vue3-essential.js
@@ -41,6 +41,7 @@ module.exports = {
'vue/require-v-for-key': 'error',
'vue/require-valid-default-prop': 'error',
'vue/return-in-computed-property': 'error',
+ 'vue/return-in-emits-validator': 'error',
'vue/use-v-on-exact': 'error',
'vue/valid-template-root': 'error',
'vue/valid-v-bind': 'error',
diff --git a/lib/index.js b/lib/index.js
index c4dbcb87c..fa2bc4347 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -97,6 +97,7 @@ module.exports = {
'require-v-for-key': require('./rules/require-v-for-key'),
'require-valid-default-prop': require('./rules/require-valid-default-prop'),
'return-in-computed-property': require('./rules/return-in-computed-property'),
+ 'return-in-emits-validator': require('./rules/return-in-emits-validator'),
'script-indent': require('./rules/script-indent'),
'singleline-html-element-content-newline': require('./rules/singleline-html-element-content-newline'),
'sort-keys': require('./rules/sort-keys'),
diff --git a/lib/rules/return-in-emits-validator.js b/lib/rules/return-in-emits-validator.js
new file mode 100644
index 000000000..dd5e551f4
--- /dev/null
+++ b/lib/rules/return-in-emits-validator.js
@@ -0,0 +1,109 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+/**
+ * @typedef {import('vue-eslint-parser').AST.ESLintExpression} Expression
+ */
+
+/**
+ * Checks if the given node value is falsy.
+ * @param {Expression} node The node to check
+ * @returns {boolean} If `true`, the given node value is falsy.
+ */
+function isFalsy (node) {
+ if (node.type === 'Literal') {
+ if (node.bigint) {
+ return node.bigint === '0'
+ } else if (!node.value) {
+ return true
+ }
+ } else if (node.type === 'Identifier') {
+ if (node.name === 'undefined' || node.name === 'NaN') {
+ return true
+ }
+ }
+ return false
+}
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'enforce that a return statement is present in emits validator',
+ categories: ['vue3-essential'],
+ url: 'https://eslint.vuejs.org/rules/return-in-emits-validator.html'
+ },
+ fixable: null, // or "code" or "whitespace"
+ schema: []
+ },
+
+ create (context) {
+ const emitsValidators = []
+
+ // ----------------------------------------------------------------------
+ // Public
+ // ----------------------------------------------------------------------
+
+ let scopeStack = null
+
+ return Object.assign({},
+ utils.defineVueVisitor(context,
+ {
+ ObjectExpression (obj, { node: vueNode }) {
+ if (obj !== vueNode) {
+ return
+ }
+ for (const emits of utils.getComponentEmits(obj)) {
+ const emitsValue = emits.value
+ if (!emitsValue) {
+ continue
+ }
+ if (emitsValue.type !== 'FunctionExpression' && emitsValue.type !== 'ArrowFunctionExpression') {
+ continue
+ }
+ emitsValidators.push(emits)
+ }
+ },
+ ':function' (node) {
+ scopeStack = { upper: scopeStack, functionNode: node, hasReturnValue: false, possibleOfReturnTrue: false }
+ },
+ ReturnStatement (node) {
+ if (node.argument) {
+ scopeStack.hasReturnValue = true
+
+ if (!isFalsy(node.argument)) {
+ scopeStack.possibleOfReturnTrue = true
+ }
+ }
+ },
+ ':function:exit' (node) {
+ if (!scopeStack.possibleOfReturnTrue) {
+ const emits = emitsValidators.find(e => e.value === node)
+ if (emits) {
+ context.report({
+ node,
+ message: scopeStack.hasReturnValue
+ ? 'Expected to return a true value in "{{name}}" emits validator.'
+ : 'Expected to return a boolean value in "{{name}}" emits validator.',
+ data: {
+ name: emits.emitName
+ }
+ })
+ }
+ }
+
+ scopeStack = scopeStack.upper
+ }
+ }
+ ),
+ )
+ }
+}
diff --git a/tests/lib/rules/return-in-emits-validator.js b/tests/lib/rules/return-in-emits-validator.js
new file mode 100644
index 000000000..8ee6b75e5
--- /dev/null
+++ b/tests/lib/rules/return-in-emits-validator.js
@@ -0,0 +1,275 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/return-in-emits-validator')
+
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2020, sourceType: 'module' }
+})
+ruleTester.run('return-in-emits-validator', rule, {
+
+ valid: [
+ {
+ 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: 'Expected to return a boolean value in "foo" emits validator.',
+ line: 5
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [{
+ message: 'Expected to return a boolean value in "foo" emits validator.',
+ line: 5
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [{
+ message: 'Expected to return a boolean value in "foo" emits validator.',
+ line: 5
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [{
+ message: 'Expected to return a boolean value in "foo" emits validator.',
+ line: 5
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Expected to return a boolean value in "foo" emits validator.',
+ line: 5
+ },
+ {
+ message: 'Expected to return a boolean value in "bar" emits validator.',
+ line: 7
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [{
+ message: 'Expected to return a boolean value in "foo" emits validator.',
+ line: 5
+ }]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [{
+ message: 'Expected to return a true value in "foo" emits validator.',
+ line: 5
+ }]
+ }
+ ]
+})