Skip to content

Add vue/no-unregistered-components rule #1114

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
merged 14 commits into from
May 15, 2020
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
2 changes: 2 additions & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
| [vue/no-side-effects-in-computed-properties](./no-side-effects-in-computed-properties.md) | disallow side effects in computed properties | |
| [vue/no-template-key](./no-template-key.md) | disallow `key` attribute on `<template>` | |
| [vue/no-textarea-mustache](./no-textarea-mustache.md) | disallow mustaches in `<textarea>` | |
| [vue/no-unregistered-components](./no-unregistered-components.md) | disallow using components that are not registered | |
| [vue/no-unused-components](./no-unused-components.md) | disallow registering components that are not used inside templates | |
| [vue/no-unused-vars](./no-unused-vars.md) | disallow unused variable definitions of v-for directives or scope attributes | |
| [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md) | disallow use v-if on the same element as v-for | |
Expand Down Expand Up @@ -162,6 +163,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
| [vue/no-side-effects-in-computed-properties](./no-side-effects-in-computed-properties.md) | disallow side effects in computed properties | |
| [vue/no-template-key](./no-template-key.md) | disallow `key` attribute on `<template>` | |
| [vue/no-textarea-mustache](./no-textarea-mustache.md) | disallow mustaches in `<textarea>` | |
| [vue/no-unregistered-components](./no-unregistered-components.md) | disallow using components that are not registered | |
| [vue/no-unused-components](./no-unused-components.md) | disallow registering components that are not used inside templates | |
| [vue/no-unused-vars](./no-unused-vars.md) | disallow unused variable definitions of v-for directives or scope attributes | |
| [vue/no-use-v-if-with-v-for](./no-use-v-if-with-v-for.md) | disallow use v-if on the same element as v-for | |
Expand Down
132 changes: 132 additions & 0 deletions docs/rules/no-unregistered-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-unregistered-components
description: disallow using components that are not registered inside templates
---
# vue/no-unregistered-components
> disallow using components that are not registered inside templates

## :book: Rule Details

This rule reports components that haven't been registered and are being used in the template.

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

```vue
<!-- ✓ GOOD -->
<template>
<div>
<h2>Lorem ipsum</h2>
<the-modal>
<component is="TheInput" />
<component :is="'TheDropdown'" />
<TheButton>CTA</TheButton>
</the-modal>
</div>
</template>

<script>
import TheButton from 'components/TheButton.vue'
import TheModal from 'components/TheModal.vue'
import TheInput from 'components/TheInput.vue'
import TheDropdown from 'components/TheDropdown.vue'

export default {
components: {
TheButton,
TheModal,
TheInput,
TheDropdown,
}
}
</script>
```

</eslint-code-block>

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

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

<script>
export default {
components: {

}
}
</script>
```

</eslint-code-block>

## :wrench: Options

```json
{
"vue/no-unregistered-components": ["error", {
"ignorePatterns": []
}]
}
```

- `ignorePatterns` ... suppresses all errors if component name matches one or more patterns
default `[]`

### `ignorePatterns: [/custom(\-\w+)+/]`

<eslint-code-block :rules="{'vue/no-unregistered-components': ['error', { 'ignorePatterns': [/custom(\-\w+)+/] }]}">

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

<script>
export default {
components: {

},
}
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/no-unregistered-components': ['error', { 'ignorePatterns': [/custom(\-\w+)+/] }]}">

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

<script>
export default {
components: {

},
}
</script>
```

</eslint-code-block>

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unregistered-components.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-unregistered-components.js)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ module.exports = {
'no-template-shadow': require('./rules/no-template-shadow'),
'no-template-target-blank': require('./rules/no-template-target-blank'),
'no-textarea-mustache': require('./rules/no-textarea-mustache'),
'no-unregistered-component': require('./rules/no-unregistered-components'),
'no-unsupported-features': require('./rules/no-unsupported-features'),
'no-unused-components': require('./rules/no-unused-components'),
'no-unused-vars': require('./rules/no-unused-vars'),
Expand Down
108 changes: 108 additions & 0 deletions lib/rules/no-unregistered-components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @fileoverview Report used components that are not registered
* @author Jesús Ángel González Novez
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const utils = require('eslint-plugin-vue/lib/utils')
const casing = require('eslint-plugin-vue/lib/utils/casing')

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow using components that are not registered',
categories: ['essential'],
url: 'https://eslint.vuejs.org/rules/no-unregistered-components.html'
},
fixable: null,
schema: [{
type: 'object',
properties: {
ignorePatterns: {
type: 'array'
}
},
additionalProperties: false
}]
},

create (context) {
const options = context.options[0] || {}
const ignorePatterns = options.ignorePatterns || []
const usedComponentNodes = []
const registeredComponents = []
let templateLocation

return utils.defineTemplateBodyVisitor(context, {
VElement (node) {
if (
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
utils.isHtmlWellKnownElementName(node.rawName) ||
utils.isSvgWellKnownElementName(node.rawName) ||
node.rawName === 'component'
) {
return
}

usedComponentNodes.push({ node, name: node.rawName })
},
"VAttribute[directive=true][key.name.name='bind'][key.argument.name='is']" (node) {
if (
!node.value ||
node.value.type !== 'VExpressionContainer' ||
!node.value.expression
) return

if (node.value.expression.type === 'Literal') {
usedComponentNodes.push({ node, name: node.value.expression.value })
}
},
"VAttribute[directive=false][key.name='is']" (node) {
usedComponentNodes.push({ node, name: node.value.value })
},
"VElement[name='template']" (rootNode) {
templateLocation = templateLocation || rootNode.loc.start
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is no longer needed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the templateLocation variable.

},
"VElement[name='template']:exit" (rootNode) {
if (
rootNode.loc.start !== templateLocation ||
utils.hasAttribute(rootNode, 'src')
) return

const registeredComponentNames = registeredComponents.map(({ name }) => casing.kebabCase(name))

usedComponentNodes
.filter(({ name }) => {
const kebabCaseName = casing.kebabCase(name)
if (ignorePatterns.find(pattern => {
const regExp = new RegExp(pattern)
return regExp.test(kebabCaseName) ||
regExp.test(casing.pascalCase(name)) ||
regExp.test(casing.camelCase(name)) ||
regExp.test(casing.snakeCase(name)) ||
regExp.test(name)
})) return false
return registeredComponentNames.indexOf(kebabCaseName) === -1
})
.forEach(({ node, name }) => context.report({
node,
message: 'The "{{name}}" component has been used but not registered.',
data: {
name
}
}))
}
}, utils.executeOnVue(context, (obj) => {
registeredComponents.push(...utils.getRegisteredComponents(obj))
}))
}
}
Loading