Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(no-reserved-component-names): add case sensitive option #2594

Merged
merged 7 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 31 additions & 1 deletion docs/rules/no-reserved-component-names.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ export default {
{
"vue/no-reserved-component-names": ["error", {
"disallowVueBuiltInComponents": false,
"disallowVue3BuiltInComponents": false
"disallowVue3BuiltInComponents": false,
"htmlElementCaseSensitive": false,
}]
}
```

- `disallowVueBuiltInComponents` (`boolean`) ... If `true`, disallow Vue.js 2.x built-in component names. Default is `false`.
- `disallowVue3BuiltInComponents` (`boolean`) ... If `true`, disallow Vue.js 3.x built-in component names. Default is `false`.
- `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).

### `"disallowVueBuiltInComponents": true`

Expand Down Expand Up @@ -73,6 +75,34 @@ export default {

</eslint-code-block>

### `"htmlElementCaseSensitive": true`

<eslint-code-block :rules="{'vue/no-reserved-component-names': ['error', {htmlElementCaseSensitive: true}]}">

```vue
<script>
/* ✓ GOOD */
export default {
name: 'Button'
}
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/no-reserved-component-names': ['error', {htmlElementCaseSensitive: true}]}">

```vue
<script>
/* ✗ BAD */
export default {
name: 'button'
}
</script>
```

</eslint-code-block>

## :couple: Related Rules

- [vue/multi-word-component-names](./multi-word-component-names.md)
Expand Down
58 changes: 38 additions & 20 deletions lib/rules/no-reserved-component-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,6 @@ function isLowercase(word) {
return /^[a-z]*$/.test(word)
}

const RESERVED_NAMES_IN_HTML = new Set([
...htmlElements,
...htmlElements.map(casing.capitalize)
])
const RESERVED_NAMES_IN_OTHERS = new Set([
...deprecatedHtmlElements,
...deprecatedHtmlElements.map(casing.capitalize),
...kebabCaseElements,
...kebabCaseElements.map(casing.pascalCase),
...svgElements,
...svgElements.filter(isLowercase).map(casing.capitalize)
])

/**
* @param {Expression | SpreadElement} node
* @returns {node is (Literal | TemplateLiteral)}
Expand All @@ -61,14 +48,14 @@ function canVerify(node) {
}

/**
* @param {string} name
* @returns {string}
* @template T
* @param {Set<T>} set
* @param {Iterable<T>} iterable
*/
function getMessageId(name) {
if (RESERVED_NAMES_IN_HTML.has(name)) return 'reservedInHtml'
if (RESERVED_NAMES_IN_VUE.has(name)) return 'reservedInVue'
if (RESERVED_NAMES_IN_VUE3.has(name)) return 'reservedInVue3'
return 'reserved'
function addAll(set, iterable) {
for (const element of iterable) {
set.add(element)
}
}

module.exports = {
Expand All @@ -90,6 +77,9 @@ module.exports = {
},
disallowVue3BuiltInComponents: {
type: 'boolean'
},
htmlElementCaseSensitive: {
type: 'boolean'
}
},
additionalProperties: false
Expand All @@ -109,6 +99,23 @@ module.exports = {
options.disallowVueBuiltInComponents === true
const disallowVue3BuiltInComponents =
options.disallowVue3BuiltInComponents === true
const htmlElementCaseSensitive = options.htmlElementCaseSensitive === true

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

if (!htmlElementCaseSensitive) {
addAll(RESERVED_NAMES_IN_HTML, htmlElements.map(casing.capitalize))
addAll(RESERVED_NAMES_IN_OTHERS, [
...deprecatedHtmlElements.map(casing.capitalize),
...kebabCaseElements.map(casing.pascalCase),
...svgElements.filter(isLowercase).map(casing.capitalize)
])
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved
}

const reservedNames = new Set([
...RESERVED_NAMES_IN_HTML,
Expand All @@ -117,6 +124,17 @@ module.exports = {
...RESERVED_NAMES_IN_OTHERS
])

/**
* @param {string} name
* @returns {string}
*/
function getMessageId(name) {
if (RESERVED_NAMES_IN_HTML.has(name)) return 'reservedInHtml'
if (RESERVED_NAMES_IN_VUE.has(name)) return 'reservedInVue'
if (RESERVED_NAMES_IN_VUE3.has(name)) return 'reservedInVue3'
return 'reserved'
}

/**
* @param {Literal | TemplateLiteral} node
*/
Expand Down
38 changes: 38 additions & 0 deletions tests/lib/rules/no-reserved-component-names.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,16 @@ const invalidElements = [
'xmp',
'Xmp'
]
const invalidLowerCaseElements = []
const invalidUpperCaseElements = []

for (const element of invalidElements) {
if (element[0] === element[0].toLowerCase()) {
invalidLowerCaseElements.push(element)
} else {
invalidUpperCaseElements.push(element)
}
}

const vue2BuiltInComponents = [
'component',
Expand Down Expand Up @@ -559,6 +569,16 @@ ruleTester.run('no-reserved-component-names', rule, {
languageOptions,
options: [{ disallowVueBuiltInComponents: true }]
})),
...invalidUpperCaseElements.map((name) => ({
filename: `${name}.vue`,
code: `
export default {
name: '${name}'
}
`,
languageOptions,
options: [{ htmlElementCaseSensitive: true }]
})),
{
filename: 'test.vue',
code: `<script setup> defineOptions({}) </script>`,
Expand Down Expand Up @@ -701,6 +721,24 @@ ruleTester.run('no-reserved-component-names', rule, {
}
]
})),
...invalidLowerCaseElements.map((name) => ({
filename: `${name}.vue`,
code: `<script setup> defineOptions({name: '${name}'}) </script>`,
languageOptions: {
parser: require('vue-eslint-parser'),
...languageOptions
},
options: [{ htmlElementCaseSensitive: true }],
errors: [
{
messageId: RESERVED_NAMES_IN_HTML.has(name)
? 'reservedInHtml'
: 'reserved',
data: { name },
line: 1
}
]
})),
...vue2BuiltInComponents.map((name) => ({
filename: `${name}.vue`,
code: `
Expand Down
Loading