You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

172 lines
4.6 KiB

/**
* @author Yosuke Ota <https://github.com/ota-meshi>
* See LICENSE file in root directory for full license.
*/
'use strict'
const utils = require('../utils')
const {
extractRefObjectReferences,
extractReactiveVariableReferences
} = require('../utils/ref-object-references')
/**
* @typedef {import('../utils/ref-object-references').RefObjectReferences} RefObjectReferences
* @typedef {import('../utils/ref-object-references').RefObjectReference} RefObjectReference
*/
/**
* Checks whether writing assigns a value to the given pattern.
* @param {Pattern | AssignmentProperty | Property} node
* @returns {boolean}
*/
function isUpdate(node) {
const parent = node.parent
if (parent.type === 'UpdateExpression' && parent.argument === node) {
// e.g. `pattern++`
return true
}
if (parent.type === 'AssignmentExpression' && parent.left === node) {
// e.g. `pattern = 42`
return true
}
if (
(parent.type === 'Property' && parent.value === node) ||
parent.type === 'ArrayPattern' ||
(parent.type === 'ObjectPattern' &&
parent.properties.includes(/** @type {any} */ (node))) ||
(parent.type === 'AssignmentPattern' && parent.left === node) ||
parent.type === 'RestElement' ||
(parent.type === 'MemberExpression' && parent.object === node)
) {
return isUpdate(parent)
}
return false
}
module.exports = {
meta: {
type: 'problem',
docs: {
description:
'disallow usages of ref objects that can lead to loss of reactivity',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-ref-object-reactivity-loss.html'
},
fixable: null,
schema: [],
messages: {
getValueInSameScope:
'Getting a value from the ref object in the same scope will cause the value to lose reactivity.',
getReactiveVariableInSameScope:
'Getting a reactive variable in the same scope will cause the value to lose reactivity.'
}
},
/**
* @param {RuleContext} context
* @returns {RuleListener}
*/
create(context) {
/**
* @typedef {object} ScopeStack
* @property {ScopeStack | null} upper
* @property {Program | FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
*/
/** @type {ScopeStack} */
let scopeStack = { upper: null, node: context.getSourceCode().ast }
/** @type {Map<CallExpression, ScopeStack>} */
const scopes = new Map()
const refObjectReferences = extractRefObjectReferences(context)
const reactiveVariableReferences =
extractReactiveVariableReferences(context)
/**
* Verify the given ref object value. `refObj = ref(); refObj.value;`
* @param {Expression | Super | ObjectPattern} node
*/
function verifyRefObjectValue(node) {
const ref = refObjectReferences.get(node)
if (!ref) {
return
}
if (scopes.get(ref.define) !== scopeStack) {
// Not in the same scope
return
}
context.report({
node,
messageId: 'getValueInSameScope'
})
}
/**
* Verify the given reactive variable. `refVal = $ref(); refVal;`
* @param {Identifier} node
*/
function verifyReactiveVariable(node) {
const ref = reactiveVariableReferences.get(node)
if (!ref || ref.escape) {
return
}
if (scopes.get(ref.define) !== scopeStack) {
// Not in the same scope
return
}
context.report({
node,
messageId: 'getReactiveVariableInSameScope'
})
}
return {
':function'(node) {
scopeStack = { upper: scopeStack, node }
},
':function:exit'() {
scopeStack = scopeStack.upper || scopeStack
},
CallExpression(node) {
scopes.set(node, scopeStack)
},
/**
* Check for `refObj.value`.
*/
'MemberExpression:exit'(node) {
if (isUpdate(node)) {
// e.g. `refObj.value = 42`, `refObj.value++`
return
}
const name = utils.getStaticPropertyName(node)
if (name !== 'value') {
return
}
verifyRefObjectValue(node.object)
},
/**
* Check for `{value} = refObj`.
*/
'ObjectPattern:exit'(node) {
const prop = utils.findAssignmentProperty(node, 'value')
if (!prop) {
return
}
verifyRefObjectValue(node)
},
/**
* Check for reactive variable`.
* @param {Identifier} node
*/
'Identifier:exit'(node) {
if (isUpdate(node)) {
// e.g. `reactiveVariable = 42`, `reactiveVariable++`
return
}
verifyReactiveVariable(node)
}
}
}
}