1 // ==========================================================================
  2 // Project:   Greenhouse
  3 // ==========================================================================
  4 /*globals Greenhouse */
  5 /*
  6 
  7  JS Beautifier
  8 ---------------
  9 
 10 
 11   Written by Einar Lielmanis, <einar@jsbeautifier.org>
 12       http://jsbeautifier.org/
 13 
 14   Originally converted to javascript by Vital, <vital76@gmail.com>
 15 
 16   You are free to use this in any way you want, in case you find this useful or working for you.
 17 
 18   Usage:
 19     js_beautify(js_source_text);
 20     js_beautify(js_source_text, options);
 21 
 22   The options are:
 23     indent_size (default 4)          — indentation size,
 24     indent_char (default space)      — character to indent with,
 25     preserve_newlines (default true) — whether existing line breaks should be preserved,
 26     indent_level (default 0)         — initial indentation level, you probably won't need this ever,
 27 
 28     space_after_anon_function (default false) — if true, then space is added between "function ()"
 29             (jslint is happy about this); if false, then the common "function()" output is used.
 30     braces_on_own_line (default false) - ANSI / Allman brace style, each opening/closing brace gets its own line.
 31 
 32     e.g
 33 
 34     js_beautify(js_source_text, {indent_size: 1, indent_char: '\t'});
 35 
 36     
 37     
 38     Copyright (c) 2009 Einar Lielmanis
 39 
 40     Permission is hereby granted, free of charge, to any person
 41     obtaining a copy of this software and associated documentation
 42     files (the "Software"), to deal in the Software without
 43     restriction, including without limitation the rights to use,
 44     copy, modify, merge, publish, distribute, sublicense, and/or sell
 45     copies of the Software, and to permit persons to whom the
 46     Software is furnished to do so, subject to the following
 47     conditions:
 48 
 49     The above copyright notice and this permission notice shall be
 50     included in all copies or substantial portions of the Software.
 51 
 52     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 53     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 54     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 55     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 56     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 57     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 58     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 59     OTHER DEALINGS IN THE SOFTWARE.
 60 */
 61 function js_beautify(js_source_text, options) {
 62 
 63     var input, output, token_text, last_type, last_text, last_last_text, last_word, flags, flag_store, indent_string;
 64     var whitespace, wordchar, punct, parser_pos, line_starters, digits;
 65     var prefix, token_type, do_block_just_closed;
 66     var wanted_newline, just_added_newline, n_newlines;
 67 
 68 
 69     // Some interpreters have unexpected results with foo = baz || bar;
 70     options = options ? options : {};
 71     var opt_braces_on_own_line = options.braces_on_own_line ? options.braces_on_own_line : false;
 72     var opt_indent_size = options.indent_size ? options.indent_size : 2;
 73     var opt_indent_char = options.indent_char ? options.indent_char : ' ';
 74     var opt_preserve_newlines = typeof options.preserve_newlines === 'undefined' ? true : options.preserve_newlines;
 75     var opt_indent_level = options.indent_level ? options.indent_level : 0; // starting indentation
 76     var opt_space_after_anon_function = options.space_after_anon_function === 'undefined' ? false : options.space_after_anon_function;
 77     var opt_keep_array_indentation = typeof options.keep_array_indentation === 'undefined' ? true : options.keep_array_indentation;
 78 
 79     just_added_newline = false;
 80 
 81     // cache the source's length.
 82     var input_length = js_source_text.length;
 83 
 84     function trim_output() {
 85         while (output.length && (output[output.length - 1] === ' ' || output[output.length - 1] === indent_string)) {
 86             output.pop();
 87         }
 88     }
 89 
 90     function print_newline(ignore_repeated) {
 91 
 92         flags.eat_next_space = false;
 93         if (opt_keep_array_indentation && is_array(flags.mode)) {
 94             return;
 95         }
 96 
 97         ignore_repeated = typeof ignore_repeated === 'undefined' ? true : ignore_repeated;
 98 
 99         flags.if_line = false;
100         trim_output();
101 
102         if (!output.length) {
103             return; // no newline on start of file
104         }
105 
106         if (output[output.length - 1] !== "\n" || !ignore_repeated) {
107             just_added_newline = true;
108             output.push("\n");
109         }
110         for (var i = 0; i < flags.indentation_level; i += 1) {
111             output.push(indent_string);
112         }
113     }
114 
115 
116 
117     function print_single_space() {
118         if (flags.eat_next_space) {
119             flags.eat_next_space = false;
120             return;
121         }
122         var last_output = ' ';
123         if (output.length) {
124             last_output = output[output.length - 1];
125         }
126         if (last_output !== ' ' && last_output !== '\n' && last_output !== indent_string) { // prevent occassional duplicate space
127             output.push(' ');
128         }
129     }
130 
131 
132     function print_token() {
133         just_added_newline = false;
134         flags.eat_next_space = false;
135         output.push(token_text);
136     }
137 
138     function indent() {
139         flags.indentation_level += 1;
140     }
141 
142 
143     function remove_indent() {
144         if (output.length && output[output.length - 1] === indent_string) {
145             output.pop();
146         }
147     }
148 
149     function set_mode(mode) {
150         if (flags) {
151             flag_store.push(flags);
152         }
153         flags = {
154             mode: mode,
155             var_line: false,
156             var_line_tainted: false,
157             var_line_reindented: false,
158             in_html_comment: false,
159             if_line: false,
160             in_case: false,
161             eat_next_space: false,
162             indentation_baseline: -1,
163             indentation_level: (flags ? flags.indentation_level + (flags.var_line_reindented ? 1 : 0) : opt_indent_level)
164         };
165     }
166 
167     function is_expression(mode) {
168         return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]' || mode === '(EXPRESSION)';
169     }
170 
171     function is_array(mode) {
172         return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]';
173     }
174 
175     function restore_mode() {
176         do_block_just_closed = flags.mode === 'DO_BLOCK';
177         if (flag_store.length > 0) {
178             flags = flag_store.pop();
179         }
180     }
181 
182 
183     function in_array(what, arr) {
184         for (var i = 0; i < arr.length; i += 1) {
185             if (arr[i] === what) {
186                 return true;
187             }
188         }
189         return false;
190     }
191 
192     // Walk backwards from the colon to find a '?' (colon is part of a ternary op)
193     // or a '{' (colon is part of a class literal).  Along the way, keep track of
194     // the blocks and expressions we pass so we only trigger on those chars in our
195     // own level, and keep track of the colons so we only trigger on the matching '?'.
196 
197 
198     function is_ternary_op() {
199         var level = 0,
200             colon_count = 0;
201         for (var i = output.length - 1; i >= 0; i--) {
202             switch (output[i]) {
203             case ':':
204                 if (level === 0) {
205                     colon_count++;
206                 }
207                 break;
208             case '?':
209                 if (level === 0) {
210                     if (colon_count === 0) {
211                         return true;
212                     } else {
213                         colon_count--;
214                     }
215                 }
216                 break;
217             case '{':
218                 if (level === 0) {
219                     return false;
220                 }
221                 level--;
222                 break;
223             case '(':
224             case '[':
225                 level--;
226                 break;
227             case ')':
228             case ']':
229             case '}':
230                 level++;
231                 break;
232             }
233         }
234     }
235 
236     function get_next_token() {
237         n_newlines = 0;
238 
239         if (parser_pos >= input_length) {
240             return ['', 'TK_EOF'];
241         }
242 
243         wanted_newline = false;
244 
245         var c = input.charAt(parser_pos);
246         parser_pos += 1;
247 
248 
249         var keep_whitespace = opt_keep_array_indentation && is_array(flags.mode);
250 
251         if (keep_whitespace) {
252 
253             //
254             // slight mess to allow nice preservation of array indentation and reindent that correctly
255             // first time when we get to the arrays:
256             // var a = [
257             // ....'something'
258             // we make note of whitespace_count = 4 into flags.indentation_baseline
259             // so we know that 4 whitespaces in original source match indent_level of reindented source
260             //
261             // and afterwards, when we get to
262             //    'something,
263             // .......'something else'
264             // we know that this should be indented to indent_level + (7 - indentation_baseline) spaces
265             //
266             var whitespace_count = 0;
267 
268             while (in_array(c, whitespace)) {
269 
270                 if (c === "\n") {
271                     trim_output();
272                     output.push("\n");
273                     just_added_newline = true;
274                     whitespace_count = 0;
275                 } else {
276                     if (c === '\t') {
277                         whitespace_count += 4;
278                     } else {
279                         whitespace_count += 1;
280                     }
281                 }
282 
283                 if (parser_pos >= input_length) {
284                     return ['', 'TK_EOF'];
285                 }
286 
287                 c = input.charAt(parser_pos);
288                 parser_pos += 1;
289 
290             }
291             if (flags.indentation_baseline === -1) {
292                 flags.indentation_baseline = whitespace_count;
293             }
294 
295             if (just_added_newline) {
296                 for (var i = 0; i < flags.indentation_level + 1; i += 1) {
297                     output.push(indent_string);
298                 }
299                 if (flags.indentation_baseline !== -1) {
300                     for (var i = 0; i < whitespace_count - flags.indentation_baseline; i++) {
301                         output.push(' ');
302                     }
303                 }
304             }
305 
306         } else {
307             while (in_array(c, whitespace)) {
308 
309                 if (c === "\n") {
310                     n_newlines += 1;
311                 }
312 
313 
314                 if (parser_pos >= input_length) {
315                     return ['', 'TK_EOF'];
316                 }
317 
318                 c = input.charAt(parser_pos);
319                 parser_pos += 1;
320 
321             }
322 
323             if (opt_preserve_newlines) {
324                 if (n_newlines > 1) {
325                     for (var i = 0; i < n_newlines; i += 1) {
326                         print_newline(i === 0);
327                         just_added_newline = true;
328                     }
329                 }
330             }
331             wanted_newline = n_newlines > 0;
332         }
333 
334 
335         if (in_array(c, wordchar)) {
336             if (parser_pos < input_length) {
337                 while (in_array(input.charAt(parser_pos), wordchar)) {
338                     c += input.charAt(parser_pos);
339                     parser_pos += 1;
340                     if (parser_pos === input_length) {
341                         break;
342                     }
343                 }
344             }
345 
346             // small and surprisingly unugly hack for 1E-10 representation
347             if (parser_pos !== input_length && c.match(/^[0-9]+[Ee]$/) && (input.charAt(parser_pos) === '-' || input.charAt(parser_pos) === '+')) {
348 
349                 var sign = input.charAt(parser_pos);
350                 parser_pos += 1;
351 
352                 var t = get_next_token(parser_pos);
353                 c += sign + t[0];
354                 return [c, 'TK_WORD'];
355             }
356 
357             if (c === 'in') { // hack for 'in' operator
358                 return [c, 'TK_OPERATOR'];
359             }
360             if (wanted_newline && last_type !== 'TK_OPERATOR' && !flags.if_line && (opt_preserve_newlines || last_text !== 'var')) {
361                 print_newline();
362             }
363             return [c, 'TK_WORD'];
364         }
365 
366         if (c === '(' || c === '[') {
367             return [c, 'TK_START_EXPR'];
368         }
369 
370         if (c === ')' || c === ']') {
371             return [c, 'TK_END_EXPR'];
372         }
373 
374         if (c === '{') {
375             return [c, 'TK_START_BLOCK'];
376         }
377 
378         if (c === '}') {
379             return [c, 'TK_END_BLOCK'];
380         }
381 
382         if (c === ';') {
383             return [c, 'TK_SEMICOLON'];
384         }
385 
386         if (c === '/') {
387             var comment = '';
388             // peek for comment /* ... */
389             var inline_comment = true;
390             if (input.charAt(parser_pos) === '*') {
391                 parser_pos += 1;
392                 if (parser_pos < input_length) {
393                     while (! (input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/') && parser_pos < input_length) {
394                         c = input.charAt(parser_pos);
395                         comment += c;
396                         if (c === '\x0d' || c === '\x0a') {
397                             inline_comment = false;
398                         }
399                         parser_pos += 1;
400                         if (parser_pos >= input_length) {
401                             break;
402                         }
403                     }
404                 }
405                 parser_pos += 2;
406                 if (inline_comment) {
407                     return ['/*' + comment + '*/', 'TK_INLINE_COMMENT'];
408                 } else {
409                     return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT'];
410                 }
411             }
412             // peek for comment // ...
413             if (input.charAt(parser_pos) === '/') {
414                 comment = c;
415                 while (input.charAt(parser_pos) !== "\x0d" && input.charAt(parser_pos) !== "\x0a") {
416                     comment += input.charAt(parser_pos);
417                     parser_pos += 1;
418                     if (parser_pos >= input_length) {
419                         break;
420                     }
421                 }
422                 parser_pos += 1;
423                 if (wanted_newline) {
424                     print_newline();
425                 }
426                 return [comment, 'TK_COMMENT'];
427             }
428 
429         }
430 
431         if (c === "'" || // string
432         c === '"' || // string
433         (c === '/' && ((last_type === 'TK_WORD' && in_array(last_text, ['return', 'do'])) || (last_type === 'TK_START_EXPR' || last_type === 'TK_START_BLOCK' || last_type === 'TK_END_BLOCK' || last_type === 'TK_OPERATOR' || last_type === 'TK_EQUALS' || last_type === 'TK_EOF' || last_type === 'TK_SEMICOLON')))) { // regexp
434             var sep = c;
435             var esc = false;
436             var resulting_string = c;
437 
438             if (parser_pos < input_length) {
439                 if (sep === '/') {
440                     //
441                     // handle regexp separately...
442                     //
443                     var in_char_class = false;
444                     while (esc || in_char_class || input.charAt(parser_pos) !== sep) {
445                         resulting_string += input.charAt(parser_pos);
446                         if (!esc) {
447                             esc = input.charAt(parser_pos) === '\\';
448                             if (input.charAt(parser_pos) === '[') {
449                                 in_char_class = true;
450                             } else if (input.charAt(parser_pos) === ']') {
451                                 in_char_class = false;
452                             }
453                         } else {
454                             esc = false;
455                         }
456                         parser_pos += 1;
457                         if (parser_pos >= input_length) {
458                             // incomplete string/rexp when end-of-file reached. 
459                             // bail out with what had been received so far.
460                             return [resulting_string, 'TK_STRING'];
461                         }
462                     }
463 
464                 } else {
465                     //
466                     // and handle string also separately
467                     //
468                     while (esc || input.charAt(parser_pos) !== sep) {
469                         resulting_string += input.charAt(parser_pos);
470                         if (!esc) {
471                             esc = input.charAt(parser_pos) === '\\';
472                         } else {
473                             esc = false;
474                         }
475                         parser_pos += 1;
476                         if (parser_pos >= input_length) {
477                             // incomplete string/rexp when end-of-file reached. 
478                             // bail out with what had been received so far.
479                             return [resulting_string, 'TK_STRING'];
480                         }
481                     }
482                 }
483 
484 
485 
486             }
487 
488             parser_pos += 1;
489 
490             resulting_string += sep;
491 
492             if (sep === '/') {
493                 // regexps may have modifiers /regexp/MOD , so fetch those, too
494                 while (parser_pos < input_length && in_array(input.charAt(parser_pos), wordchar)) {
495                     resulting_string += input.charAt(parser_pos);
496                     parser_pos += 1;
497                 }
498             }
499             return [resulting_string, 'TK_STRING'];
500         }
501 
502         if (c === '#') {
503             // Spidermonkey-specific sharp variables for circular references
504             // https://developer.mozilla.org/En/Sharp_variables_in_JavaScript
505             // http://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935
506             var sharp = '#';
507             if (parser_pos < input_length && in_array(input.charAt(parser_pos), digits)) {
508                 do {
509                     c = input.charAt(parser_pos);
510                     sharp += c;
511                     parser_pos += 1;
512                 } while (parser_pos < input_length && c !== '#' && c !== '=');
513                 if (c === '#') {
514                     // 
515                 } else if (input.charAt(parser_pos) == '[' && input.charAt(parser_pos + 1) === ']') {
516                     sharp += '[]';
517                     parser_pos += 2;
518                 } else if (input.charAt(parser_pos) == '{' && input.charAt(parser_pos + 1) === '}') {
519                     sharp += '{}';
520                     parser_pos += 2;
521                 }
522                 return [sharp, 'TK_WORD'];
523             }
524         }
525 
526         if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '<!--') {
527             parser_pos += 3;
528             flags.in_html_comment = true;
529             return ['<!--', 'TK_COMMENT'];
530         }
531 
532         if (c === '-' && flags.in_html_comment && input.substring(parser_pos - 1, parser_pos + 2) === '-->') {
533             flags.in_html_comment = false;
534             parser_pos += 2;
535             if (wanted_newline) {
536                 print_newline();
537             }
538             return ['-->', 'TK_COMMENT'];
539         }
540 
541         if (in_array(c, punct)) {
542             while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) {
543                 c += input.charAt(parser_pos);
544                 parser_pos += 1;
545                 if (parser_pos >= input_length) {
546                     break;
547                 }
548             }
549 
550             if (c === '=') {
551                 return [c, 'TK_EQUALS'];
552             } else {
553                 return [c, 'TK_OPERATOR'];
554             }
555         }
556 
557         return [c, 'TK_UNKNOWN'];
558     }
559 
560     //----------------------------------
561     indent_string = '';
562     while (opt_indent_size > 0) {
563         indent_string += opt_indent_char;
564         opt_indent_size -= 1;
565     }
566 
567     input = js_source_text;
568 
569     last_word = ''; // last 'TK_WORD' passed
570     last_type = 'TK_START_EXPR'; // last token type
571     last_text = ''; // last token text
572     last_last_text = ''; // pre-last token text
573     output = [];
574 
575     do_block_just_closed = false;
576 
577     whitespace = "\n\r\t ".split('');
578     wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');
579     digits = '0123456789'.split('');
580 
581     punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |= ::'.split(' ');
582 
583     // words which should always start on new line.
584     line_starters = 'continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(',');
585 
586     // states showing if we are currently in expression (i.e. "if" case) - 'EXPRESSION', or in usual block (like, procedure), 'BLOCK'.
587     // some formatting depends on that.
588     flag_store = [];
589     set_mode('BLOCK');
590 
591     parser_pos = 0;
592     while (true) {
593         var t = get_next_token(parser_pos);
594         token_text = t[0];
595         token_type = t[1];
596         if (token_type === 'TK_EOF') {
597             break;
598         }
599 
600         switch (token_type) {
601 
602         case 'TK_START_EXPR':
603 
604             if (token_text === '[') {
605 
606                 if (last_type === 'TK_WORD' || last_text === ')') {
607                     // this is array index specifier, break immediately
608                     // a[x], fn()[x]
609                     if (in_array(last_text, line_starters)) {
610                         print_single_space();
611                     }
612                     set_mode('(EXPRESSION)');
613                     print_token();
614                     break;
615                 }
616 
617                 if (flags.mode === '[EXPRESSION]' || flags.mode === '[INDENTED-EXPRESSION]') {
618                     if (last_last_text === ']' && last_text === ',') {
619                         // ], [ goes to new line
620                         if (flags.mode === '[EXPRESSION]') {
621                             flags.mode = '[INDENTED-EXPRESSION]';
622                             if (!opt_keep_array_indentation) {
623                                 indent();
624                             }
625                         }
626                         set_mode('[EXPRESSION]');
627                         if (!opt_keep_array_indentation) {
628                             print_newline();
629                         }
630                     } else if (last_text === '[') {
631                         if (flags.mode === '[EXPRESSION]') {
632                             flags.mode = '[INDENTED-EXPRESSION]';
633                             if (!opt_keep_array_indentation) {
634                                 indent();
635                             }
636                         }
637                         set_mode('[EXPRESSION]');
638 
639                         if (!opt_keep_array_indentation) {
640                             print_newline();
641                         }
642                     } else {
643                         set_mode('[EXPRESSION]');
644                     }
645                 } else {
646                     set_mode('[EXPRESSION]');
647                 }
648 
649 
650 
651             } else {
652                 set_mode('(EXPRESSION)');
653             }
654 
655             if (last_text === ';' || last_type === 'TK_START_BLOCK') {
656                 print_newline();
657             } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK') {
658                 // do nothing on (( and )( and ][ and ]( ..
659             } else if (last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
660                 print_single_space();
661             } else if (last_word === 'function') {
662                 // function() vs function ()
663                 if (opt_space_after_anon_function) {
664                     print_single_space();
665                 }
666             } else if (in_array(last_text, line_starters) || last_text === 'catch') {
667                 print_single_space();
668             }
669             print_token();
670 
671             break;
672 
673         case 'TK_END_EXPR':
674             if (token_text === ']') {
675                 if (opt_keep_array_indentation) {
676                     if (last_text === '}') {
677                         // trim_output();
678                         // print_newline(true);
679                         remove_indent();
680                         print_token();
681                         restore_mode();
682                         break;
683                     }
684                 } else {
685                     if (flags.mode === '[INDENTED-EXPRESSION]') {
686                         if (last_text === ']') {
687                             restore_mode();
688                             print_newline();
689                             print_token();
690                             break;
691                         }
692                     }
693                 }
694             }
695             restore_mode();
696             print_token();
697             break;
698 
699         case 'TK_START_BLOCK':
700 
701             if (last_word === 'do') {
702                 set_mode('DO_BLOCK');
703             } else {
704                 set_mode('BLOCK');
705             }
706             if (opt_braces_on_own_line) {
707                 if (last_type !== 'TK_OPERATOR') {
708                     print_newline(true);
709                 }
710                 print_token();
711                 indent();
712             } else {
713                 if (last_type !== 'TK_OPERATOR' && last_type !== 'TK_START_EXPR') {
714                     if (last_type === 'TK_START_BLOCK') {
715                         print_newline();
716                     } else {
717                         print_single_space();
718                     }
719                 }
720                 indent();
721                 print_token();
722             }
723 
724             break;
725 
726         case 'TK_END_BLOCK':
727             restore_mode();
728             if (opt_braces_on_own_line) {
729                 print_newline();
730                 if (flags.var_line_reindented) {
731                     output.push(indent_string);
732                 }
733                 print_token();
734             } else {
735                 if (last_type === 'TK_START_BLOCK') {
736                     // nothing
737                     if (just_added_newline) {
738                         remove_indent();
739                     } else {
740                         // {}
741                         trim_output();
742                     }
743                 } else {
744                     print_newline();
745                     if (flags.var_line_reindented) {
746                         output.push(indent_string);
747                     }
748                 }
749                 print_token();
750             }
751             break;
752 
753         case 'TK_WORD':
754 
755             // no, it's not you. even I have problems understanding how this works
756             // and what does what.
757             if (do_block_just_closed) {
758                 // do {} ## while ()
759                 print_single_space();
760                 print_token();
761                 print_single_space();
762                 do_block_just_closed = false;
763                 break;
764             }
765 
766             if (token_text === 'function') {
767                 if ((just_added_newline || last_text == ';') && last_text !== '{') {
768                     // make sure there is a nice clean space of at least one blank line
769                     // before a new function definition
770                     n_newlines = just_added_newline ? n_newlines : 0;
771 
772                     for (var i = 0; i < 2 - n_newlines; i++) {
773                         print_newline(false);
774                     }
775 
776                 }
777             }
778             if (token_text === 'case' || token_text === 'default') {
779                 if (last_text === ':') {
780                     // switch cases following one another
781                     remove_indent();
782                 } else {
783                     // case statement starts in the same line where switch
784                     flags.indentation_level--;
785                     print_newline();
786                     flags.indentation_level++;
787                 }
788                 print_token();
789                 flags.in_case = true;
790                 break;
791             }
792 
793             prefix = 'NONE';
794 
795             if (last_type === 'TK_END_BLOCK') {
796                 if (!in_array(token_text.toLowerCase(), ['else', 'catch', 'finally'])) {
797                     prefix = 'NEWLINE';
798                 } else {
799                     if (opt_braces_on_own_line) {
800                         prefix = 'NEWLINE';
801                     } else {
802                         prefix = 'SPACE';
803                         print_single_space();
804                     }
805                 }
806             } else if (last_type === 'TK_SEMICOLON' && (flags.mode === 'BLOCK' || flags.mode === 'DO_BLOCK')) {
807                 prefix = 'NEWLINE';
808             } else if (last_type === 'TK_SEMICOLON' && is_expression(flags.mode)) {
809                 prefix = 'SPACE';
810             } else if (last_type === 'TK_STRING') {
811                 prefix = 'NEWLINE';
812             } else if (last_type === 'TK_WORD') {
813                 prefix = 'SPACE';
814             } else if (last_type === 'TK_START_BLOCK') {
815                 prefix = 'NEWLINE';
816             } else if (last_type === 'TK_END_EXPR') {
817                 print_single_space();
818                 prefix = 'NEWLINE';
819             }
820 
821             if (last_type !== 'TK_END_BLOCK' && in_array(token_text.toLowerCase(), ['else', 'catch', 'finally'])) {
822                 print_newline();
823             } else if (in_array(token_text, line_starters) || prefix === 'NEWLINE') {
824                 if (last_text === 'else') {
825                     // no need to force newline on else break
826                     print_single_space();
827                 } else if ((last_type === 'TK_START_EXPR' || last_text === '=' || last_text === ',') && token_text === 'function') {
828                     // no need to force newline on 'function': (function
829                     // DONOTHING
830                 } else if (last_text === 'return' || last_text === 'throw') {
831                     // no newline between 'return nnn'
832                     print_single_space();
833                 } else if (last_type !== 'TK_END_EXPR') {
834                     if ((last_type !== 'TK_START_EXPR' || token_text !== 'var') && last_text !== ':') {
835                         // no need to force newline on 'var': for (var x = 0...)
836                         if (token_text === 'if' && last_word === 'else' && last_text !== '{') {
837                             // no newline for } else if {
838                             print_single_space();
839                         } else {
840                             print_newline();
841                         }
842                     }
843                 } else {
844                     if (in_array(token_text, line_starters) && last_text !== ')') {
845                         print_newline();
846                     }
847                 }
848             } else if (prefix === 'SPACE') {
849                 print_single_space();
850             }
851             print_token();
852             last_word = token_text;
853 
854             if (token_text === 'var') {
855                 flags.var_line = true;
856                 flags.var_line_tainted = false;
857             }
858 
859             if (token_text === 'if' || token_text === 'else') {
860                 flags.if_line = true;
861             }
862 
863             break;
864 
865         case 'TK_SEMICOLON':
866 
867             print_token();
868             flags.var_line = false;
869             break;
870 
871         case 'TK_STRING':
872 
873             if (last_type === 'TK_START_BLOCK' || last_type === 'TK_END_BLOCK' || last_type === 'TK_SEMICOLON') {
874                 print_newline();
875             } else if (last_type === 'TK_WORD') {
876                 print_single_space();
877             }
878             print_token();
879             break;
880 
881         case 'TK_EQUALS':
882             if (flags.var_line) {
883                 // just got an '=' in a var-line, different formatting/line-breaking, etc will now be done
884                 flags.var_line_tainted = true;
885             }
886             print_single_space();
887             print_token();
888             print_single_space();
889             break;
890 
891         case 'TK_OPERATOR':
892 
893             var space_before = true;
894             var space_after = true;
895 
896             if (flags.var_line && token_text === ',' && (is_expression(flags.mode))) {
897                 // do not break on comma, for(var a = 1, b = 2)
898                 flags.var_line_tainted = false;
899             }
900 
901             if (flags.var_line) {
902                 if (token_text === ',') {
903                     if (flags.var_line_tainted) {
904                         print_token();
905                         print_newline();
906                         output.push(indent_string);
907                         flags.var_line_reindented = true;
908                         flags.var_line_tainted = false;
909                         break;
910                     } else {
911                         flags.var_line_tainted = false;
912                     }
913                 // } else if (token_text === ':') {
914                     // hmm, when does this happen? tests don't catch this
915                     // flags.var_line = false;
916                 }
917             }
918 
919             if (last_text === 'return' || last_text === 'throw') {
920                 // "return" had a special handling in TK_WORD. Now we need to return the favor
921                 print_single_space();
922                 print_token();
923                 break;
924             }
925 
926             if (token_text === ':' && flags.in_case) {
927                 print_token(); // colon really asks for separate treatment
928                 print_newline();
929                 flags.in_case = false;
930                 break;
931             }
932 
933             if (token_text === '::') {
934                 // no spaces around exotic namespacing syntax operator
935                 print_token();
936                 break;
937             }
938 
939             if (token_text === ',') {
940                 if (flags.var_line) {
941                     if (flags.var_line_tainted) {
942                         print_token();
943                         print_newline();
944                         flags.var_line_tainted = false;
945                     } else {
946                         print_token();
947                         print_single_space();
948                     }
949                 } else if (last_type === 'TK_END_BLOCK' && flags.mode !== "(EXPRESSION)") {
950                     print_token();
951                     print_newline();
952                 } else {
953                     if (flags.mode === 'BLOCK') {
954                         print_token();
955                         print_newline();
956                     } else {
957                         // EXPR or DO_BLOCK
958                         print_token();
959                         print_single_space();
960                     }
961                 }
962                 break;
963             // } else if (in_array(token_text, ['--', '++', '!']) || (in_array(token_text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS']) || in_array(last_text, line_starters) || in_array(last_text, ['==', '!=', '+=', '-=', '*=', '/=', '+', '-'])))) { 
964             } else if (in_array(token_text, ['--', '++', '!']) || (in_array(token_text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || in_array(last_text, line_starters)))) { 
965                 // unary operators (and binary +/- pretending to be unary) special cases
966 
967                 space_before = false;
968                 space_after = false;
969 
970                 if (last_text === ';' && is_expression(flags.mode)) {
971                     // for (;; ++i)
972                     //        ^^^
973                     space_before = true;
974                 }
975                 if (last_type === 'TK_WORD' && in_array(last_text, line_starters)) {
976                     space_before = true;
977                 }
978 
979                 if (flags.mode === 'BLOCK' && (last_text === '{' || last_text === ';')) {
980                     // { foo; --i }
981                     // foo(); --bar;
982                     print_newline();
983                 }
984             } else if (token_text === '.') {
985                 // decimal digits or object.property
986                 space_before = false;
987 
988             } else if (token_text === ':') {
989                 if ( ! is_ternary_op()) {
990                     space_before = false;
991                 }
992             }
993             if (space_before) {
994                 print_single_space();
995             }
996 
997             print_token();
998 
999             if (space_after) {
1000                 print_single_space();
1001             }
1002 
1003             if (token_text === '!') {
1004                 // flags.eat_next_space = true;
1005             }
1006 
1007             break;
1008 
1009         case 'TK_BLOCK_COMMENT':
1010 
1011             var lines = token_text.split(/\x0a|\x0d\x0a/);
1012 
1013             print_newline();
1014             output.push(lines[0]);
1015             for (var i = 1, l = lines.length; i < l; i++) {
1016                 print_newline();
1017                 output.push(' ');
1018                 output.push(lines[i].replace(/^\s\s*|\s\s*$/, ''));
1019             }
1020 
1021             print_newline();
1022             break;
1023 
1024         case 'TK_INLINE_COMMENT':
1025 
1026             print_single_space();
1027             print_token();
1028             if (is_expression(flags.mode)) {
1029                 print_single_space();
1030             } else {
1031                 print_newline();
1032             }
1033             break;
1034 
1035         case 'TK_COMMENT':
1036 
1037             // print_newline();
1038             if (wanted_newline) {
1039                 print_newline();
1040             } else {
1041                 print_single_space();
1042             }
1043             print_token();
1044             print_newline();
1045             break;
1046 
1047         case 'TK_UNKNOWN':
1048             print_token();
1049             break;
1050         }
1051 
1052         last_last_text = last_text;
1053         last_type = token_type;
1054         last_text = token_text;
1055     }
1056 
1057     return output.join('').replace(/[\n ]+$/, '');
1058 }
1059