Skip to content
This repository
Browse code

update to jsdeminifier 0.4.2

closes #16
  • Loading branch information...
commit 72273bb1fa05a5747ef5f03017749dbc8044b666 1 parent 72c587e
benmmurphy authored March 23, 2013
1,597  chrome/content/worker.js
... ...
@@ -1,4 +1,3 @@
1  
-
2 1
 onmessage = function(event) {
3 2
   var old_js = event.data.join("");
4 3
   var new_js = old_js;
@@ -12,7 +11,7 @@ onmessage = function(event) {
12 11
   this.close();
13 12
 }
14 13
 
15  
-/*jslint onevar: false, plusplus: false */
  14
+/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
16 15
 /*
17 16
 
18 17
  JS Beautifier
@@ -23,6 +22,7 @@ onmessage = function(event) {
23 22
       http://jsbeautifier.org/
24 23
 
25 24
   Originally converted to javascript by Vital, <vital76@gmail.com>
  25
+  "End braces on own line" added by Chris J. Shull, <chrisjshull@gmail.com>
26 26
 
27 27
   You are free to use this in any way you want, in case you find this useful or working for you.
28 28
 
@@ -31,158 +31,435 @@ onmessage = function(event) {
31 31
     js_beautify(js_source_text, options);
32 32
 
33 33
   The options are:
34  
-    indent_size (default 4)          — indentation size,
35  
-    indent_char (default space)      — character to indent with,
36  
-    preserve_newlines (default true) — whether existing line breaks should be preserved,
37  
-    indent_level (default 0)         — initial indentation level, you probably won't need this ever,
  34
+    indent_size (default 4)          - indentation size,
  35
+    indent_char (default space)      - character to indent with,
  36
+    preserve_newlines (default true) - whether existing line breaks should be preserved,
  37
+    max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk,
38 38
 
39  
-    space_after_anon_function (default false) — if true, then space is added between "function ()"
40  
-            (jslint is happy about this); if false, then the common "function()" output is used.
41  
-    braces_on_own_line (default false) - ANSI / Allman brace style, each opening/closing brace gets its own line.
  39
+    jslint_happy (default false) - if true, then jslint-stricter mode is enforced.
42 40
 
43  
-    e.g
  41
+            jslint_happy   !jslint_happy
  42
+            ---------------------------------
  43
+             function ()      function()
44 44
 
45  
-    js_beautify(js_source_text, {indent_size: 1, indent_char: '\t'});
  45
+    brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "expand-strict"
  46
+            put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line.
46 47
 
  48
+            expand-strict: put brace on own line even in such cases:
47 49
 
48  
-*/
  50
+                var a =
  51
+                {
  52
+                    a: 5,
  53
+                    b: 6
  54
+                }
  55
+            This mode may break your scripts - e.g "return { a: 1 }" will be broken into two lines, so beware.
49 56
 
  57
+    space_before_conditional (default true) - should the space before conditional statement be added, "if(true)" vs "if (true)",
  58
+
  59
+    unescape_strings (default false) - should printable characters in strings encoded in \xNN notation be unescaped, "example" vs "\x65\x78\x61\x6d\x70\x6c\x65"
  60
+
  61
+    wrap_line_length (default unlimited) - lines should wrap at next opportunity after this number of characters.
  62
+          NOTE: This is not a hard limit. Lines will continue until a point where a newline would
  63
+                be preserved if it were present.
  64
+
  65
+    e.g
50 66
 
  67
+    js_beautify(js_source_text, {
  68
+      'indent_size': 1,
  69
+      'indent_char': '\t'
  70
+    });
51 71
 
  72
+*/
52 73
 function js_beautify(js_source_text, options) {
  74
+    "use strict";
  75
+    var beautifier = new Beautifier(js_source_text, options);
  76
+    return beautifier.beautify();
  77
+}
53 78
 
  79
+function Beautifier(js_source_text, options) {
  80
+    "use strict";
54 81
     var input, output, token_text, last_type, last_text, last_last_text, last_word, flags, flag_store, indent_string;
55 82
     var whitespace, wordchar, punct, parser_pos, line_starters, digits;
56  
-    var prefix, token_type, do_block_just_closed;
57  
-    var wanted_newline, just_added_newline, n_newlines;
  83
+    var prefix, token_type;
  84
+    var wanted_newline, n_newlines, output_wrapped, output_space_before_token, whitespace_before_token;
  85
+    var input_length;
  86
+    var handlers, MODE, opt;
  87
+    var preindent_string = '';
  88
+
  89
+    whitespace = "\n\r\t ".split('');
  90
+    wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');
  91
+    digits = '0123456789'.split('');
  92
+
  93
+    punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |= ::';
  94
+    punct += ' <%= <% %> <?= <? ?>'; // try to be a good boy and try not to break the markup language identifiers
  95
+    punct = punct.split(' ');
  96
+
  97
+    // words which should always start on new line.
  98
+    line_starters = 'continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(',');
  99
+
  100
+    MODE = {
  101
+        BlockStatement: 'BlockStatement', // 'BLOCK'
  102
+        Statement: 'Statement', // 'STATEMENT'
  103
+        ObjectLiteral: 'ObjectLiteral', // 'OBJECT',
  104
+        ArrayLiteral: 'ArrayLiteral', //'[EXPRESSION]',
  105
+        ForInitializer: 'ForInitializer', //'(FOR-EXPRESSION)',
  106
+        Conditional: 'Conditional', //'(COND-EXPRESSION)',
  107
+        Expression: 'Expression' //'(EXPRESSION)'
  108
+    };
  109
+
  110
+    handlers = {
  111
+        'TK_START_EXPR': handle_start_expr,
  112
+        'TK_END_EXPR': handle_end_expr,
  113
+        'TK_START_BLOCK': handle_start_block,
  114
+        'TK_END_BLOCK': handle_end_block,
  115
+        'TK_WORD': handle_word,
  116
+        'TK_SEMICOLON': handle_semicolon,
  117
+        'TK_STRING': handle_string,
  118
+        'TK_EQUALS': handle_equals,
  119
+        'TK_OPERATOR': handle_operator,
  120
+        'TK_COMMA': handle_comma,
  121
+        'TK_BLOCK_COMMENT': handle_block_comment,
  122
+        'TK_INLINE_COMMENT': handle_inline_comment,
  123
+        'TK_COMMENT': handle_comment,
  124
+        'TK_DOT': handle_dot,
  125
+        'TK_UNKNOWN': handle_unknown
  126
+    };
58 127
 
59 128
 
60 129
     // Some interpreters have unexpected results with foo = baz || bar;
61 130
     options = options ? options : {};
62  
-    var opt_braces_on_own_line = options.braces_on_own_line ? options.braces_on_own_line : false;
63  
-    var opt_indent_size = options.indent_size ? options.indent_size : 4;
64  
-    var opt_indent_char = options.indent_char ? options.indent_char : ' ';
65  
-    var opt_preserve_newlines = typeof options.preserve_newlines === 'undefined' ? true : options.preserve_newlines;
66  
-    var opt_indent_level = options.indent_level ? options.indent_level : 0; // starting indentation
67  
-    var opt_space_after_anon_function = options.space_after_anon_function === 'undefined' ? false : options.space_after_anon_function;
68  
-    var opt_keep_array_indentation = typeof options.keep_array_indentation === 'undefined' ? false : options.keep_array_indentation;
  131
+    opt = {};
69 132
 
70  
-    just_added_newline = false;
  133
+    // compatibility
  134
+    if (options.space_after_anon_function !== undefined && options.jslint_happy === undefined) {
  135
+        options.jslint_happy = options.space_after_anon_function;
  136
+    }
  137
+    if (options.braces_on_own_line !== undefined) { //graceful handling of deprecated option
  138
+        opt.brace_style = options.braces_on_own_line ? "expand" : "collapse";
  139
+    }
  140
+    opt.brace_style = options.brace_style ? options.brace_style : (opt.brace_style ? opt.brace_style : "collapse");
  141
+
  142
+    opt.indent_size = options.indent_size ? parseInt(options.indent_size, 10) : 4;
  143
+    opt.indent_char = options.indent_char ? options.indent_char : ' ';
  144
+    opt.preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
  145
+    opt.break_chained_methods = (options.break_chained_methods === undefined) ? false : options.break_chained_methods;
  146
+    opt.max_preserve_newlines = (options.max_preserve_newlines === undefined) ? 0 : parseInt(options.max_preserve_newlines, 10);
  147
+    opt.jslint_happy = (options.jslint_happy === undefined) ? false : options.jslint_happy;
  148
+    opt.keep_array_indentation = (options.keep_array_indentation === undefined) ? false : options.keep_array_indentation;
  149
+    opt.space_before_conditional= (options.space_before_conditional === undefined) ? true : options.space_before_conditional;
  150
+    opt.unescape_strings = (options.unescape_strings === undefined) ? false : options.unescape_strings;
  151
+    opt.wrap_line_length = (options.wrap_line_length === undefined) ? 0 : parseInt(options.wrap_line_length, 10);
71 152
 
  153
+    //----------------------------------
  154
+    indent_string = '';
  155
+    while (opt.indent_size > 0) {
  156
+        indent_string += opt.indent_char;
  157
+        opt.indent_size -= 1;
  158
+    }
  159
+
  160
+    while (js_source_text && (js_source_text.charAt(0) === ' ' || js_source_text.charAt(0) === '\t')) {
  161
+        preindent_string += js_source_text.charAt(0);
  162
+        js_source_text = js_source_text.substring(1);
  163
+    }
  164
+    input = js_source_text;
72 165
     // cache the source's length.
73  
-    var input_length = js_source_text.length;
  166
+    input_length = js_source_text.length;
74 167
 
75  
-    function trim_output() {
76  
-        while (output.length && (output[output.length - 1] === ' ' || output[output.length - 1] === indent_string)) {
  168
+    last_word = ''; // last 'TK_WORD' passed
  169
+    last_type = 'TK_START_EXPR'; // last token type
  170
+    last_text = ''; // last token text
  171
+    last_last_text = ''; // pre-last token text
  172
+    output = [];
  173
+    output_wrapped = false;
  174
+    output_space_before_token = false;
  175
+    whitespace_before_token = [];
  176
+
  177
+    // Stack of parsing/formatting states, including MODE.
  178
+    // We tokenize, parse, and output in an almost purely a forward-only stream of token input
  179
+    // and formatted output.  This makes the beautifier less accurate than full parsers
  180
+    // but also far more tolerant of syntax errors.
  181
+    //
  182
+    // For example, the default mode is MODE.BlockStatement. If we see a '{' we push a new frame of type
  183
+    // MODE.BlockStatement on the the stack, even though it could be object literal.  If we later
  184
+    // encounter a ":", we'll switch to to MODE.ObjectLiteral.  If we then see a ";",
  185
+    // most full parsers would die, but the beautifier gracefully falls back to
  186
+    // MODE.BlockStatement and continues on.
  187
+    flag_store = [];
  188
+    set_mode(MODE.BlockStatement);
  189
+
  190
+    parser_pos = 0;
  191
+
  192
+    this.beautify = function () {
  193
+        /*jshint onevar:true */
  194
+        var t, i, keep_whitespace, sweet_code;
  195
+
  196
+        while (true) {
  197
+            t = get_next_token();
  198
+            token_text = t[0];
  199
+            token_type = t[1];
  200
+
  201
+            if (token_type === 'TK_EOF') {
  202
+                break;
  203
+            }
  204
+
  205
+            keep_whitespace = opt.keep_array_indentation && is_array(flags.mode);
  206
+
  207
+            if (keep_whitespace) {
  208
+                for (i = 0; i < n_newlines; i += 1) {
  209
+                    print_newline(true);
  210
+                }
  211
+            } else {
  212
+                wanted_newline = n_newlines > 0;
  213
+                if (opt.max_preserve_newlines && n_newlines > opt.max_preserve_newlines) {
  214
+                    n_newlines = opt.max_preserve_newlines;
  215
+                }
  216
+
  217
+                if (opt.preserve_newlines) {
  218
+                    if (n_newlines > 1) {
  219
+                        print_newline();
  220
+                        for (i = 1; i < n_newlines; i += 1) {
  221
+                            print_newline(true);
  222
+                        }
  223
+                    }
  224
+                }
  225
+            }
  226
+
  227
+            handlers[token_type]();
  228
+
  229
+            // The cleanest handling of inline comments is to treat them as though they aren't there.
  230
+            // Just continue formatting and the behavior should be logical.
  231
+            // Also ignore unknown tokens.  Again, this should result in better behavior.
  232
+            if (token_type !== 'TK_INLINE_COMMENT' && token_type !== 'TK_COMMENT' &&
  233
+                token_type !== 'TK_UNKNOWN') {
  234
+                last_last_text = last_text;
  235
+                last_type = token_type;
  236
+                last_text = token_text;
  237
+            }
  238
+        }
  239
+
  240
+        sweet_code = preindent_string + output.join('').replace(/[\r\n ]+$/, '');
  241
+        return sweet_code;
  242
+    };
  243
+
  244
+    function trim_output(eat_newlines) {
  245
+        eat_newlines = (eat_newlines === undefined) ? false : eat_newlines;
  246
+        while (output.length && (output[output.length - 1] === ' ' || output[output.length - 1] === indent_string || output[output.length - 1] === preindent_string || (eat_newlines && (output[output.length - 1] === '\n' || output[output.length - 1] === '\r')))) {
77 247
             output.pop();
78 248
         }
79 249
     }
80 250
 
81  
-    function is_array(mode) {
82  
-        return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]';
  251
+    function trim(s) {
  252
+        return s.replace(/^\s\s*|\s\s*$/, '');
83 253
     }
84 254
 
  255
+    // we could use just string.split, but
  256
+    // IE doesn't like returning empty strings
85 257
 
86  
-    function print_newline(ignore_repeated) {
  258
+    function split_newlines(s) {
  259
+        //return s.split(/\x0d\x0a|\x0a/);
87 260
 
88  
-        flags.eat_next_space = false;
89  
-        if (opt_keep_array_indentation && is_array(flags.mode)) {
90  
-            return;
  261
+        s = s.replace(/\x0d/g, '');
  262
+        var out = [],
  263
+            idx = s.indexOf("\n");
  264
+        while (idx !== -1) {
  265
+            out.push(s.substring(0, idx));
  266
+            s = s.substring(idx + 1);
  267
+            idx = s.indexOf("\n");
  268
+        }
  269
+        if (s.length) {
  270
+            out.push(s);
  271
+        }
  272
+        return out;
  273
+    }
  274
+
  275
+    function just_added_newline() {
  276
+        return output.length && output[output.length - 1] === "\n";
  277
+    }
  278
+
  279
+    function _last_index_of(arr, find) {
  280
+        var i = arr.length - 1;
  281
+        if (i < 0) {
  282
+            i += arr.length;
91 283
         }
  284
+        if (i > arr.length - 1) {
  285
+            i = arr.length - 1;
  286
+        }
  287
+        for (i++; i-- > 0;) {
  288
+            if (i in arr && arr[i] === find) {
  289
+                return i;
  290
+            }
  291
+        }
  292
+        return -1;
  293
+    }
92 294
 
93  
-        ignore_repeated = typeof ignore_repeated === 'undefined' ? true : ignore_repeated;
  295
+    function allow_wrap_or_preserved_newline(force_linewrap) {
  296
+        force_linewrap = (force_linewrap === undefined) ? false : force_linewrap;
  297
+        if (opt.wrap_line_length && !force_linewrap) {
  298
+            var current_line = '';
  299
+            var proposed_line_length = 0;
  300
+            var start_line = _last_index_of(output, '\n') + 1;
  301
+            // never wrap the first token of a line.
  302
+            if (start_line < output.length) {
  303
+                current_line = output.slice(start_line).join('');
  304
+                proposed_line_length = current_line.length + token_text.length +
  305
+                    (output_space_before_token ? 1 : 0);
  306
+                if (proposed_line_length >= opt.wrap_line_length) {
  307
+                    force_linewrap = true;
  308
+                }
  309
+            }
  310
+        }
  311
+        if (((opt.preserve_newlines && wanted_newline) || force_linewrap) && !just_added_newline()) {
  312
+            print_newline(false, true);
  313
+            output_wrapped = true;
  314
+            wanted_newline = false;
  315
+        }
  316
+    }
  317
+
  318
+    function print_newline(force_newline, preserve_statement_flags) {
  319
+        output_wrapped = false;
  320
+        output_space_before_token = false;
  321
+
  322
+        if (!preserve_statement_flags) {
  323
+            if (last_text !== ';') {
  324
+                while (flags.mode === MODE.Statement && !flags.if_block) {
  325
+                    restore_mode();
  326
+                }
  327
+            }
  328
+        }
  329
+
  330
+        if (flags.mode === MODE.ArrayLiteral) {
  331
+            flags.multiline_array = true;
  332
+        }
94 333
 
95  
-        flags.if_line = false;
96  
-        trim_output();
97 334
 
98 335
         if (!output.length) {
99 336
             return; // no newline on start of file
100 337
         }
101 338
 
102  
-        if (output[output.length - 1] !== "\n" || !ignore_repeated) {
103  
-            just_added_newline = true;
  339
+        if (force_newline || !just_added_newline()) {
104 340
             output.push("\n");
105 341
         }
106  
-        for (var i = 0; i < flags.indentation_level; i += 1) {
107  
-            output.push(indent_string);
108  
-        }
109  
-        if (flags.var_line && flags.var_line_reindented) {
110  
-            if (opt_indent_char === ' ') {
111  
-                output.push('    '); // var_line always pushes 4 spaces, so that the variables would be one under another
  342
+    }
  343
+
  344
+    function print_token_line_indentation() {
  345
+        if (just_added_newline()) {
  346
+            if (opt.keep_array_indentation && is_array(flags.mode) && whitespace_before_token.length) {
  347
+                output.push(whitespace_before_token.join('') + '');
112 348
             } else {
113  
-                output.push(indent_string); // skip space-stuffing, if indenting with a tab
  349
+                if (preindent_string) {
  350
+                    output.push(preindent_string);
  351
+                }
  352
+
  353
+                print_indent_string(flags.indentation_level);
  354
+                print_indent_string(flags.var_line && flags.var_line_reindented);
  355
+                print_indent_string(output_wrapped);
114 356
             }
115 357
         }
116 358
     }
117 359
 
118  
-
119  
-
120  
-    function print_single_space() {
121  
-        if (flags.eat_next_space) {
122  
-            flags.eat_next_space = false;
123  
-            return;
  360
+    function print_indent_string(level) {
  361
+        if (level === undefined) {
  362
+            level = 1;
  363
+        } else if (typeof level !== 'number') {
  364
+            level = level ? 1 : 0;
124 365
         }
125  
-        var last_output = ' ';
126  
-        if (output.length) {
127  
-            last_output = output[output.length - 1];
128  
-        }
129  
-        if (last_output !== ' ' && last_output !== '\n' && last_output !== indent_string) { // prevent occassional duplicate space
130  
-            output.push(' ');
  366
+
  367
+        // Never indent your first output indent at the start of the file
  368
+        if (last_text !== '') {
  369
+            for (var i = 0; i < level; i += 1) {
  370
+                output.push(indent_string);
  371
+            }
131 372
         }
132 373
     }
133 374
 
  375
+    function print_token_space_before() {
  376
+        if (output_space_before_token && output.length) {
  377
+            var last_output = output[output.length - 1];
  378
+            if (!just_added_newline() && last_output !== ' ' && last_output !== indent_string) { // prevent occassional duplicate space
  379
+                output.push(' ');
  380
+            }
  381
+        }
  382
+    }
134 383
 
135  
-    function print_token() {
136  
-        just_added_newline = false;
137  
-        flags.eat_next_space = false;
138  
-        output.push(token_text);
  384
+    function print_token(printable_token) {
  385
+        printable_token = printable_token || token_text;
  386
+        print_token_line_indentation();
  387
+        output_wrapped = false;
  388
+        print_token_space_before();
  389
+        output_space_before_token = false;
  390
+        output.push(printable_token);
139 391
     }
140 392
 
141 393
     function indent() {
142 394
         flags.indentation_level += 1;
143 395
     }
144 396
 
145  
-
146  
-    function remove_indent() {
147  
-        if (output.length && output[output.length - 1] === indent_string) {
148  
-            output.pop();
149  
-        }
150  
-    }
151  
-
152 397
     function set_mode(mode) {
153 398
         if (flags) {
154 399
             flag_store.push(flags);
155 400
         }
156 401
         flags = {
157  
-            previous_mode: flags ? flags.mode : 'BLOCK',
  402
+            previous_mode: flags ? flags.mode : MODE.BlockStatement,
158 403
             mode: mode,
159 404
             var_line: false,
160 405
             var_line_tainted: false,
161 406
             var_line_reindented: false,
162 407
             in_html_comment: false,
163  
-            if_line: false,
164  
-            in_case: false,
165  
-            eat_next_space: false,
166  
-            indentation_baseline: -1,
167  
-            indentation_level: (flags ? flags.indentation_level + ((flags.var_line && flags.var_line_reindented) ? 1 : 0) : opt_indent_level)
  408
+            multiline_array: false,
  409
+            if_block: false,
  410
+            do_block: false,
  411
+            do_while: false,
  412
+            in_case_statement: false, // switch(..){ INSIDE HERE }
  413
+            in_case: false, // we're on the exact line with "case 0:"
  414
+            case_body: false, // the indented case-action block
  415
+            indentation_level: (flags ? flags.indentation_level + ((flags.var_line && flags.var_line_reindented) ? 1 : 0) : 0),
  416
+            ternary_depth: 0
168 417
         };
169 418
     }
170 419
 
171 420
     function is_array(mode) {
172  
-        return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]';
  421
+        return mode === MODE.ArrayLiteral;
173 422
     }
174 423
 
175 424
     function is_expression(mode) {
176  
-        return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]' || mode === '(EXPRESSION)';
  425
+        return in_array(mode, [MODE.ArrayLiteral, MODE.Expression, MODE.ForInitializer, MODE.Conditional]);
177 426
     }
178 427
 
179 428
     function restore_mode() {
180  
-        do_block_just_closed = flags.mode === 'DO_BLOCK';
181 429
         if (flag_store.length > 0) {
  430
+            var mode = flags.mode;
182 431
             flags = flag_store.pop();
  432
+            flags.previous_mode = mode;
  433
+        }
  434
+    }
  435
+
  436
+    function start_of_statement() {
  437
+        if (
  438
+        (last_text === 'do' ||
  439
+            (last_text === 'else' && token_text !== 'if') ||
  440
+            (last_type === 'TK_END_EXPR' && (flags.previous_mode === MODE.ForInitializer || flags.previous_mode === MODE.Conditional)))) {
  441
+            allow_wrap_or_preserved_newline();
  442
+            set_mode(MODE.Statement);
  443
+            indent();
  444
+            output_wrapped = false;
  445
+            return true;
  446
+        }
  447
+        return false;
  448
+    }
  449
+
  450
+    function all_lines_start_with(lines, c) {
  451
+        for (var i = 0; i < lines.length; i++) {
  452
+            var line = trim(lines[i]);
  453
+            if (line.charAt(0) !== c) {
  454
+                return false;
  455
+            }
183 456
         }
  457
+        return true;
184 458
     }
185 459
 
  460
+    function is_special_word(word) {
  461
+        return in_array(word, ['case', 'return', 'do', 'if', 'throw', 'else']);
  462
+    }
186 463
 
187 464
     function in_array(what, arr) {
188 465
         for (var i = 0; i < arr.length; i += 1) {
@@ -193,51 +470,86 @@ function js_beautify(js_source_text, options) {
193 470
         return false;
194 471
     }
195 472
 
196  
-    // Walk backwards from the colon to find a '?' (colon is part of a ternary op)
197  
-    // or a '{' (colon is part of a class literal).  Along the way, keep track of
198  
-    // the blocks and expressions we pass so we only trigger on those chars in our
199  
-    // own level, and keep track of the colons so we only trigger on the matching '?'.
  473
+    function unescape_string(s) {
  474
+        var esc = false,
  475
+            out = '',
  476
+            pos = 0,
  477
+            s_hex = '',
  478
+            escaped = 0,
  479
+            c;
  480
+
  481
+        while (esc || pos < s.length) {
  482
+
  483
+            c = s.charAt(pos);
  484
+            pos++;
  485
+
  486
+            if (esc) {
  487
+                esc = false;
  488
+                if (c === 'x') {
  489
+                    // simple hex-escape \x24
  490
+                    s_hex = s.substr(pos, 2);
  491
+                    pos += 2;
  492
+                } else if (c === 'u') {
  493
+                    // unicode-escape, \u2134
  494
+                    s_hex = s.substr(pos, 4);
  495
+                    pos += 4;
  496
+                } else {
  497
+                    // some common escape, e.g \n
  498
+                    out += '\\' + c;
  499
+                    continue;
  500
+                }
  501
+                if (!s_hex.match(/^[0123456789abcdefABCDEF]+$/)) {
  502
+                    // some weird escaping, bail out,
  503
+                    // leaving whole string intact
  504
+                    return s;
  505
+                }
200 506
 
  507
+                escaped = parseInt(s_hex, 16);
201 508
 
202  
-    function is_ternary_op() {
203  
-        var level = 0,
204  
-            colon_count = 0;
205  
-        for (var i = output.length - 1; i >= 0; i--) {
206  
-            switch (output[i]) {
207  
-            case ':':
208  
-                if (level === 0) {
209  
-                    colon_count++;
210  
-                }
211  
-                break;
212  
-            case '?':
213  
-                if (level === 0) {
214  
-                    if (colon_count === 0) {
215  
-                        return true;
  509
+                if (escaped >= 0x00 && escaped < 0x20) {
  510
+                    // leave 0x00...0x1f escaped
  511
+                    if (c === 'x') {
  512
+                        out += '\\x' + s_hex;
216 513
                     } else {
217  
-                        colon_count--;
  514
+                        out += '\\u' + s_hex;
218 515
                     }
  516
+                    continue;
  517
+                } else if (escaped === 0x22 || escaped === 0x27 || escaped === 0x5c) {
  518
+                    // single-quote, apostrophe, backslash - escape these
  519
+                    out += '\\' + String.fromCharCode(escaped);
  520
+                } else if (c === 'x' && escaped > 0x7e && escaped <= 0xff) {
  521
+                    // we bail out on \x7f..\xff,
  522
+                    // leaving whole string escaped,
  523
+                    // as it's probably completely binary
  524
+                    return s;
  525
+                } else {
  526
+                    out += String.fromCharCode(escaped);
219 527
                 }
220  
-                break;
221  
-            case '{':
222  
-                if (level === 0) {
223  
-                    return false;
224  
-                }
225  
-                level--;
226  
-                break;
227  
-            case '(':
228  
-            case '[':
229  
-                level--;
230  
-                break;
231  
-            case ')':
232  
-            case ']':
233  
-            case '}':
234  
-                level++;
235  
-                break;
  528
+            } else if (c === '\\') {
  529
+                esc = true;
  530
+            } else {
  531
+                out += c;
236 532
             }
237 533
         }
  534
+        return out;
  535
+    }
  536
+
  537
+    function is_next(find) {
  538
+        var local_pos = parser_pos;
  539
+        var c = input.charAt(local_pos);
  540
+        while (in_array(c, whitespace) && c !== find) {
  541
+            local_pos++;
  542
+            if (local_pos >= input_length) {
  543
+                return false;
  544
+            }
  545
+            c = input.charAt(local_pos);
  546
+        }
  547
+        return c === find;
238 548
     }
239 549
 
240 550
     function get_next_token() {
  551
+        var i, resulting_string;
  552
+
241 553
         n_newlines = 0;
242 554
 
243 555
         if (parser_pos >= input_length) {
@@ -245,98 +557,32 @@ function js_beautify(js_source_text, options) {
245 557
         }
246 558
 
247 559
         wanted_newline = false;
  560
+        whitespace_before_token = [];
248 561
 
249 562
         var c = input.charAt(parser_pos);
250 563
         parser_pos += 1;
251 564
 
  565
+        while (in_array(c, whitespace)) {
252 566
 
253  
-        var keep_whitespace = opt_keep_array_indentation && is_array(flags.mode);
254  
-
255  
-        if (keep_whitespace) {
256  
-
257  
-            //
258  
-            // slight mess to allow nice preservation of array indentation and reindent that correctly
259  
-            // first time when we get to the arrays:
260  
-            // var a = [
261  
-            // ....'something'
262  
-            // we make note of whitespace_count = 4 into flags.indentation_baseline
263  
-            // so we know that 4 whitespaces in original source match indent_level of reindented source
264  
-            //
265  
-            // and afterwards, when we get to
266  
-            //    'something,
267  
-            // .......'something else'
268  
-            // we know that this should be indented to indent_level + (7 - indentation_baseline) spaces
269  
-            //
270  
-            var whitespace_count = 0;
271  
-
272  
-            while (in_array(c, whitespace)) {
273  
-
274  
-                if (c === "\n") {
275  
-                    trim_output();
276  
-                    output.push("\n");
277  
-                    just_added_newline = true;
278  
-                    whitespace_count = 0;
279  
-                } else {
280  
-                    if (c === '\t') {
281  
-                        whitespace_count += 4;
282  
-                    } else {
283  
-                        whitespace_count += 1;
284  
-                    }
285  
-                }
286  
-
287  
-                if (parser_pos >= input_length) {
288  
-                    return ['', 'TK_EOF'];
  567
+            if (c === '\n') {
  568
+                n_newlines += 1;
  569
+                whitespace_before_token = [];
  570
+            } else if (n_newlines) {
  571
+                if (c === indent_string) {
  572
+                    whitespace_before_token.push(indent_string);
  573
+                } else if (c !== '\r') {
  574
+                    whitespace_before_token.push(' ');
289 575
                 }
290  
-
291  
-                c = input.charAt(parser_pos);
292  
-                parser_pos += 1;
293  
-
294  
-            }
295  
-            if (flags.indentation_baseline === -1) {
296  
-                flags.indentation_baseline = whitespace_count;
297 576
             }
298 577
 
299  
-            if (just_added_newline) {
300  
-                var i;
301  
-                for (i = 0; i < flags.indentation_level + 1; i += 1) {
302  
-                    output.push(indent_string);
303  
-                }
304  
-                if (flags.indentation_baseline !== -1) {
305  
-                    for (i = 0; i < whitespace_count - flags.indentation_baseline; i++) {
306  
-                        output.push(' ');
307  
-                    }
308  
-                }
  578
+            if (parser_pos >= input_length) {
  579
+                return ['', 'TK_EOF'];
309 580
             }
310 581
 
311  
-        } else {
312  
-            while (in_array(c, whitespace)) {
313  
-
314  
-                if (c === "\n") {
315  
-                    n_newlines += 1;
316  
-                }
317  
-
318  
-
319  
-                if (parser_pos >= input_length) {
320  
-                    return ['', 'TK_EOF'];
321  
-                }
322  
-
323  
-                c = input.charAt(parser_pos);
324  
-                parser_pos += 1;
325  
-
326  
-            }
327  
-
328  
-            if (opt_preserve_newlines) {
329  
-                if (n_newlines > 1) {
330  
-                    for (i = 0; i < n_newlines; i += 1) {
331  
-                        print_newline(i === 0);
332  
-                        just_added_newline = true;
333  
-                    }
334  
-                }
335  
-            }
336  
-            wanted_newline = n_newlines > 0;
  582
+            c = input.charAt(parser_pos);
  583
+            parser_pos += 1;
337 584
         }
338 585
 
339  
-
340 586
         if (in_array(c, wordchar)) {
341 587
             if (parser_pos < input_length) {
342 588
                 while (in_array(input.charAt(parser_pos), wordchar)) {
@@ -354,7 +600,7 @@ function js_beautify(js_source_text, options) {
354 600
                 var sign = input.charAt(parser_pos);
355 601
                 parser_pos += 1;
356 602
 
357  
-                var t = get_next_token(parser_pos);
  603
+                var t = get_next_token();
358 604
                 c += sign + t[0];
359 605
                 return [c, 'TK_WORD'];
360 606
             }
@@ -362,9 +608,6 @@ function js_beautify(js_source_text, options) {
362 608
             if (c === 'in') { // hack for 'in' operator
363 609
                 return [c, 'TK_OPERATOR'];
364 610
             }
365  
-            if (wanted_newline && last_type !== 'TK_OPERATOR' && !flags.if_line && (opt_preserve_newlines || last_text !== 'var')) {
366  
-                print_newline();
367  
-            }
368 611
             return [c, 'TK_WORD'];
369 612
         }
370 613
 
@@ -395,10 +638,10 @@ function js_beautify(js_source_text, options) {
395 638
             if (input.charAt(parser_pos) === '*') {
396 639
                 parser_pos += 1;
397 640
                 if (parser_pos < input_length) {
398  
-                    while (! (input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/') && parser_pos < input_length) {
  641
+                    while (parser_pos < input_length && !(input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/')) {
399 642
                         c = input.charAt(parser_pos);
400 643
                         comment += c;
401  
-                        if (c === '\x0d' || c === '\x0a') {
  644
+                        if (c === "\n" || c === "\r") {
402 645
                             inline_comment = false;
403 646
                         }
404 647
                         parser_pos += 1;
@@ -408,7 +651,7 @@ function js_beautify(js_source_text, options) {
408 651
                     }
409 652
                 }
410 653
                 parser_pos += 2;
411  
-                if (inline_comment) {
  654
+                if (inline_comment && n_newlines === 0) {
412 655
                     return ['/*' + comment + '*/', 'TK_INLINE_COMMENT'];
413 656
                 } else {
414 657
                     return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT'];
@@ -417,28 +660,30 @@ function js_beautify(js_source_text, options) {
417 660
             // peek for comment // ...
418 661
             if (input.charAt(parser_pos) === '/') {
419 662
                 comment = c;
420  
-                while (input.charAt(parser_pos) !== "\x0d" && input.charAt(parser_pos) !== "\x0a") {
  663
+                while (input.charAt(parser_pos) !== '\r' && input.charAt(parser_pos) !== '\n') {
421 664
                     comment += input.charAt(parser_pos);
422 665
                     parser_pos += 1;
423 666
                     if (parser_pos >= input_length) {
424 667
                         break;
425 668
                     }
426 669
                 }
427  
-                parser_pos += 1;
428  
-                if (wanted_newline) {
429  
-                    print_newline();
430  
-                }
431 670
                 return [comment, 'TK_COMMENT'];
432 671
             }
433 672
 
434 673
         }
435 674
 
436  
-        if (c === "'" || // string
437  
-        c === '"' || // string
438  
-        (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
439  
-            var sep = c;
440  
-            var esc = false;
441  
-            var resulting_string = c;
  675
+        if (c === "'" || c === '"' || // string
  676
+        (c === '/' &&
  677
+            ((last_type === 'TK_WORD' && is_special_word(last_text)) ||
  678
+            (last_type === 'TK_END_EXPR' && in_array(flags.previous_mode, [MODE.Conditional, MODE.ForInitializer])) ||
  679
+            (in_array(last_type, ['TK_COMMENT', 'TK_START_EXPR', 'TK_START_BLOCK',
  680
+                'TK_END_BLOCK', 'TK_OPERATOR', 'TK_EQUALS', 'TK_EOF', 'TK_SEMICOLON', 'TK_COMMA'
  681
+        ]))))) { // regexp
  682
+            var sep = c,
  683
+                esc = false,
  684
+                has_char_escapes = false;
  685
+
  686
+            resulting_string = c;
442 687
 
443 688
             if (parser_pos < input_length) {
444 689
                 if (sep === '/') {
@@ -472,10 +717,13 @@ function js_beautify(js_source_text, options) {
472 717
                     //
473 718
                     while (esc || input.charAt(parser_pos) !== sep) {
474 719
                         resulting_string += input.charAt(parser_pos);
475  
-                        if (!esc) {
476  
-                            esc = input.charAt(parser_pos) === '\\';
477  
-                        } else {
  720
+                        if (esc) {
  721
+                            if (input.charAt(parser_pos) === 'x' || input.charAt(parser_pos) === 'u') {
  722
+                                has_char_escapes = true;
  723
+                            }
478 724
                             esc = false;
  725
+                        } else {
  726
+                            esc = input.charAt(parser_pos) === '\\';
479 727
                         }
480 728
                         parser_pos += 1;
481 729
                         if (parser_pos >= input_length) {
@@ -484,16 +732,17 @@ function js_beautify(js_source_text, options) {
484 732
                             return [resulting_string, 'TK_STRING'];
485 733
                         }
486 734
                     }
487  
-                }
488  
-
489  
-
490 735
 
  736
+                }
491 737
             }
492 738
 
493 739
             parser_pos += 1;
494  
-
495 740
             resulting_string += sep;
496 741
 
  742
+            if (has_char_escapes && opt.unescape_strings) {
  743
+                resulting_string = unescape_string(resulting_string);
  744
+            }
  745
+
497 746
             if (sep === '/') {
498 747
                 // regexps may have modifiers /regexp/MOD , so fetch those, too
499 748
                 while (parser_pos < input_length && in_array(input.charAt(parser_pos), wordchar)) {
@@ -505,6 +754,21 @@ function js_beautify(js_source_text, options) {
505 754
         }
506 755
 
507 756
         if (c === '#') {
  757
+
  758
+
  759
+            if (output.length === 0 && input.charAt(parser_pos) === '!') {
  760
+                // shebang
  761
+                resulting_string = c;
  762
+                while (parser_pos < input_length && c !== '\n') {
  763
+                    c = input.charAt(parser_pos);
  764
+                    resulting_string += c;
  765
+                    parser_pos += 1;
  766
+                }
  767
+                return [trim(resulting_string) + '\n', 'TK_UNKNOWN'];
  768
+            }
  769
+
  770
+
  771
+
508 772
             // Spidermonkey-specific sharp variables for circular references
509 773
             // https://developer.mozilla.org/En/Sharp_variables_in_JavaScript
510 774
             // http://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935
@@ -530,19 +794,25 @@ function js_beautify(js_source_text, options) {
530 794
 
531 795
         if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '<!--') {
532 796
             parser_pos += 3;
  797
+            c = '<!--';
  798
+            while (input.charAt(parser_pos) !== '\n' && parser_pos < input_length) {
  799
+                c += input.charAt(parser_pos);
  800
+                parser_pos++;
  801
+            }
533 802
             flags.in_html_comment = true;
534  
-            return ['<!--', 'TK_COMMENT'];
  803
+            return [c, 'TK_COMMENT'];
535 804
         }
536 805
 
537 806
         if (c === '-' && flags.in_html_comment && input.substring(parser_pos - 1, parser_pos + 2) === '-->') {
538 807
             flags.in_html_comment = false;
539 808
             parser_pos += 2;
540  
-            if (wanted_newline) {
541  
-                print_newline();
542  
-            }
543 809
             return ['-->', 'TK_COMMENT'];
544 810
         }
545 811
 
  812
+        if (c === '.') {
  813
+            return [c, 'TK_DOT'];
  814
+        }
  815
+
546 816
         if (in_array(c, punct)) {
547 817
             while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) {
548 818
                 c += input.charAt(parser_pos);
@@ -552,7 +822,9 @@ function js_beautify(js_source_text, options) {
552 822
                 }
553 823
             }
554 824
 
555  
-            if (c === '=') {
  825
+            if (c === ',') {
  826
+                return [c, 'TK_COMMA'];
  827
+            } else if (c === '=') {
556 828
                 return [c, 'TK_EQUALS'];
557 829
             } else {
558 830
                 return [c, 'TK_OPERATOR'];
@@ -562,534 +834,585 @@ function js_beautify(js_source_text, options) {
562 834
         return [c, 'TK_UNKNOWN'];
563 835
     }
564 836
 
565  
-    //----------------------------------
566  
-    indent_string = '';
567  
-    while (opt_indent_size > 0) {
568  
-        indent_string += opt_indent_char;
569  
-        opt_indent_size -= 1;
570  
-    }
571  
-
572  
-    input = js_source_text;
573  
-
574  
-    last_word = ''; // last 'TK_WORD' passed
575  
-    last_type = 'TK_START_EXPR'; // last token type
576  
-    last_text = ''; // last token text
577  
-    last_last_text = ''; // pre-last token text
578  
-    output = [];
579  
-
580  
-    do_block_just_closed = false;
581  
-
582  
-    whitespace = "\n\r\t ".split('');
583  
-    wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');
584  
-    digits = '0123456789'.split('');
585  
-
586  
-    punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |= ::'.split(' ');
587  
-
588  
-    // words which should always start on new line.
589  
-    line_starters = 'continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(',');
590  
-
591  
-    // states showing if we are currently in expression (i.e. "if" case) - 'EXPRESSION', or in usual block (like, procedure), 'BLOCK'.
592  
-    // some formatting depends on that.
593  
-    flag_store = [];
594  
-    set_mode('BLOCK');
595  
-
596  
-    parser_pos = 0;
597  
-    while (true) {
598  
-        var t = get_next_token(parser_pos);
599  
-        token_text = t[0];
600  
-        token_type = t[1];
601  
-        if (token_type === 'TK_EOF') {
602  
-            break;
  837
+    function handle_start_expr() {
  838
+        if (start_of_statement()) {
  839
+            // The conditional starts the statement if appropriate.
603 840
         }
604 841
 
605  
-        switch (token_type) {
606  
-
607  
-        case 'TK_START_EXPR':
608  
-
609  
-            if (token_text === '[') {
  842
+        if (token_text === '[') {
610 843
 
611  
-                if (last_type === 'TK_WORD' || last_text === ')') {
612  
-                    // this is array index specifier, break immediately
613  
-                    // a[x], fn()[x]
614  
-                    if (in_array(last_text, line_starters)) {
615  
-                        print_single_space();
616  
-                    }
617  
-                    set_mode('(EXPRESSION)');
618  
-                    print_token();
619  
-                    break;
  844
+            if (last_type === 'TK_WORD' || last_text === ')') {
  845
+                // this is array index specifier, break immediately
  846
+                // a[x], fn()[x]
  847
+                if (in_array(last_text, line_starters)) {
  848
+                    output_space_before_token = true;
620 849
                 }
  850
+                set_mode(MODE.Expression);
  851
+                print_token();
  852
+                return;
  853
+            }
621 854
 
622  
-                if (flags.mode === '[EXPRESSION]' || flags.mode === '[INDENTED-EXPRESSION]') {
623  
-                    if (last_last_text === ']' && last_text === ',') {
624  
-                        // ], [ goes to new line
625  
-                        if (flags.mode === '[EXPRESSION]') {
626  
-                            flags.mode = '[INDENTED-EXPRESSION]';
627  
-                            if (!opt_keep_array_indentation) {
628  
-                                indent();
629  
-                            }
630  
-                        }
631  
-                        set_mode('[EXPRESSION]');
632  
-                        if (!opt_keep_array_indentation) {
633  
-                            print_newline();
634  
-                        }
635  
-                    } else if (last_text === '[') {
636  
-                        if (flags.mode === '[EXPRESSION]') {
637  
-                            flags.mode = '[INDENTED-EXPRESSION]';
638  
-                            if (!opt_keep_array_indentation) {
639  
-                                indent();
640  
-                            }
641  
-                        }
642  
-                        set_mode('[EXPRESSION]');
643  
-
644  
-                        if (!opt_keep_array_indentation) {
645  
-                            print_newline();
646  
-                        }
647  
-                    } else {
648  
-                        set_mode('[EXPRESSION]');
  855
+            if (is_array(flags.mode)) {
  856
+                if ((last_text === '[') ||
  857
+                    (last_last_text === ']' && last_text === ',')) {
  858
+                    // ], [ goes to new line
  859
+                    if (!opt.keep_array_indentation) {
  860
+                        print_newline();
649 861
                     }
650  
-                } else {
651  
-                    set_mode('[EXPRESSION]');
652 862
                 }
  863
+            }
653 864
 
654  
-
655  
-
  865
+        } else {
  866
+            if (last_text === 'for') {
  867
+                set_mode(MODE.ForInitializer);
  868
+            } else if (in_array(last_text, ['if', 'while'])) {
  869
+                set_mode(MODE.Conditional);
656 870
             } else {
657  
-                set_mode('(EXPRESSION)');
  871
+                set_mode(MODE.Expression);
658 872
             }
  873
+        }
659 874
 
660  
-            if (last_text === ';' || last_type === 'TK_START_BLOCK') {
  875
+        if (last_text === ';' || last_type === 'TK_START_BLOCK') {
  876
+            print_newline();
  877
+        } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || last_text === '.') {
  878
+            if (wanted_newline) {
661 879
                 print_newline();
662  
-            } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || last_text === '.') {
663  
-                // do nothing on (( and )( and ][ and ]( and .(
664  
-            } else if (last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
665  
-                print_single_space();
666  
-            } else if (last_word === 'function') {
667  
-                // function() vs function ()
668  
-                if (opt_space_after_anon_function) {
669  
-                    print_single_space();
670  
-                }
671  
-            } else if (in_array(last_text, line_starters) || last_text === 'catch') {
672  
-                print_single_space();
673 880
             }
674  
-            print_token();
  881
+            // do nothing on (( and )( and ][ and ]( and .(
  882
+        } else if (last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
  883
+            output_space_before_token = true;
  884
+        } else if (last_word === 'function' || last_word === 'typeof') {
  885
+            // function() vs function ()
  886
+            if (opt.jslint_happy) {
  887
+                output_space_before_token = true;
  888
+            }
  889
+        } else if (in_array(last_text, line_starters) || last_text === 'catch') {
  890
+            if (opt.space_before_conditional) {
  891
+                output_space_before_token = true;
  892
+            }
  893
+        }
675 894
 
676  
-            break;
677  
-
678  
-        case 'TK_END_EXPR':
679  
-            if (token_text === ']') {
680  
-                if (opt_keep_array_indentation) {
681  
-                    if (last_text === '}') {
682  
-                        // trim_output();
683  
-                        // print_newline(true);
684  
-                        remove_indent();
685  
-                        print_token();
686  
-                        restore_mode();
687  
-                        break;
688  
-                    }
689  
-                } else {
690  
-                    if (flags.mode === '[INDENTED-EXPRESSION]') {
691  
-                        if (last_text === ']') {
692  
-                            restore_mode();
693  
-                            print_newline();
694  
-                            print_token();
695  
-                            break;
696  
-                        }
697  
-                    }
  895
+        // Support of this kind of newline preservation.
  896
+        // a = (b &&
  897
+        //     (c || d));
  898
+        if (token_text === '(') {
  899
+            if (last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
  900
+                if (flags.mode !== MODE.ObjectLiteral) {
  901
+                    allow_wrap_or_preserved_newline();
698 902
                 }
699 903
             }