-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.js
151 lines (115 loc) · 3.64 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
var _ = require( 'lodash' ),
esprimaParser = require( 'esprima' ).parse;
var isFunctionCall = function( node ) {
return node.type === 'CallExpression';
};
function Parser ( text, config ) {
var config = config || {};
config.prefix = config.prefix || 'app/routes';
this.config = config;
this.tree = {};
this.inspectClass(text, 'Router.map', [ 'resource', 'route' ]);
}
Parser.prototype = Object.create({
inspectClass: function ( code, path, props ) {
var mapCall,
properties,
ast;
ast = esprimaParser( code, { range: true, comment: true } );
mapCall = this.findMapDefinition( path, ast );
if ( !mapCall || mapCall.arguments.length < 1 ) {
return this.tree;
}
properties = mapCall.arguments[0].body.body;
properties.forEach( function ( prop ) {
if ( _.contains( props, prop.expression.callee.property.name ) ) {
this.buildTree( prop.expression.arguments );
}
}.bind(this));
},
findMapDefinition: function ( path, root ) {
var calls = this.findCalls( path, root, 1 );
if (_.any(calls)) {
return calls[0];
}
},
findCalls: function ( path, root, limit ) {
var calls = [];
if ( !root ) {
return;
}
limit = limit || 50;
this.walkAst( root, function ( node ) {
if ( this.matchesPath( node.callee, path ) ) {
calls.push(node);
}
return calls.length < limit;
}.bind( this ), isFunctionCall );
return calls;
},
walkAst: function ( root, nodeHandler, pred ) {
var nodesToVisit, node, cont;
if (!root || !nodeHandler) {
return;
}
// If no predicate specified, visit every node
pred = pred || _.constant( true );
nodesToVisit = _.isArray( root ) ? root : [ root ];
while ( node = nodesToVisit.shift() ) {
if ( pred(node) ) {
cont = nodeHandler.call( this, node );
if (!cont) {
return;
}
}
// Add children to the queue
if ( _.isObject( node ) ) {
nodesToVisit = nodesToVisit.concat(
_.compact( _.flatten( _.values( node ) ) ) );
}
}
},
matchesPath: function ( node, path ) {
var pathSegments, pathSegment, firstPathSegment, current;
if ( !node || !path ) {
return false;
}
pathSegments = path.split('.');
firstPathSegment = pathSegments.shift();
current = node;
// Traverse up to the top level of the object graph, and compare each name
// with the corresponding segment of the path
while ( pathSegment = pathSegments.pop() ) {
if (!current.property || current.property.name !== pathSegment) {
return false;
}
current = current.object;
}
// Top object in the object graph corresponds to the first path segment
return current.name === firstPathSegment;
},
buildTree: function ( nodes ) {
// Set the top level nodes
this.tree[ nodes[ 0 ].value ] = this.config.prefix + '/' + nodes[ 0 ].value
// Start creating the branches of the tree
if ( nodes.length > 1 && nodes[ 1 ].type === 'FunctionExpression' ) {
this.createBranch( nodes[ 0 ].value, nodes[ 1 ].body.body );
}
},
createBranch: function ( root, nodes ) {
var node, name, path;
while( node = nodes.shift() ) {
name = node.expression.arguments[ 0 ].value;
path = root + '/' + name;
this.tree[ path ] = this.config.prefix + '/' + path;
// If we're nested lets recurse
if ( node.expression.arguments.length === 2 ) {
this.createBranch( path, node.expression.arguments[ 1 ].body.body );
}
}
},
exportTree: function () {
return this.tree;
}
});
module.exports = Parser;