Skip to content

Commit af3700f

Browse files
authored
feat(no-ref-as-operand): use ref.value to replace ref when emit (#2680)
1 parent 827ab4b commit af3700f

File tree

3 files changed

+521
-75
lines changed

3 files changed

+521
-75
lines changed

docs/rules/no-ref-as-operand.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ You must use `.value` to access the `Ref` value.
2525
import { ref } from 'vue'
2626
2727
export default {
28-
setup() {
28+
setup(_props, { emit }) {
2929
const count = ref(0)
3030
const ok = ref(true)
3131
@@ -34,12 +34,14 @@ export default {
3434
count.value + 1
3535
1 + count.value
3636
var msg = ok.value ? 'yes' : 'no'
37+
emit('increment', count.value)
3738
3839
/* ✗ BAD */
3940
count++
4041
count + 1
4142
1 + count
4243
var msg = ok ? 'yes' : 'no'
44+
emit('increment', count)
4345
4446
return {
4547
count

lib/rules/no-ref-as-operand.js

Lines changed: 248 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
'use strict'
66

7+
const { findVariable } = require('@eslint-community/eslint-utils')
78
const { extractRefObjectReferences } = require('../utils/ref-object-references')
89
const utils = require('../utils')
910

@@ -24,6 +25,40 @@ function isRefInit(data) {
2425
}
2526
return data.defineChain.includes(/** @type {any} */ (init))
2627
}
28+
29+
/**
30+
* Get the callee member node from the given CallExpression
31+
* @param {CallExpression} node CallExpression
32+
*/
33+
function getNameParamNode(node) {
34+
const nameLiteralNode = node.arguments[0]
35+
if (nameLiteralNode && utils.isStringLiteral(nameLiteralNode)) {
36+
const name = utils.getStringLiteralValue(nameLiteralNode)
37+
if (name != null) {
38+
return { name, loc: nameLiteralNode.loc }
39+
}
40+
}
41+
42+
// cannot check
43+
return null
44+
}
45+
46+
/**
47+
* Get the callee member node from the given CallExpression
48+
* @param {CallExpression} node CallExpression
49+
*/
50+
function getCalleeMemberNode(node) {
51+
const callee = utils.skipChainExpression(node.callee)
52+
53+
if (callee.type === 'MemberExpression') {
54+
const name = utils.getStaticPropertyName(callee)
55+
if (name) {
56+
return { name, member: callee }
57+
}
58+
}
59+
return null
60+
}
61+
2762
module.exports = {
2863
meta: {
2964
type: 'suggestion',
@@ -44,6 +79,22 @@ module.exports = {
4479
create(context) {
4580
/** @type {RefObjectReferences} */
4681
let refReferences
82+
const setupContexts = new Map()
83+
84+
/**
85+
* Collect identifier id
86+
* @param {Identifier} node
87+
* @param {Set<Identifier>} referenceIds
88+
*/
89+
function collectReferenceIds(node, referenceIds) {
90+
const variable = findVariable(utils.getScope(context, node), node)
91+
if (!variable) {
92+
return
93+
}
94+
for (const reference of variable.references) {
95+
referenceIds.add(reference.identifier)
96+
}
97+
}
4798

4899
/**
49100
* @param {Identifier} node
@@ -64,90 +115,213 @@ module.exports = {
64115
}
65116
})
66117
}
67-
return {
68-
Program() {
69-
refReferences = extractRefObjectReferences(context)
70-
},
71-
// if (refValue)
72-
/** @param {Identifier} node */
73-
'IfStatement>Identifier'(node) {
74-
reportIfRefWrapped(node)
75-
},
76-
// switch (refValue)
77-
/** @param {Identifier} node */
78-
'SwitchStatement>Identifier'(node) {
79-
reportIfRefWrapped(node)
80-
},
81-
// -refValue, +refValue, !refValue, ~refValue, typeof refValue
82-
/** @param {Identifier} node */
83-
'UnaryExpression>Identifier'(node) {
84-
reportIfRefWrapped(node)
85-
},
86-
// refValue++, refValue--
87-
/** @param {Identifier} node */
88-
'UpdateExpression>Identifier'(node) {
89-
reportIfRefWrapped(node)
90-
},
91-
// refValue+1, refValue-1
92-
/** @param {Identifier} node */
93-
'BinaryExpression>Identifier'(node) {
118+
119+
/**
120+
* @param {CallExpression} node
121+
*/
122+
function reportWrappedIdentifiers(node) {
123+
const nodes = node.arguments.filter((node) => node.type === 'Identifier')
124+
for (const node of nodes) {
94125
reportIfRefWrapped(node)
95-
},
96-
// refValue+=1, refValue-=1, foo+=refValue, foo-=refValue
97-
/** @param {Identifier & {parent: AssignmentExpression}} node */
98-
'AssignmentExpression>Identifier'(node) {
99-
if (node.parent.operator === '=' && node.parent.left !== node) {
126+
}
127+
}
128+
129+
const programNode = context.getSourceCode().ast
130+
131+
const callVisitor = {
132+
/**
133+
* @param {CallExpression} node
134+
* @param {import('../utils').VueObjectData} [info]
135+
*/
136+
CallExpression(node, info) {
137+
const nameWithLoc = getNameParamNode(node)
138+
if (!nameWithLoc) {
139+
// cannot check
100140
return
101141
}
102-
reportIfRefWrapped(node)
103-
},
104-
// refValue || other, refValue && other. ignore: other || refValue
105-
/** @param {Identifier & {parent: LogicalExpression}} node */
106-
'LogicalExpression>Identifier'(node) {
107-
if (node.parent.left !== node) {
142+
143+
// verify setup context
144+
const setupContext = setupContexts.get(info ? info.node : programNode)
145+
if (!setupContext) {
108146
return
109147
}
110-
// Report only constants.
111-
const data = refReferences.get(node)
148+
149+
const { contextReferenceIds, emitReferenceIds } = setupContext
112150
if (
113-
!data ||
114-
!data.variableDeclaration ||
115-
data.variableDeclaration.kind !== 'const'
151+
node.callee.type === 'Identifier' &&
152+
emitReferenceIds.has(node.callee)
116153
) {
117-
return
154+
// verify setup(props,{emit}) {emit()}
155+
reportWrappedIdentifiers(node)
156+
} else {
157+
const emit = getCalleeMemberNode(node)
158+
if (
159+
emit &&
160+
emit.name === 'emit' &&
161+
emit.member.object.type === 'Identifier' &&
162+
contextReferenceIds.has(emit.member.object)
163+
) {
164+
// verify setup(props,context) {context.emit()}
165+
reportWrappedIdentifiers(node)
166+
}
118167
}
119-
reportIfRefWrapped(node)
120-
},
121-
// refValue ? x : y
122-
/** @param {Identifier & {parent: ConditionalExpression}} node */
123-
'ConditionalExpression>Identifier'(node) {
124-
if (node.parent.test !== node) {
125-
return
168+
}
169+
}
170+
171+
return utils.compositingVisitors(
172+
{
173+
Program() {
174+
refReferences = extractRefObjectReferences(context)
175+
},
176+
// if (refValue)
177+
/** @param {Identifier} node */
178+
'IfStatement>Identifier'(node) {
179+
reportIfRefWrapped(node)
180+
},
181+
// switch (refValue)
182+
/** @param {Identifier} node */
183+
'SwitchStatement>Identifier'(node) {
184+
reportIfRefWrapped(node)
185+
},
186+
// -refValue, +refValue, !refValue, ~refValue, typeof refValue
187+
/** @param {Identifier} node */
188+
'UnaryExpression>Identifier'(node) {
189+
reportIfRefWrapped(node)
190+
},
191+
// refValue++, refValue--
192+
/** @param {Identifier} node */
193+
'UpdateExpression>Identifier'(node) {
194+
reportIfRefWrapped(node)
195+
},
196+
// refValue+1, refValue-1
197+
/** @param {Identifier} node */
198+
'BinaryExpression>Identifier'(node) {
199+
reportIfRefWrapped(node)
200+
},
201+
// refValue+=1, refValue-=1, foo+=refValue, foo-=refValue
202+
/** @param {Identifier & {parent: AssignmentExpression}} node */
203+
'AssignmentExpression>Identifier'(node) {
204+
if (node.parent.operator === '=' && node.parent.left !== node) {
205+
return
206+
}
207+
reportIfRefWrapped(node)
208+
},
209+
// refValue || other, refValue && other. ignore: other || refValue
210+
/** @param {Identifier & {parent: LogicalExpression}} node */
211+
'LogicalExpression>Identifier'(node) {
212+
if (node.parent.left !== node) {
213+
return
214+
}
215+
// Report only constants.
216+
const data = refReferences.get(node)
217+
if (
218+
!data ||
219+
!data.variableDeclaration ||
220+
data.variableDeclaration.kind !== 'const'
221+
) {
222+
return
223+
}
224+
reportIfRefWrapped(node)
225+
},
226+
// refValue ? x : y
227+
/** @param {Identifier & {parent: ConditionalExpression}} node */
228+
'ConditionalExpression>Identifier'(node) {
229+
if (node.parent.test !== node) {
230+
return
231+
}
232+
reportIfRefWrapped(node)
233+
},
234+
// `${refValue}`
235+
/** @param {Identifier} node */
236+
'TemplateLiteral>Identifier'(node) {
237+
reportIfRefWrapped(node)
238+
},
239+
// refValue.x
240+
/** @param {Identifier & {parent: MemberExpression}} node */
241+
'MemberExpression>Identifier'(node) {
242+
if (node.parent.object !== node) {
243+
return
244+
}
245+
const name = utils.getStaticPropertyName(node.parent)
246+
if (
247+
name === 'value' ||
248+
name == null ||
249+
// WritableComputedRef
250+
name === 'effect'
251+
) {
252+
return
253+
}
254+
reportIfRefWrapped(node)
126255
}
127-
reportIfRefWrapped(node)
128256
},
129-
// `${refValue}`
130-
/** @param {Identifier} node */
131-
'TemplateLiteral>Identifier'(node) {
132-
reportIfRefWrapped(node)
133-
},
134-
// refValue.x
135-
/** @param {Identifier & {parent: MemberExpression}} node */
136-
'MemberExpression>Identifier'(node) {
137-
if (node.parent.object !== node) {
138-
return
139-
}
140-
const name = utils.getStaticPropertyName(node.parent)
141-
if (
142-
name === 'value' ||
143-
name == null ||
144-
// WritableComputedRef
145-
name === 'effect'
146-
) {
147-
return
257+
utils.defineScriptSetupVisitor(context, {
258+
onDefineEmitsEnter(node) {
259+
if (
260+
!node.parent ||
261+
node.parent.type !== 'VariableDeclarator' ||
262+
node.parent.init !== node
263+
) {
264+
return
265+
}
266+
267+
const emitParam = node.parent.id
268+
if (emitParam.type !== 'Identifier') {
269+
return
270+
}
271+
272+
// const emit = defineEmits()
273+
const emitReferenceIds = new Set()
274+
collectReferenceIds(emitParam, emitReferenceIds)
275+
276+
setupContexts.set(programNode, {
277+
contextReferenceIds: new Set(),
278+
emitReferenceIds
279+
})
280+
},
281+
...callVisitor
282+
}),
283+
utils.defineVueVisitor(context, {
284+
onSetupFunctionEnter(node, { node: vueNode }) {
285+
const contextParam = utils.skipDefaultParamValue(node.params[1])
286+
if (!contextParam) {
287+
// no arguments
288+
return
289+
}
290+
if (
291+
contextParam.type === 'RestElement' ||
292+
contextParam.type === 'ArrayPattern'
293+
) {
294+
// cannot check
295+
return
296+
}
297+
298+
const contextReferenceIds = new Set()
299+
const emitReferenceIds = new Set()
300+
if (contextParam.type === 'ObjectPattern') {
301+
const emitProperty = utils.findAssignmentProperty(
302+
contextParam,
303+
'emit'
304+
)
305+
if (!emitProperty || emitProperty.value.type !== 'Identifier') {
306+
return
307+
}
308+
309+
// `setup(props, {emit})`
310+
collectReferenceIds(emitProperty.value, emitReferenceIds)
311+
} else {
312+
// `setup(props, context)`
313+
collectReferenceIds(contextParam, contextReferenceIds)
314+
}
315+
setupContexts.set(vueNode, {
316+
contextReferenceIds,
317+
emitReferenceIds
318+
})
319+
},
320+
...callVisitor,
321+
onVueObjectExit(node) {
322+
setupContexts.delete(node)
148323
}
149-
reportIfRefWrapped(node)
150-
}
151-
}
324+
})
325+
)
152326
}
153327
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy