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 ac5aa92

Browse files
feat(prefer-use-template-ref): add support for fix option
1 parent dc06535 commit ac5aa92

File tree

4 files changed

+239
-3
lines changed

4 files changed

+239
-3
lines changed

‎docs/rules/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +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`/`shallowRef` for template refs | | :hammer: |
273+
| [vue/prefer-use-template-ref](./prefer-use-template-ref.md) | require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs | :wrench: | :hammer: |
274274
| [vue/require-default-export](./require-default-export.md) | require components to be the default export | | :warning: |
275275
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | | :hammer: |
276276
| [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | :bulb: | :hammer: |

‎docs/rules/prefer-use-template-ref.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ since: v9.31.0
1010

1111
> require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs
1212
13+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
14+
1315
## :book: Rule Details
1416

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

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

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

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

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

5052
```vue
5153
<template>

‎lib/rules/prefer-use-template-ref.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,59 @@ function getScriptRefsFromSetupFunction(body) {
4444
return refDeclarators.map(convertDeclaratorToScriptRef)
4545
}
4646

47+
/** @param node {Statement | ModuleDeclaration} */
48+
function createIndent(node) {
49+
const indentSize = node.loc.start.column
50+
51+
return ' '.repeat(indentSize)
52+
}
53+
54+
/**
55+
* @param context {RuleContext}
56+
* @param fixer {RuleFixer}
57+
* */
58+
function addUseTemplateRefImport(context, fixer) {
59+
const sourceCode = context.sourceCode
60+
61+
if (!sourceCode) {
62+
return
63+
}
64+
65+
const body = sourceCode.ast.body
66+
67+
const imports = body.filter((node) => node.type === 'ImportDeclaration')
68+
69+
const vueDestructuredImport = imports.find(
70+
(importStatement) =>
71+
importStatement.source.value === 'vue' &&
72+
importStatement.specifiers.some(
73+
(specifier) => specifier.type === 'ImportSpecifier'
74+
)
75+
)
76+
77+
if (vueDestructuredImport) {
78+
const importSpecifierLast = vueDestructuredImport.specifiers.at(-1)
79+
80+
// @ts-ignore
81+
return fixer.insertTextAfter(importSpecifierLast, `,useTemplateRef`)
82+
}
83+
84+
const lastImport = imports.at(-1)
85+
86+
const importStatement = "import {useTemplateRef} from 'vue';"
87+
88+
if (lastImport) {
89+
const indent = createIndent(lastImport)
90+
91+
return fixer.insertTextAfter(lastImport, `\n${indent}${importStatement}`)
92+
}
93+
94+
const firstNode = body[0]
95+
const indent = createIndent(firstNode)
96+
97+
return fixer.insertTextBefore(firstNode, `${importStatement}\n${indent}`)
98+
}
99+
47100
/** @type {import("eslint").Rule.RuleModule} */
48101
module.exports = {
49102
meta: {
@@ -54,6 +107,7 @@ module.exports = {
54107
categories: undefined,
55108
url: 'https://eslint.vuejs.org/rules/prefer-use-template-ref.html'
56109
},
110+
fixable: 'code',
57111
schema: [],
58112
messages: {
59113
preferUseTemplateRef: "Replace '{{name}}' with 'useTemplateRef'."
@@ -93,6 +147,8 @@ module.exports = {
93147
}),
94148
{
95149
'Program:exit'() {
150+
let missingImportChecked = false
151+
96152
for (const templateRef of templateRefs) {
97153
const scriptRef = scriptRefs.find(
98154
(scriptRef) => scriptRef.ref === templateRef
@@ -108,6 +164,32 @@ module.exports = {
108164
data: {
109165
// @ts-ignore
110166
name: scriptRef.node?.callee?.name
167+
},
168+
fix(fixer) {
169+
/** @type {Fix[]} */
170+
const fixes = []
171+
172+
const replaceFunctionFix = fixer.replaceText(
173+
scriptRef.node,
174+
`useTemplateRef('${scriptRef.ref}')`
175+
)
176+
177+
fixes.push(replaceFunctionFix)
178+
179+
if (!missingImportChecked) {
180+
missingImportChecked = true
181+
182+
const missingImportFix = addUseTemplateRefImport(
183+
context,
184+
fixer
185+
)
186+
187+
if (missingImportFix) {
188+
fixes.push(missingImportFix)
189+
}
190+
}
191+
192+
return fixes
111193
}
112194
})
113195
}

‎tests/lib/rules/prefer-use-template-ref.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,15 @@ tester.run('prefer-use-template-ref', rule, {
266266
const root = ref();
267267
</script>
268268
`,
269+
output: `
270+
<template>
271+
<div ref="root"/>
272+
</template>
273+
<script setup>
274+
import { ref,useTemplateRef } from 'vue';
275+
const root = useTemplateRef('root');
276+
</script>
277+
`,
269278
errors: [
270279
{
271280
messageId: 'preferUseTemplateRef',
@@ -290,6 +299,17 @@ tester.run('prefer-use-template-ref', rule, {
290299
const link = ref();
291300
</script>
292301
`,
302+
output: `
303+
<template>
304+
<button ref="button">Content</button>
305+
<a href="" ref="link">Link</a>
306+
</template>
307+
<script setup>
308+
import { ref,useTemplateRef } from 'vue';
309+
const buttonRef = ref();
310+
const link = useTemplateRef('link');
311+
</script>
312+
`,
293313
errors: [
294314
{
295315
messageId: 'preferUseTemplateRef',
@@ -314,6 +334,17 @@ tester.run('prefer-use-template-ref', rule, {
314334
const link = ref();
315335
</script>
316336
`,
337+
output: `
338+
<template>
339+
<h1 ref="heading">Heading</h1>
340+
<a href="" ref="link">Link</a>
341+
</template>
342+
<script setup>
343+
import { ref,useTemplateRef } from 'vue';
344+
const heading = useTemplateRef('heading');
345+
const link = useTemplateRef('link');
346+
</script>
347+
`,
317348
errors: [
318349
{
319350
messageId: 'preferUseTemplateRef',
@@ -351,6 +382,22 @@ tester.run('prefer-use-template-ref', rule, {
351382
}
352383
</script>
353384
`,
385+
output: `
386+
<template>
387+
<p>Button clicked {{counter}} times.</p>
388+
<button ref="button">Click</button>
389+
</template>
390+
<script>
391+
import { ref,useTemplateRef } from 'vue';
392+
export default {
393+
name: 'Counter',
394+
setup() {
395+
const counter = ref(0);
396+
const button = useTemplateRef('button');
397+
}
398+
}
399+
</script>
400+
`,
354401
errors: [
355402
{
356403
messageId: 'preferUseTemplateRef',
@@ -373,6 +420,15 @@ tester.run('prefer-use-template-ref', rule, {
373420
const root = shallowRef();
374421
</script>
375422
`,
423+
output: `
424+
<template>
425+
<div ref="root"/>
426+
</template>
427+
<script setup>
428+
import { shallowRef,useTemplateRef } from 'vue';
429+
const root = useTemplateRef('root');
430+
</script>
431+
`,
376432
errors: [
377433
{
378434
messageId: 'preferUseTemplateRef',
@@ -383,6 +439,102 @@ tester.run('prefer-use-template-ref', rule, {
383439
column: 22
384440
}
385441
]
442+
},
443+
{
444+
filename: 'missing-import.vue',
445+
code: `
446+
<template>
447+
<p>Button clicked {{counter}} times.</p>
448+
<button ref="button">Click</button>
449+
</template>
450+
<script>
451+
import { isEqual } from 'lodash';
452+
export default {
453+
name: 'Counter',
454+
setup() {
455+
const counter = ref(0);
456+
if (isEqual(counter.value, 0)) {
457+
console.log('Counter is reset');
458+
}
459+
const button = ref();
460+
}
461+
}
462+
</script>
463+
`,
464+
output: `
465+
<template>
466+
<p>Button clicked {{counter}} times.</p>
467+
<button ref="button">Click</button>
468+
</template>
469+
<script>
470+
import { isEqual } from 'lodash';
471+
import {useTemplateRef} from 'vue';
472+
export default {
473+
name: 'Counter',
474+
setup() {
475+
const counter = ref(0);
476+
if (isEqual(counter.value, 0)) {
477+
console.log('Counter is reset');
478+
}
479+
const button = useTemplateRef('button');
480+
}
481+
}
482+
</script>
483+
`,
484+
errors: [
485+
{
486+
messageId: 'preferUseTemplateRef',
487+
data: {
488+
name: 'ref'
489+
},
490+
line: 15,
491+
column: 28
492+
}
493+
]
494+
},
495+
{
496+
filename: 'no-imports.vue',
497+
code: `
498+
<template>
499+
<p>Button clicked {{counter}} times.</p>
500+
<button ref="button">Click</button>
501+
</template>
502+
<script>
503+
export default {
504+
name: 'Counter',
505+
setup() {
506+
const counter = ref(0);
507+
const button = ref();
508+
}
509+
}
510+
</script>
511+
`,
512+
output: `
513+
<template>
514+
<p>Button clicked {{counter}} times.</p>
515+
<button ref="button">Click</button>
516+
</template>
517+
<script>
518+
import {useTemplateRef} from 'vue';
519+
export default {
520+
name: 'Counter',
521+
setup() {
522+
const counter = ref(0);
523+
const button = useTemplateRef('button');
524+
}
525+
}
526+
</script>
527+
`,
528+
errors: [
529+
{
530+
messageId: 'preferUseTemplateRef',
531+
data: {
532+
name: 'ref'
533+
},
534+
line: 11,
535+
column: 28
536+
}
537+
]
386538
}
387539
]
388540
})

0 commit comments

Comments
(0)

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