Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b34b1a9

Browse files
committed
Add new vue/require-mayberef-unwrap rule
1 parent 3d9e15e commit b34b1a9

File tree

5 files changed

+640
-0
lines changed

5 files changed

+640
-0
lines changed

‎docs/rules/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ Rules in this category are enabled for all presets provided by eslint-plugin-vue
9797
| [vue/no-watch-after-await] | disallow asynchronously registered `watch` | | :three::hammer: |
9898
| [vue/prefer-import-from-vue] | enforce import from 'vue' instead of import from '@vue/*' | :wrench: | :three::hammer: |
9999
| [vue/require-component-is] | require `v-bind:is` of `<component>` elements | | :three::two::warning: |
100+
| [vue/require-mayberef-unwrap] | require `MaybeRef` values to be unwrapped with `unref()` before using in conditions | :bulb: | :three::warning: |
100101
| [vue/require-prop-type-constructor] | require prop type to be a constructor | :wrench: | :three::two::hammer: |
101102
| [vue/require-render-return] | enforce render function to always return value | | :three::two::warning: |
102103
| [vue/require-slots-as-functions] | enforce properties of `$slots` to be used as a function | | :three::warning: |
@@ -564,6 +565,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
564565
[vue/require-explicit-slots]: ./require-explicit-slots.md
565566
[vue/require-expose]: ./require-expose.md
566567
[vue/require-macro-variable-name]: ./require-macro-variable-name.md
568+
[vue/require-mayberef-unwrap]: ./require-mayberef-unwrap.md
567569
[vue/require-name-property]: ./require-name-property.md
568570
[vue/require-prop-comment]: ./require-prop-comment.md
569571
[vue/require-prop-type-constructor]: ./require-prop-type-constructor.md

‎docs/rules/require-mayberef-unwrap.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/require-mayberef-unwrap
5+
description: require `MaybeRef` values to be unwrapped with `unref()` before using in conditions
6+
---
7+
8+
# vue/require-mayberef-unwrap
9+
10+
> require `MaybeRef` values to be unwrapped with `unref()` before using in conditions
11+
12+
- :gear: This rule is included in all of `"plugin:vue/essential"`, `*.configs["flat/essential"]`, `"plugin:vue/strongly-recommended"`, `*.configs["flat/strongly-recommended"]`, `"plugin:vue/recommended"` and `*.configs["flat/recommended"]`.
13+
- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
14+
15+
16+
## :book: Rule Details
17+
18+
`MaybeRef<T>` and `MaybeRefOrGetter<T>` are TypeScript utility types provided by Vue.
19+
They allow a value to be either a plain value **or** a `Ref<T>`. When such a variable is used in a boolean context you must first unwrap it with `unref()` so that the actual inner value is evaluated.
20+
This rule reports (and can auto-fix) places where a `MaybeRef*` value is used directly in a conditional expression, logical expression, unary operator, etc.
21+
22+
<eslint-code-block fix :rules="{'vue/require-mayberef-unwrap': ['error']}">
23+
24+
```vue
25+
<script setup lang="ts">
26+
import { ref, unref, type MaybeRef } from 'vue'
27+
28+
const maybeRef: MaybeRef<boolean> = ref(false)
29+
30+
/* ✓ GOOD */
31+
if (unref(maybeRef)) {
32+
console.log('good')
33+
}
34+
const result = unref(maybeRef) ? 'true' : 'false'
35+
36+
/* ✗ BAD */
37+
if (maybeRef) {
38+
console.log('bad')
39+
}
40+
const alt = maybeRef ? 'true' : 'false'
41+
</script>
42+
```
43+
44+
</eslint-code-block>
45+
46+
### What is considered **incorrect** ?
47+
48+
The following patterns are **incorrect**:
49+
50+
```ts
51+
// Condition without unref
52+
if (maybeRef) {}
53+
54+
// Ternary operator
55+
const result = maybeRef ? 'a' : 'b'
56+
57+
// Logical expressions
58+
const value = maybeRef || 'fallback'
59+
60+
// Unary operators
61+
const negated = !maybeRef
62+
63+
// Type queries & wrappers
64+
const t = typeof maybeRef
65+
const b = Boolean(maybeRef)
66+
```
67+
68+
### What is considered **correct** ?
69+
70+
```ts
71+
if (unref(maybeRef)) {}
72+
const result = unref(maybeRef) ? 'a' : 'b'
73+
```
74+
75+
## :wrench: Options
76+
77+
Nothing.
78+
79+
## :books: Further Reading
80+
81+
- [Guide – Reactivity – `unref`](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#unref)
82+
- [API – `MaybeRef`](https://vuejs.org/api/utility-types.html#mayberef)
83+
- [API – `MaybeRefOrGetter`](https://vuejs.org/api/utility-types.html#maybereforgetter)
84+
85+
## :rocket: Version
86+
87+
This rule will be introduced in a future release of eslint-plugin-vue.
88+
89+
## :mag: Implementation
90+
91+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-mayberef-unwrap.js)
92+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-mayberef-unwrap.js)

‎lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ const plugin = {
223223
'require-explicit-slots': require('./rules/require-explicit-slots'),
224224
'require-expose': require('./rules/require-expose'),
225225
'require-macro-variable-name': require('./rules/require-macro-variable-name'),
226+
'require-mayberef-unwrap': require('./rules/require-mayberef-unwrap'),
226227
'require-name-property': require('./rules/require-name-property'),
227228
'require-prop-comment': require('./rules/require-prop-comment'),
228229
'require-prop-type-constructor': require('./rules/require-prop-type-constructor'),

‎lib/rules/require-mayberef-unwrap.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* @author 2nofa11
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const { findVariable } = require('@eslint-community/eslint-utils')
8+
const utils = require('../utils')
9+
10+
module.exports = {
11+
meta: {
12+
type: 'problem',
13+
docs: {
14+
description: 'require `MaybeRef` values to be unwrapped with `unref()` before using in conditions',
15+
categories: undefined,
16+
url: 'https://eslint.vuejs.org/rules/require-mayberef-unwrap.html'
17+
},
18+
fixable: null,
19+
hasSuggestions: true,
20+
schema: [],
21+
messages: {
22+
requireUnref: 'MaybeRef should be unwrapped with `unref()` before using in conditions. Use `unref({{name}})` instead.',
23+
wrapWithUnref: 'Wrap with unref().'
24+
}
25+
},
26+
/** @param {RuleContext} context */
27+
create(context) {
28+
/**
29+
* Determine if identifier should be considered MaybeRef
30+
* @param {Identifier} node
31+
*/
32+
function isMaybeRef(node) {
33+
const variable = findVariable(utils.getScope(context, node), node)
34+
const id = variable?.defs[0]?.node?.id
35+
if (id?.type === 'Identifier' && id.typeAnnotation) {
36+
return isMaybeRefTypeNode(id.typeAnnotation.typeAnnotation)
37+
}
38+
return false
39+
}
40+
41+
/**
42+
* Check TypeScript type node for MaybeRef/MaybeRefOrGetter
43+
* @param {import('@typescript-eslint/types').TSESTree.TypeNode | undefined} typeNode
44+
* @returns {boolean}
45+
*/
46+
function isMaybeRefTypeNode(typeNode) {
47+
if (!typeNode) return false
48+
if (typeNode.type === 'TSTypeReference') {
49+
if (typeNode.typeName && typeNode.typeName.type === 'Identifier') {
50+
return (
51+
typeNode.typeName.name === 'MaybeRef' ||
52+
typeNode.typeName.name === 'MaybeRefOrGetter'
53+
)
54+
}
55+
}
56+
if (typeNode.type === 'TSUnionType') {
57+
return typeNode.types.some((t) => isMaybeRefTypeNode(t))
58+
}
59+
return false
60+
}
61+
62+
/**
63+
* Reports if the identifier is a MaybeRef type
64+
* @param {Identifier} node
65+
*/
66+
function reportIfMaybeRef(node) {
67+
if (!isMaybeRef(node)) return
68+
69+
const sourceCode = context.getSourceCode()
70+
context.report({
71+
node,
72+
messageId: 'requireUnref',
73+
data: { name: node.name },
74+
suggest: [
75+
{
76+
messageId: 'wrapWithUnref',
77+
/** @param {*} fixer */
78+
fix(fixer) {
79+
return fixer.replaceText(node, `unref(${sourceCode.getText(node)})`)
80+
}
81+
}
82+
]
83+
})
84+
}
85+
86+
return {
87+
// if (maybeRef)
88+
/** @param {Identifier} node */
89+
'IfStatement>Identifier'(node) {
90+
reportIfMaybeRef(node)
91+
},
92+
// maybeRef ? x : y
93+
/** @param {Identifier & {parent: ConditionalExpression}} node */
94+
'ConditionalExpression>Identifier'(node) {
95+
if (node.parent.test === node) {
96+
reportIfMaybeRef(node)
97+
}
98+
},
99+
// !maybeRef, +maybeRef, -maybeRef, ~maybeRef, typeof maybeRef
100+
/** @param {Identifier} node */
101+
'UnaryExpression>Identifier'(node) {
102+
reportIfMaybeRef(node)
103+
},
104+
// maybeRef || other, maybeRef && other, maybeRef ?? other
105+
/** @param {Identifier & {parent: LogicalExpression}} node */
106+
'LogicalExpression>Identifier'(node) {
107+
reportIfMaybeRef(node)
108+
},
109+
// maybeRef == x, maybeRef != x, maybeRef === x, maybeRef !== x
110+
/** @param {Identifier} node */
111+
'BinaryExpression>Identifier'(node) {
112+
reportIfMaybeRef(node)
113+
},
114+
// Boolean(maybeRef), String(maybeRef)
115+
/** @param {Identifier} node */
116+
'CallExpression>Identifier'(node) {
117+
if (node.parent &&
118+
node.parent.type === 'CallExpression' &&
119+
node.parent.callee &&
120+
node.parent.callee.type === 'Identifier' &&
121+
['Boolean', 'String'].includes(node.parent.callee.name) &&
122+
node.parent.arguments[0] === node) {
123+
reportIfMaybeRef(node)
124+
}
125+
}
126+
}
127+
}
128+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /