Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: Add vue/no-ref-as-operand rule #1065

Merged
merged 6 commits into from Mar 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -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 | |
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 @@ -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'),
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