diff --git a/lib/rules/no-ref-as-operand.js b/lib/rules/no-ref-as-operand.js
index 067be4ec3..9661b2b60 100644
--- a/lib/rules/no-ref-as-operand.js
+++ b/lib/rules/no-ref-as-operand.js
@@ -9,8 +9,21 @@ 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))
+}
module.exports = {
meta: {
type: 'suggestion',
@@ -37,7 +50,7 @@ module.exports = {
*/
function reportIfRefWrapped(node) {
const data = refReferences.get(node)
- if (!data) {
+ if (!isRefInit(data)) {
return
}
context.report({
diff --git a/lib/utils/ref-object-references.js b/lib/utils/ref-object-references.js
index 1b3781bbb..0dd9abcd4 100644
--- a/lib/utils/ref-object-references.js
+++ b/lib/utils/ref-object-references.js
@@ -20,19 +20,23 @@ const { ReferenceTracker } = eslintUtils
* @property {MemberExpression | CallExpression} node
* @property {string} method
* @property {CallExpression} define
+ * @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*
* @typedef {object} RefObjectReferenceForPattern
* @property {'pattern'} type
* @property {ObjectPattern} node
* @property {string} method
* @property {CallExpression} define
+ * @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*
* @typedef {object} RefObjectReferenceForIdentifier
* @property {'expression' | 'pattern'} type
* @property {Identifier} node
+ * @property {VariableDeclarator | null} variableDeclarator
* @property {VariableDeclaration | null} variableDeclaration
* @property {string} method
* @property {CallExpression} define
+ * @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*
* @typedef {RefObjectReferenceForIdentifier | RefObjectReferenceForExpression | RefObjectReferenceForPattern} RefObjectReference
*/
@@ -258,6 +262,13 @@ module.exports = {
extractReactiveVariableReferences
}
+/**
+ * @typedef {object} RefObjectReferenceContext
+ * @property {string} method
+ * @property {CallExpression} define
+ * @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
+ */
+
/**
* @implements {RefObjectReferences}
*/
@@ -312,12 +323,19 @@ class RefObjectReferenceExtractor {
type: 'expression',
node,
method,
- define: node
+ define: node,
+ defineChain: [node]
})
}
return
}
+ const ctx = {
+ method,
+ define: node,
+ defineChain: [node]
+ }
+
if (method === 'toRefs') {
const propertyReferenceExtractor = definePropertyReferenceExtractor(
this.context
@@ -327,63 +345,65 @@ class RefObjectReferenceExtractor {
for (const name of propertyReferences.allProperties().keys()) {
for (const nest of propertyReferences.getNestNodes(name)) {
if (nest.type === 'expression') {
- this.processMemberExpression(nest.node, method, node)
+ this.processMemberExpression(nest.node, ctx)
} else if (nest.type === 'pattern') {
- this.processPattern(nest.node, method, node)
+ this.processPattern(nest.node, ctx)
}
}
}
} else {
- this.processPattern(pattern, method, node)
+ this.processPattern(pattern, ctx)
}
}
/**
- * @param {Expression} node
- * @param {string} method
- * @param {CallExpression} define
+ * @param {MemberExpression | Identifier} node
+ * @param {RefObjectReferenceContext} ctx
*/
- processExpression(node, method, define) {
+ processExpression(node, ctx) {
const parent = node.parent
if (parent.type === 'AssignmentExpression') {
if (parent.operator === '=' && parent.right === node) {
// `(foo = obj.mem)`
- this.processPattern(parent.left, method, define)
+ this.processPattern(parent.left, {
+ ...ctx,
+ defineChain: [node, ...ctx.defineChain]
+ })
return true
}
} else if (parent.type === 'VariableDeclarator' && parent.init === node) {
// `const foo = obj.mem`
- this.processPattern(parent.id, method, define)
+ this.processPattern(parent.id, {
+ ...ctx,
+ defineChain: [node, ...ctx.defineChain]
+ })
return true
}
return false
}
/**
* @param {MemberExpression} node
- * @param {string} method
- * @param {CallExpression} define
+ * @param {RefObjectReferenceContext} ctx
*/
- processMemberExpression(node, method, define) {
- if (this.processExpression(node, method, define)) {
+ processMemberExpression(node, ctx) {
+ if (this.processExpression(node, ctx)) {
return
}
this.references.set(node, {
type: 'expression',
node,
- method,
- define
+ ...ctx
})
}
/**
* @param {Pattern} node
- * @param {string} method
- * @param {CallExpression} define
+ * @param {RefObjectReferenceContext} ctx
*/
- processPattern(node, method, define) {
+ processPattern(node, ctx) {
switch (node.type) {
case 'Identifier': {
- this.processIdentifierPattern(node, method, define)
+ this.processIdentifierPattern(node, ctx)
break
}
case 'ArrayPattern':
@@ -395,13 +415,12 @@ class RefObjectReferenceExtractor {
this.references.set(node, {
type: 'pattern',
node,
- method,
- define
+ ...ctx
})
return
}
case 'AssignmentPattern': {
- this.processPattern(node.left, method, define)
+ this.processPattern(node.left, ctx)
return
}
// No default
@@ -410,10 +429,9 @@ class RefObjectReferenceExtractor {
/**
* @param {Identifier} node
- * @param {string} method
- * @param {CallExpression} define
+ * @param {RefObjectReferenceContext} ctx
*/
- processIdentifierPattern(node, method, define) {
+ processIdentifierPattern(node, ctx) {
if (this._processedIds.has(node)) {
return
}
@@ -434,16 +452,16 @@ class RefObjectReferenceExtractor {
}
if (
reference.isRead() &&
- this.processExpression(reference.identifier, method, define)
+ this.processExpression(reference.identifier, ctx)
) {
continue
}
this.references.set(reference.identifier, {
type: reference.isWrite() ? 'pattern' : 'expression',
node: reference.identifier,
- method,
- define,
- variableDeclaration: def ? def.parent : null
+ variableDeclarator: def ? def.node : null,
+ variableDeclaration: def ? def.parent : null,
+ ...ctx
})
}
}
diff --git a/tests/lib/rules/no-ref-as-operand.js b/tests/lib/rules/no-ref-as-operand.js
index fb564ce17..1cdbb1041 100644
--- a/tests/lib/rules/no-ref-as-operand.js
+++ b/tests/lib/rules/no-ref-as-operand.js
@@ -148,6 +148,30 @@ tester.run('no-ref-as-operand', rule, {
const isComp = foo.effect
`
+ },
+ {
+ code: `
+
+ `
+ },
+ {
+ code: `
+
+ `
}
],
invalid: [
@@ -669,6 +693,40 @@ tester.run('no-ref-as-operand', rule, {
messageId: 'requireDotValue'
}
]
+ },
+ {
+ code: `
+
+ `,
+ output: `
+
+ `,
+ errors: [
+ {
+ message:
+ 'Must use `.value` to read or write the value wrapped by `ref()`.',
+ line: 10,
+ column: 7
+ }
+ ]
}
]
})