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.
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');
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;
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 = {};
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;
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
56 // remove shebang before pipe because piped commands might not know how
59 if (_options.get('esformatter.allowShebang')) {
60 prefix = getShebang(str);
62 str = str.replace(prefix, '');
66 var pipeCommands = _options.get('pipe');
69 str = pipe.run(pipeCommands.before, str).toString();
72 str = doFormat(str, opts);
75 str = pipe.run(pipeCommands.after, str).toString();
78 // we only restore bang after pipe because piped commands might not know how
84 function getShebang(str) {
85 var result = (/^#!.+\n/).exec(str);
86 return result ? result[0] : '';
90 function doFormat(str) {
91 str = plugins.stringBefore(str);
92 var ast = rocambole.parse(str);
93 transform(ast, BYPASS_OPTIONS);
95 str = plugins.stringAfter(str);
100 function transform(ast, opts) {
101 if (opts !== BYPASS_OPTIONS) {
104 _shouldRemoveTrailingWs = Boolean(_options.get('whiteSpace.removeTrailing'));
106 plugins.transformBefore(ast);
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);
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);
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);
131 function transformNode(node) {
132 plugins.nodeBefore(node);
133 addBrAroundNode(node);
135 var hook = hooks[node.type];
136 if (hook && 'format' in hook) {
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);
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);
155 function preprocessToken(token) {
156 if (_tk.isComment(token)) {
157 _br.limit(token, token.type);
159 plugins.tokenBefore(token);
163 function postprocessToken(token) {
164 if (_tk.isComment(token)) {
165 processComment(token);
166 } else if (_shouldRemoveTrailingWs && _tk.isWs(token)) {
167 removeTrailingWs(token);
169 plugins.tokenAfter(token);
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);
182 function removeTrailingWs(token) {
183 if (_tk.isBr(token.next) || !token.next) {