Skip to content

Commit

Permalink
New: Add vue/no-ref-as-operand rule (#1065)
Browse files Browse the repository at this point in the history
* Add no-ref-as-operand rule

* update

* Add testcases

* update

* Fixed testcases

* update doc
  • Loading branch information
ota-meshi committed Mar 14, 2020
1 parent bd770c2 commit 11c9a94
Show file tree
Hide file tree
Showing 6 changed files with 521 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -167,6 +167,7 @@ For example:
| [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: |
| [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 | |
Expand Down
58 changes: 58 additions & 0 deletions 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.

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

```vue
<script>
import { ref } from 'vue'
export default {
setup () {
const count = ref(0)
const ok = ref(true)
/* ✓ GOOD */
count.value++
count.value + 1
1 + count.value
var msg = ok.value ? 'yes' : 'no'
/* ✗ BAD */
count++
count + 1
1 + count
var msg = ok ? 'yes' : 'no'
return {
count
}
}
}
</script>
```

</eslint-code-block>

## :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)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -52,6 +52,7 @@ module.exports = {
'no-multi-spaces': require('./rules/no-multi-spaces'),
'no-multiple-template-root': require('./rules/no-multiple-template-root'),
'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'),
Expand Down
124 changes: 124 additions & 0 deletions 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)
}
}
}
}
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -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",
Expand Down

0 comments on commit 11c9a94

Please sign in to comment.