Skip to content

Commit 11c9a94

Browse files
authored
New: Add vue/no-ref-as-operand rule (#1065)
* Add no-ref-as-operand rule * update * Add testcases * update * Fixed testcases * update doc
1 parent bd770c2 commit 11c9a94

File tree

6 files changed

+521
-2
lines changed

6 files changed

+521
-2
lines changed

Diff for: docs/rules/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ For example:
167167
| [vue/no-deprecated-v-bind-sync](./no-deprecated-v-bind-sync.md) | disallow use of deprecated `.sync` modifier on `v-bind` directive (in Vue.js 3.0.0+) | :wrench: |
168168
| [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | |
169169
| [vue/no-irregular-whitespace](./no-irregular-whitespace.md) | disallow irregular whitespace | |
170+
| [vue/no-ref-as-operand](./no-ref-as-operand.md) | disallow use of value wrapped by `ref()` (Composition API) as an operand | |
170171
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
171172
| [vue/no-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax | |
172173
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | |

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

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-ref-as-operand
5+
description: disallow use of value wrapped by `ref()` (Composition API) as an operand
6+
---
7+
# vue/no-ref-as-operand
8+
> disallow use of value wrapped by `ref()` (Composition API) as an operand
9+
10+
## :book: Rule Details
11+
12+
This rule reports cases where a ref is used incorrectly as an operand.
13+
14+
<eslint-code-block :rules="{'vue/no-ref-as-operand': ['error']}">
15+
16+
```vue
17+
<script>
18+
import { ref } from 'vue'
19+
20+
export default {
21+
setup () {
22+
const count = ref(0)
23+
const ok = ref(true)
24+
25+
/* ✓ GOOD */
26+
count.value++
27+
count.value + 1
28+
1 + count.value
29+
var msg = ok.value ? 'yes' : 'no'
30+
31+
/* ✗ BAD */
32+
count++
33+
count + 1
34+
1 + count
35+
var msg = ok ? 'yes' : 'no'
36+
37+
return {
38+
count
39+
}
40+
}
41+
}
42+
</script>
43+
```
44+
45+
</eslint-code-block>
46+
47+
## :wrench: Options
48+
49+
Nothing.
50+
51+
## :books: Further reading
52+
53+
- [Vue RFCs - 0013-composition-api](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0013-composition-api.md)
54+
55+
## :mag: Implementation
56+
57+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-ref-as-operand.js)
58+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-ref-as-operand.js)

Diff for: lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ module.exports = {
5252
'no-multi-spaces': require('./rules/no-multi-spaces'),
5353
'no-multiple-template-root': require('./rules/no-multiple-template-root'),
5454
'no-parsing-error': require('./rules/no-parsing-error'),
55+
'no-ref-as-operand': require('./rules/no-ref-as-operand'),
5556
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
5657
'no-reserved-keys': require('./rules/no-reserved-keys'),
5758
'no-restricted-syntax': require('./rules/no-restricted-syntax'),

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

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
const { ReferenceTracker, findVariable } = require('eslint-utils')
7+
8+
module.exports = {
9+
meta: {
10+
type: 'suggestion',
11+
docs: {
12+
description: 'disallow use of value wrapped by `ref()` (Composition API) as an operand',
13+
category: undefined,
14+
url: 'https://eslint.vuejs.org/rules/no-ref-as-operand.html'
15+
},
16+
fixable: null,
17+
schema: [],
18+
messages: {
19+
requireDotValue: 'Must use `.value` to read or write the value wrapped by `ref()`.'
20+
}
21+
},
22+
create (context) {
23+
const refReferenceIds = new Map()
24+
25+
function reportIfRefWrapped (node) {
26+
if (!refReferenceIds.has(node)) {
27+
return
28+
}
29+
context.report({
30+
node,
31+
messageId: 'requireDotValue'
32+
})
33+
}
34+
return {
35+
'Program' () {
36+
const tracker = new ReferenceTracker(context.getScope())
37+
const traceMap = {
38+
vue: {
39+
[ReferenceTracker.ESM]: true,
40+
ref: {
41+
[ReferenceTracker.CALL]: true
42+
}
43+
}
44+
}
45+
46+
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
47+
const variableDeclarator = node.parent
48+
if (
49+
!variableDeclarator ||
50+
variableDeclarator.type !== 'VariableDeclarator' ||
51+
variableDeclarator.id.type !== 'Identifier'
52+
) {
53+
continue
54+
}
55+
const variable = findVariable(context.getScope(), variableDeclarator.id)
56+
if (!variable) {
57+
continue
58+
}
59+
const variableDeclaration = (
60+
variableDeclarator.parent &&
61+
variableDeclarator.parent.type === 'VariableDeclaration' &&
62+
variableDeclarator.parent
63+
) || null
64+
for (const reference of variable.references) {
65+
if (!reference.isRead()) {
66+
continue
67+
}
68+
69+
refReferenceIds.set(reference.identifier, {
70+
variableDeclarator,
71+
variableDeclaration
72+
})
73+
}
74+
}
75+
},
76+
// if (refValue)
77+
'IfStatement>Identifier' (node) {
78+
reportIfRefWrapped(node)
79+
},
80+
// switch (refValue)
81+
'SwitchStatement>Identifier' (node) {
82+
reportIfRefWrapped(node)
83+
},
84+
// -refValue, +refValue, !refValue, ~refValue, typeof refValue
85+
'UnaryExpression>Identifier' (node) {
86+
reportIfRefWrapped(node)
87+
},
88+
// refValue++, refValue--
89+
'UpdateExpression>Identifier' (node) {
90+
reportIfRefWrapped(node)
91+
},
92+
// refValue+1, refValue-1
93+
'BinaryExpression>Identifier' (node) {
94+
reportIfRefWrapped(node)
95+
},
96+
// refValue+=1, refValue-=1, foo+=refValue, foo-=refValue
97+
'AssignmentExpression>Identifier' (node) {
98+
reportIfRefWrapped(node)
99+
},
100+
// refValue || other, refValue && other. ignore: other || refValue
101+
'LogicalExpression>Identifier' (node) {
102+
if (node.parent.left !== node) {
103+
return
104+
}
105+
// Report only constants.
106+
const info = refReferenceIds.get(node)
107+
if (!info) {
108+
return
109+
}
110+
if (!info.variableDeclaration || info.variableDeclaration.kind !== 'const') {
111+
return
112+
}
113+
reportIfRefWrapped(node)
114+
},
115+
// refValue ? x : y
116+
'ConditionalExpression>Identifier' (node) {
117+
if (node.parent.test !== node) {
118+
return
119+
}
120+
reportIfRefWrapped(node)
121+
}
122+
}
123+
}
124+
}

Diff for: package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@
4747
"eslint": "^5.0.0 || ^6.0.0"
4848
},
4949
"dependencies": {
50+
"eslint-utils": "^2.0.0",
5051
"natural-compare": "^1.4.0",
51-
"vue-eslint-parser": "^7.0.0",
52-
"semver": "^5.6.0"
52+
"semver": "^5.6.0",
53+
"vue-eslint-parser": "^7.0.0"
5354
},
5455
"devDependencies": {
5556
"@types/node": "^4.2.16",

0 commit comments

Comments
 (0)