Skip to content

Commit 235076b

Browse files
committed
feat: init rule
1 parent 16c8778 commit 235076b

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed

docs/rules/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ For example:
287287
| [vue/v-for-delimiter-style](./v-for-delimiter-style.md) | enforce `v-for` directive's delimiter style | :wrench: | :lipstick: |
288288
| [vue/v-if-else-key](./v-if-else-key.md) | require key attribute for conditionally rendered repeated components | :wrench: | :warning: |
289289
| [vue/v-on-handler-style](./v-on-handler-style.md) | enforce writing style for handlers in `v-on` directives | :wrench: | :hammer: |
290+
| [vue/valid-component-name](./valid-component-name.md) | enforce consistency in component names | | :warning: |
290291
| [vue/valid-define-options](./valid-define-options.md) | enforce valid `defineOptions` compiler macro | | :warning: |
291292

292293
</rules-table>

docs/rules/valid-component-name.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/valid-component-name
5+
description: enforce consistency in component names
6+
---
7+
8+
# vue/valid-component-name
9+
10+
> enforce consistency in component names
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>
13+
14+
## :book: Rule Details
15+
16+
This rule ....
17+
18+
<eslint-code-block :rules="{'vue/valid-component-name': ['error']}">
19+
20+
```vue
21+
<template>
22+
23+
</template>
24+
```
25+
26+
</eslint-code-block>
27+
28+
## :wrench: Options
29+
30+
Nothing.
31+
32+
## :mag: Implementation
33+
34+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-component-name.js)
35+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-component-name.js)

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ const plugin = {
253253
'v-on-style': require('./rules/v-on-style'),
254254
'v-slot-style': require('./rules/v-slot-style'),
255255
'valid-attribute-name': require('./rules/valid-attribute-name'),
256+
'valid-component-name': require('./rules/valid-component-name'),
256257
'valid-define-emits': require('./rules/valid-define-emits'),
257258
'valid-define-options': require('./rules/valid-define-options'),
258259
'valid-define-props': require('./rules/valid-define-props'),

lib/rules/valid-component-name.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* @author Wayne Zhang
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
const { toRegExp } = require('../utils/regexp')
9+
10+
const htmlElements = require('../utils/html-elements.json')
11+
const deprecatedHtmlElements = require('../utils/deprecated-html-elements.json')
12+
const svgElements = require('../utils/svg-elements.json')
13+
14+
const RESERVED_NAMES_IN_VUE = new Set(
15+
require('../utils/vue2-builtin-components')
16+
)
17+
const RESERVED_NAMES_IN_VUE3 = new Set(
18+
require('../utils/vue3-builtin-components')
19+
)
20+
const kebabCaseElements = [
21+
'annotation-xml',
22+
'color-profile',
23+
'font-face',
24+
'font-face-src',
25+
'font-face-uri',
26+
'font-face-format',
27+
'font-face-name',
28+
'missing-glyph'
29+
]
30+
31+
const RESERVED_NAMES_IN_HTML = new Set(htmlElements)
32+
const RESERVED_NAMES_IN_OTHERS = new Set([
33+
...deprecatedHtmlElements,
34+
...kebabCaseElements,
35+
...svgElements
36+
])
37+
38+
const reservedNames = new Set([
39+
...RESERVED_NAMES_IN_HTML,
40+
...RESERVED_NAMES_IN_VUE,
41+
...RESERVED_NAMES_IN_VUE3,
42+
...RESERVED_NAMES_IN_OTHERS
43+
])
44+
45+
module.exports = {
46+
meta: {
47+
type: 'problem',
48+
docs: {
49+
description: 'enforce consistency in component names',
50+
categories: undefined,
51+
url: 'https://eslint.vuejs.org/rules/valid-component-name.html'
52+
},
53+
fixable: null,
54+
schema: [
55+
{
56+
type: 'object',
57+
additionalProperties: false,
58+
properties: {
59+
allow: {
60+
type: 'array',
61+
items: { type: 'string' },
62+
uniqueItems: true,
63+
additionalItems: false
64+
}
65+
}
66+
}
67+
],
68+
messages: {
69+
invalidName: 'Component name "{{name}}" is not valid.'
70+
}
71+
},
72+
/** @param {RuleContext} context */
73+
create(context) {
74+
const options = context.options[0] || {}
75+
/** @type {RegExp[]} */
76+
const allow = (options.allow || []).map(toRegExp)
77+
78+
/** @param {string} name */
79+
function isAllowedTarget(name) {
80+
return reservedNames.has(name) || allow.some((re) => re.test(name))
81+
}
82+
83+
return utils.defineTemplateBodyVisitor(context, {
84+
VElement(node) {
85+
const name = node.rawName
86+
if (isAllowedTarget(name)) {
87+
return
88+
}
89+
90+
context.report({
91+
node,
92+
loc: node.loc,
93+
messageId: 'invalidName',
94+
data: {
95+
name
96+
}
97+
})
98+
}
99+
})
100+
}
101+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* @author Wayne Zhang
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('../../eslint-compat').RuleTester
8+
const rule = require('../../../lib/rules/valid-component-name')
9+
10+
const tester = new RuleTester({
11+
languageOptions: {
12+
parser: require('vue-eslint-parser'),
13+
ecmaVersion: 2020,
14+
sourceType: 'module'
15+
}
16+
})
17+
18+
tester.run('valid-component-name', rule, {
19+
valid: [
20+
'<template><keep-alive></keep-alive></template>',
21+
'<template><button/></template>',
22+
{
23+
filename: 'test.vue',
24+
code: `
25+
<template>
26+
<foo-button/>
27+
<div-bar/>
28+
</template>
29+
`,
30+
options: [{ allow: ['/^foo-/', '/-bar$/'] }]
31+
}
32+
],
33+
invalid: [
34+
{
35+
filename: 'test.vue',
36+
code: `
37+
<template>
38+
<Button/>
39+
<foo-button/>
40+
</template>
41+
`,
42+
errors: [
43+
{
44+
messageId: 'invalidName',
45+
data: { name: 'Button' },
46+
line: 3
47+
},
48+
{
49+
messageId: 'invalidName',
50+
data: { name: 'foo-button' },
51+
line: 4
52+
}
53+
]
54+
},
55+
{
56+
filename: 'test.vue',
57+
code: `
58+
<template>
59+
<bar-button/>
60+
<foo/>
61+
</template>
62+
`,
63+
options: [{ allow: ['/^foo-/', 'bar'] }],
64+
errors: [
65+
{
66+
messageId: 'invalidName',
67+
data: { name: 'bar-button' },
68+
line: 3
69+
},
70+
{
71+
messageId: 'invalidName',
72+
data: { name: 'foo' },
73+
line: 4
74+
}
75+
]
76+
}
77+
]
78+
})

0 commit comments

Comments
 (0)