Skip to content

Commit a5fb774

Browse files
feat: add prefer-use-template-ref rule (#2554)
Co-authored-by: Flo Edelmann <[email protected]>
1 parent e13089e commit a5fb774

File tree

5 files changed

+485
-0
lines changed

5 files changed

+485
-0
lines changed

Diff for: docs/rules/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ For example:
270270
| [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md) | enforce `Boolean` comes first in component prop types | :bulb: | :warning: |
271271
| [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: | :hammer: |
272272
| [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: | :hammer: |
273+
| [vue/prefer-use-template-ref](./prefer-use-template-ref.md) | require using `useTemplateRef` instead of `ref` for template refs | | :hammer: |
273274
| [vue/require-default-export](./require-default-export.md) | require components to be the default export | | :warning: |
274275
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | | :hammer: |
275276
| [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | :bulb: | :hammer: |

Diff for: docs/rules/prefer-use-template-ref.md

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/prefer-use-template-ref
5+
description: require using `useTemplateRef` instead of `ref` for template refs
6+
---
7+
8+
# vue/prefer-use-template-ref
9+
10+
> require using `useTemplateRef` instead of `ref` for template refs
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>
13+
14+
## :book: Rule Details
15+
16+
Vue 3.5 introduced a new way of obtaining template refs via
17+
the [`useTemplateRef()`](https://vuejs.org/guide/essentials/template-refs.html#accessing-the-refs) API.
18+
19+
This rule enforces using the new `useTemplateRef` function instead of `ref` for template refs.
20+
21+
<eslint-code-block :rules="{'vue/prefer-use-template-ref': ['error']}">
22+
23+
```vue
24+
<template>
25+
<button ref="submitRef">Submit</button>
26+
<button ref="closeRef">Close</button>
27+
</template>
28+
29+
<script setup>
30+
import { ref, useTemplateRef } from 'vue';
31+
32+
/* ✓ GOOD */
33+
const submitRef = useTemplateRef('submitRef');
34+
const submitButton = useTemplateRef('submitRef');
35+
const closeButton = useTemplateRef('closeRef');
36+
37+
/* ✗ BAD */
38+
const closeRef = ref();
39+
</script>
40+
```
41+
42+
</eslint-code-block>
43+
44+
This rule skips `ref` template function refs as these should be used to allow custom implementation of storing `ref`. If you prefer
45+
`useTemplateRef`, you have to change the value of the template `ref` to a string.
46+
47+
<eslint-code-block :rules="{'vue/prefer-use-template-ref': ['error']}">
48+
49+
```vue
50+
<template>
51+
<button :ref="ref => button = ref">Content</button>
52+
</template>
53+
54+
<script setup>
55+
import { ref } from 'vue';
56+
57+
/* ✓ GOOD */
58+
const button = ref();
59+
</script>
60+
```
61+
62+
</eslint-code-block>
63+
64+
## :wrench: Options
65+
66+
Nothing.
67+
68+
## :mag: Implementation
69+
70+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-use-template-ref.js)
71+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-use-template-ref.js)

Diff for: lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ const plugin = {
208208
'prefer-separate-static-class': require('./rules/prefer-separate-static-class'),
209209
'prefer-template': require('./rules/prefer-template'),
210210
'prefer-true-attribute-shorthand': require('./rules/prefer-true-attribute-shorthand'),
211+
'prefer-use-template-ref': require('./rules/prefer-use-template-ref'),
211212
'prop-name-casing': require('./rules/prop-name-casing'),
212213
'quote-props': require('./rules/quote-props'),
213214
'require-component-is': require('./rules/require-component-is'),

Diff for: lib/rules/prefer-use-template-ref.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @author Thomasan1999
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
/** @param expression {Expression | null} */
10+
function expressionIsRef(expression) {
11+
// @ts-ignore
12+
return expression?.callee?.name === 'ref'
13+
}
14+
15+
/** @type {import("eslint").Rule.RuleModule} */
16+
module.exports = {
17+
meta: {
18+
type: 'suggestion',
19+
docs: {
20+
description:
21+
'require using `useTemplateRef` instead of `ref` for template refs',
22+
categories: undefined,
23+
url: 'https://eslint.vuejs.org/rules/prefer-use-template-ref.html'
24+
},
25+
schema: [],
26+
messages: {
27+
preferUseTemplateRef: "Replace 'ref' with 'useTemplateRef'."
28+
}
29+
},
30+
/** @param {RuleContext} context */
31+
create(context) {
32+
/** @type Set<string> */
33+
const templateRefs = new Set()
34+
35+
/**
36+
* @typedef ScriptRef
37+
* @type {{node: Expression, ref: string}}
38+
*/
39+
40+
/**
41+
* @type ScriptRef[] */
42+
const scriptRefs = []
43+
44+
return utils.compositingVisitors(
45+
utils.defineTemplateBodyVisitor(
46+
context,
47+
{
48+
'VAttribute[directive=false]'(node) {
49+
if (node.key.name === 'ref' && node.value?.value) {
50+
templateRefs.add(node.value.value)
51+
}
52+
}
53+
},
54+
{
55+
VariableDeclarator(declarator) {
56+
if (!expressionIsRef(declarator.init)) {
57+
return
58+
}
59+
60+
scriptRefs.push({
61+
// @ts-ignore
62+
node: declarator.init,
63+
// @ts-ignore
64+
ref: declarator.id.name
65+
})
66+
}
67+
}
68+
),
69+
{
70+
'Program:exit'() {
71+
for (const templateRef of templateRefs) {
72+
const scriptRef = scriptRefs.find(
73+
(scriptRef) => scriptRef.ref === templateRef
74+
)
75+
76+
if (!scriptRef) {
77+
continue
78+
}
79+
80+
context.report({
81+
node: scriptRef.node,
82+
messageId: 'preferUseTemplateRef'
83+
})
84+
}
85+
}
86+
}
87+
)
88+
}
89+
}

0 commit comments

Comments
 (0)