Skip to content

Commit

Permalink
Add multi-word-component-names rule
Browse files Browse the repository at this point in the history
  • Loading branch information
csordasmarton committed Oct 17, 2021
1 parent 7bca4d3 commit a7b8030
Show file tree
Hide file tree
Showing 7 changed files with 427 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/rules/README.md
Expand Up @@ -39,6 +39,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi

| Rule ID | Description | |
|:--------|:------------|:---|
| [vue/multi-word-component-names](./multi-word-component-names.md) | require component names to be always multi-word | |
| [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher | |
| [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties | |
| [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: |
Expand Down Expand Up @@ -171,6 +172,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi

| Rule ID | Description | |
|:--------|:------------|:---|
| [vue/multi-word-component-names](./multi-word-component-names.md) | require component names to be always multi-word | |
| [vue/no-arrow-functions-in-watch](./no-arrow-functions-in-watch.md) | disallow using arrow functions to define watcher | |
| [vue/no-async-in-computed-properties](./no-async-in-computed-properties.md) | disallow asynchronous actions in computed properties | |
| [vue/no-custom-modifiers-on-v-model](./no-custom-modifiers-on-v-model.md) | disallow custom modifiers on v-model used on the component | |
Expand Down
58 changes: 58 additions & 0 deletions docs/rules/multi-word-component-names.md
@@ -0,0 +1,58 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/multi-word-component-names
description: require component names to be always multi-word
---
# vue/multi-word-component-names

> require component names to be always multi-word
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
- :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"`.

## :book: Rule Details

This rule ....

<eslint-code-block :rules="{'vue/multi-word-component-names': ['error']}">

```vue
<template>
/* ✓ GOOD */
Vue.component('todo-item', {
// ...
})
export default {
name: 'TodoItem',
// ...
}
/* ✗ BAD */
Vue.component('todo', {
// ...
})
export default {
name: 'Todo',
// ...
}
</template>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :books: Further Reading

- [Style guide - Multi-word component names](https://vuejs.org/v2/style-guide/#Multi-word-component-names-essential)

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/multi-word-component-names.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/multi-word-component-names.js)
1 change: 1 addition & 0 deletions lib/configs/essential.js
Expand Up @@ -6,6 +6,7 @@
module.exports = {
extends: require.resolve('./base'),
rules: {
'vue/multi-word-component-names': 'error',
'vue/no-arrow-functions-in-watch': 'error',
'vue/no-async-in-computed-properties': 'error',
'vue/no-custom-modifiers-on-v-model': 'error',
Expand Down
1 change: 1 addition & 0 deletions lib/configs/vue3-essential.js
Expand Up @@ -6,6 +6,7 @@
module.exports = {
extends: require.resolve('./base'),
rules: {
'vue/multi-word-component-names': 'error',
'vue/no-arrow-functions-in-watch': 'error',
'vue/no-async-in-computed-properties': 'error',
'vue/no-deprecated-data-object-declaration': 'error',
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -47,6 +47,7 @@ module.exports = {
'match-component-file-name': require('./rules/match-component-file-name'),
'max-attributes-per-line': require('./rules/max-attributes-per-line'),
'max-len': require('./rules/max-len'),
'multi-word-component-names': require('./rules/multi-word-component-names'),
'multiline-html-element-content-newline': require('./rules/multiline-html-element-content-newline'),
'mustache-interpolation-spacing': require('./rules/mustache-interpolation-spacing'),
'name-property-casing': require('./rules/name-property-casing'),
Expand Down
111 changes: 111 additions & 0 deletions lib/rules/multi-word-component-names.js
@@ -0,0 +1,111 @@
/**
* @author Marton Csordas
* See LICENSE file in root directory for full license.
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------
const path = require('path')

const casing = require('../utils/casing')
const utils = require('../utils')

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
// ------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'require component names to be always multi-word',
categories: ['vue3-essential', 'essential'],
url: 'https://eslint.vuejs.org/rules/multi-word-component-names.html'
},
schema: [],
messages: {
unexpected: 'Component name "{{value}}" should always be multi-word.'
}
},
/** @param {RuleContext} context */
create(context) {
const fileName = context.getFilename()
let componentName = path.parse(fileName).name

return utils.compositingVisitors(
{
/** @param {Program} node */
Program(node) {
if (
!node.body.length &&
utils.isVueFile(fileName) &&
!isValidComponentName(componentName)
) {
context.report({
messageId: 'unexpected',
data: {
value: componentName
},
loc: { line: 1, column: 0 }
})
}
}
},

utils.executeOnVue(context, (obj) => {
const node = utils.findProperty(obj, 'name')

// Check if the component has a name property.
if (node) {
const valueNode = node.value
if (valueNode.type !== 'Literal') return

componentName = `${valueNode.value}`
} 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}`
}

if (!isValidComponentName(componentName)) {
context.report({
messageId: 'unexpected',
data: {
value: componentName
},
node: node || obj
})
}
})
)
}
}

0 comments on commit a7b8030

Please sign in to comment.