Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ For example:
| [vue/prefer-prop-type-boolean-first] | enforce `Boolean` comes first in component prop types | :bulb: | :warning: |
| [vue/prefer-separate-static-class] | require static class names in template to be in a separate `class` attribute | :wrench: | :hammer: |
| [vue/prefer-true-attribute-shorthand] | require shorthand form attribute when `v-bind` value is `true` | :bulb: | :hammer: |
| [vue/prefer-use-template-ref] | require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs | | :hammer: |
| [vue/prefer-use-template-ref] | require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs | :wrench: | :hammer: |
| [vue/require-default-export] | require components to be the default export | | :warning: |
| [vue/require-direct-export] | require the component to be directly exported | | :hammer: |
| [vue/require-emit-validator] | require type definitions in emits | :bulb: | :hammer: |
Expand Down
6 changes: 4 additions & 2 deletions docs/rules/prefer-use-template-ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ since: v9.31.0

> require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs

- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.

## :book: Rule Details

Vue 3.5 introduced a new way of obtaining template refs via
the [`useTemplateRef()`](https://vuejs.org/guide/essentials/template-refs.html#accessing-the-refs) API.

This rule enforces using the new `useTemplateRef` function instead of `ref`/`shallowRef` for template refs.

<eslint-code-block :rules="{'vue/prefer-use-template-ref': ['error']}">
<eslint-code-block fix :rules="{'vue/prefer-use-template-ref': ['error']}">

```vue
<template>
Expand Down Expand Up @@ -45,7 +47,7 @@ This rule enforces using the new `useTemplateRef` function instead of `ref`/`sha
This rule skips `ref` template function refs as these should be used to allow custom implementation of storing `ref`. If you prefer
`useTemplateRef`, you have to change the value of the template `ref` to a string.

<eslint-code-block :rules="{'vue/prefer-use-template-ref': ['error']}">
<eslint-code-block fix :rules="{'vue/prefer-use-template-ref': ['error']}">

```vue
<template>
Expand Down
11 changes: 11 additions & 0 deletions lib/rules/prefer-use-template-ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ module.exports = {
categories: undefined,
url: 'https://eslint.vuejs.org/rules/prefer-use-template-ref.html'
},
fixable: 'code',
schema: [],
messages: {
preferUseTemplateRef: "Replace '{{name}}' with 'useTemplateRef'."
}
},
/** @param {RuleContext} context */
create(context) {
const sourceCode = context.sourceCode
/** @type Set<string> */
const templateRefs = new Set()

Expand Down Expand Up @@ -97,6 +99,15 @@ module.exports = {
messageId: 'preferUseTemplateRef',
data: {
name: /** @type {Identifier} */ (scriptRef.callee).name
},
fix(fixer) {
const typeArgs =
scriptRef.typeArguments ?? scriptRef.typeParameters

return fixer.replaceText(
scriptRef,
`useTemplateRef${typeArgs ? sourceCode.getText(typeArgs) : ''}('${templateRef}')`
)
}
})
}
Expand Down
169 changes: 160 additions & 9 deletions tests/lib/rules/prefer-use-template-ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,25 @@ tester.run('prefer-use-template-ref', rule, {
const root = ref();
</script>
`,
output: `
<template>
<div ref="root"/>
</template>
<script setup>
import { ref } from 'vue';
const root = useTemplateRef('root');
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 7,
column: 22
column: 22,
endLine: 7,
endColumn: 27
}
]
},
Expand All @@ -335,14 +346,27 @@ tester.run('prefer-use-template-ref', rule, {
const link = ref();
</script>
`,
output: `
<template>
<button ref="button">Content</button>
<a href="" ref="link">Link</a>
</template>
<script setup>
import { ref } from 'vue';
const buttonRef = ref();
const link = useTemplateRef('link');
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 9,
column: 22
column: 22,
endLine: 9,
endColumn: 27
}
]
},
Expand All @@ -359,22 +383,37 @@ tester.run('prefer-use-template-ref', rule, {
const link = ref();
</script>
`,
output: `
<template>
<h1 ref="heading">Heading</h1>
<a href="" ref="link">Link</a>
</template>
<script setup>
import { ref } from 'vue';
const heading = useTemplateRef('heading');
const link = useTemplateRef('link');
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 8,
column: 25
column: 25,
endLine: 8,
endColumn: 30
},
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 9,
column: 22
column: 22,
endLine: 9,
endColumn: 27
}
]
},
Expand All @@ -396,14 +435,32 @@ tester.run('prefer-use-template-ref', rule, {
}
</script>
`,
output: `
<template>
<p>Button clicked {{counter}} times.</p>
<button ref="button">Click</button>
</template>
<script>
import { ref } from 'vue';
export default {
name: 'Counter',
setup() {
const counter = ref(0);
const button = useTemplateRef('button');
}
}
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 12,
column: 28
column: 28,
endLine: 12,
endColumn: 33
}
]
},
Expand All @@ -418,14 +475,25 @@ tester.run('prefer-use-template-ref', rule, {
const root = shallowRef();
</script>
`,
output: `
<template>
<div ref="root"/>
</template>
<script setup>
import { shallowRef } from 'vue';
const root = useTemplateRef('root');
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'shallowRef'
},
line: 7,
column: 22
column: 22,
endLine: 7,
endColumn: 34
}
]
},
Expand All @@ -444,14 +512,29 @@ tester.run('prefer-use-template-ref', rule, {
}
</script>
`,
output: `
<template>
<button ref="button">Click</button>
</template>
<script>
import { ref } from 'vue';
export default {
setup: () => {
const button = useTemplateRef('button');
}
}
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 9,
column: 28
column: 28,
endLine: 9,
endColumn: 33
}
]
},
Expand All @@ -467,6 +550,20 @@ tester.run('prefer-use-template-ref', rule, {
const root = ref()
</script>

<script>
const A = 'foo'
</script>
`,
output: `
<template>
<div ref="root" :data-a="A" />
</template>

<script setup>
import { ref } from 'vue'
const root = useTemplateRef('root')
</script>

<script>
const A = 'foo'
</script>
Expand All @@ -478,7 +575,9 @@ tester.run('prefer-use-template-ref', rule, {
name: 'ref'
},
line: 8,
column: 20
column: 20,
endLine: 8,
endColumn: 25
}
]
},
Expand All @@ -498,14 +597,66 @@ tester.run('prefer-use-template-ref', rule, {
const root = ref()
</script>
`,
output: `
<template>
<div ref="root" :data-a="A" />
</template>

<script>
const A = 'foo'
</script>

<script setup>
import { ref } from 'vue'
const root = useTemplateRef('root')
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 12,
column: 20
column: 20,
endLine: 12,
endColumn: 25
}
]
},
{
filename: 'ref-with-typeArguments.vue',
code: `
<template>
<div ref="root" />
</template>
<script setup lang="ts">
const root = ref<HTMLElement | null>()
</script>
`,
output: `
<template>
<div ref="root" />
</template>
<script setup lang="ts">
const root = useTemplateRef<HTMLElement | null>('root')
</script>
`,
languageOptions: {
parserOptions: {
parser: require('@typescript-eslint/parser')
}
},
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 6,
column: 22,
endLine: 6,
endColumn: 47
}
]
}
Expand Down
Loading