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 vue/no-unregistered-components rule #1114

Merged
merged 14 commits into from May 15, 2020
2 changes: 2 additions & 0 deletions docs/rules/README.md
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
@@ -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.
jesusgn90 marked this conversation as resolved.
Show resolved Hide resolved

<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+)+/] }]}">
jesusgn90 marked this conversation as resolved.
Show resolved Hide resolved

```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+)+/] }]}">
jesusgn90 marked this conversation as resolved.
Show resolved Hide resolved

```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
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
@@ -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'],
jesusgn90 marked this conversation as resolved.
Show resolved Hide resolved
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) {
jesusgn90 marked this conversation as resolved.
Show resolved Hide resolved
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')
jesusgn90 marked this conversation as resolved.
Show resolved Hide resolved
) 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
jesusgn90 marked this conversation as resolved.
Show resolved Hide resolved
})
.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))
}))
}
}