Skip to content

Commit 083ea82

Browse files
authored
Improved vue/no-ref-as-operand rule. (#1180)
- Changed `vue/no-ref-as-operand` rule to additionally track variables generated by `computed`, `toRef`, `customRef` and `shallowRef`. - Changed `vue/no-ref-as-operand` rule to report incorrect use of TemplateLiteral and MemberExpression.
1 parent 52f34e9 commit 083ea82

File tree

3 files changed

+151
-11
lines changed

3 files changed

+151
-11
lines changed

Diff for: docs/rules/no-ref-as-operand.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ description: disallow use of value wrapped by `ref()` (Composition API) as an op
1111

1212
## :book: Rule Details
1313

14-
This rule reports cases where a ref is used incorrectly as an operand.
14+
This rule reports cases where a ref is used incorrectly as an operand.
15+
You must use `.value` to access the `Ref` value.
1516

1617
<eslint-code-block :rules="{'vue/no-ref-as-operand': ['error']}">
1718

Diff for: lib/rules/no-ref-as-operand.js

+47-9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
'use strict'
66
const { ReferenceTracker, findVariable } = require('eslint-utils')
7+
const utils = require('../utils')
78

89
module.exports = {
910
meta: {
@@ -18,19 +19,23 @@ module.exports = {
1819
schema: [],
1920
messages: {
2021
requireDotValue:
21-
'Must use `.value` to read or write the value wrapped by `ref()`.'
22+
'Must use `.value` to read or write the value wrapped by `{{method}}()`.'
2223
}
2324
},
2425
create(context) {
2526
const refReferenceIds = new Map()
2627

2728
function reportIfRefWrapped(node) {
28-
if (!refReferenceIds.has(node)) {
29+
const data = refReferenceIds.get(node)
30+
if (!data) {
2931
return
3032
}
3133
context.report({
3234
node,
33-
messageId: 'requireDotValue'
35+
messageId: 'requireDotValue',
36+
data: {
37+
method: data.method
38+
}
3439
})
3540
}
3641
return {
@@ -41,11 +46,23 @@ module.exports = {
4146
[ReferenceTracker.ESM]: true,
4247
ref: {
4348
[ReferenceTracker.CALL]: true
49+
},
50+
computed: {
51+
[ReferenceTracker.CALL]: true
52+
},
53+
toRef: {
54+
[ReferenceTracker.CALL]: true
55+
},
56+
customRef: {
57+
[ReferenceTracker.CALL]: true
58+
},
59+
shallowRef: {
60+
[ReferenceTracker.CALL]: true
4461
}
4562
}
4663
}
4764

48-
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
65+
for (const { node, path } of tracker.iterateEsmReferences(traceMap)) {
4966
const variableDeclarator = node.parent
5067
if (
5168
!variableDeclarator ||
@@ -73,7 +90,8 @@ module.exports = {
7390

7491
refReferenceIds.set(reference.identifier, {
7592
variableDeclarator,
76-
variableDeclaration
93+
variableDeclaration,
94+
method: path[1]
7795
})
7896
}
7997
}
@@ -108,13 +126,13 @@ module.exports = {
108126
return
109127
}
110128
// Report only constants.
111-
const info = refReferenceIds.get(node)
112-
if (!info) {
129+
const data = refReferenceIds.get(node)
130+
if (!data) {
113131
return
114132
}
115133
if (
116-
!info.variableDeclaration ||
117-
info.variableDeclaration.kind !== 'const'
134+
!data.variableDeclaration ||
135+
data.variableDeclaration.kind !== 'const'
118136
) {
119137
return
120138
}
@@ -126,6 +144,26 @@ module.exports = {
126144
return
127145
}
128146
reportIfRefWrapped(node)
147+
},
148+
// `${refValue}`
149+
'TemplateLiteral>Identifier'(node) {
150+
reportIfRefWrapped(node)
151+
},
152+
// refValue.x
153+
'MemberExpression>Identifier'(node) {
154+
if (node.parent.object !== node) {
155+
return
156+
}
157+
const name = utils.getStaticPropertyName(node.parent)
158+
if (
159+
name === 'value' ||
160+
name == null ||
161+
// WritableComputedRef
162+
name === 'effect'
163+
) {
164+
return
165+
}
166+
reportIfRefWrapped(node)
129167
}
130168
}
131169
}

Diff for: tests/lib/rules/no-ref-as-operand.js

+102-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,25 @@ tester.run('no-ref-as-operand', rule, {
103103
import { ref } from 'vue'
104104
const count = ref
105105
count++
106-
`
106+
`,
107+
{
108+
code: `
109+
<script>
110+
import { ref, computed, toRef, customRef, shallowRef } from 'vue'
111+
const foo = shallowRef({})
112+
foo[bar] = 123
113+
</script>
114+
`
115+
},
116+
{
117+
code: `
118+
<script>
119+
import { ref, computed, toRef, customRef, shallowRef } from 'vue'
120+
const foo = shallowRef({})
121+
const isComp = foo.effect
122+
</script>
123+
`
124+
}
107125
],
108126
invalid: [
109127
{
@@ -332,6 +350,89 @@ tester.run('no-ref-as-operand', rule, {
332350
line: 9
333351
}
334352
]
353+
},
354+
{
355+
code: `
356+
<script>
357+
import { ref, computed, toRef, customRef, shallowRef } from 'vue'
358+
let count = ref(0)
359+
let cntcnt = computed(()=>count.value+count.value)
360+
361+
const state = reactive({
362+
foo: 1,
363+
bar: 2
364+
})
365+
366+
const fooRef = toRef(state, 'foo')
367+
368+
let value = 'hello'
369+
const cref = customRef((track, trigger) => {
370+
return {
371+
get() {
372+
track()
373+
return value
374+
},
375+
set(newValue) {
376+
clearTimeout(timeout)
377+
timeout = setTimeout(() => {
378+
value = newValue
379+
trigger()
380+
}, delay)
381+
}
382+
}
383+
})
384+
385+
const foo = shallowRef({})
386+
387+
count++ // error
388+
cntcnt++ // error
389+
390+
const s = \`\${fooRef} : \${cref}\` // error x 2
391+
392+
const n = foo + 1 // error
393+
</script>
394+
`,
395+
errors: [
396+
{
397+
message:
398+
'Must use `.value` to read or write the value wrapped by `ref()`.',
399+
line: 33
400+
},
401+
{
402+
message:
403+
'Must use `.value` to read or write the value wrapped by `computed()`.',
404+
line: 34
405+
},
406+
{
407+
message:
408+
'Must use `.value` to read or write the value wrapped by `toRef()`.',
409+
line: 36
410+
},
411+
{
412+
message:
413+
'Must use `.value` to read or write the value wrapped by `customRef()`.',
414+
line: 36
415+
},
416+
{
417+
message:
418+
'Must use `.value` to read or write the value wrapped by `shallowRef()`.',
419+
line: 38
420+
}
421+
]
422+
},
423+
{
424+
code: `
425+
<script>
426+
import { ref, computed, toRef, customRef, shallowRef } from 'vue'
427+
const foo = shallowRef({})
428+
foo.bar = 123
429+
</script>
430+
`,
431+
errors: [
432+
{
433+
messageId: 'requireDotValue'
434+
}
435+
]
335436
}
336437
]
337438
})

0 commit comments

Comments
 (0)