Skip to content

Make rule vue/no-unregistered-components ignore recursive components #1305

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

Merged
Show file tree
Hide file tree
Changes from 3 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
53 changes: 52 additions & 1 deletion docs/rules/no-unregistered-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ are ignored by default.
```json
{
"vue/no-unregistered-components": ["error", {
"ignorePatterns": []
"ignorePatterns": [],
"ignoreRecursive": false
}]
}
```
Expand Down Expand Up @@ -131,6 +132,56 @@ are ignored by default.

</eslint-code-block>

- `ignoreRecursive` Suppresses all errors if component name matches its parent component name.

```
Beware: recursive components can cause infinite loops, so make sure you use it with a condition.
```

### `ignoreRecursive: true`

Note that you have to declare explicitly the `name` property in your component to make the recursive component work. See https://vuejs.org/v2/guide/components-edge-cases.html#Recursive-Components

<eslint-code-block :rules="{'vue/no-unregistered-components': ['error', { 'ignoreRecursive': true }]}">

```vue
<!-- ✓ GOOD -->
<template>
<div>
<h2>Lorem ipsum</h2>
<CustomComponent />
</div>
</template>

<script>
export default {
name: 'CustomComponent'
}
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/no-unregistered-components': ['error', { 'ignoreRecursive': true }]}">

```vue
<!-- ✗ BAD -->
<template>
<div>
<h2>Lorem ipsum</h2>
<CustomComponent />
</div>
</template>

<script>
export default {
// name is not declared
}
</script>
```

</eslint-code-block>

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unregistered-components.js)
Expand Down
35 changes: 31 additions & 4 deletions lib/rules/no-unregistered-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ module.exports = {
properties: {
ignorePatterns: {
type: 'array'
},
ignoreRecursive: {
type: 'boolean'
}
},
additionalProperties: false
Expand All @@ -73,14 +76,33 @@ module.exports = {
const options = context.options[0] || {}
/** @type {string[]} */
const ignorePatterns = options.ignorePatterns || []
/** @type {boolean} */
const ignoreRecursive = options.ignoreRecursive || false
/** @type { { node: VElement | VDirective | VAttribute, name: string }[] } */
const usedComponentNodes = []
/** @type { { node: Property, name: string }[] } */
const registeredComponents = []
/** @type {string} */
let componentName = ''

return utils.defineTemplateBodyVisitor(
context,
{
return utils.compositingVisitors(
utils.defineVueVisitor(context, {
/** @param {ObjectExpression} obj */
onVueObjectEnter(obj) {
const nameProperty = obj.properties.find(
/**
* @param {ESNode} p
* @returns {p is (Property & { key: Identifier & {name: 'name'}, value: ObjectExpression })}
*/
(p) => p.key.name === 'name'
)

if (nameProperty) {
componentName = nameProperty.value.value
}
}
}),
utils.defineTemplateBodyVisitor(context, {
/** @param {VElement} node */
VElement(node) {
if (
Expand Down Expand Up @@ -156,6 +178,11 @@ module.exports = {
)
return false

// Check recursive components if they are ignored
if (ignoreRecursive) {
return kebabCaseName !== casing.kebabCase(componentName)
}

// Component registered as `foo-bar` cannot be used as `FooBar`
if (
casing.isPascalCase(name) &&
Expand All @@ -178,7 +205,7 @@ module.exports = {
})
)
}
},
}),
utils.executeOnVue(context, (obj) => {
registeredComponents.push(...utils.getRegisteredComponents(obj))
})
Expand Down
190 changes: 190 additions & 0 deletions tests/lib/rules/no-unregistered-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,96 @@ tester.run('no-unregistered-components', rule, {
}
</script>
`
},
{
filename: 'test.vue',
code: `
<template>
<CustomComponent />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
options: [
{
ignoreRecursive: true
}
]
},
{
filename: 'test.vue',
code: `
<template>
<custom-component />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
options: [
{
ignoreRecursive: true
}
]
},
{
filename: 'test.vue',
code: `
<template>
<component :is="'CustomComponent'" />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
options: [
{
ignoreRecursive: true
}
]
},
{
filename: 'test.vue',
code: `
<template>
<component is="CustomComponent" />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
options: [
{
ignoreRecursive: true
}
]
},
{
filename: 'test.vue',
code: `
<template>
<div v-is="'CustomComponent'" />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
options: [
{
ignoreRecursive: true
}
]
}
],
invalid: [
Expand Down Expand Up @@ -583,6 +673,106 @@ tester.run('no-unregistered-components', rule, {
line: 3
}
]
},
{
filename: 'test.vue',
code: `
<template>
<CustomComponent />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
errors: [
{
message:
'The "CustomComponent" component has been used but not registered.',
line: 3
}
]
},
{
filename: 'test.vue',
code: `
<template>
<custom-component />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
errors: [
{
message:
'The "custom-component" component has been used but not registered.',
line: 3
}
]
},
{
filename: 'test.vue',
code: `
<template>
<component :is="'CustomComponent'" />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
errors: [
{
message:
'The "CustomComponent" component has been used but not registered.',
line: 3
}
]
},
{
filename: 'test.vue',
code: `
<template>
<component is="CustomComponent" />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
errors: [
{
message:
'The "CustomComponent" component has been used but not registered.',
line: 3
}
]
},
{
filename: 'test.vue',
code: `
<template>
<div v-is="'CustomComponent'" />
</template>
<script>
export default {
name: 'CustomComponent'
}
</script>
`,
errors: [
{
message:
'The "CustomComponent" component has been used but not registered.',
line: 3
}
]
}
]
})