4
4
*/
5
5
'use strict'
6
6
7
+ const { findVariable } = require ( '@eslint-community/eslint-utils' )
7
8
const { extractRefObjectReferences } = require ( '../utils/ref-object-references' )
8
9
const utils = require ( '../utils' )
9
10
@@ -24,6 +25,40 @@ function isRefInit(data) {
24
25
}
25
26
return data . defineChain . includes ( /** @type {any } */ ( init ) )
26
27
}
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
+
27
62
module . exports = {
28
63
meta : {
29
64
type : 'suggestion' ,
@@ -44,6 +79,22 @@ module.exports = {
44
79
create ( context ) {
45
80
/** @type {RefObjectReferences } */
46
81
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
+ }
47
98
48
99
/**
49
100
* @param {Identifier } node
@@ -64,90 +115,213 @@ module.exports = {
64
115
}
65
116
} )
66
117
}
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 ) {
94
125
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
100
140
return
101
141
}
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 ) {
108
146
return
109
147
}
110
- // Report only constants.
111
- const data = refReferences . get ( node )
148
+
149
+ const { contextReferenceIds , emitReferenceIds } = setupContext
112
150
if (
113
- ! data ||
114
- ! data . variableDeclaration ||
115
- data . variableDeclaration . kind !== 'const'
151
+ node . callee . type === 'Identifier' &&
152
+ emitReferenceIds . has ( node . callee )
116
153
) {
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
+ }
118
167
}
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 )
126
255
}
127
- reportIfRefWrapped ( node )
128
256
} ,
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 )
148
323
}
149
- reportIfRefWrapped ( node )
150
- }
151
- }
324
+ } )
325
+ )
152
326
}
153
327
}
0 commit comments