Skip to content
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