Skip to content

Commit a7b8030

Browse files
committed
Add multi-word-component-names rule
1 parent 7bca4d3 commit a7b8030

File tree

7 files changed

+427
-0
lines changed

7 files changed

+427
-0
lines changed

docs/rules/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
3939

4040
| Rule ID | Description | |
4141
|:--------|:------------|:---|
42+
| [vue/multi-word-component-names](./multi-word-component-names.md) | require component names to be always multi-word | |
4243
| [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher | |
4344
| [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties | |
4445
| [vue/no-deprecated-data-object-declaration](./no-deprecated-data-object-declaration.md) | disallow using deprecated object declaration on data (in Vue.js 3.0.0+) | :wrench: |
@@ -171,6 +172,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
171172

172173
| Rule ID | Description | |
173174
|:--------|:------------|:---|
175+
| [vue/multi-word-component-names](./multi-word-component-names.md) | require component names to be always multi-word | |
174176
| [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher | |
175177
| [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties | |
176178
| [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component | |
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/multi-word-component-names
5+
description: require component names to be always multi-word
6+
---
7+
# vue/multi-word-component-names
8+
9+
> require component names to be always multi-word
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
13+
14+
## :book: Rule Details
15+
16+
This rule ....
17+
18+
<eslint-code-block :rules="{'vue/multi-word-component-names': ['error']}">
19+
20+
```vue
21+
<template>
22+
/* ✓ GOOD */
23+
Vue.component('todo-item', {
24+
// ...
25+
})
26+
27+
export default {
28+
name: 'TodoItem',
29+
// ...
30+
}
31+
32+
/* ✗ BAD */
33+
34+
Vue.component('todo', {
35+
// ...
36+
})
37+
38+
export default {
39+
name: 'Todo',
40+
// ...
41+
}
42+
</template>
43+
```
44+
45+
</eslint-code-block>
46+
47+
## :wrench: Options
48+
49+
Nothing.
50+
51+
## :books: Further Reading
52+
53+
- [Style guide - Multi-word component names](https://vuejs.org/v2/style-guide/#Multi-word-component-names-essential)
54+
55+
## :mag: Implementation
56+
57+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/multi-word-component-names.js)
58+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/multi-word-component-names.js)

lib/configs/essential.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
module.exports = {
77
extends: require.resolve('./base'),
88
rules: {
9+
'vue/multi-word-component-names': 'error',
910
'vue/no-arrow-functions-in-watch': 'error',
1011
'vue/no-async-in-computed-properties': 'error',
1112
'vue/no-custom-modifiers-on-v-model': 'error',

lib/configs/vue3-essential.js

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
module.exports = {
77
extends: require.resolve('./base'),
88
rules: {
9+
'vue/multi-word-component-names': 'error',
910
'vue/no-arrow-functions-in-watch': 'error',
1011
'vue/no-async-in-computed-properties': 'error',
1112
'vue/no-deprecated-data-object-declaration': 'error',

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ module.exports = {
4747
'match-component-file-name': require('./rules/match-component-file-name'),
4848
'max-attributes-per-line': require('./rules/max-attributes-per-line'),
4949
'max-len': require('./rules/max-len'),
50+
'multi-word-component-names': require('./rules/multi-word-component-names'),
5051
'multiline-html-element-content-newline': require('./rules/multiline-html-element-content-newline'),
5152
'mustache-interpolation-spacing': require('./rules/mustache-interpolation-spacing'),
5253
'name-property-casing': require('./rules/name-property-casing'),
+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @author Marton Csordas
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
const path = require('path')
11+
12+
const casing = require('../utils/casing')
13+
const utils = require('../utils')
14+
15+
const RESERVED_NAMES_IN_VUE3 = new Set(
16+
require('../utils/vue3-builtin-components')
17+
)
18+
19+
// ------------------------------------------------------------------------------
20+
// Helpers
21+
// ------------------------------------------------------------------------------
22+
23+
/**
24+
* Returns true if the given component name is valid, otherwise false.
25+
* @param {string} name
26+
* */
27+
function isValidComponentName(name) {
28+
if (name.toLowerCase() === 'app' || RESERVED_NAMES_IN_VUE3.has(name)) {
29+
return true
30+
} else {
31+
const elements = casing.kebabCase(name).split('-')
32+
return elements.length > 1
33+
}
34+
}
35+
36+
// ------------------------------------------------------------------------------
37+
// Rule Definition
38+
// ------------------------------------------------------------------------------
39+
40+
module.exports = {
41+
meta: {
42+
type: 'problem',
43+
docs: {
44+
description: 'require component names to be always multi-word',
45+
categories: ['vue3-essential', 'essential'],
46+
url: 'https://eslint.vuejs.org/rules/multi-word-component-names.html'
47+
},
48+
schema: [],
49+
messages: {
50+
unexpected: 'Component name "{{value}}" should always be multi-word.'
51+
}
52+
},
53+
/** @param {RuleContext} context */
54+
create(context) {
55+
const fileName = context.getFilename()
56+
let componentName = path.parse(fileName).name
57+
58+
return utils.compositingVisitors(
59+
{
60+
/** @param {Program} node */
61+
Program(node) {
62+
if (
63+
!node.body.length &&
64+
utils.isVueFile(fileName) &&
65+
!isValidComponentName(componentName)
66+
) {
67+
context.report({
68+
messageId: 'unexpected',
69+
data: {
70+
value: componentName
71+
},
72+
loc: { line: 1, column: 0 }
73+
})
74+
}
75+
}
76+
},
77+
78+
utils.executeOnVue(context, (obj) => {
79+
const node = utils.findProperty(obj, 'name')
80+
81+
// Check if the component has a name property.
82+
if (node) {
83+
const valueNode = node.value
84+
if (valueNode.type !== 'Literal') return
85+
86+
componentName = `${valueNode.value}`
87+
} else if (
88+
obj.parent.type === 'CallExpression' &&
89+
obj.parent.arguments.length === 2
90+
) {
91+
// The component is registered globally with 'Vue.component', where
92+
// the first paremter is the component name.
93+
const argument = obj.parent.arguments[0]
94+
if (argument.type !== 'Literal') return
95+
96+
componentName = `${argument.value}`
97+
}
98+
99+
if (!isValidComponentName(componentName)) {
100+
context.report({
101+
messageId: 'unexpected',
102+
data: {
103+
value: componentName
104+
},
105+
node: node || obj
106+
})
107+
}
108+
})
109+
)
110+
}
111+
}

0 commit comments

Comments
 (0)