diff --git a/docs/rules/README.md b/docs/rules/README.md
index ab1f5b220..72faa9698 100644
--- a/docs/rules/README.md
+++ b/docs/rules/README.md
@@ -160,6 +160,7 @@ For example:
| [vue/no-deprecated-slot-scope-attribute](./no-deprecated-slot-scope-attribute.md) | disallow deprecated `slot-scope` attribute (in Vue.js 2.6.0+) | :wrench: |
| [vue/no-empty-pattern](./no-empty-pattern.md) | disallow empty destructuring patterns | |
| [vue/no-irregular-whitespace](./no-irregular-whitespace.md) | disallow irregular whitespace | |
+| [vue/no-ref-as-operand](./no-ref-as-operand.md) | disallow use of value wrapped by `ref()` (Composition API) as an operand | |
| [vue/no-reserved-component-names](./no-reserved-component-names.md) | disallow the use of reserved names in component definitions | |
| [vue/no-restricted-syntax](./no-restricted-syntax.md) | disallow specified syntax | |
| [vue/no-static-inline-styles](./no-static-inline-styles.md) | disallow static inline `style` attributes | |
diff --git a/docs/rules/no-ref-as-operand.md b/docs/rules/no-ref-as-operand.md
new file mode 100644
index 000000000..e6d0b94b3
--- /dev/null
+++ b/docs/rules/no-ref-as-operand.md
@@ -0,0 +1,58 @@
+---
+pageClass: rule-details
+sidebarDepth: 0
+title: vue/no-ref-as-operand
+description: disallow use of value wrapped by `ref()` (Composition API) as an operand
+---
+# vue/no-ref-as-operand
+> disallow use of value wrapped by `ref()` (Composition API) as an operand
+
+## :book: Rule Details
+
+This rule reports cases where a ref is used incorrectly as an operand.
+
+
+
+```vue
+
+```
+
+
+
+## :wrench: Options
+
+Nothing.
+
+## :books: Further reading
+
+- [Vue RFCs - 0013-composition-api](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0013-composition-api.md)
+
+## :mag: Implementation
+
+- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-ref-as-operand.js)
+- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-ref-as-operand.js)
diff --git a/lib/index.js b/lib/index.js
index 02e65ba96..023e499c0 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -48,6 +48,7 @@ module.exports = {
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
'no-multi-spaces': require('./rules/no-multi-spaces'),
'no-parsing-error': require('./rules/no-parsing-error'),
+ 'no-ref-as-operand': require('./rules/no-ref-as-operand'),
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
'no-reserved-keys': require('./rules/no-reserved-keys'),
'no-restricted-syntax': require('./rules/no-restricted-syntax'),
diff --git a/lib/rules/no-ref-as-operand.js b/lib/rules/no-ref-as-operand.js
new file mode 100644
index 000000000..ea57d9da4
--- /dev/null
+++ b/lib/rules/no-ref-as-operand.js
@@ -0,0 +1,124 @@
+/**
+ * @author Yosuke Ota
+ * See LICENSE file in root directory for full license.
+ */
+'use strict'
+const { ReferenceTracker, findVariable } = require('eslint-utils')
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow use of value wrapped by `ref()` (Composition API) as an operand',
+ category: undefined,
+ url: 'https://eslint.vuejs.org/rules/no-ref-as-operand.html'
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ requireDotValue: 'Must use `.value` to read or write the value wrapped by `ref()`.'
+ }
+ },
+ create (context) {
+ const refReferenceIds = new Map()
+
+ function reportIfRefWrapped (node) {
+ if (!refReferenceIds.has(node)) {
+ return
+ }
+ context.report({
+ node,
+ messageId: 'requireDotValue'
+ })
+ }
+ return {
+ 'Program' () {
+ const tracker = new ReferenceTracker(context.getScope())
+ const traceMap = {
+ vue: {
+ [ReferenceTracker.ESM]: true,
+ ref: {
+ [ReferenceTracker.CALL]: true
+ }
+ }
+ }
+
+ for (const { node } of tracker.iterateEsmReferences(traceMap)) {
+ const variableDeclarator = node.parent
+ if (
+ !variableDeclarator ||
+ variableDeclarator.type !== 'VariableDeclarator' ||
+ variableDeclarator.id.type !== 'Identifier'
+ ) {
+ continue
+ }
+ const variable = findVariable(context.getScope(), variableDeclarator.id)
+ if (!variable) {
+ continue
+ }
+ const variableDeclaration = (
+ variableDeclarator.parent &&
+ variableDeclarator.parent.type === 'VariableDeclaration' &&
+ variableDeclarator.parent
+ ) || null
+ for (const reference of variable.references) {
+ if (!reference.isRead()) {
+ continue
+ }
+
+ refReferenceIds.set(reference.identifier, {
+ variableDeclarator,
+ variableDeclaration
+ })
+ }
+ }
+ },
+ // if (refValue)
+ 'IfStatement>Identifier' (node) {
+ reportIfRefWrapped(node)
+ },
+ // switch (refValue)
+ 'SwitchStatement>Identifier' (node) {
+ reportIfRefWrapped(node)
+ },
+ // -refValue, +refValue, !refValue, ~refValue, typeof refValue
+ 'UnaryExpression>Identifier' (node) {
+ reportIfRefWrapped(node)
+ },
+ // refValue++, refValue--
+ 'UpdateExpression>Identifier' (node) {
+ reportIfRefWrapped(node)
+ },
+ // refValue+1, refValue-1
+ 'BinaryExpression>Identifier' (node) {
+ reportIfRefWrapped(node)
+ },
+ // refValue+=1, refValue-=1, foo+=refValue, foo-=refValue
+ 'AssignmentExpression>Identifier' (node) {
+ reportIfRefWrapped(node)
+ },
+ // refValue || other, refValue && other. ignore: other || refValue
+ 'LogicalExpression>Identifier' (node) {
+ if (node.parent.left !== node) {
+ return
+ }
+ // Report only constants.
+ const info = refReferenceIds.get(node)
+ if (!info) {
+ return
+ }
+ if (!info.variableDeclaration || info.variableDeclaration.kind !== 'const') {
+ return
+ }
+ reportIfRefWrapped(node)
+ },
+ // refValue ? x : y
+ 'ConditionalExpression>Identifier' (node) {
+ if (node.parent.test !== node) {
+ return
+ }
+ reportIfRefWrapped(node)
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 1744009a5..099b182a4 100644
--- a/package.json
+++ b/package.json
@@ -47,9 +47,10 @@
"eslint": "^5.0.0 || ^6.0.0"
},
"dependencies": {
+ "eslint-utils": "^2.0.0",
"natural-compare": "^1.4.0",
- "vue-eslint-parser": "^7.0.0",
- "semver": "^5.6.0"
+ "semver": "^5.6.0",
+ "vue-eslint-parser": "^7.0.0"
},
"devDependencies": {
"@types/node": "^4.2.16",
diff --git a/tests/lib/rules/no-ref-as-operand.js b/tests/lib/rules/no-ref-as-operand.js
new file mode 100644
index 000000000..b8632c88b
--- /dev/null
+++ b/tests/lib/rules/no-ref-as-operand.js
@@ -0,0 +1,334 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+const RuleTester = require('eslint').RuleTester
+const rule = require('../../../lib/rules/no-ref-as-operand')
+
+const tester = new RuleTester({
+ parser: require.resolve('vue-eslint-parser'),
+ parserOptions: { ecmaVersion: 2019, sourceType: 'module' }
+})
+
+tester.run('no-ref-as-operand', rule, {
+ valid: [
+ `
+ import { ref } from 'vue'
+ const count = ref(0)
+ console.log(count.value) // 0
+
+ count.value++
+ console.log(count.value) // 1
+ `,
+ `
+
+ `,
+ `
+ import { ref } from 'vue'
+ const count = ref(0)
+ if (count.value) {}
+ switch (count.value) {}
+ var foo = -count.value
+ var foo = +count.value
+ count.value++
+ count.value--
+ count.value + 1
+ 1 - count.value
+ count.value || other
+ count.value && other
+ var foo = count.value ? x : y
+ `,
+ `
+ import { ref } from 'vue'
+ const foo = ref(true)
+ if (bar) foo
+ `,
+ `
+ import { ref } from 'vue'
+ const foo = ref(true)
+ var a = other || foo // ignore
+ var b = other && foo // ignore
+
+ let bar = ref(true)
+ var a = bar || other
+ var b = bar || other
+ `,
+ `
+ import { ref } from 'vue'
+ let count = not_ref(0)
+
+ count++
+ `,
+ `
+ import { ref } from 'vue'
+ const foo = ref(0)
+ const bar = ref(0)
+ var baz = x ? foo : bar
+ `,
+ `
+ import { ref } from 'vue'
+ // Probably wrong, but not checked by this rule.
+ const {value} = ref(0)
+ value++
+ `,
+ `
+ import { ref } from 'vue'
+ const count = ref(0)
+ function foo() {
+ let count = 0
+ count++
+ }
+ `,
+ `
+ import { ref } from 'unknown'
+ const count = ref(0)
+ count++
+ `,
+ `
+ import { ref } from 'vue'
+ const count = ref
+ count++
+ `
+ ],
+ invalid: [
+ {
+ code: `
+ import { ref } from 'vue'
+ let count = ref(0)
+
+ count++ // error
+ console.log(count + 1) // error
+ console.log(1 + count) // error
+ `,
+ errors: [
+ {
+ message: 'Must use `.value` to read or write the value wrapped by `ref()`.',
+ line: 5,
+ column: 7,
+ endLine: 5,
+ endColumn: 12
+ },
+ {
+ message: 'Must use `.value` to read or write the value wrapped by `ref()`.',
+ line: 6,
+ column: 19,
+ endLine: 6,
+ endColumn: 24
+ },
+ {
+ message: 'Must use `.value` to read or write the value wrapped by `ref()`.',
+ line: 7,
+ column: 23,
+ endLine: 7,
+ endColumn: 28
+ }
+ ]
+ },
+ {
+ code: `
+
+ `,
+ errors: [
+ {
+ messageId: 'requireDotValue',
+ line: 8,
+ column: 13,
+ endLine: 8,
+ endColumn: 18
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 9,
+ column: 25,
+ endLine: 9,
+ endColumn: 30
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 10,
+ column: 29,
+ endLine: 10,
+ endColumn: 34
+ }
+ ]
+ },
+ {
+ code: `
+ import { ref } from 'vue'
+ const foo = ref(true)
+ if (foo) {
+ //
+ }
+ `,
+ errors: [
+ {
+ messageId: 'requireDotValue',
+ line: 4
+ }
+ ]
+ },
+ {
+ code: `
+ import { ref } from 'vue'
+ const foo = ref(true)
+ switch (foo) {
+ //
+ }
+ `,
+ errors: [
+ {
+ messageId: 'requireDotValue',
+ line: 4
+ }
+ ]
+ },
+ {
+ code: `
+ import { ref } from 'vue'
+ const foo = ref(0)
+ var a = -foo
+ var b = +foo
+ var c = !foo
+ var d = ~foo
+ `,
+ errors: [
+ {
+ messageId: 'requireDotValue',
+ line: 4
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 5
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 6
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 7
+ }
+ ]
+ },
+ {
+ code: `
+ import { ref } from 'vue'
+ let foo = ref(0)
+ foo += 1
+ foo -= 1
+ baz += foo
+ baz -= foo
+ `,
+ errors: [
+ {
+ messageId: 'requireDotValue',
+ line: 4
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 5
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 6
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 7
+ }
+ ]
+ },
+ {
+ code: `
+ import { ref } from 'vue'
+ const foo = ref(true)
+ var a = foo || other
+ var b = foo && other
+ `,
+ errors: [
+ {
+ messageId: 'requireDotValue',
+ line: 4
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 5
+ }
+ ]
+ },
+ {
+ code: `
+ import { ref } from 'vue'
+ let foo = ref(true)
+ var a = foo ? x : y
+ `,
+ errors: [
+ {
+ messageId: 'requireDotValue',
+ line: 4
+ }
+ ]
+ },
+ {
+ code: `
+
+ `,
+ errors: [
+ {
+ messageId: 'requireDotValue',
+ line: 7
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 8
+ },
+ {
+ messageId: 'requireDotValue',
+ line: 9
+ }
+ ]
+ }
+ ]
+})