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,7 @@ module.exports = {
44
79
create ( context ) {
45
80
/** @type {RefObjectReferences } */
46
81
let refReferences
82
+ const setupContexts = new Map ( )
47
83
48
84
/**
49
85
* @param {Identifier } node
@@ -64,90 +100,244 @@ module.exports = {
64
100
}
65
101
} )
66
102
}
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 ) {
94
- 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 ) {
100
- return
101
- }
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 ) {
108
- return
109
- }
110
- // Report only constants.
111
- const data = refReferences . get ( node )
112
- if (
113
- ! data ||
114
- ! data . variableDeclaration ||
115
- data . variableDeclaration . kind !== 'const'
116
- ) {
117
- return
118
- }
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
126
- }
127
- reportIfRefWrapped ( node )
128
- } ,
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 ) {
103
+
104
+ const programNode = context . getSourceCode ( ) . ast
105
+
106
+ const callVisitor = {
107
+ /**
108
+ * @param {CallExpression } node
109
+ * @param {import('../utils').VueObjectData } [info]
110
+ */
111
+ CallExpression ( node , info ) {
112
+ const nameWithLoc = getNameParamNode ( node )
113
+ if ( ! nameWithLoc ) {
114
+ // cannot check
138
115
return
139
116
}
140
- const name = utils . getStaticPropertyName ( node . parent )
141
- if (
142
- name === 'value' ||
143
- name == null ||
144
- // WritableComputedRef
145
- name === 'effect'
146
- ) {
147
- return
117
+
118
+ // verify setup context
119
+ const setupContext = setupContexts . get ( info ? info . node : programNode )
120
+ if ( setupContext ) {
121
+ const { contextReferenceIds, emitReferenceIds } = setupContext
122
+ if (
123
+ node . callee . type === 'Identifier' &&
124
+ emitReferenceIds . has ( node . callee )
125
+ ) {
126
+ // verify setup(props,{emit}) {emit()}
127
+ node . arguments
128
+ . filter (
129
+ ( node ) =>
130
+ node . type === 'Identifier' &&
131
+ isRefInit ( refReferences . get ( node ) )
132
+ )
133
+ . forEach ( ( node ) => {
134
+ reportIfRefWrapped ( node )
135
+ } )
136
+ } else {
137
+ const emit = getCalleeMemberNode ( node )
138
+ if (
139
+ emit &&
140
+ emit . name === 'emit' &&
141
+ emit . member . object . type === 'Identifier' &&
142
+ contextReferenceIds . has ( emit . member . object )
143
+ ) {
144
+ // verify setup(props,context) {context.emit()}
145
+ emit . member . parent . arguments
146
+ . filter (
147
+ ( node ) =>
148
+ node . type === 'Identifier' &&
149
+ isRefInit ( refReferences . get ( node ) )
150
+ )
151
+ . forEach ( ( node ) => {
152
+ reportIfRefWrapped ( node )
153
+ } )
154
+ }
155
+ }
148
156
}
149
- reportIfRefWrapped ( node )
150
157
}
151
158
}
159
+
160
+ return utils . compositingVisitors (
161
+ {
162
+ Program ( ) {
163
+ refReferences = extractRefObjectReferences ( context )
164
+ } ,
165
+ // if (refValue)
166
+ /** @param {Identifier } node */
167
+ 'IfStatement>Identifier' ( node ) {
168
+ reportIfRefWrapped ( node )
169
+ } ,
170
+ // switch (refValue)
171
+ /** @param {Identifier } node */
172
+ 'SwitchStatement>Identifier' ( node ) {
173
+ reportIfRefWrapped ( node )
174
+ } ,
175
+ // -refValue, +refValue, !refValue, ~refValue, typeof refValue
176
+ /** @param {Identifier } node */
177
+ 'UnaryExpression>Identifier' ( node ) {
178
+ reportIfRefWrapped ( node )
179
+ } ,
180
+ // refValue++, refValue--
181
+ /** @param {Identifier } node */
182
+ 'UpdateExpression>Identifier' ( node ) {
183
+ reportIfRefWrapped ( node )
184
+ } ,
185
+ // refValue+1, refValue-1
186
+ /** @param {Identifier } node */
187
+ 'BinaryExpression>Identifier' ( node ) {
188
+ reportIfRefWrapped ( node )
189
+ } ,
190
+ // refValue+=1, refValue-=1, foo+=refValue, foo-=refValue
191
+ /** @param {Identifier & {parent: AssignmentExpression} } node */
192
+ 'AssignmentExpression>Identifier' ( node ) {
193
+ if ( node . parent . operator === '=' && node . parent . left !== node ) {
194
+ return
195
+ }
196
+ reportIfRefWrapped ( node )
197
+ } ,
198
+ // refValue || other, refValue && other. ignore: other || refValue
199
+ /** @param {Identifier & {parent: LogicalExpression} } node */
200
+ 'LogicalExpression>Identifier' ( node ) {
201
+ if ( node . parent . left !== node ) {
202
+ return
203
+ }
204
+ // Report only constants.
205
+ const data = refReferences . get ( node )
206
+ if (
207
+ ! data ||
208
+ ! data . variableDeclaration ||
209
+ data . variableDeclaration . kind !== 'const'
210
+ ) {
211
+ return
212
+ }
213
+ reportIfRefWrapped ( node )
214
+ } ,
215
+ // refValue ? x : y
216
+ /** @param {Identifier & {parent: ConditionalExpression} } node */
217
+ 'ConditionalExpression>Identifier' ( node ) {
218
+ if ( node . parent . test !== node ) {
219
+ return
220
+ }
221
+ reportIfRefWrapped ( node )
222
+ } ,
223
+ // `${refValue}`
224
+ /** @param {Identifier } node */
225
+ 'TemplateLiteral>Identifier' ( node ) {
226
+ reportIfRefWrapped ( node )
227
+ } ,
228
+ // refValue.x
229
+ /** @param {Identifier & {parent: MemberExpression} } node */
230
+ 'MemberExpression>Identifier' ( node ) {
231
+ if ( node . parent . object !== node ) {
232
+ return
233
+ }
234
+ const name = utils . getStaticPropertyName ( node . parent )
235
+ if (
236
+ name === 'value' ||
237
+ name == null ||
238
+ // WritableComputedRef
239
+ name === 'effect'
240
+ ) {
241
+ return
242
+ }
243
+ reportIfRefWrapped ( node )
244
+ }
245
+ } ,
246
+ utils . compositingVisitors (
247
+ utils . defineScriptSetupVisitor ( context , {
248
+ onDefineEmitsEnter ( node ) {
249
+ if (
250
+ ! node . parent ||
251
+ node . parent . type !== 'VariableDeclarator' ||
252
+ node . parent . init !== node
253
+ ) {
254
+ return
255
+ }
256
+
257
+ const emitParam = node . parent . id
258
+ if ( emitParam . type !== 'Identifier' ) {
259
+ return
260
+ }
261
+
262
+ // const emit = defineEmits()
263
+ const variable = findVariable (
264
+ utils . getScope ( context , emitParam ) ,
265
+ emitParam
266
+ )
267
+ if ( ! variable ) {
268
+ return
269
+ }
270
+ const emitReferenceIds = new Set ( )
271
+ for ( const reference of variable . references ) {
272
+ emitReferenceIds . add ( reference . identifier )
273
+ }
274
+ setupContexts . set ( programNode , {
275
+ contextReferenceIds : new Set ( ) ,
276
+ emitReferenceIds
277
+ } )
278
+ } ,
279
+ ...callVisitor
280
+ } ) ,
281
+ utils . defineVueVisitor ( context , {
282
+ onSetupFunctionEnter ( node , { node : vueNode } ) {
283
+ const contextParam = utils . skipDefaultParamValue ( node . params [ 1 ] )
284
+ if ( ! contextParam ) {
285
+ // no arguments
286
+ return
287
+ }
288
+ if (
289
+ contextParam . type === 'RestElement' ||
290
+ contextParam . type === 'ArrayPattern'
291
+ ) {
292
+ // cannot check
293
+ return
294
+ }
295
+ const contextReferenceIds = new Set ( )
296
+ const emitReferenceIds = new Set ( )
297
+ if ( contextParam . type === 'ObjectPattern' ) {
298
+ const emitProperty = utils . findAssignmentProperty (
299
+ contextParam ,
300
+ 'emit'
301
+ )
302
+ if ( ! emitProperty || emitProperty . value . type !== 'Identifier' ) {
303
+ return
304
+ }
305
+ const emitParam = emitProperty . value
306
+ // `setup(props, {emit})`
307
+ const variable = findVariable (
308
+ utils . getScope ( context , emitParam ) ,
309
+ emitParam
310
+ )
311
+ if ( ! variable ) {
312
+ return
313
+ }
314
+ for ( const reference of variable . references ) {
315
+ emitReferenceIds . add ( reference . identifier )
316
+ }
317
+ } else {
318
+ // `setup(props, context)`
319
+ const variable = findVariable (
320
+ utils . getScope ( context , contextParam ) ,
321
+ contextParam
322
+ )
323
+ if ( ! variable ) {
324
+ return
325
+ }
326
+ for ( const reference of variable . references ) {
327
+ contextReferenceIds . add ( reference . identifier )
328
+ }
329
+ }
330
+ setupContexts . set ( vueNode , {
331
+ contextReferenceIds,
332
+ emitReferenceIds
333
+ } )
334
+ } ,
335
+ ...callVisitor ,
336
+ onVueObjectExit ( node ) {
337
+ setupContexts . delete ( node )
338
+ }
339
+ } )
340
+ )
341
+ )
152
342
}
153
343
}
0 commit comments