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' }] + } + ] +})