/** * @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} */ 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() } }, utils.compositingVisitors( utils.isScriptSetup(context) ? { Program() { const globalScope = context.getSourceCode().scopeManager.globalScope if (!globalScope) { return } for (const variable of globalScope.variables) { if (variable.defs.length > 0) { usedRefs.add(variable.name) } } const moduleScope = globalScope.childScopes.find( (scope) => scope.type === 'module' ) if (!moduleScope) { return } for (const variable of moduleScope.variables) { if (variable.defs.length > 0) { usedRefs.add(variable.name) } } } } : {}, utils.defineVueVisitor(context, { onVueObjectEnter(node) { for (const prop of utils.iterateProperties( node, new Set(['setup']) )) { usedRefs.add(prop.name) } } }), { 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) } } ) ) } }