@@ -241,5 +241,148 @@ module.exports = {
241
241
assert ( typeof name === 'string' )
242
242
243
243
return VOID_ELEMENT_NAMES . has ( name . toLowerCase ( ) )
244
+ } ,
245
+
246
+ /**
247
+ * Parse member expression node to get array with all of it's parts
248
+ * @param {ASTNode } MemberExpression
249
+ * @returns {Array }
250
+ */
251
+ parseMemberExpression ( node ) {
252
+ const members = [ ]
253
+ let memberExpression
254
+
255
+ if ( node . type === 'MemberExpression' ) {
256
+ memberExpression = node
257
+
258
+ while ( memberExpression . type === 'MemberExpression' ) {
259
+ if ( memberExpression . property . type === 'Identifier' ) {
260
+ members . push ( memberExpression . property . name )
261
+ }
262
+ memberExpression = memberExpression . object
263
+ }
264
+
265
+ if ( memberExpression . type === 'ThisExpression' ) {
266
+ members . push ( 'this' )
267
+ } else if ( memberExpression . type === 'Identifier' ) {
268
+ members . push ( memberExpression . name )
269
+ }
270
+ }
271
+
272
+ return members . reverse ( )
273
+ } ,
274
+
275
+ /**
276
+ * Get all computed properties by looking at all component's properties
277
+ * @param {ObjectExpression } Object with component definition
278
+ * @return {Array } Array of computed properties in format: [{key: String, value: ASTNode}]
279
+ */
280
+ getComputedProperties ( componentObject ) {
281
+ const computedPropertiesNode = componentObject . properties
282
+ . filter ( p =>
283
+ p . key . type === 'Identifier' &&
284
+ p . key . name === 'computed' &&
285
+ p . value . type === 'ObjectExpression'
286
+ ) [ 0 ]
287
+
288
+ if ( ! computedPropertiesNode ) { return [ ] }
289
+
290
+ return computedPropertiesNode . value . properties
291
+ . filter ( cp => cp . type === 'Property' )
292
+ . map ( cp => {
293
+ const key = cp . key . name
294
+ let value
295
+
296
+ if ( cp . value . type === 'FunctionExpression' ) {
297
+ value = cp . value . body
298
+ } else if ( cp . value . type === 'ObjectExpression' ) {
299
+ value = cp . value . properties
300
+ . filter ( p =>
301
+ p . key . type === 'Identifier' &&
302
+ p . key . name === 'get' &&
303
+ p . value . type === 'FunctionExpression'
304
+ )
305
+ . map ( p => p . value . body ) [ 0 ]
306
+ }
307
+
308
+ return { key, value }
309
+ } )
310
+ } ,
311
+
312
+ /**
313
+ * Check whether the given node is a Vue component based
314
+ * on the filename and default export type
315
+ * export default {} in .vue || .jsx
316
+ * @param {ASTNode } node Node to check
317
+ * @param {string } path File name with extension
318
+ * @returns {boolean }
319
+ */
320
+ isVueComponentFile ( node , path ) {
321
+ const isVueFile = path . endsWith ( '.vue' ) || path . endsWith ( '.jsx' )
322
+ return isVueFile &&
323
+ node . type === 'ExportDefaultDeclaration' &&
324
+ node . declaration . type === 'ObjectExpression'
325
+ } ,
326
+
327
+ /**
328
+ * Check whether given node is Vue component
329
+ * Vue.component('xxx', {}) || component('xxx', {})
330
+ * @param {ASTNode } node Node to check
331
+ * @returns {boolean }
332
+ */
333
+ isVueComponent ( node ) {
334
+ const callee = node . callee
335
+
336
+ const isFullVueComponent = node . type === 'CallExpression' &&
337
+ callee . type === 'MemberExpression' &&
338
+ callee . object . type === 'Identifier' &&
339
+ callee . object . name === 'Vue' &&
340
+ callee . property . type === 'Identifier' &&
341
+ callee . property . name === 'component' &&
342
+ node . arguments . length &&
343
+ node . arguments . slice ( - 1 ) [ 0 ] . type === 'ObjectExpression'
344
+
345
+ const isDestructedVueComponent = callee . type === 'Identifier' &&
346
+ callee . name === 'component'
347
+
348
+ return isFullVueComponent || isDestructedVueComponent
349
+ } ,
350
+
351
+ /**
352
+ * Check whether given node is new Vue instance
353
+ * new Vue({})
354
+ * @param {ASTNode } node Node to check
355
+ * @returns {boolean }
356
+ */
357
+ isVueInstance ( node ) {
358
+ const callee = node . callee
359
+ return node . type === 'NewExpression' &&
360
+ callee . type === 'Identifier' &&
361
+ callee . name === 'Vue' &&
362
+ node . arguments . length &&
363
+ node . arguments [ 0 ] . type === 'ObjectExpression'
364
+ } ,
365
+
366
+ executeOnVueComponent ( context , cb ) {
367
+ const filePath = context . getFilename ( )
368
+ const _this = this
369
+
370
+ return {
371
+ 'ExportDefaultDeclaration:exit' ( node ) {
372
+ // export default {} in .vue || .jsx
373
+ if ( ! _this . isVueComponentFile ( node , filePath ) ) return
374
+ cb ( node . declaration )
375
+ } ,
376
+ 'CallExpression:exit' ( node ) {
377
+ // Vue.component('xxx', {}) || component('xxx', {})
378
+ if ( ! _this . isVueComponent ( node ) ) return
379
+ cb ( node . arguments . slice ( - 1 ) [ 0 ] )
380
+ } ,
381
+ 'NewExpression:exit' ( node ) {
382
+ // new Vue({})
383
+ if ( ! _this . isVueInstance ( node ) ) return
384
+ cb ( node . arguments [ 0 ] )
385
+ }
386
+ }
244
387
}
245
388
}
0 commit comments