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

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

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 52 additions & 1 deletion docs/rules/no-unregistered-components.md
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
Expand Up @@ -62,6 +62,9 @@ module.exports = {
properties: {
ignorePatterns: {
type: 'array'
},
ignoreRecursive: {
arrudaje marked this conversation as resolved.
Show resolved Hide resolved
arrudaje marked this conversation as resolved.
Show resolved Hide resolved
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 = ''
arrudaje marked this conversation as resolved.
Show resolved Hide resolved

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

if (nameProperty) {
componentName = nameProperty.value.value
arrudaje marked this conversation as resolved.
Show resolved Hide resolved
}
}
}),
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
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
}
]
}
]
})