a7ed3994bf2bdcac6bd0ef6ce9a2ff4791853fe8
[platform/framework/web/crosswalk-tizen.git] /
1 'use strict';
2
3
4 // non-destructive changes to EcmaScript code using an "enhanced" AST for the
5 // process, it updates the tokens in place and add/remove spaces & line breaks
6 // based on user settings.
7 // not using any kind of code rewrite based on string concatenation to avoid
8 // breaking the program correctness and/or undesired side-effects.
9
10
11 var _br = require('rocambole-linebreak');
12 var _options = require('./options');
13 var _tk = require('rocambole-token');
14 var _ws = require('rocambole-whitespace');
15 var addBrAroundNode = require('./lineBreakAroundNode');
16 var expressionParentheses = require('./hooks/expressionParentheses');
17 var hooks = require('./hooks');
18 var indent = require('./indent');
19 var plugins = require('./plugins');
20 var rocambole = require('rocambole');
21 var pipe = require('./pipe');
22
23 // esprima@2.1 introduces a "handler" property on TryStatement, so we would
24 // loop the same node twice (see jquery/esprima/issues/1031 and #264)
25 rocambole.BYPASS_RECURSION.handler = true;
26
27 // ---
28
29
30 var _shouldRemoveTrailingWs;
31 // used to make sure we don't call setOptions twice when executing `transform`
32 // from inside `format`
33 var BYPASS_OPTIONS = {};
34
35
36 // ---
37
38
39 exports.hooks = hooks;
40 exports.format = format;
41 exports.transform = transform;
42 exports.rc = _options.getRc;
43 exports.register = plugins.register;
44 exports.unregister = plugins.unregister;
45 exports.unregisterAll = plugins.unregisterAll;
46
47
48 // ---
49
50
51 function format(str, opts) {
52   // we need to load and register the plugins as soon as possible otherwise
53   // `stringBefore` won't be called and default settings won't be used
54   _options.set(opts);
55
56   // remove shebang before pipe because piped commands might not know how
57   // to handle it
58   var prefix = '';
59   if (_options.get('esformatter.allowShebang')) {
60     prefix = getShebang(str);
61     if (prefix) {
62       str = str.replace(prefix, '');
63     }
64   }
65
66   var pipeCommands = _options.get('pipe');
67
68   if (pipeCommands) {
69     str = pipe.run(pipeCommands.before, str).toString();
70   }
71
72   str = doFormat(str, opts);
73
74   if (pipeCommands) {
75     str = pipe.run(pipeCommands.after, str).toString();
76   }
77
78   // we only restore bang after pipe because piped commands might not know how
79   // to handle it
80   return prefix + str;
81 }
82
83
84 function getShebang(str) {
85   var result = (/^#!.+\n/).exec(str);
86   return result ? result[0] : '';
87 }
88
89
90 function doFormat(str) {
91   str = plugins.stringBefore(str);
92   var ast = rocambole.parse(str);
93   transform(ast, BYPASS_OPTIONS);
94   str = ast.toString();
95   str = plugins.stringAfter(str);
96   return str;
97 }
98
99
100 function transform(ast, opts) {
101   if (opts !== BYPASS_OPTIONS) {
102     _options.set(opts);
103   }
104   _shouldRemoveTrailingWs = Boolean(_options.get('whiteSpace.removeTrailing'));
105
106   plugins.transformBefore(ast);
107
108   _tk.eachInBetween(ast.startToken, ast.endToken, preprocessToken);
109   rocambole.moonwalk(ast, transformNode);
110   _tk.eachInBetween(ast.startToken, ast.endToken, postprocessToken);
111   _br.limitBeforeEndOfFile(ast);
112
113   // indent should come after all other transformations since it depends on
114   // line breaks caused by "parent" nodes, otherwise it will cause conflicts.
115   // it should also happen after the postprocessToken since it adds line breaks
116   // before/after comments and that changes the indent logic
117   indent.transform(ast);
118
119   // plugin transformation comes after the indentation since we assume user
120   // knows what he is doing (will increase flexibility and allow plugin to
121   // override the indentation logic)
122   // we have an alias "transform" to match v0.3 API, but favor `transformAfter`
123   // moving forward. (we might deprecate "transform" in the future)
124   plugins.transform(ast);
125   plugins.transformAfter(ast);
126
127   return ast;
128 }
129
130
131 function transformNode(node) {
132   plugins.nodeBefore(node);
133   addBrAroundNode(node);
134
135   var hook = hooks[node.type];
136   if (hook && 'format' in hook) {
137     hook.format(node);
138   }
139
140   // empty program doesn't have startToken or endToken
141   if (node.startToken) {
142     // automatic white space comes afterwards since line breaks introduced by
143     // the hooks affects it
144     _ws.limitBefore(node.startToken, node.type);
145     _ws.limitAfter(node.endToken, node.type);
146   }
147
148   // handle parenthesis automatically since it is needed by multiple node types
149   // and it avoids code duplication and reduces complexity of each hook
150   expressionParentheses.addSpaceInside(node);
151   plugins.nodeAfter(node);
152 }
153
154
155 function preprocessToken(token) {
156   if (_tk.isComment(token)) {
157     _br.limit(token, token.type);
158   }
159   plugins.tokenBefore(token);
160 }
161
162
163 function postprocessToken(token) {
164   if (_tk.isComment(token)) {
165     processComment(token);
166   } else if (_shouldRemoveTrailingWs && _tk.isWs(token)) {
167     removeTrailingWs(token);
168   }
169   plugins.tokenAfter(token);
170 }
171
172
173 function processComment(token) {
174   _ws.limitBefore(token, token.type);
175   // only block comment needs space afterwards
176   if (token.type === 'BlockComment') {
177     _ws.limitAfter(token, token.type);
178   }
179 }
180
181
182 function removeTrailingWs(token) {
183   if (_tk.isBr(token.next) || !token.next) {
184     _tk.remove(token);
185   }
186 }