d910fad21e25d9d66e6ae8df4ce4508147403d95
[platform/framework/web/crosswalk-tizen.git] /
1 /*jshint node:true */
2 "use strict";
3
4
5 var esprima = require('esprima');
6
7
8
9 // ---
10
11 // we expose the flags so other tools can tweak the values (#8)
12 exports.BYPASS_RECURSION = {
13     root : true,
14     comments : true,
15     tokens : true,
16
17     loc : true,
18     range : true,
19
20     parent : true,
21     next : true,
22     prev : true,
23
24     // IMPORTANT! "value" can't be bypassed since it is used by object
25     // expression
26     type : true,
27     raw : true,
28
29     startToken : true,
30     endToken : true
31 };
32
33
34 // ---
35
36 var _addLocInfo;
37
38 // ---
39
40
41 // parse string and return an augmented AST
42 exports.parse = function parse(source, opts){
43     _addLocInfo = opts && opts.loc;
44     source = source.toString();
45
46     var ast = esprima.parse(source, {
47         loc : _addLocInfo,
48         range : true,
49         tokens : true,
50         comment : true
51     });
52
53     // we augment just root node since program is "empty"
54     // can't check `ast.body.length` because it might contain just comments
55     if (!ast.tokens.length && !ast.comments.length) {
56         ast.depth = 0;
57         ast.startToken = ast.endToken = null;
58         ast.toString = _nodeProto.toString;
59         return ast;
60     }
61
62     instrumentTokens(ast, source);
63
64     // update program range since it doesn't include white spaces and comments
65     // before/after the program body by default
66     var lastToken = ast.tokens[ast.tokens.length - 1];
67     ast.range[0] = ast.tokens[0].range[0];
68     ast.range[1] = lastToken.range[1];
69     if (_addLocInfo) {
70         ast.loc.start.line = 0;
71         ast.loc.start.column = 0;
72         ast.loc.end.line = lastToken.loc.end.line;
73         ast.loc.end.column = lastToken.loc.end.column;
74     }
75
76     var toString = _nodeProto.toString;
77     var instrumentNodes = function(node, parent, prev, next){
78
79         node.parent = parent;
80         node.prev = prev;
81         node.next = next;
82         node.depth = parent? parent.depth + 1 : 0; // used later for moonwalk
83
84         node.toString = toString;
85
86         // we do not add nextToken and prevToken to avoid updating even more
87         // references during each remove/before/after you can grab the
88         // prev/next token by simply accesing the startToken.prev and
89         // endToken.next
90         var prevToken = prev? prev.endToken : (parent? parent.startToken : null);
91         var nextToken = parent? parent.endToken : null;
92         node.startToken = prevToken? getNodeStartToken(prevToken, node.range) : ast.tokens[0];
93         node.endToken = nextToken? getNodeEndToken(nextToken, node.range) : ast.tokens[ast.tokens.length - 1];
94     };
95     recursiveWalk(ast, instrumentNodes);
96
97     return ast;
98 };
99
100
101 var _nodeProto = {};
102
103 // get the node string
104 _nodeProto.toString = function(){
105     var str = '';
106     var token = this.startToken;
107     if (!token) return str;
108     do {
109         str += ('raw' in token)? token.raw : token.value;
110         token = token.next;
111     } while (token && token !== this.endToken.next);
112     return str;
113 };
114
115
116 function getNodeStartToken(token, range){
117     var startRange = range[0];
118     while (token){
119         if (token.range[0] >= startRange) {
120             return token;
121         }
122         token = token.next;
123     }
124 }
125
126 function getNodeEndToken(token, range){
127     var endRange = range[1];
128     while (token){
129         if (token.range[1] <= endRange) {
130             return token;
131         }
132         token = token.prev;
133     }
134 }
135
136
137
138 function getPrevToken(tokens, range){
139     var result, token,
140         startRange = range[0],
141         n = tokens.length;
142     while (n--) {
143         token = tokens[n];
144         if (token.range[1] <= startRange) {
145             result = token;
146             break;
147         }
148     }
149     return result;
150 }
151
152
153
154
155
156
157 function instrumentTokens(ast, source){
158
159     var tokens = ast.tokens;
160
161
162     // --- inject comments into tokens list
163     var comments = ast.comments;
164     var comment,
165         q = -1,
166         nComments = comments.length;
167
168     while (++q < nComments) {
169         comment = comments[q];
170         // we edit it in place since it is faster, will also affect
171         comment.raw = comment.type === 'Block'? '/*'+ comment.value +'*/' : '//'+ comment.value;
172         comment.type += 'Comment';
173
174         var prevToken = getPrevToken(tokens, comment.range);
175         var prevIndex = prevToken? tokens.indexOf(prevToken) : -1;
176         tokens.splice(prevIndex + 1, 0, comment);
177     }
178
179
180     // --- inject white spaces and line breaks
181
182     // we create a new array since it's simpler than using splice, it will
183     // also avoid mistakes
184     var result = [];
185
186     // insert white spaces before start of program
187     var wsTokens;
188     var firstToken = ast.tokens[0];
189     var raw;
190     if ( firstToken.range[0] ) {
191         raw = source.substring(0, firstToken.range[0]);
192         result = result.concat( getWhiteSpaceTokens(raw, null) );
193     }
194
195     // insert white spaces between regular tokens
196     // faster than forEach and reduce lookups
197     var i = -1,
198         nTokens = tokens.length,
199         token, prev;
200     var k, nWs;
201     while (++i < nTokens) {
202         token = tokens[i];
203         if (i) {
204             if (prev.range[1] < token.range[0]) {
205                 wsTokens = getWhiteSpaceTokens(source.substring(prev.range[1], token.range[0]), prev);
206                 // faster than concat or push.apply
207                 k = -1;
208                 nWs = wsTokens.length;
209                 while (++k < nWs) {
210                     result.push(wsTokens[k]);
211                 }
212             }
213         }
214         result.push(token);
215         prev = token;
216     }
217
218     // insert white spaces after end of program
219     var lastToken = ast.tokens[ast.tokens.length - 1];
220     if (lastToken.range[1] < source.length) {
221         wsTokens = getWhiteSpaceTokens(source.substring(lastToken.range[1], source.length), lastToken);
222         k = -1;
223         nWs = wsTokens.length;
224         while (++k < nWs) {
225             result.push(wsTokens[k]);
226         }
227     }
228
229     // --- instrument tokens
230
231     // need to come afterwards since we add line breaks and comments
232     var n;
233     for (i = 0, n = result.length, token; i < n; i++) {
234         token = result[i];
235         token.prev = i? result[i - 1] : undefined;
236         token.next = result[i + 1];
237         token.root = ast; // used internally
238         // original indent is very important for block comments since some
239         // transformations require manipulation of raw comment value
240         if (
241           token.type === 'BlockComment' &&
242           token.prev && token.prev.type === 'WhiteSpace' &&
243           (!token.prev.prev || (token.prev.prev.type === 'LineBreak'))
244         ) {
245           token.originalIndent = token.prev.value;
246         }
247     }
248
249     ast.tokens = result;
250 }
251
252
253 function getWhiteSpaceTokens(raw, prev){
254     var whiteSpaces = getWhiteSpaces(raw);
255
256     var startRange = prev? prev.range[1] : 0;
257     // line starts at 1 !!!
258     var startLine, startColumn;
259     if (_addLocInfo) {
260         startLine = prev? prev.loc.end.line : 1;
261         startColumn = prev? prev.loc.end.column : 0;
262     }
263
264     var tokens = [];
265     for (var i = 0, n = whiteSpaces.length, value; i < n; i++){
266         value = whiteSpaces[i];
267
268         var wsToken = { value : value };
269         var isBr = '\r\n'.indexOf(value) >= 0;
270         wsToken.type = isBr? 'LineBreak' : 'WhiteSpace';
271         wsToken.range = [startRange, startRange + value.length];
272
273         if (_addLocInfo) {
274             wsToken.loc = {
275                 start : {
276                     line : startLine,
277                     column : startColumn
278                 },
279                 end : {
280                     line : startLine, // yes, br starts and end on same line
281                     column : startColumn + value.length
282                 }
283             };
284
285             if (isBr) {
286                 // next token after a <br> always starts at zero and on next line
287                 startLine = wsToken.loc.end.line + 1;
288                 startColumn = 0;
289             } else {
290                 startLine = wsToken.loc.end.line;
291                 startColumn = wsToken.loc.end.column;
292             }
293         }
294
295         startRange += value.length;
296         tokens.push(wsToken);
297     }
298
299     return tokens;
300 }
301
302
303 function getWhiteSpaces(source) {
304     var result = [];
305     var whiteSpaces = source.split('');
306     var buf = '';
307     for (var value, i = 0, nSpaces = whiteSpaces.length; i < nSpaces; i++) {
308         value = whiteSpaces[i];
309         switch(value){
310             case '\n':
311                 if (buf === '\r') {
312                     // DOS line break
313                     result.push(buf + value);
314                 } else {
315                     if (buf) {
316                         result.push(buf);
317                     }
318                     // unix break
319                     result.push(value);
320                 }
321                 buf = '';
322                 break;
323             case '\r':
324                 // might be multiple consecutive Mac breaks
325                 if (buf) {
326                     result.push(buf);
327                 }
328                 buf = value;
329                 break;
330             default:
331                 if (buf === '\r') {
332                     result.push(buf);
333                     buf = value;
334                 } else {
335                     // group multiple white spaces into same token
336                     buf += value;
337                 }
338         }
339     }
340     if (buf) {
341         result.push(buf);
342     }
343     return result;
344 }
345
346
347
348 exports.recursive = recursiveWalk;
349
350 // heavily inspired by node-falafel
351 // walk nodes recursively starting from root
352 function recursiveWalk(node, fn, parent, prev, next){
353     // sparse arrays might have `null` elements, so we skip those for now
354     // see issue #15
355     if ( !node || fn(node, parent, prev, next) === false ) {
356         return; // stop recursion
357     }
358
359     // faster than for in
360     var keys = Object.keys(node),
361         child, key;
362
363     for (var i = 0, nKeys = keys.length; i < nKeys; i++) {
364
365         key = keys[i];
366         child = node[key];
367
368         // only need to recurse real nodes and arrays
369         // ps: typeof null == 'object'
370         if (!child || typeof child !== 'object' || exports.BYPASS_RECURSION[key]) {
371             continue;
372         }
373
374         // inception
375         if (typeof child.type === 'string') { // faster than boolean coercion
376             recursiveWalk(child, fn, node);
377         } else if ( typeof child.length === 'number' ) { // faster than Array.isArray and boolean coercion
378             // faster than forEach
379             for (var k = 0, nChilds = child.length; k < nChilds; k++) {
380                 recursiveWalk(child[k], fn, node, (k? child[k - 1] : undefined), child[k + 1] );
381             }
382         }
383
384     }
385
386 }
387
388
389
390 // walk AST starting from leaf nodes
391 exports.moonwalk = function moonwalk(ast, fn){
392     if (typeof ast === 'string') {
393         ast = exports.parse(ast);
394     }
395
396     // we create a separate array for each depth and than we flatten it to
397     // boost performance, way faster than doing an insertion sort
398     var swap = [];
399     recursiveWalk(ast, function(node){
400         if (! swap[node.depth]) {
401             swap[node.depth] = [];
402         }
403         swap[node.depth].push(node);
404     });
405
406     var nodes = [];
407     var nDepths = swap.length, cur;
408     while (cur = swap[--nDepths]) {
409         for (var i = 0, n = cur.length; i < n; i++) {
410             nodes.push(cur[i]);
411         }
412     }
413
414     nodes.forEach(fn);
415     return ast;
416 };
417