@@ -241,5 +241,148 @@ module.exports = {
241241 assert ( typeof name === 'string' )
242242
243243 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+ }
244387 }
245388}
0 commit comments