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

Issue:
New rule `vue/require-macro-variable-name`
**What rule do you want to change?**
New config fields:
- Require using a defined name for the `define*` variable (e.g. `{ "name": "props" }`)
- Require name prefix in html (not for `vue/define-emits-declaration` e.g. `{ "prefix": true }`)

**Does this change cause the rule to produce more or fewer warnings?**
More (depends on configuration).

**How will the change be implemented? (New option, new default behavior, etc.)?**
The default behavior does not change the current behavior of the rule.

**Please provide some example code that this change will affect:**

`"vue/define-props-declaration": ["error", "type-based", { "name": "props" } ]`
```ts
interface Props {
  active: boolean;
}
// GOOD
const props = defineProps<Props>();
// BAD
const myProps = defineProps<Props>();
```
`"vue/define-props-declaration": ["error", "type-based", { "prefix": true } ]`
```html
<template>
  <!-- GOOD-->
  {{ props.active }}
  <!-- BAD-->
  {{ active }}
  {{ $props.active }}
</template>
```
`"vue/define-slots-declaration": ["error", "always", { "name": "slots", "prefix": true } ]`
```html
<template>
  <!-- GOOD-->
  {{ slots }}
  {{ slots.default }}
  <!-- BAD-->
  {{ $slots }}
  {{ $slots.default }}
</template>
```
`"vue/define-attrs-declaration": ["error", "always", { "name": "attrs", "prefix": true } ]`
```html
<template>
  <!-- GOOD-->
  {{ attrs }}
  <!-- BAD-->
  {{ $attrs}}
</template>
```

**What does the rule currently do for this code?**
https://eslint.vuejs.org/rules/define-props-declaration.html




Discussion: Those are two different requests. The second one sounds like #2172; this should not be part of the `vue/define-*-declaration` rules.

The first one sounds good, but might also be better suited as a new rule (one for all, with good defaults). I suggest the name `vue/require-macro-variable-name` with one options object to customize the names. The default would be:

```js
{
  defineProps: 'props',
  defineEmits: 'emit',
  defineSlots: 'slots',
  useSlots: 'slots',
  useAttrs: 'attrs',
}
```
@FloEdelmann I like your proposal :)



Fix (patch):
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 `<script>` | :wrench: | :lipstick: |
diff --git a/docs/rules/require-macro-variable-name.md b/docs/rules/require-macro-variable-name.md
new file mode 100644
index 000000000..ef7400e1d
--- /dev/null
+++ b/docs/rules/require-macro-variable-name.md
@@ -0,0 +1,84 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/require-macro-variable-name
+description: require a certain macro variable name
+---
+# vue/require-macro-variable-name
+
+> require a certain macro variable name
+
+- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
+- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
+
+## :book: Rule Details
+
+This rule reports macro variables not corresponding to the specified name.
+
+<eslint-code-block :rules="{'vue/require-macro-variable-name': ['error']}">
+
+```vue
+<!-- ✓ GOOD -->
+<script setup>
+const props = defineProps({ msg: String })
+const emit = defineEmits(['update:msg'])
+</script>
+```
+
+</eslint-code-block>
+
+<eslint-code-block :rules="{'vue/require-macro-variable-name': ['error']}">
+
+```vue
+<!-- ✗ BAD  -->
+<script setup>
+const propsDefined = defineProps({ msg: String })
+const emitsDefined = defineEmits(['update:msg'])
+</script>
+```
+
+</eslint-code-block>
+
+## :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
+
+<eslint-code-block :rules="{'vue/require-macro-variable-name': ['error', {
+    'defineProps': 'propsCustom',
+    'defineEmits': 'emitCustom',
+    'defineSlots': 'slotsCustom',
+    'useSlots': 'slotsCustom',
+    'useAttrs': 'attrsCustom'
+  }]}">
+
+```vue
+<script setup>
+const slotsCustom = defineSlots()
+const attrsCustom = useAttrs()
+</script>
+```
+
+</eslint-code-block>
+
+## :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])
+                  }
+                }
+              ]
+            })
+          }
+        }
+      }
+    })
+  }
+}