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

Add disallowVueBuiltInComponents and disallowVue3BuiltInComponents option that reports Vue built-in component names to the vue/no-reserved-component-names rule. #1116

Merged
merged 1 commit into from May 9, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 43 additions & 2 deletions docs/rules/no-reserved-component-names.md
Expand Up @@ -9,7 +9,7 @@ description: disallow the use of reserved names in component definitions

## :book: Rule Details

This rule prevents name collisions between vue components and standard html elements.
This rule prevents name collisions between Vue components and standard HTML elements and built-in components.

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

Expand All @@ -26,14 +26,55 @@ export default {

## :wrench: Options

Nothing.
```json
{
"vue/no-reserved-component-names": ["error", {
"disallowVueBuiltInComponents": false,
"disallowVue3BuiltInComponents": 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`.

### `"disallowVueBuiltInComponents": true`

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

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

</eslint-code-block>

### `"disallowVue3BuiltInComponents": true`

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

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

</eslint-code-block>

## :books: Further reading

- [List of html elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element)
- [List of SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG/Element)
- [Kebab case elements](https://stackoverflow.com/questions/22545621/do-custom-elements-require-a-dash-in-their-name/22545622#22545622)
- [Valid custom element name](https://w3c.github.io/webcomponents/spec/custom/#valid-custom-element-name)
- [API - Built-In Components](https://vuejs.org/v2/api/index.html#Built-In-Components)

## :mag: Implementation

Expand Down
85 changes: 69 additions & 16 deletions lib/rules/no-reserved-component-names.js
Expand Up @@ -22,20 +22,44 @@ const kebabCaseElements = [
'missing-glyph'
]

// https://vuejs.org/v2/api/index.html#Built-In-Components
const vueBuiltInComponents = [
'component',
'transition',
'transition-group',
'keep-alive',
'slot'
]

const vue3BuiltInComponents = [
'teleport',
'suspense'
]

const isLowercase = (word) => /^[a-z]*$/.test(word)
const capitalizeFirstLetter = (word) => word[0].toUpperCase() + word.substring(1, word.length)

const RESERVED_NAMES = new Set(
[
...kebabCaseElements,
...kebabCaseElements.map(casing.pascalCase),
...htmlElements,
...htmlElements.map(capitalizeFirstLetter),
...deprecatedHtmlElements,
...deprecatedHtmlElements.map(capitalizeFirstLetter),
...svgElements,
...svgElements.filter(isLowercase).map(capitalizeFirstLetter)
])
const RESERVED_NAMES_IN_HTML = new Set([
...htmlElements,
...htmlElements.map(capitalizeFirstLetter)
])
const RESERVED_NAMES_IN_VUE = new Set([
...vueBuiltInComponents,
...vueBuiltInComponents.map(casing.pascalCase)
])
const RESERVED_NAMES_IN_VUE3 = new Set([
...RESERVED_NAMES_IN_VUE,
...vue3BuiltInComponents,
...vue3BuiltInComponents.map(casing.pascalCase)
])
const RESERVED_NAMES_IN_OTHERS = new Set([
...deprecatedHtmlElements,
...deprecatedHtmlElements.map(capitalizeFirstLetter),
...kebabCaseElements,
...kebabCaseElements.map(casing.pascalCase),
...svgElements,
...svgElements.filter(isLowercase).map(capitalizeFirstLetter)
])

// ------------------------------------------------------------------------------
// Rule Definition
Expand All @@ -46,14 +70,41 @@ module.exports = {
type: 'suggestion',
docs: {
description: 'disallow the use of reserved names in component definitions',
categories: undefined, // 'essential'
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-reserved-component-names.html'
},
fixable: null,
schema: []
schema: [{
type: 'object',
properties: {
disallowVueBuiltInComponents: {
type: 'boolean'
},
disallowVue3BuiltInComponents: {
type: 'boolean'
}
}
}],
messages: {
reserved: 'Name "{{name}}" is reserved.',
reservedInHtml: 'Name "{{name}}" is reserved in HTML.',
reservedInVue: 'Name "{{name}}" is reserved in Vue.js.',
reservedInVue3: 'Name "{{name}}" is reserved in Vue.js 3.x.'
}
},

create (context) {
const options = context.options[0] || {}
const disallowVueBuiltInComponents = options.disallowVueBuiltInComponents === true
const disallowVue3BuiltInComponents = options.disallowVue3BuiltInComponents === true

const reservedNames = new Set([
...RESERVED_NAMES_IN_HTML,
...(disallowVueBuiltInComponents ? RESERVED_NAMES_IN_VUE : []),
...(disallowVue3BuiltInComponents ? RESERVED_NAMES_IN_VUE3 : []),
...RESERVED_NAMES_IN_OTHERS
])

function canVerify (node) {
return node.type === 'Literal' || (
node.type === 'TemplateLiteral' &&
Expand All @@ -70,15 +121,17 @@ module.exports = {
} else {
name = node.value
}
if (RESERVED_NAMES.has(name)) {
if (reservedNames.has(name)) {
report(node, name)
}
}

function report (node, name) {
context.report({
node: node,
message: 'Name "{{name}}" is reserved.',
messageId: RESERVED_NAMES_IN_HTML.has(name) ? 'reservedInHtml'
: RESERVED_NAMES_IN_VUE.has(name) ? 'reservedInVue'
: RESERVED_NAMES_IN_VUE3.has(name) ? 'reservedInVue3' : 'reserved',
data: {
name: name
}
Expand All @@ -98,7 +151,7 @@ module.exports = {
utils.executeOnVue(context, (obj) => {
// Report if a component has been registered locally with a reserved name.
utils.getRegisteredComponents(obj)
.filter(({ name }) => RESERVED_NAMES.has(name))
.filter(({ name }) => reservedNames.has(name))
.forEach(({ node, name }) => report(node, name))

const node = obj.properties
Expand Down