diff --git a/docs/rules/index.md b/docs/rules/index.md
index c3e4b8ed7..d6469f3e0 100644
--- a/docs/rules/index.md
+++ b/docs/rules/index.md
@@ -262,6 +262,7 @@ For example:
| [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-expose](./require-expose.md) | require declare public properties using `expose` | :bulb: | :hammer: |
+| [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/script-indent](./script-indent.md) | enforce consistent indentation in `
+```
+
+
+
+
+
+```vue
+
+
+```
+
+
+
+## :wrench: Options
+
+```json
+{
+ "vue/require-macro-variable-name": ["error", {
+ "defineProps": "props",
+ "defineEmits": "emit",
+ "defineSlots": "slots",
+ "useSlots": "slots",
+ "useAttrs": "attrs"
+ }]
+}
+```
+
+- `defineProps` - The name of the macro variable for `defineProps`. default: `props`
+- `defineEmits` - The name of the macro variable for `defineEmits`. default: `emit`
+- `defineSlots` - The name of the macro variable for `defineSlots`. default: `slots`
+- `useSlots` - The name of the macro variable for `useSlots`. default: `slots`
+- `useAttrs` - The name of the macro variable for `useAttrs`. default: `attrs`
+
+### With custom macro variable names
+
+
+
+```vue
+
+```
+
+
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-macro-variable-name.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-macro-variable-name.js)
diff --git a/lib/index.js b/lib/index.js
index 8ac5d9383..6ca29d49f 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -181,6 +181,7 @@ module.exports = {
'require-emit-validator': require('./rules/require-emit-validator'),
'require-explicit-emits': require('./rules/require-explicit-emits'),
'require-expose': require('./rules/require-expose'),
+ 'require-macro-variable-name': require('./rules/require-macro-variable-name'),
'require-name-property': require('./rules/require-name-property'),
'require-prop-comment': require('./rules/require-prop-comment'),
'require-prop-type-constructor': require('./rules/require-prop-type-constructor'),
diff --git a/lib/rules/require-macro-variable-name.js b/lib/rules/require-macro-variable-name.js
new file mode 100644
index 000000000..e52c1196c
--- /dev/null
+++ b/lib/rules/require-macro-variable-name.js
@@ -0,0 +1,111 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const utils = require('../utils')
+
+const DEFAULT_OPTIONS = {
+ defineProps: 'props',
+ defineEmits: 'emit',
+ defineSlots: 'slots',
+ useSlots: 'slots',
+ useAttrs: 'attrs'
+}
+
+module.exports = {
+ meta: {
+ hasSuggestions: true,
+ type: 'suggestion',
+ docs: {
+ description: 'require a certain macro variable name',
+ categories: undefined,
+ url: 'https://eslint.vuejs.org/rules/require-macro-variable-name.html'
+ },
+ fixable: null,
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ defineProps: {
+ type: 'string',
+ default: DEFAULT_OPTIONS.defineProps
+ },
+ defineEmits: {
+ type: 'string',
+ default: DEFAULT_OPTIONS.defineEmits
+ },
+ defineSlots: {
+ type: 'string',
+ default: DEFAULT_OPTIONS.defineSlots
+ },
+ useSlots: {
+ type: 'string',
+ default: DEFAULT_OPTIONS.useSlots
+ },
+ useAttrs: {
+ type: 'string',
+ default: DEFAULT_OPTIONS.useAttrs
+ }
+ },
+ additionalProperties: false
+ }
+ ],
+ messages: {
+ requireName:
+ 'The variable name of "{{macroName}}" must be "{{variableName}}".',
+ changeName: 'Change the variable name to "{{variableName}}".'
+ }
+ },
+ /** @param {RuleContext} context */
+ create(context) {
+ const options = context.options[0] || DEFAULT_OPTIONS
+ const relevantMacros = new Set([
+ ...Object.keys(DEFAULT_OPTIONS),
+ 'withDefaults'
+ ])
+
+ return utils.defineScriptSetupVisitor(context, {
+ VariableDeclarator(node) {
+ if (
+ node.init &&
+ node.init.type === 'CallExpression' &&
+ node.init.callee.type === 'Identifier' &&
+ relevantMacros.has(node.init.callee.name)
+ ) {
+ const macroName =
+ node.init.callee.name === 'withDefaults'
+ ? 'defineProps'
+ : node.init.callee.name
+
+ if (
+ node.id.type === 'Identifier' &&
+ node.id.name !== options[macroName]
+ ) {
+ context.report({
+ node: node.id,
+ loc: node.id.loc,
+ messageId: 'requireName',
+ data: {
+ macroName,
+ variableName: options[macroName]
+ },
+ suggest: [
+ {
+ messageId: 'changeName',
+ data: {
+ variableName: options[macroName]
+ },
+ fix(fixer) {
+ return fixer.replaceText(node.id, options[macroName])
+ }
+ }
+ ]
+ })
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/tests/lib/rules/require-macro-variable-name.js b/tests/lib/rules/require-macro-variable-name.js
new file mode 100644
index 000000000..c54c224a5
--- /dev/null
+++ b/tests/lib/rules/require-macro-variable-name.js
@@ -0,0 +1,324 @@
+/**
+ * @author ItMaga
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/require-macro-variable-name')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: {
+ ecmaVersion: 2020,
+ sourceType: 'module'
+ }
+})
+
+const customOptions = {
+ defineProps: 'customProps',
+ defineEmits: 'customEmits',
+ defineSlots: 'customSlots',
+ useSlots: 'customUseSlots',
+ useAttrs: 'customUseAttrs'
+}
+
+tester.run('require-macro-variable-name', 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: `
+
+ `,
+ options: [customOptions]
+ }
+ ],
+ invalid: [
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'The variable name of "defineProps" must be "props".',
+ line: 3,
+ column: 15,
+ suggestions: [
+ {
+ desc: 'Change the variable name to "props".',
+ output: `
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'The variable name of "defineEmits" must be "emit".',
+ line: 3,
+ column: 15,
+ suggestions: [
+ {
+ desc: 'Change the variable name to "emit".',
+ output: `
+
+ `
+ }
+ ]
+ },
+ {
+ message: 'The variable name of "defineSlots" must be "slots".',
+ line: 4,
+ column: 15,
+ suggestions: [
+ {
+ desc: 'Change the variable name to "slots".',
+ output: `
+
+ `
+ }
+ ]
+ },
+ {
+ message: 'The variable name of "useAttrs" must be "attrs".',
+ line: 5,
+ column: 15,
+ suggestions: [
+ {
+ desc: 'Change the variable name to "attrs".',
+ output: `
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'The variable name of "useSlots" must be "slots".',
+ line: 3,
+ column: 15,
+ suggestions: [
+ {
+ desc: 'Change the variable name to "slots".',
+ output: `
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'The variable name of "defineProps" must be "props".',
+ line: 3,
+ column: 15,
+ suggestions: [
+ {
+ desc: 'Change the variable name to "props".',
+ output: `
+
+ `
+ }
+ ]
+ }
+ ]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: `The variable name of "defineSlots" must be "${customOptions.defineSlots}".`,
+ line: 3,
+ column: 15,
+ suggestions: [
+ {
+ desc: `Change the variable name to "${customOptions.defineSlots}".`,
+ output: `
+
+ `
+ }
+ ]
+ },
+ {
+ message: `The variable name of "useSlots" must be "${customOptions.useSlots}".`,
+ line: 4,
+ column: 15,
+ suggestions: [
+ {
+ desc: `Change the variable name to "${customOptions.useSlots}".`,
+ output: `
+
+ `
+ }
+ ]
+ },
+ {
+ message: `The variable name of "useAttrs" must be "${customOptions.useAttrs}".`,
+ line: 5,
+ column: 15,
+ suggestions: [
+ {
+ desc: `Change the variable name to "${customOptions.useAttrs}".`,
+ output: `
+
+ `
+ }
+ ]
+ }
+ ],
+ options: [customOptions]
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ errors: [
+ {
+ message: `The variable name of "useAttrs" must be "attrs".`,
+ line: 4,
+ column: 15,
+ suggestions: [
+ {
+ desc: `Change the variable name to "attrs".`,
+ output: `
+
+ `
+ }
+ ]
+ }
+ ],
+ options: [{ defineSlots: 'slotsCustom' }]
+ }
+ ]
+})