Skip to content

Commit 9205cc8

Browse files
waynzhFloEdelmann
andauthored
feat(no-reserved-component-names): add case sensitive option (#2594)
Co-authored-by: Flo Edelmann <[email protected]>
1 parent 4729a3b commit 9205cc8

File tree

3 files changed

+107
-21
lines changed

3 files changed

+107
-21
lines changed

docs/rules/no-reserved-component-names.md

+31-1
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@ export default {
3535
{
3636
"vue/no-reserved-component-names": ["error", {
3737
"disallowVueBuiltInComponents": false,
38-
"disallowVue3BuiltInComponents": false
38+
"disallowVue3BuiltInComponents": false,
39+
"htmlElementCaseSensitive": false,
3940
}]
4041
}
4142
```
4243

4344
- `disallowVueBuiltInComponents` (`boolean`) ... If `true`, disallow Vue.js 2.x built-in component names. Default is `false`.
4445
- `disallowVue3BuiltInComponents` (`boolean`) ... If `true`, disallow Vue.js 3.x built-in component names. Default is `false`.
46+
- `htmlElementCaseSensitive` (`boolean`) ... If `true`, component names must exactly match the case of an HTML element to be considered conflicting. Default is `false` (i.e. case-insensitve comparison).
4547

4648
### `"disallowVueBuiltInComponents": true`
4749

@@ -73,6 +75,34 @@ export default {
7375

7476
</eslint-code-block>
7577

78+
### `"htmlElementCaseSensitive": true`
79+
80+
<eslint-code-block :rules="{'vue/no-reserved-component-names': ['error', {htmlElementCaseSensitive: true}]}">
81+
82+
```vue
83+
<script>
84+
/* ✓ GOOD */
85+
export default {
86+
name: 'Button'
87+
}
88+
</script>
89+
```
90+
91+
</eslint-code-block>
92+
93+
<eslint-code-block :rules="{'vue/no-reserved-component-names': ['error', {htmlElementCaseSensitive: true}]}">
94+
95+
```vue
96+
<script>
97+
/* ✗ BAD */
98+
export default {
99+
name: 'button'
100+
}
101+
</script>
102+
```
103+
104+
</eslint-code-block>
105+
76106
## :couple: Related Rules
77107

78108
- [vue/multi-word-component-names](./multi-word-component-names.md)

lib/rules/no-reserved-component-names.js

+38-20
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,6 @@ function isLowercase(word) {
3434
return /^[a-z]*$/.test(word)
3535
}
3636

37-
const RESERVED_NAMES_IN_HTML = new Set([
38-
...htmlElements,
39-
...htmlElements.map(casing.capitalize)
40-
])
41-
const RESERVED_NAMES_IN_OTHERS = new Set([
42-
...deprecatedHtmlElements,
43-
...deprecatedHtmlElements.map(casing.capitalize),
44-
...kebabCaseElements,
45-
...kebabCaseElements.map(casing.pascalCase),
46-
...svgElements,
47-
...svgElements.filter(isLowercase).map(casing.capitalize)
48-
])
49-
5037
/**
5138
* @param {Expression | SpreadElement} node
5239
* @returns {node is (Literal | TemplateLiteral)}
@@ -61,14 +48,14 @@ function canVerify(node) {
6148
}
6249

6350
/**
64-
* @param {string} name
65-
* @returns {string}
51+
* @template T
52+
* @param {Set<T>} set
53+
* @param {Iterable<T>} iterable
6654
*/
67-
function getMessageId(name) {
68-
if (RESERVED_NAMES_IN_HTML.has(name)) return 'reservedInHtml'
69-
if (RESERVED_NAMES_IN_VUE.has(name)) return 'reservedInVue'
70-
if (RESERVED_NAMES_IN_VUE3.has(name)) return 'reservedInVue3'
71-
return 'reserved'
55+
function addAll(set, iterable) {
56+
for (const element of iterable) {
57+
set.add(element)
58+
}
7259
}
7360

7461
module.exports = {
@@ -90,6 +77,9 @@ module.exports = {
9077
},
9178
disallowVue3BuiltInComponents: {
9279
type: 'boolean'
80+
},
81+
htmlElementCaseSensitive: {
82+
type: 'boolean'
9383
}
9484
},
9585
additionalProperties: false
@@ -109,6 +99,23 @@ module.exports = {
10999
options.disallowVueBuiltInComponents === true
110100
const disallowVue3BuiltInComponents =
111101
options.disallowVue3BuiltInComponents === true
102+
const htmlElementCaseSensitive = options.htmlElementCaseSensitive === true
103+
104+
const RESERVED_NAMES_IN_HTML = new Set(htmlElements)
105+
const RESERVED_NAMES_IN_OTHERS = new Set([
106+
...deprecatedHtmlElements,
107+
...kebabCaseElements,
108+
...svgElements
109+
])
110+
111+
if (!htmlElementCaseSensitive) {
112+
addAll(RESERVED_NAMES_IN_HTML, htmlElements.map(casing.capitalize))
113+
addAll(RESERVED_NAMES_IN_OTHERS, [
114+
...deprecatedHtmlElements.map(casing.capitalize),
115+
...kebabCaseElements.map(casing.pascalCase),
116+
...svgElements.filter(isLowercase).map(casing.capitalize)
117+
])
118+
}
112119

113120
const reservedNames = new Set([
114121
...RESERVED_NAMES_IN_HTML,
@@ -117,6 +124,17 @@ module.exports = {
117124
...RESERVED_NAMES_IN_OTHERS
118125
])
119126

127+
/**
128+
* @param {string} name
129+
* @returns {string}
130+
*/
131+
function getMessageId(name) {
132+
if (RESERVED_NAMES_IN_HTML.has(name)) return 'reservedInHtml'
133+
if (RESERVED_NAMES_IN_VUE.has(name)) return 'reservedInVue'
134+
if (RESERVED_NAMES_IN_VUE3.has(name)) return 'reservedInVue3'
135+
return 'reserved'
136+
}
137+
120138
/**
121139
* @param {Literal | TemplateLiteral} node
122140
*/

tests/lib/rules/no-reserved-component-names.js

+38
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,16 @@ const invalidElements = [
410410
'xmp',
411411
'Xmp'
412412
]
413+
const invalidLowerCaseElements = []
414+
const invalidUpperCaseElements = []
415+
416+
for (const element of invalidElements) {
417+
if (element[0] === element[0].toLowerCase()) {
418+
invalidLowerCaseElements.push(element)
419+
} else {
420+
invalidUpperCaseElements.push(element)
421+
}
422+
}
413423

414424
const vue2BuiltInComponents = [
415425
'component',
@@ -559,6 +569,16 @@ ruleTester.run('no-reserved-component-names', rule, {
559569
languageOptions,
560570
options: [{ disallowVueBuiltInComponents: true }]
561571
})),
572+
...invalidUpperCaseElements.map((name) => ({
573+
filename: `${name}.vue`,
574+
code: `
575+
export default {
576+
name: '${name}'
577+
}
578+
`,
579+
languageOptions,
580+
options: [{ htmlElementCaseSensitive: true }]
581+
})),
562582
{
563583
filename: 'test.vue',
564584
code: `<script setup> defineOptions({}) </script>`,
@@ -701,6 +721,24 @@ ruleTester.run('no-reserved-component-names', rule, {
701721
}
702722
]
703723
})),
724+
...invalidLowerCaseElements.map((name) => ({
725+
filename: `${name}.vue`,
726+
code: `<script setup> defineOptions({name: '${name}'}) </script>`,
727+
languageOptions: {
728+
parser: require('vue-eslint-parser'),
729+
...languageOptions
730+
},
731+
options: [{ htmlElementCaseSensitive: true }],
732+
errors: [
733+
{
734+
messageId: RESERVED_NAMES_IN_HTML.has(name)
735+
? 'reservedInHtml'
736+
: 'reserved',
737+
data: { name },
738+
line: 1
739+
}
740+
]
741+
})),
704742
...vue2BuiltInComponents.map((name) => ({
705743
filename: `${name}.vue`,
706744
code: `

0 commit comments

Comments
 (0)