| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- /**
- * @author Yosuke Ota
- * See LICENSE file in root directory for full license.
- */
- 'use strict'
- const { findVariable } = require('@eslint-community/eslint-utils')
- const { extractRefObjectReferences } = require('../utils/ref-object-references')
- const utils = require('../utils')
- /**
- * @typedef {import('../utils/ref-object-references').RefObjectReferences} RefObjectReferences
- * @typedef {import('../utils/ref-object-references').RefObjectReferenceForIdentifier} RefObjectReferenceForIdentifier
- */
- /**
- * Checks whether the given identifier reference has been initialized with a ref object.
- * @param {RefObjectReferenceForIdentifier | null} data
- * @returns {data is RefObjectReferenceForIdentifier}
- */
- function isRefInit(data) {
- const init = data && data.variableDeclarator && data.variableDeclarator.init
- if (!init) {
- return false
- }
- return data.defineChain.includes(/** @type {any} */ (init))
- }
- /**
- * Get the callee member node from the given CallExpression
- * @param {CallExpression} node CallExpression
- */
- function getNameParamNode(node) {
- const nameLiteralNode = node.arguments[0]
- if (nameLiteralNode && utils.isStringLiteral(nameLiteralNode)) {
- const name = utils.getStringLiteralValue(nameLiteralNode)
- if (name != null) {
- return { name, loc: nameLiteralNode.loc }
- }
- }
- // cannot check
- return null
- }
- /**
- * Get the callee member node from the given CallExpression
- * @param {CallExpression} node CallExpression
- */
- function getCalleeMemberNode(node) {
- const callee = utils.skipChainExpression(node.callee)
- if (callee.type === 'MemberExpression') {
- const name = utils.getStaticPropertyName(callee)
- if (name) {
- return { name, member: callee }
- }
- }
- return null
- }
- module.exports = {
- meta: {
- type: 'suggestion',
- docs: {
- description:
- 'disallow use of value wrapped by `ref()` (Composition API) as an operand',
- categories: ['vue3-essential', 'vue2-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) {
- /** @type {RefObjectReferences} */
- let refReferences
- const setupContexts = new Map()
- /**
- * Collect identifier id
- * @param {Identifier} node
- * @param {Set<Identifier>} referenceIds
- */
- function collectReferenceIds(node, referenceIds) {
- const variable = findVariable(utils.getScope(context, node), node)
- if (!variable) {
- return
- }
- for (const reference of variable.references) {
- referenceIds.add(reference.identifier)
- }
- }
- /**
- * @param {Identifier} node
- */
- function reportIfRefWrapped(node) {
- const data = refReferences.get(node)
- if (!isRefInit(data)) {
- return
- }
- context.report({
- node,
- messageId: 'requireDotValue',
- data: {
- method: data.method
- },
- fix(fixer) {
- return fixer.insertTextAfter(node, '.value')
- }
- })
- }
- /**
- * @param {CallExpression} node
- */
- function reportWrappedIdentifiers(node) {
- const nodes = node.arguments.filter((node) => node.type === 'Identifier')
- for (const node of nodes) {
- reportIfRefWrapped(node)
- }
- }
- const programNode = context.getSourceCode().ast
- const callVisitor = {
- /**
- * @param {CallExpression} node
- * @param {import('../utils').VueObjectData} [info]
- */
- CallExpression(node, info) {
- const nameWithLoc = getNameParamNode(node)
- if (!nameWithLoc) {
- // cannot check
- return
- }
- // verify setup context
- const setupContext = setupContexts.get(info ? info.node : programNode)
- if (!setupContext) {
- return
- }
- const { contextReferenceIds, emitReferenceIds } = setupContext
- if (
- node.callee.type === 'Identifier' &&
- emitReferenceIds.has(node.callee)
- ) {
- // verify setup(props,{emit}) {emit()}
- reportWrappedIdentifiers(node)
- } else {
- const emit = getCalleeMemberNode(node)
- if (
- emit &&
- emit.name === 'emit' &&
- emit.member.object.type === 'Identifier' &&
- contextReferenceIds.has(emit.member.object)
- ) {
- // verify setup(props,context) {context.emit()}
- reportWrappedIdentifiers(node)
- }
- }
- }
- }
- return utils.compositingVisitors(
- {
- Program() {
- refReferences = extractRefObjectReferences(context)
- },
- // 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 = refReferences.get(node)
- if (
- !data ||
- !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)
- }
- },
- utils.defineScriptSetupVisitor(context, {
- onDefineEmitsEnter(node) {
- if (
- !node.parent ||
- node.parent.type !== 'VariableDeclarator' ||
- node.parent.init !== node
- ) {
- return
- }
- const emitParam = node.parent.id
- if (emitParam.type !== 'Identifier') {
- return
- }
- // const emit = defineEmits()
- const emitReferenceIds = new Set()
- collectReferenceIds(emitParam, emitReferenceIds)
- setupContexts.set(programNode, {
- contextReferenceIds: new Set(),
- emitReferenceIds
- })
- },
- ...callVisitor
- }),
- utils.defineVueVisitor(context, {
- onSetupFunctionEnter(node, { node: vueNode }) {
- const contextParam = utils.skipDefaultParamValue(node.params[1])
- if (!contextParam) {
- // no arguments
- return
- }
- if (
- contextParam.type === 'RestElement' ||
- contextParam.type === 'ArrayPattern'
- ) {
- // cannot check
- return
- }
- const contextReferenceIds = new Set()
- const emitReferenceIds = new Set()
- if (contextParam.type === 'ObjectPattern') {
- const emitProperty = utils.findAssignmentProperty(
- contextParam,
- 'emit'
- )
- if (!emitProperty || emitProperty.value.type !== 'Identifier') {
- return
- }
- // `setup(props, {emit})`
- collectReferenceIds(emitProperty.value, emitReferenceIds)
- } else {
- // `setup(props, context)`
- collectReferenceIds(contextParam, contextReferenceIds)
- }
- setupContexts.set(vueNode, {
- contextReferenceIds,
- emitReferenceIds
- })
- },
- ...callVisitor,
- onVueObjectExit(node) {
- setupContexts.delete(node)
- }
- })
- )
- }
- }
|