Skip to content

Commit

Permalink
Rename vue/no-ref-object-destructure to `vue/no-ref-object-reactivi…
Browse files Browse the repository at this point in the history
…ty-loss` (#2269)
  • Loading branch information
ota-meshi committed Aug 9, 2023
1 parent 4112be5 commit 7422a0e
Show file tree
Hide file tree
Showing 7 changed files with 692 additions and 160 deletions.
3 changes: 2 additions & 1 deletion docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ For example:
| [vue/no-empty-component-block](./no-empty-component-block.md) | disallow the `<template>` `<script>` `<style>` block to be empty | | :hammer: |
| [vue/no-multiple-objects-in-class](./no-multiple-objects-in-class.md) | disallow to pass multiple objects into array to class | | :hammer: |
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | :bulb: | :hammer: |
| [vue/no-ref-object-destructure](./no-ref-object-destructure.md) | disallow destructuring of ref objects that can lead to loss of reactivity | | :warning: |
| [vue/no-ref-object-reactivity-loss](./no-ref-object-reactivity-loss.md) | disallow usages of ref objects that can lead to loss of reactivity | | :warning: |
| [vue/no-required-prop-with-default](./no-required-prop-with-default.md) | enforce props with default values to be optional | :wrench::bulb: | :warning: |
| [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block | | :hammer: |
| [vue/no-restricted-call-after-await](./no-restricted-call-after-await.md) | disallow asynchronously called restricted methods | | :hammer: |
Expand Down Expand Up @@ -339,6 +339,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
|:--------|:------------|
| [vue/component-tags-order](./component-tags-order.md) | [vue/block-order](./block-order.md) |
| [vue/no-invalid-model-keys](./no-invalid-model-keys.md) | [vue/valid-model-definition](./valid-model-definition.md) |
| [vue/no-ref-object-destructure](./no-ref-object-destructure.md) | [vue/no-ref-object-reactivity-loss](./no-ref-object-reactivity-loss.md) |
| [vue/no-setup-props-destructure](./no-setup-props-destructure.md) | [vue/no-setup-props-reactivity-loss](./no-setup-props-reactivity-loss.md) |
| [vue/script-setup-uses-vars](./script-setup-uses-vars.md) | (no replacement) |
| [vue/v-on-function-call](./v-on-function-call.md) | [vue/v-on-handler-style](./v-on-handler-style.md) |
Expand Down
6 changes: 4 additions & 2 deletions docs/rules/no-ref-object-destructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
pageClass: rule-details
sidebarDepth: 0
title: vue/no-ref-object-destructure
description: disallow destructuring of ref objects that can lead to loss of reactivity
description: disallow usages of ref objects that can lead to loss of reactivity
since: v9.5.0
---
# vue/no-ref-object-destructure

> disallow destructuring of ref objects that can lead to loss of reactivity
> disallow usages of ref objects that can lead to loss of reactivity
- :no_entry_sign: This rule was **deprecated** and replaced by [vue/no-ref-object-reactivity-loss](no-ref-object-reactivity-loss.md) rule.

## :book: Rule Details

Expand Down
55 changes: 55 additions & 0 deletions docs/rules/no-ref-object-reactivity-loss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-ref-object-reactivity-loss
description: disallow usages of ref objects that can lead to loss of reactivity
---
# vue/no-ref-object-reactivity-loss

> disallow usages of ref objects that can lead to loss of reactivity
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>

## :book: Rule Details

This rule reports the usages of ref objects causing the value to lose reactivity.

<eslint-code-block :rules="{'vue/no-ref-object-reactivity-loss': ['error']}" language="javascript" filename="example.js" >

```js
import { ref } from 'vue'
const count = ref(0)
const v1 = count.value /* ✗ BAD */
const { value: v2 } = count /* ✗ BAD */
const v3 = computed(() => count.value /* ✓ GOOD */)
const v4 = fn(count.value) /* ✗ BAD */
const v5 = fn(count) /* ✓ GOOD */
const v6 = computed(() => fn(count.value) /* ✓ GOOD */)
```

</eslint-code-block>

This rule also supports Reactivity Transform, but Reactivity Transform is an experimental feature and may have false positives due to future Vue changes.
See the [RFC](https://github.com/vuejs/rfcs/pull/420) for more information on Reactivity Transform.

<eslint-code-block :rules="{'vue/no-ref-object-reactivity-loss': ['error']}" language="javascript" filename="example.js" >

```js
const count = $ref(0)
const v1 = count /* ✗ BAD */
const v2 = $computed(() => count /* ✓ GOOD */)
const v3 = fn(count) /* ✗ BAD */
const v4 = fn($$(count)) /* ✓ GOOD */
const v5 = $computed(() => fn(count) /* ✓ GOOD */)
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-ref-object-reactivity-loss.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-ref-object-reactivity-loss.js)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ module.exports = {
'no-potential-component-option-typo': require('./rules/no-potential-component-option-typo'),
'no-ref-as-operand': require('./rules/no-ref-as-operand'),
'no-ref-object-destructure': require('./rules/no-ref-object-destructure'),
'no-ref-object-reactivity-loss': require('./rules/no-ref-object-reactivity-loss'),
'no-required-prop-with-default': require('./rules/no-required-prop-with-default'),
'no-reserved-component-names': require('./rules/no-reserved-component-names'),
'no-reserved-keys': require('./rules/no-reserved-keys'),
Expand Down
167 changes: 10 additions & 157 deletions lib/rules/no-ref-object-destructure.js
Original file line number Diff line number Diff line change
@@ -1,171 +1,24 @@
/**
* @author Yosuke Ota <https://github.com/ota-meshi>
* @author Yosuke Ota
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')
const {
extractRefObjectReferences,
extractReactiveVariableReferences
} = require('../utils/ref-object-references')

/**
* @typedef {import('../utils/ref-object-references').RefObjectReferences} RefObjectReferences
* @typedef {import('../utils/ref-object-references').RefObjectReference} RefObjectReference
*/

/**
* Checks whether writing assigns a value to the given pattern.
* @param {Pattern | AssignmentProperty | Property} node
* @returns {boolean}
*/
function isUpdate(node) {
const parent = node.parent
if (parent.type === 'UpdateExpression' && parent.argument === node) {
// e.g. `pattern++`
return true
}
if (parent.type === 'AssignmentExpression' && parent.left === node) {
// e.g. `pattern = 42`
return true
}
if (
(parent.type === 'Property' && parent.value === node) ||
parent.type === 'ArrayPattern' ||
(parent.type === 'ObjectPattern' &&
parent.properties.includes(/** @type {any} */ (node))) ||
(parent.type === 'AssignmentPattern' && parent.left === node) ||
parent.type === 'RestElement' ||
(parent.type === 'MemberExpression' && parent.object === node)
) {
return isUpdate(parent)
}
return false
}
const baseRule = require('./no-ref-object-reactivity-loss')

module.exports = {
// eslint-disable-next-line eslint-plugin/require-meta-schema, eslint-plugin/prefer-message-ids, internal/no-invalid-meta, eslint-plugin/require-meta-type -- inherit schema from base rule
meta: {
type: 'problem',
...baseRule.meta,
// eslint-disable-next-line eslint-plugin/require-meta-docs-description, internal/no-invalid-meta-docs-categories, eslint-plugin/meta-property-ordering
docs: {
description:
'disallow destructuring of ref objects that can lead to loss of reactivity',
categories: undefined,
...baseRule.meta.docs,
url: 'https://eslint.vuejs.org/rules/no-ref-object-destructure.html'
},
fixable: null,
schema: [],
messages: {
getValueInSameScope:
'Getting a value from the ref object in the same scope will cause the value to lose reactivity.',
getReactiveVariableInSameScope:
'Getting a reactive variable in the same scope will cause the value to lose reactivity.'
}
deprecated: true,
replacedBy: ['no-ref-object-reactivity-loss']
},
/**
* @param {RuleContext} context
* @returns {RuleListener}
*/
/** @param {RuleContext} context */
create(context) {
/**
* @typedef {object} ScopeStack
* @property {ScopeStack | null} upper
* @property {Program | FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
*/
/** @type {ScopeStack} */
let scopeStack = { upper: null, node: context.getSourceCode().ast }
/** @type {Map<CallExpression, ScopeStack>} */
const scopes = new Map()

const refObjectReferences = extractRefObjectReferences(context)
const reactiveVariableReferences =
extractReactiveVariableReferences(context)

/**
* Verify the given ref object value. `refObj = ref(); refObj.value;`
* @param {Expression | Super | ObjectPattern} node
*/
function verifyRefObjectValue(node) {
const ref = refObjectReferences.get(node)
if (!ref) {
return
}
if (scopes.get(ref.define) !== scopeStack) {
// Not in the same scope
return
}

context.report({
node,
messageId: 'getValueInSameScope'
})
}

/**
* Verify the given reactive variable. `refVal = $ref(); refVal;`
* @param {Identifier} node
*/
function verifyReactiveVariable(node) {
const ref = reactiveVariableReferences.get(node)
if (!ref || ref.escape) {
return
}
if (scopes.get(ref.define) !== scopeStack) {
// Not in the same scope
return
}

context.report({
node,
messageId: 'getReactiveVariableInSameScope'
})
}

return {
':function'(node) {
scopeStack = { upper: scopeStack, node }
},
':function:exit'() {
scopeStack = scopeStack.upper || scopeStack
},
CallExpression(node) {
scopes.set(node, scopeStack)
},
/**
* Check for `refObj.value`.
*/
'MemberExpression:exit'(node) {
if (isUpdate(node)) {
// e.g. `refObj.value = 42`, `refObj.value++`
return
}
const name = utils.getStaticPropertyName(node)
if (name !== 'value') {
return
}
verifyRefObjectValue(node.object)
},
/**
* Check for `{value} = refObj`.
*/
'ObjectPattern:exit'(node) {
const prop = utils.findAssignmentProperty(node, 'value')
if (!prop) {
return
}
verifyRefObjectValue(node)
},
/**
* Check for reactive variable`.
* @param {Identifier} node
*/
'Identifier:exit'(node) {
if (isUpdate(node)) {
// e.g. `reactiveVariable = 42`, `reactiveVariable++`
return
}
verifyReactiveVariable(node)
}
}
return baseRule.create(context)
}
}

0 comments on commit 7422a0e

Please sign in to comment.