-
-
Notifications
You must be signed in to change notification settings - Fork 681
/
Copy pathprefer-use-template-ref.js
201 lines (167 loc) · 5.04 KB
/
prefer-use-template-ref.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/**
* @author Thomasan1999
* See LICENSE file in root directory for full license.
*/
'use strict'
const utils = require('../utils')
/**
* @typedef ScriptRef
* @type {{node: Expression, ref: string}}
*/
/**
* @param declarator {VariableDeclarator}
* @returns {ScriptRef}
* */
function convertDeclaratorToScriptRef(declarator) {
return {
// @ts-ignore
node: declarator.init,
// @ts-ignore
ref: declarator.id.name
}
}
/**
* @param body {(Statement | ModuleDeclaration)[]}
* @returns {ScriptRef[]}
* */
function getScriptRefsFromSetupFunction(body) {
/** @type {VariableDeclaration[]} */
const variableDeclarations = body.filter(
(child) => child.type === 'VariableDeclaration'
)
const variableDeclarators = variableDeclarations.map(
(declaration) => declaration.declarations[0]
)
const refDeclarators = variableDeclarators.filter((declarator) =>
// @ts-ignore
['ref', 'shallowRef'].includes(declarator.init?.callee?.name)
)
return refDeclarators.map(convertDeclaratorToScriptRef)
}
/** @param node {Statement | ModuleDeclaration} */
function createIndent(node) {
const indentSize = node.loc.start.column
return ' '.repeat(indentSize)
}
/**
* @param context {RuleContext}
* @param fixer {RuleFixer}
* */
function addUseTemplateRefImport(context, fixer) {
const sourceCode = context.getSourceCode()
if (!sourceCode) {
return
}
const body = sourceCode.ast.body
const imports = body.filter((node) => node.type === 'ImportDeclaration')
const vueDestructuredImport = imports.find(
(importStatement) =>
importStatement.source.value === 'vue' &&
importStatement.specifiers.some(
(specifier) => specifier.type === 'ImportSpecifier'
)
)
if (vueDestructuredImport) {
const importSpecifiers = vueDestructuredImport.specifiers
const lastImportSpecifier = importSpecifiers[importSpecifiers.length - 1]
// @ts-ignore
return fixer.insertTextAfter(lastImportSpecifier, `,useTemplateRef`)
}
const lastImport = imports[imports.length - 1]
const importStatement = "import {useTemplateRef} from 'vue';"
if (lastImport) {
const indent = createIndent(lastImport)
return fixer.insertTextAfter(lastImport, `\n${indent}${importStatement}`)
}
const firstNode = body[0]
const indent = createIndent(firstNode)
return fixer.insertTextBefore(firstNode, `${importStatement}\n${indent}`)
}
/** @type {import("eslint").Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description:
'require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs',
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) {
/** @type Set<string> */
const templateRefs = new Set()
/**
* @type ScriptRef[] */
const scriptRefs = []
return utils.compositingVisitors(
utils.defineTemplateBodyVisitor(context, {
'VAttribute[directive=false]'(node) {
if (node.key.name === 'ref' && node.value?.value) {
templateRefs.add(node.value.value)
}
}
}),
utils.defineVueVisitor(context, {
onSetupFunctionEnter(node) {
// @ts-ignore
const newScriptRefs = getScriptRefsFromSetupFunction(node.body.body)
scriptRefs.push(...newScriptRefs)
}
}),
utils.defineScriptSetupVisitor(context, {
Program(node) {
const newScriptRefs = getScriptRefsFromSetupFunction(node.body)
scriptRefs.push(...newScriptRefs)
}
}),
{
'Program:exit'() {
let missingImportChecked = false
for (const templateRef of templateRefs) {
const scriptRef = scriptRefs.find(
(scriptRef) => scriptRef.ref === templateRef
)
if (!scriptRef) {
continue
}
context.report({
node: scriptRef.node,
messageId: 'preferUseTemplateRef',
data: {
// @ts-ignore
name: scriptRef.node?.callee?.name
},
fix(fixer) {
/** @type {Fix[]} */
const fixes = []
const replaceFunctionFix = fixer.replaceText(
scriptRef.node,
`useTemplateRef('${scriptRef.ref}')`
)
fixes.push(replaceFunctionFix)
if (!missingImportChecked) {
missingImportChecked = true
const missingImportFix = addUseTemplateRefImport(
context,
fixer
)
if (missingImportFix) {
fixes.push(missingImportFix)
}
}
return fixes
}
})
}
}
}
)
}
}