/** * @author Yosuke Ota * See LICENSE file in root directory for full license. */ 'use strict' const { ReferenceTracker, findVariable } = require('eslint-utils') const utils = require('../utils') module.exports = { meta: { type: 'suggestion', docs: { description: 'disallow use of value wrapped by `ref()` (Composition API) as an operand', categories: ['vue3-essential'], url: 'https://eslint.vuejs.org/rules/no-ref-as-operand.html' }, fixable: 'code', schema: [], messages: { requireDotValue: 'Must use `.value` to read or write the value wrapped by `{{method}}()`.' } }, /** @param {RuleContext} context */ create(context) { /** * @typedef {object} ReferenceData * @property {VariableDeclarator} variableDeclarator * @property {VariableDeclaration | null} variableDeclaration * @property {string} method */ /** @type {Map} */ const refReferenceIds = new Map() /** * @param {Identifier} node */ function reportIfRefWrapped(node) { const data = refReferenceIds.get(node) if (!data) { return } context.report({ node, messageId: 'requireDotValue', data: { method: data.method }, fix(fixer) { return fixer.insertTextAfter(node, '.value') } }) } return { Program() { const tracker = new ReferenceTracker(context.getScope()) const traceMap = utils.createCompositionApiTraceMap({ [ReferenceTracker.ESM]: true, ref: { [ReferenceTracker.CALL]: true }, computed: { [ReferenceTracker.CALL]: true }, toRef: { [ReferenceTracker.CALL]: true }, customRef: { [ReferenceTracker.CALL]: true }, shallowRef: { [ReferenceTracker.CALL]: true } }) for (const { node, path } of tracker.iterateEsmReferences(traceMap)) { const variableDeclarator = node.parent if ( !variableDeclarator || variableDeclarator.type !== 'VariableDeclarator' || variableDeclarator.id.type !== 'Identifier' ) { continue } const variable = findVariable( context.getScope(), variableDeclarator.id ) if (!variable) { continue } const variableDeclaration = (variableDeclarator.parent && variableDeclarator.parent.type === 'VariableDeclaration' && variableDeclarator.parent) || null for (const reference of variable.references) { if (!reference.isRead()) { continue } refReferenceIds.set(reference.identifier, { variableDeclarator, variableDeclaration, method: path[1] }) } } }, // if (refValue) /** @param {Identifier} node */ 'IfStatement>Identifier'(node) { reportIfRefWrapped(node) }, // switch (refValue) /** @param {Identifier} node */ 'SwitchStatement>Identifier'(node) { reportIfRefWrapped(node) }, // -refValue, +refValue, !refValue, ~refValue, typeof refValue /** @param {Identifier} node */ 'UnaryExpression>Identifier'(node) { reportIfRefWrapped(node) }, // refValue++, refValue-- /** @param {Identifier} node */ 'UpdateExpression>Identifier'(node) { reportIfRefWrapped(node) }, // refValue+1, refValue-1 /** @param {Identifier} node */ 'BinaryExpression>Identifier'(node) { reportIfRefWrapped(node) }, // refValue+=1, refValue-=1, foo+=refValue, foo-=refValue /** @param {Identifier & {parent: AssignmentExpression}} node */ 'AssignmentExpression>Identifier'(node) { if (node.parent.operator === '=' && node.parent.left !== node) { return } reportIfRefWrapped(node) }, // refValue || other, refValue && other. ignore: other || refValue /** @param {Identifier & {parent: LogicalExpression}} node */ 'LogicalExpression>Identifier'(node) { if (node.parent.left !== node) { return } // Report only constants. const data = refReferenceIds.get(node) if (!data) { return } if ( !data.variableDeclaration || data.variableDeclaration.kind !== 'const' ) { return } reportIfRefWrapped(node) }, // refValue ? x : y /** @param {Identifier & {parent: ConditionalExpression}} node */ 'ConditionalExpression>Identifier'(node) { if (node.parent.test !== node) { return } reportIfRefWrapped(node) }, // `${refValue}` /** @param {Identifier} node */ 'TemplateLiteral>Identifier'(node) { reportIfRefWrapped(node) }, // refValue.x /** @param {Identifier & {parent: MemberExpression}} node */ 'MemberExpression>Identifier'(node) { if (node.parent.object !== node) { return } const name = utils.getStaticPropertyName(node.parent) if ( name === 'value' || name == null || // WritableComputedRef name === 'effect' ) { return } reportIfRefWrapped(node) } } } }