44 */
55'use strict'
66
7+ const { findVariable } = require ( '@eslint-community/eslint-utils' )
78const { extractRefObjectReferences } = require ( '../utils/ref-object-references' )
89const 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+
2762module . exports = {
2863 meta : {
2964 type : 'suggestion' ,
@@ -44,6 +79,7 @@ module.exports = {
4479 create ( context ) {
4580 /** @type {RefObjectReferences } */
4681 let refReferences
82+ const setupContexts = new Map ( )
4783
4884 /**
4985 * @param {Identifier } node
@@ -64,90 +100,244 @@ module.exports = {
64100 }
65101 } )
66102 }
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
138115 return
139116 }
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+ }
148156 }
149- reportIfRefWrapped ( node )
150157 }
151158 }
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+ )
152342 }
153343}
0 commit comments