diff --git a/docs/rules/README.md b/docs/rules/README.md
index de00e7e53..8ae175625 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -212,7 +212,9 @@ For example:
| [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: | :hammer: |
| [vue/component-options-name-casing](./component-options-name-casing.md) | enforce the casing of component name in `components` options | :wrench::bulb: | :hammer: |
| [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce specific casing for custom event name | | :hammer: |
+| [vue/define-emits-declaration](./define-emits-declaration.md) | enforce declaration style of `defineEmits` | | :hammer: |
| [vue/define-macros-order](./define-macros-order.md) | enforce order of `defineEmits` and `defineProps` compiler macros | :wrench: | :lipstick: |
+| [vue/define-props-declaration](./define-props-declaration.md) | enforce declaration style of `defineProps` | | :hammer: |
| [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute | | :hammer: |
| [vue/html-comment-content-newline](./html-comment-content-newline.md) | enforce unified line brake in HTML comments | :wrench: | :lipstick: |
| [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: | :lipstick: |
diff --git a/docs/rules/define-emits-declaration.md b/docs/rules/define-emits-declaration.md
new file mode 100644
index 000000000..50af9f951
--- /dev/null
+++ b/docs/rules/define-emits-declaration.md
@@ -0,0 +1,78 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/define-emits-declaration
+description: enforce declaration style of `defineEmits`
+---
+# vue/define-emits-declaration
+
+> enforce declaration style of `defineEmits`
+
+- :exclamation: ***This rule has not been released yet.***
+
+## :book: Rule Details
+
+This rule enforces `defineEmits` typing style which you should use `type-based` or `runtime` declaration.
+
+This rule only works in setup script and `lang="ts"`.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+ "vue/define-emits-declaration": ["error", "type-based" | "runtime"]
+```
+
+- `type-based` (default) enforces type-based declaration
+- `runtime` enforces runtime declaration
+
+### `runtime`
+
+
+
+```vue
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/define-props-declaration](./define-props-declaration.md)
+- [vue/valid-define-emits](./valid-define-emits.md)
+
+## :books: Further Reading
+
+- [`defineEmits`](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)
+- [Typescript-only-features of `defineEmits`](https://vuejs.org/api/sfc-script-setup.html#typescript-only-features)
+- [Guide - Typing-component-emits](https://vuejs.org/guide/typescript/composition-api.html#typing-component-emits)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/define-emits-declaration.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-emits-declaration.js)
diff --git a/docs/rules/define-props-declaration.md b/docs/rules/define-props-declaration.md
new file mode 100644
index 000000000..db68791fb
--- /dev/null
+++ b/docs/rules/define-props-declaration.md
@@ -0,0 +1,79 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/define-props-declaration
+description: enforce declaration style of `defineProps`
+---
+# vue/define-props-declaration
+
+> enforce declaration style of `defineProps`
+
+- :exclamation: ***This rule has not been released yet.***
+
+## :book: Rule Details
+
+This rule enforces `defineProps` typing style which you should use `type-based` or `runtime` declaration.
+
+This rule only works in setup script and `lang="ts"`.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+```json
+ "vue/define-props-declaration": ["error", "type-based" | "runtime"]
+```
+
+- `type-based` (default) enforces type-based declaration
+- `runtime` enforces runtime declaration
+
+### `"runtime"`
+
+
+
+```vue
+
+```
+
+
+
+## :couple: Related Rules
+
+- [vue/define-emits-declaration](./define-emits-declaration.md)
+- [vue/valid-define-props](./valid-define-props.md)
+
+## :books: Further Reading
+
+- [`defineProps`](https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits)
+- [Typescript-only-features of `defineProps`](https://vuejs.org/api/sfc-script-setup.html#typescript-only-features)
+- [Guide - Typing-component-props](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/define-props-declaration.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/define-props-declaration.js)
diff --git a/docs/rules/valid-define-emits.md b/docs/rules/valid-define-emits.md
index 007a584e7..f7de8454c 100644
--- a/docs/rules/valid-define-emits.md
+++ b/docs/rules/valid-define-emits.md
@@ -27,8 +27,8 @@ This rule reports `defineEmits` compiler macros in the following cases:
```vue
```
@@ -38,8 +38,8 @@ This rule reports `defineEmits` compiler macros in the following cases:
```vue
```
@@ -49,8 +49,8 @@ This rule reports `defineEmits` compiler macros in the following cases:
```vue
```
@@ -60,11 +60,11 @@ This rule reports `defineEmits` compiler macros in the following cases:
```vue
```
@@ -74,9 +74,9 @@ This rule reports `defineEmits` compiler macros in the following cases:
```vue
```
@@ -86,8 +86,8 @@ This rule reports `defineEmits` compiler macros in the following cases:
```vue
```
@@ -97,9 +97,9 @@ This rule reports `defineEmits` compiler macros in the following cases:
```vue
```
@@ -109,13 +109,13 @@ This rule reports `defineEmits` compiler macros in the following cases:
```vue
```
@@ -125,8 +125,8 @@ This rule reports `defineEmits` compiler macros in the following cases:
```vue
```
@@ -136,6 +136,11 @@ This rule reports `defineEmits` compiler macros in the following cases:
Nothing.
+## :couple: Related Rules
+
+- [vue/define-emits-declaration](./define-emits-declaration.md)
+- [vue/valid-define-props](./valid-define-props.md)
+
## :rocket: Version
This rule was introduced in eslint-plugin-vue v7.13.0
diff --git a/docs/rules/valid-define-props.md b/docs/rules/valid-define-props.md
index 960573bb0..4345d2428 100644
--- a/docs/rules/valid-define-props.md
+++ b/docs/rules/valid-define-props.md
@@ -27,8 +27,8 @@ This rule reports `defineProps` compiler macros in the following cases:
```vue
```
@@ -38,8 +38,8 @@ This rule reports `defineProps` compiler macros in the following cases:
```vue
```
@@ -49,8 +49,8 @@ This rule reports `defineProps` compiler macros in the following cases:
```vue
```
@@ -60,11 +60,11 @@ This rule reports `defineProps` compiler macros in the following cases:
```vue
```
@@ -74,9 +74,9 @@ This rule reports `defineProps` compiler macros in the following cases:
```vue
```
@@ -86,8 +86,8 @@ This rule reports `defineProps` compiler macros in the following cases:
```vue
```
@@ -97,9 +97,9 @@ This rule reports `defineProps` compiler macros in the following cases:
```vue
```
@@ -109,13 +109,13 @@ This rule reports `defineProps` compiler macros in the following cases:
```vue
```
@@ -125,8 +125,8 @@ This rule reports `defineProps` compiler macros in the following cases:
```vue
```
@@ -136,6 +136,11 @@ This rule reports `defineProps` compiler macros in the following cases:
Nothing.
+## :couple: Related Rules
+
+- [vue/define-props-declaration](./define-props-declaration.md)
+- [vue/valid-define-emits](./valid-define-emits.md)
+
## :rocket: Version
This rule was introduced in eslint-plugin-vue v7.13.0
diff --git a/lib/index.js b/lib/index.js
index ca3a93084..04afd4af3 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -27,7 +27,9 @@ module.exports = {
'component-options-name-casing': require('./rules/component-options-name-casing'),
'component-tags-order': require('./rules/component-tags-order'),
'custom-event-name-casing': require('./rules/custom-event-name-casing'),
+ 'define-emits-declaration': require('./rules/define-emits-declaration'),
'define-macros-order': require('./rules/define-macros-order'),
+ 'define-props-declaration': require('./rules/define-props-declaration'),
'dot-location': require('./rules/dot-location'),
'dot-notation': require('./rules/dot-notation'),
eqeqeq: require('./rules/eqeqeq'),
diff --git a/lib/rules/define-emits-declaration.js b/lib/rules/define-emits-declaration.js
new file mode 100644
index 000000000..758097f9a
--- /dev/null
+++ b/lib/rules/define-emits-declaration.js
@@ -0,0 +1,68 @@
+/**
+ * @author Amorites
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce declaration style of `defineEmits`',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/define-emits-declaration.html'
+ },
+ fixable: null,
+ messages: {
+ hasArg: 'Use type-based declaration instead of runtime declaration.',
+ hasTypeArg: 'Use runtime declaration instead of type-based declaration.'
+ },
+ schema: [
+ {
+ enum: ['type-based', 'runtime']
+ }
+ ]
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const scriptSetup = utils.getScriptSetupElement(context)
+ if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) {
+ return {}
+ }
+
+ const defineType = context.options[0] || 'type-based'
+ return utils.defineScriptSetupVisitor(context, {
+ onDefineEmitsEnter(node) {
+ switch (defineType) {
+ case 'type-based':
+ if (node.arguments.length > 0) {
+ context.report({
+ node,
+ messageId: 'hasArg'
+ })
+ }
+ break
+
+ case 'runtime':
+ if (node.typeParameters && node.typeParameters.params.length > 0) {
+ context.report({
+ node,
+ messageId: 'hasTypeArg'
+ })
+ }
+ break
+ }
+ }
+ })
+ }
+}
diff --git a/lib/rules/define-props-declaration.js b/lib/rules/define-props-declaration.js
new file mode 100644
index 000000000..2a73ed10a
--- /dev/null
+++ b/lib/rules/define-props-declaration.js
@@ -0,0 +1,68 @@
+/**
+ * @author Amorites
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'enforce declaration style of `defineProps`',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/define-props-declaration.html'
+ },
+ fixable: null,
+ messages: {
+ hasArg: 'Use type-based declaration instead of runtime declaration.',
+ hasTypeArg: 'Use runtime declaration instead of type-based declaration.'
+ },
+ schema: [
+ {
+ enum: ['type-based', 'runtime']
+ }
+ ]
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const scriptSetup = utils.getScriptSetupElement(context)
+ if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) {
+ return {}
+ }
+
+ const defineType = context.options[0] || 'type-based'
+ return utils.defineScriptSetupVisitor(context, {
+ onDefinePropsEnter(node) {
+ switch (defineType) {
+ case 'type-based':
+ if (node.arguments.length > 0) {
+ context.report({
+ node,
+ messageId: 'hasArg'
+ })
+ }
+ break
+
+ case 'runtime':
+ if (node.typeParameters && node.typeParameters.params.length > 0) {
+ context.report({
+ node,
+ messageId: 'hasTypeArg'
+ })
+ }
+ break
+ }
+ }
+ })
+ }
+}
diff --git a/tests/lib/rules/define-emits-declaration.js b/tests/lib/rules/define-emits-declaration.js
new file mode 100644
index 000000000..279d80497
--- /dev/null
+++ b/tests/lib/rules/define-emits-declaration.js
@@ -0,0 +1,151 @@
+/**
+ * @author Amorites
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/define-emits-declaration')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('define-emits-declaration', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ },
+ options: ['type-based']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['runtime']
+ },
+ {
+ filename: 'test.vue',
+ // ignore code without defineEmits
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ }
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Use type-based declaration instead of runtime declaration.',
+ line: 3
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Use type-based declaration instead of runtime declaration.',
+ line: 3
+ }
+ ],
+ options: ['type-based']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ },
+ errors: [
+ {
+ message: 'Use runtime declaration instead of type-based declaration.',
+ line: 3
+ }
+ ],
+ options: ['runtime']
+ }
+ ]
+})
diff --git a/tests/lib/rules/define-props-declaration.js b/tests/lib/rules/define-props-declaration.js
new file mode 100644
index 000000000..67ea338ec
--- /dev/null
+++ b/tests/lib/rules/define-props-declaration.js
@@ -0,0 +1,157 @@
+/**
+ * @author Amorites
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/define-props-declaration')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+tester.run('define-props-declaration', rule, {
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ }
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ },
+ options: ['type-based']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ options: ['runtime']
+ },
+ {
+ filename: 'test.vue',
+ // ignore script without lang="ts"
+ code: `
+
+ `
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ }
+ },
+ {
+ filename: 'test.vue',
+ // ignore non-setup script
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ }
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Use type-based declaration instead of runtime declaration.',
+ line: 3
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Use type-based declaration instead of runtime declaration.',
+ line: 3
+ }
+ ],
+ options: ['type-based']
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parserOptions: {
+ parser: require.resolve('@typescript-eslint/parser')
+ },
+ options: ['runtime'],
+ errors: [
+ {
+ message: 'Use runtime declaration instead of type-based declaration.',
+ line: 3
+ }
+ ]
+ }
+ ]
+})