Skip to content

Commit

Permalink
Add vue/no-unused-refs rule (#1474)
Browse files Browse the repository at this point in the history
* Add `vue/no-unused-refs` rule

* update doc
  • Loading branch information
ota-meshi committed Apr 12, 2021
1 parent 084bfe3 commit 238de1b
Show file tree
Hide file tree
Showing 5 changed files with 641 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/rules/README.md
Expand Up @@ -300,6 +300,7 @@ For example:
| [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: |
| [vue/no-duplicate-attr-inheritance](./no-duplicate-attr-inheritance.md) | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` | |
| [vue/no-empty-component-block](./no-empty-component-block.md) | disallow the `<template>` `<script>` `<style>` block to be empty | |
| [vue/no-invalid-model-keys](./no-invalid-model-keys.md) | require valid keys in model option | |
| [vue/no-multiple-objects-in-class](./no-multiple-objects-in-class.md) | disallow to pass multiple objects into array to class | |
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | |
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
Expand All @@ -315,6 +316,7 @@ For example:
| [vue/no-unregistered-components](./no-unregistered-components.md) | disallow using components that are not registered inside templates | |
| [vue/no-unsupported-features](./no-unsupported-features.md) | disallow unsupported Vue.js syntax on the specified version | :wrench: |
| [vue/no-unused-properties](./no-unused-properties.md) | disallow unused properties | |
| [vue/no-unused-refs](./no-unused-refs.md) | disallow unused refs | |
| [vue/no-useless-mustaches](./no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: |
| [vue/no-useless-v-bind](./no-useless-v-bind.md) | disallow unnecessary `v-bind` directives | :wrench: |
| [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: |
Expand Down
50 changes: 50 additions & 0 deletions docs/rules/no-unused-refs.md
@@ -0,0 +1,50 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-unused-refs
description: disallow unused refs
---
# vue/no-unused-refs

> disallow unused refs
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>

## :book: Rule Details

This rule is aimed at eliminating unused refs.
This rule reports refs that are defined using the `ref` attribute in `<template>` but are not used via `$refs`.

::: warning Note
This rule cannot be checked for use in other components (e.g. `mixins`, Access via `$refs.x.$refs`).
:::

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

```vue
<template>
<!-- ✓ GOOD -->
<input ref="foo" />
<!-- ✗ BAD (`bar` is not used) -->
<input ref="bar" />
</template>
<script>
export default {
mounted() {
this.$refs.foo.value = 'foo'
}
}
</script>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-unused-refs.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-unused-refs.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -117,6 +117,7 @@ module.exports = {
'no-unsupported-features': require('./rules/no-unsupported-features'),
'no-unused-components': require('./rules/no-unused-components'),
'no-unused-properties': require('./rules/no-unused-properties'),
'no-unused-refs': require('./rules/no-unused-refs'),
'no-unused-vars': require('./rules/no-unused-vars'),
'no-use-v-if-with-v-for': require('./rules/no-use-v-if-with-v-for'),
'no-useless-concat': require('./rules/no-useless-concat'),
Expand Down
204 changes: 204 additions & 0 deletions lib/rules/no-unused-refs.js
@@ -0,0 +1,204 @@
/**
* @fileoverview Disallow unused refs.
* @author Yosuke Ota
*/
'use strict'

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

const utils = require('../utils')

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

/**
* Extract names from references objects.
* @param {VReference[]} references
*/
function getReferences(references) {
return references.filter((ref) => ref.variable == null).map((ref) => ref.id)
}

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

module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow unused refs',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-unused-refs.html'
},
fixable: null,
schema: [],
messages: {
unused: "'{{name}}' is defined as ref, but never used."
}
},
/** @param {RuleContext} context */
create(context) {
/** @type {Set<string>} */
const usedRefs = new Set()

/** @type {VLiteral[]} */
const defineRefs = []
let hasUnknown = false

/**
* Report all unused refs.
*/
function reportUnusedRefs() {
for (const defineRef of defineRefs) {
if (usedRefs.has(defineRef.value)) {
continue
}

context.report({
node: defineRef,
messageId: 'unused',
data: {
name: defineRef.value
}
})
}
}

/**
* Extract the use ref names for ObjectPattern.
* @param {ObjectPattern} node
* @returns {void}
*/
function extractUsedForObjectPattern(node) {
for (const prop of node.properties) {
if (prop.type === 'Property') {
const name = utils.getStaticPropertyName(prop)
if (name) {
usedRefs.add(name)
} else {
hasUnknown = true
return
}
} else {
hasUnknown = true
return
}
}
}
/**
* Extract the use ref names.
* @param {Identifier | MemberExpression} refsNode
* @returns {void}
*/
function extractUsedForPattern(refsNode) {
/** @type {Identifier | MemberExpression | ChainExpression} */
let node = refsNode
while (node.parent.type === 'ChainExpression') {
node = node.parent
}
const parent = node.parent
if (parent.type === 'AssignmentExpression') {
if (parent.right === node) {
if (parent.left.type === 'ObjectPattern') {
// `({foo} = $refs)`
extractUsedForObjectPattern(parent.left)
} else if (parent.left.type === 'Identifier') {
// `foo = $refs`
hasUnknown = true
}
}
} else if (parent.type === 'VariableDeclarator') {
if (parent.init === node) {
if (parent.id.type === 'ObjectPattern') {
// `const {foo} = $refs`
extractUsedForObjectPattern(parent.id)
} else if (parent.id.type === 'Identifier') {
// `const foo = $refs`
hasUnknown = true
}
}
} else if (parent.type === 'MemberExpression') {
if (parent.object === node) {
// `$refs.foo`
const name = utils.getStaticPropertyName(parent)
if (name) {
usedRefs.add(name)
} else {
hasUnknown = true
}
}
} else if (parent.type === 'CallExpression') {
const argIndex = parent.arguments.indexOf(node)
if (argIndex > -1) {
// `foo($refs)`
hasUnknown = true
}
} else if (
parent.type === 'ForInStatement' ||
parent.type === 'ReturnStatement'
) {
hasUnknown = true
}
}

return utils.defineTemplateBodyVisitor(
context,
{
/**
* @param {VExpressionContainer} node
*/
VExpressionContainer(node) {
if (hasUnknown) {
return
}
for (const id of getReferences(node.references)) {
if (id.name !== '$refs') {
continue
}
extractUsedForPattern(id)
}
},
/**
* @param {VAttribute} node
*/
'VAttribute[directive=false]'(node) {
if (hasUnknown) {
return
}
if (node.key.name === 'ref' && node.value != null) {
defineRefs.push(node.value)
}
},
"VElement[parent.type!='VElement']:exit"() {
if (hasUnknown) {
return
}
reportUnusedRefs()
}
},
{
Identifier(id) {
if (hasUnknown) {
return
}
if (id.name !== '$refs') {
return
}
/** @type {Identifier | MemberExpression} */
let refsNode = id
if (id.parent.type === 'MemberExpression') {
if (id.parent.property === id) {
// `this.$refs.foo`
refsNode = id.parent
}
}
extractUsedForPattern(refsNode)
}
}
)
}
}

0 comments on commit 238de1b

Please sign in to comment.