diff --git a/docs/rules/multi-word-component-names.md b/docs/rules/multi-word-component-names.md
index 0baefe4d5..33e48fb00 100644
--- a/docs/rules/multi-word-component-names.md
+++ b/docs/rules/multi-word-component-names.md
@@ -65,6 +65,7 @@ export default {
```vue
+
+```
+
+
+
+
+
+```vue
+
+```
+
+
## :books: Further Reading
-- [Style guide - Multi-word component names](https://vuejs.org/v2/style-guide/#Multi-word-component-names-essential)
+- [Style guide - Multi-word component names](https://v3.vuejs.org/style-guide/#multi-word-component-names-essential)
## :rocket: Version
diff --git a/lib/rules/multi-word-component-names.js b/lib/rules/multi-word-component-names.js
index 46e7bfac0..18cc79601 100644
--- a/lib/rules/multi-word-component-names.js
+++ b/lib/rules/multi-word-component-names.js
@@ -15,23 +15,6 @@ const RESERVED_NAMES_IN_VUE3 = new Set(
require('../utils/vue3-builtin-components')
)
-// ------------------------------------------------------------------------------
-// Helpers
-// ------------------------------------------------------------------------------
-
-/**
- * Returns true if the given component name is valid, otherwise false.
- * @param {string} name
- * */
-function isValidComponentName(name) {
- if (name.toLowerCase() === 'app' || RESERVED_NAMES_IN_VUE3.has(name)) {
- return true
- } else {
- const elements = casing.kebabCase(name).split('-')
- return elements.length > 1
- }
-}
-
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
@@ -44,22 +27,92 @@ module.exports = {
categories: ['vue3-essential', 'essential'],
url: 'https://eslint.vuejs.org/rules/multi-word-component-names.html'
},
- schema: [],
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ ignores: {
+ type: 'array',
+ items: { type: 'string' },
+ uniqueItems: true,
+ additionalItems: false
+ }
+ },
+ additionalProperties: false
+ }
+ ],
messages: {
unexpected: 'Component name "{{value}}" should always be multi-word.'
}
},
/** @param {RuleContext} context */
create(context) {
- const fileName = context.getFilename()
- let componentName = fileName.replace(/\.[^/.]+$/, '')
+ /** @type {Set} */
+ const ignores = new Set()
+ ignores.add('App')
+ ignores.add('app')
+ for (const ignore of (context.options[0] && context.options[0].ignores) ||
+ []) {
+ ignores.add(ignore)
+ if (casing.isPascalCase(ignore)) {
+ // PascalCase
+ ignores.add(casing.kebabCase(ignore))
+ }
+ }
+ let hasVue = false
+ let hasName = false
+
+ /**
+ * Returns true if the given component name is valid, otherwise false.
+ * @param {string} name
+ * */
+ function isValidComponentName(name) {
+ if (ignores.has(name) || RESERVED_NAMES_IN_VUE3.has(name)) {
+ return true
+ }
+ const elements = casing.kebabCase(name).split('-')
+ return elements.length > 1
+ }
+
+ /**
+ * @param {Expression | SpreadElement} nameNode
+ */
+ function validateName(nameNode) {
+ if (nameNode.type !== 'Literal') return
+ const componentName = `${nameNode.value}`
+ if (!isValidComponentName(componentName)) {
+ context.report({
+ node: nameNode,
+ messageId: 'unexpected',
+ data: {
+ value: componentName
+ }
+ })
+ }
+ }
return utils.compositingVisitors(
+ utils.executeOnCallVueComponent(context, (node) => {
+ hasVue = true
+ if (node.arguments.length !== 2) return
+ hasName = true
+ validateName(node.arguments[0])
+ }),
+ utils.executeOnVue(context, (obj) => {
+ hasVue = true
+ const node = utils.findProperty(obj, 'name')
+ if (!node) return
+ hasName = true
+ validateName(node.value)
+ }),
{
/** @param {Program} node */
- Program(node) {
+ 'Program:exit'(node) {
+ if (hasName) return
+ if (!hasVue && node.body.length > 0) return
+ const fileName = context.getFilename()
+ const componentName = fileName.replace(/\.[^/.]+$/, '')
if (
- !node.body.length &&
utils.isVueFile(fileName) &&
!isValidComponentName(componentName)
) {
@@ -72,44 +125,7 @@ module.exports = {
})
}
}
- },
-
- utils.executeOnVue(context, (obj) => {
- const node = utils.findProperty(obj, 'name')
-
- /** @type {SourceLocation | null} */
- let loc = null
-
- // Check if the component has a name property.
- if (node) {
- const valueNode = node.value
- if (valueNode.type !== 'Literal') return
-
- componentName = `${valueNode.value}`
- loc = node.loc
- } else if (
- obj.parent.type === 'CallExpression' &&
- obj.parent.arguments.length === 2
- ) {
- // The component is registered globally with 'Vue.component', where
- // the first paremter is the component name.
- const argument = obj.parent.arguments[0]
- if (argument.type !== 'Literal') return
-
- componentName = `${argument.value}`
- loc = argument.loc
- }
-
- if (!isValidComponentName(componentName)) {
- context.report({
- messageId: 'unexpected',
- data: {
- value: componentName
- },
- loc: loc || { line: 1, column: 0 }
- })
- }
- })
+ }
)
}
}
diff --git a/tests/lib/rules/multi-word-component-names.js b/tests/lib/rules/multi-word-component-names.js
index 65b777984..d143f6388 100644
--- a/tests/lib/rules/multi-word-component-names.js
+++ b/tests/lib/rules/multi-word-component-names.js
@@ -158,6 +158,17 @@ tester.run('multi-word-component-names', rule, {
Vue.component('TheTest', {})
`
+ },
+ {
+ filename: 'test.vue',
+ options: [{ ignores: ['Todo'] }],
+ code: `
+
+ `
}
],
invalid: [
@@ -248,6 +259,23 @@ tester.run('multi-word-component-names', rule, {
line: 3
}
]
+ },
+ {
+ filename: 'test.vue',
+ options: [{ ignores: ['Todo'] }],
+ code: `
+
+ `,
+ errors: [
+ {
+ message: 'Component name "Item" should always be multi-word.',
+ line: 4
+ }
+ ]
}
]
})