Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ For example:
| [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: | :lipstick: |
| [vue/v-if-else-key](./v-if-else-key.md) | require key attribute for conditionally rendered repeated components | :wrench: | :warning: |
| [vue/v-on-handler-style](./v-on-handler-style.md) | enforce writing style for handlers in `v-on` directives | :wrench: | :hammer: |
| [vue/valid-component-name](./valid-component-name.md) | enforce consistency in component names | | :warning: |
| [vue/valid-define-options](./valid-define-options.md) | enforce valid `defineOptions` compiler macro | | :warning: |

</rules-table>
Expand Down
62 changes: 62 additions & 0 deletions docs/rules/valid-component-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/valid-component-name
description: enforce consistency in component names
---

# vue/valid-component-name

> enforce consistency in component names

- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>

## :book: Rule Details

This rule enforces consistency in component names.

<eslint-code-block :rules="{ 'vue/valid-component-name': ['error'] }">

```vue
<template>
<!-- ✓ GOOD -->
<button/>
<keep-alive></keep-alive>

<!-- ✗ BAD -->
<custom-component />
</template>
```

</eslint-code-block>

## :wrench: Options

```json
{
"vue/valid-component-name": ["error", {
"allow": []
}]
}
```

### `"allow"`

<eslint-code-block :rules="{'vue/valid-component-name': ['error', { 'allow': ['/^custom-/'] }]}">

```vue
<template>
<!-- ✓ GOOD -->
<custom-component />

<!-- ✗ BAD -->
<my-component />
</template>
```

</eslint-code-block>

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-component-name.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-component-name.js)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@
'v-on-style': require('./rules/v-on-style'),
'v-slot-style': require('./rules/v-slot-style'),
'valid-attribute-name': require('./rules/valid-attribute-name'),
'valid-component-name': require('./rules/valid-component-name'),
'valid-define-emits': require('./rules/valid-define-emits'),
'valid-define-options': require('./rules/valid-define-options'),
'valid-define-props': require('./rules/valid-define-props'),
Expand Down Expand Up @@ -282,7 +283,7 @@
vue: require('./processor')
},
environments: {
// TODO Remove in the next major version

Check warning on line 286 in lib/index.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'todo' comment: 'TODO Remove in the next major version'
/** @deprecated */
'setup-compiler-macros': {
globals: {
Expand Down
101 changes: 101 additions & 0 deletions lib/rules/valid-component-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* @author Wayne Zhang
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')
const { toRegExp } = require('../utils/regexp')

const htmlElements = require('../utils/html-elements.json')
const deprecatedHtmlElements = require('../utils/deprecated-html-elements.json')
const svgElements = require('../utils/svg-elements.json')

const RESERVED_NAMES_IN_VUE = new Set(
require('../utils/vue2-builtin-components')
)
const RESERVED_NAMES_IN_VUE3 = new Set(
require('../utils/vue3-builtin-components')
)
const kebabCaseElements = [
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph'
]

const RESERVED_NAMES_IN_HTML = new Set(htmlElements)
const RESERVED_NAMES_IN_OTHERS = new Set([
...deprecatedHtmlElements,
...kebabCaseElements,
...svgElements
])

const reservedNames = new Set([
...RESERVED_NAMES_IN_HTML,
...RESERVED_NAMES_IN_VUE,
...RESERVED_NAMES_IN_VUE3,
...RESERVED_NAMES_IN_OTHERS
])

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'enforce consistency in component names',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/valid-component-name.html'
},
fixable: null,
schema: [
{
type: 'object',
additionalProperties: false,
properties: {
allow: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
additionalItems: false
}
}
}
],
messages: {
invalidName: 'Component name "{{name}}" is not valid.'
}
},
/** @param {RuleContext} context */
create(context) {
const options = context.options[0] || {}
/** @type {RegExp[]} */
const allow = (options.allow || []).map(toRegExp)

/** @param {string} name */
function isAllowedTarget(name) {
return reservedNames.has(name) || allow.some((re) => re.test(name))
}

return utils.defineTemplateBodyVisitor(context, {
VElement(node) {
const name = node.rawName
if (isAllowedTarget(name)) {
return
}

context.report({
node,
loc: node.loc,
messageId: 'invalidName',
data: {
name
}
})
}
})
}
}
78 changes: 78 additions & 0 deletions tests/lib/rules/valid-component-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* @author Wayne Zhang
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('../../eslint-compat').RuleTester
const rule = require('../../../lib/rules/valid-component-name')

const tester = new RuleTester({
languageOptions: {
parser: require('vue-eslint-parser'),
ecmaVersion: 2020,
sourceType: 'module'
}
})

tester.run('valid-component-name', rule, {
valid: [
'<template><keep-alive></keep-alive></template>',
'<template><button/></template>',
{
filename: 'test.vue',
code: `
<template>
<foo-button/>
<div-bar/>
</template>
`,
options: [{ allow: ['/^foo-/', '/-bar$/'] }]
}
],
invalid: [
{
filename: 'test.vue',
code: `
<template>
<Button/>
<foo-button/>
</template>
`,
errors: [
{
messageId: 'invalidName',
data: { name: 'Button' },
line: 3
},
{
messageId: 'invalidName',
data: { name: 'foo-button' },
line: 4
}
]
},
{
filename: 'test.vue',
code: `
<template>
<bar-button/>
<foo/>
</template>
`,
options: [{ allow: ['/^foo-/', 'bar'] }],
errors: [
{
messageId: 'invalidName',
data: { name: 'bar-button' },
line: 3
},
{
messageId: 'invalidName',
data: { name: 'foo' },
line: 4
}
]
}
]
})
Loading