Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
View | Details | Raw Unified | Return to bug 496437 | Differences between
and this patch

Collapse All | Expand All

(-)a/bundles/org.eclipse.orion.client.javascript/web/beautifier/beautifier.js (+28 lines)
Added Link Here
1
/*eslint-env amd, node*/
2
define([
3
	"./lib/beautify-js",
4
	"./lib/beautify-css",
5
	"./lib/beautify-html"
6
], function(js_beautify, css_beautify, html_beautify) {
7
8
	function get_beautify(js_beautify, css_beautify, html_beautify) {
9
		// the default is js
10
		var beautify = function(src, config) {
11
			return js_beautify.js_beautify(src, config);
12
		};
13
	
14
		// short aliases
15
		beautify.js = js_beautify.js_beautify;
16
		beautify.css = css_beautify.css_beautify;
17
		beautify.html = html_beautify.html_beautify;
18
	
19
		// legacy aliases
20
		beautify.js_beautify = js_beautify.js_beautify;
21
		beautify.css_beautify = css_beautify.css_beautify;
22
		beautify.html_beautify = html_beautify.html_beautify;
23
	
24
		return beautify;
25
	}
26
27
	return get_beautify(js_beautify, css_beautify, html_beautify);
28
});
(-)a/bundles/org.eclipse.orion.client.javascript/web/beautifier/lib/beautify-css.js (+490 lines)
Added Link Here
1
/*
2
3
  The MIT License (MIT)
4
5
  Copyright (c) 2007-2013 Einar Lielmanis and contributors.
6
7
  Permission is hereby granted, free of charge, to any person
8
  obtaining a copy of this software and associated documentation files
9
  (the "Software"), to deal in the Software without restriction,
10
  including without limitation the rights to use, copy, modify, merge,
11
  publish, distribute, sublicense, and/or sell copies of the Software,
12
  and to permit persons to whom the Software is furnished to do so,
13
  subject to the following conditions:
14
15
  The above copyright notice and this permission notice shall be
16
  included in all copies or substantial portions of the Software.
17
18
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
22
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
23
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
  SOFTWARE.
26
27
28
 CSS Beautifier
29
---------------
30
31
    Written by Harutyun Amirjanyan, (amirjanyan@gmail.com)
32
33
    Based on code initially developed by: Einar Lielmanis, <einar@jsbeautifier.org>
34
        http://jsbeautifier.org/
35
36
    Usage:
37
        css_beautify(source_text);
38
        css_beautify(source_text, options);
39
40
    The options are (default in brackets):
41
        indent_size (4)                         - indentation size,
42
        indent_char (space)                     - character to indent with,
43
        selector_separator_newline (true)       - separate selectors with newline or
44
                                                  not (e.g. "a,\nbr" or "a, br")
45
        end_with_newline (false)                - end with a newline
46
        newline_between_rules (true)            - add a new line after every css rule
47
        space_around_selector_separator (false) - ensure space around selector separators:
48
                                                  '>', '+', '~' (e.g. "a>b" -> "a > b")
49
    e.g
50
51
    css_beautify(css_source_text, {
52
      'indent_size': 1,
53
      'indent_char': '\t',
54
      'selector_separator': ' ',
55
      'end_with_newline': false,
56
      'newline_between_rules': true,
57
      'space_around_selector_separator': true
58
    });
59
*/
60
61
// http://www.w3.org/TR/CSS21/syndata.html#tokenization
62
// http://www.w3.org/TR/css3-syntax/
63
/*eslint-env amd */
64
define([], function() {
65
	
66
    function css_beautify(source_text, options) {
67
        options = options || {};
68
        source_text = source_text || '';
69
        // HACK: newline parsing inconsistent. This brute force normalizes the input.
70
        source_text = source_text.replace(/\r\n|[\r\u2028\u2029]/g, '\n');
71
72
        var indentSize = options.indent_size || 4;
73
        var indentCharacter = options.indent_char || ' ';
74
        var selectorSeparatorNewline = (options.selector_separator_newline === undefined) ? true : options.selector_separator_newline;
75
        var end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
76
        var newline_between_rules = (options.newline_between_rules === undefined) ? true : options.newline_between_rules;
77
        var spaceAroundSelectorSeparator = (options.space_around_selector_separator === undefined) ? false : options.space_around_selector_separator;
78
        var eol = options.eol ? options.eol : '\n';
79
80
        // compatibility
81
        if (typeof indentSize === "string") {
82
            indentSize = parseInt(indentSize, 10);
83
        }
84
85
        if (options.indent_with_tabs) {
86
            indentCharacter = '\t';
87
            indentSize = 1;
88
        }
89
90
        eol = eol.replace(/\\r/, '\r').replace(/\\n/, '\n');
91
92
93
        // tokenizer
94
        var whiteRe = /^\s+$/;
95
96
        var pos = -1,
97
            ch;
98
        var parenLevel = 0;
99
100
        function next() {
101
            ch = source_text.charAt(++pos);
102
            return ch || '';
103
        }
104
105
        function peek(skipWhitespace) {
106
            var result = '';
107
            var prev_pos = pos;
108
            if (skipWhitespace) {
109
                eatWhitespace();
110
            }
111
            result = source_text.charAt(pos + 1) || '';
112
            pos = prev_pos - 1;
113
            next();
114
            return result;
115
        }
116
117
        function eatString(endChars) {
118
            var start = pos;
119
            while (next()) {
120
                if (ch === "\\") {
121
                    next();
122
                } else if (endChars.indexOf(ch) !== -1) {
123
                    break;
124
                } else if (ch === "\n") {
125
                    break;
126
                }
127
            }
128
            return source_text.substring(start, pos + 1);
129
        }
130
131
        function peekString(endChar) {
132
            var prev_pos = pos;
133
            var str = eatString(endChar);
134
            pos = prev_pos - 1;
135
            next();
136
            return str;
137
        }
138
139
        function eatWhitespace() {
140
            var result = '';
141
            while (whiteRe.test(peek())) {
142
                next();
143
                result += ch;
144
            }
145
            return result;
146
        }
147
148
        function skipWhitespace() {
149
            var result = '';
150
            if (ch && whiteRe.test(ch)) {
151
                result = ch;
152
            }
153
            while (whiteRe.test(next())) {
154
                result += ch;
155
            }
156
            return result;
157
        }
158
159
        function eatComment(singleLine) {
160
            var start = pos;
161
            singleLine = peek() === "/";
162
            next();
163
            while (next()) {
164
                if (!singleLine && ch === "*" && peek() === "/") {
165
                    next();
166
                    break;
167
                } else if (singleLine && ch === "\n") {
168
                    return source_text.substring(start, pos);
169
                }
170
            }
171
172
            return source_text.substring(start, pos) + ch;
173
        }
174
175
176
        function lookBack(str) {
177
            return source_text.substring(pos - str.length, pos).toLowerCase() ===
178
                str;
179
        }
180
181
        // Nested pseudo-class if we are insideRule
182
        // and the next special character found opens
183
        // a new block
184
        function foundNestedPseudoClass() {
185
            var openParen = 0;
186
            for (var i = pos + 1; i < source_text.length; i++) {
187
                var ch = source_text.charAt(i);
188
                if (ch === "{") {
189
                    return true;
190
                } else if (ch === '(') {
191
                    // pseudoclasses can contain ()
192
                    openParen += 1;
193
                } else if (ch === ')') {
194
                    if (openParen === 0) {
195
                        return false;
196
                    }
197
                    openParen -= 1;
198
                } else if (ch === ";" || ch === "}") {
199
                    return false;
200
                }
201
            }
202
            return false;
203
        }
204
205
        // printer
206
        var basebaseIndentString = source_text.match(/^[\t ]*/)[0];
207
        var singleIndent = new Array(indentSize + 1).join(indentCharacter);
208
        var indentLevel = 0;
209
        var nestedLevel = 0;
210
211
        function indent() {
212
            indentLevel++;
213
            basebaseIndentString += singleIndent;
214
        }
215
216
        function outdent() {
217
            indentLevel--;
218
            basebaseIndentString = basebaseIndentString.slice(0, -indentSize);
219
        }
220
221
        var print = {};
222
        print["{"] = function(ch) {
223
            print.singleSpace();
224
            output.push(ch);
225
            print.newLine();
226
        };
227
        print["}"] = function(ch) {
228
            print.newLine();
229
            output.push(ch);
230
            print.newLine();
231
        };
232
233
        print._lastCharWhitespace = function() {
234
            return whiteRe.test(output[output.length - 1]);
235
        };
236
237
        print.newLine = function(keepWhitespace) {
238
            if (output.length) {
239
                if (!keepWhitespace && output[output.length - 1] !== '\n') {
240
                    print.trim();
241
                }
242
243
                output.push('\n');
244
245
                if (basebaseIndentString) {
246
                    output.push(basebaseIndentString);
247
                }
248
            }
249
        };
250
        print.singleSpace = function() {
251
            if (output.length && !print._lastCharWhitespace()) {
252
                output.push(' ');
253
            }
254
        };
255
256
        print.preserveSingleSpace = function() {
257
            if (isAfterSpace) {
258
                print.singleSpace();
259
            }
260
        };
261
262
        print.trim = function() {
263
            while (print._lastCharWhitespace()) {
264
                output.pop();
265
            }
266
        };
267
268
269
        var output = [];
270
        /*_____________________--------------------_____________________*/
271
272
        var insideRule = false;
273
        var insidePropertyValue = false;
274
        var enteringConditionalGroup = false;
275
        var top_ch = '';
276
        var last_top_ch = '';
277
278
        while (true) {
279
            var whitespace = skipWhitespace();
280
            var isAfterSpace = whitespace !== '';
281
            var isAfterNewline = whitespace.indexOf('\n') !== -1;
282
            last_top_ch = top_ch;
283
            top_ch = ch;
284
285
            if (!ch) {
286
                break;
287
            } else if (ch === '/' && peek() === '*') { /* css comment */
288
                var header = indentLevel === 0;
289
290
                if (isAfterNewline || header) {
291
                    print.newLine();
292
                }
293
294
                output.push(eatComment());
295
                print.newLine();
296
                if (header) {
297
                    print.newLine(true);
298
                }
299
            } else if (ch === '/' && peek() === '/') { // single line comment
300
                if (!isAfterNewline && last_top_ch !== '{') {
301
                    print.trim();
302
                }
303
                print.singleSpace();
304
                output.push(eatComment());
305
                print.newLine();
306
            } else if (ch === '@') {
307
                print.preserveSingleSpace();
308
309
                // deal with less propery mixins @{...}
310
                if (peek() === '{') {
311
                    output.push(eatString('}'));
312
                } else {
313
                    output.push(ch);
314
315
                    // strip trailing space, if present, for hash property checks
316
                    var variableOrRule = peekString(": ,;{}()[]/='\"");
317
318
                    if (variableOrRule.match(/[ :]$/)) {
319
                        // we have a variable or pseudo-class, add it and insert one space before continuing
320
                        next();
321
                        variableOrRule = eatString(": ").replace(/\s$/, '');
322
                        output.push(variableOrRule);
323
                        print.singleSpace();
324
                    }
325
326
                    variableOrRule = variableOrRule.replace(/\s$/, '');
327
328
                    // might be a nesting at-rule
329
                    if (variableOrRule in css_beautify.NESTED_AT_RULE) {
330
                        nestedLevel += 1;
331
                        if (variableOrRule in css_beautify.CONDITIONAL_GROUP_RULE) {
332
                            enteringConditionalGroup = true;
333
                        }
334
                    }
335
                }
336
            } else if (ch === '#' && peek() === '{') {
337
                print.preserveSingleSpace();
338
                output.push(eatString('}'));
339
            } else if (ch === '{') {
340
                if (peek(true) === '}') {
341
                    eatWhitespace();
342
                    next();
343
                    print.singleSpace();
344
                    output.push("{}");
345
                    print.newLine();
346
                    if (newline_between_rules && indentLevel === 0) {
347
                        print.newLine(true);
348
                    }
349
                } else {
350
                    indent();
351
                    print["{"](ch);
352
                    // when entering conditional groups, only rulesets are allowed
353
                    if (enteringConditionalGroup) {
354
                        enteringConditionalGroup = false;
355
                        insideRule = (indentLevel > nestedLevel);
356
                    } else {
357
                        // otherwise, declarations are also allowed
358
                        insideRule = (indentLevel >= nestedLevel);
359
                    }
360
                }
361
            } else if (ch === '}') {
362
                outdent();
363
                print["}"](ch);
364
                insideRule = false;
365
                insidePropertyValue = false;
366
                if (nestedLevel) {
367
                    nestedLevel--;
368
                }
369
                if (newline_between_rules && indentLevel === 0) {
370
                    print.newLine(true);
371
                }
372
            } else if (ch === ":") {
373
                eatWhitespace();
374
                if ((insideRule || enteringConditionalGroup) &&
375
                    !(lookBack("&") || foundNestedPseudoClass())) {
376
                    // 'property: value' delimiter
377
                    // which could be in a conditional group query
378
                    insidePropertyValue = true;
379
                    output.push(':');
380
                    print.singleSpace();
381
                } else {
382
                    // sass/less parent reference don't use a space
383
                    // sass nested pseudo-class don't use a space
384
                    if (peek() === ":") {
385
                        // pseudo-element
386
                        next();
387
                        output.push("::");
388
                    } else {
389
                        // pseudo-class
390
                        output.push(':');
391
                    }
392
                }
393
            } else if (ch === '"' || ch === '\'') {
394
                print.preserveSingleSpace();
395
                output.push(eatString(ch));
396
            } else if (ch === ';') {
397
                insidePropertyValue = false;
398
                output.push(ch);
399
                print.newLine();
400
            } else if (ch === '(') { // may be a url
401
                if (lookBack("url")) {
402
                    output.push(ch);
403
                    eatWhitespace();
404
                    if (next()) {
405
                        if (ch !== ')' && ch !== '"' && ch !== '\'') {
406
                            output.push(eatString(')'));
407
                        } else {
408
                            pos--;
409
                        }
410
                    }
411
                } else {
412
                    parenLevel++;
413
                    print.preserveSingleSpace();
414
                    output.push(ch);
415
                    eatWhitespace();
416
                }
417
            } else if (ch === ')') {
418
                output.push(ch);
419
                parenLevel--;
420
            } else if (ch === ',') {
421
                output.push(ch);
422
                eatWhitespace();
423
                if (selectorSeparatorNewline && !insidePropertyValue && parenLevel < 1) {
424
                    print.newLine();
425
                } else {
426
                    print.singleSpace();
427
                }
428
            } else if (ch === '>' || ch === '+' || ch === '~') {
429
                //handl selector separator spacing
430
                if (spaceAroundSelectorSeparator && !insidePropertyValue && parenLevel < 1) {
431
                    print.singleSpace();
432
                    output.push(ch);
433
                    print.singleSpace();
434
                } else {
435
                    output.push(ch);
436
                }
437
            } else if (ch === ']') {
438
                output.push(ch);
439
            } else if (ch === '[') {
440
                print.preserveSingleSpace();
441
                output.push(ch);
442
            } else if (ch === '=') { // no whitespace before or after
443
                eatWhitespace();
444
                ch = '=';
445
                output.push(ch);
446
            } else {
447
                print.preserveSingleSpace();
448
                output.push(ch);
449
            }
450
        }
451
452
453
        var sweetCode = '';
454
        if (basebaseIndentString) {
455
            sweetCode += basebaseIndentString;
456
        }
457
458
        sweetCode += output.join('').replace(/[\r\n\t ]+$/, '');
459
460
        // establish end_with_newline
461
        if (end_with_newline) {
462
            sweetCode += '\n';
463
        }
464
465
        if (eol !== '\n') {
466
            sweetCode = sweetCode.replace(/[\n]/g, eol);
467
        }
468
469
        return sweetCode;
470
    }
471
472
    // https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule
473
    css_beautify.NESTED_AT_RULE = {
474
        "@page": true,
475
        "@font-face": true,
476
        "@keyframes": true,
477
        // also in CONDITIONAL_GROUP_RULE below
478
        "@media": true,
479
        "@supports": true,
480
        "@document": true
481
    };
482
    css_beautify.CONDITIONAL_GROUP_RULE = {
483
        "@media": true,
484
        "@supports": true,
485
        "@document": true
486
    };
487
    return {
488
        css_beautify: css_beautify
489
    };
490
});
(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/formatting.js (+194 lines)
Added Link Here
1
 /*******************************************************************************
2
 * @license
3
 * Copyright (c) 2016 IBM Corporation and others.
4
 * All rights reserved. This program and the accompanying materials are made 
5
 * available under the terms of the Eclipse Public License v1.0 
6
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
7
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
8
 *
9
 * Contributors:
10
 *     IBM Corporation - initial API and implementation
11
 *******************************************************************************/
12
/*eslint-env amd, browser*/
13
define([
14
'orion/objects',
15
'orion/Deferred',
16
'javascript/finder'
17
], function(Objects, Deferred) {
18
	var config = {
19
		// 0:off, 1:warning, 2:error
20
		defaults: {
21
			"indent_size": 1,
22
			"indent_char": " ",
23
			"eol": "\n",
24
			"indent_level": 0,
25
			"indent_with_tabs": false,
26
			"preserve_newlines": true,
27
			"max_preserve_newlines": 10,
28
			"jslint_happy": false,
29
			"space_after_anon_function": false,
30
			"brace_style": "collapse",
31
			"keep_array_indentation": false,
32
			"keep_function_indentation": false,
33
			"space_before_conditional": true,
34
			"break_chained_methods": false,
35
			"eval_code": false,
36
			"unescape_strings": false,
37
			"wrap_line_length": 0,
38
			"wrap_attributes": "auto",
39
			"wrap_attributes_indent_size": 4,
40
			"end_with_newline": false
41
		},
42
		
43
		setOption: function(ruleId, value, key) {
44
			if(Array.isArray(this.rules[ruleId])) {
45
				var ruleConfig = this.rules[ruleId];
46
				if (key) {
47
					ruleConfig[1] = ruleConfig[1] || {};
48
					ruleConfig[1][key] = value;
49
				} else {
50
					ruleConfig[0] = value;
51
				}
52
			}
53
			else {
54
				this.rules[ruleId] = value;
55
			}
56
		},
57
		
58
		/**
59
		 * @description Resets the rules to their default values
60
		 * @function
61
		 */
62
		setDefaults: function setDefaults() {
63
			this.rules = Object.create(null);
64
			var keys = Object.keys(this.defaults);
65
			for(var i = 0; i < keys.length; i++) {
66
				var key = keys[i];
67
				this.rules[key] = this.defaults[key];
68
			}
69
		}
70
	};
71
72
	/**
73
	 * @name javascript.JavaScriptFormatting
74
	 * @description creates a new instance of the javascript formatter
75
	 * @constructor
76
	 * @public
77
	 * @param {Worker} ternWorker
78
	 */
79
	function JavaScriptFormatting(ternWorker, jsProject) {
80
		this.ternworker = ternWorker;
81
		this.project = jsProject;
82
		config.setDefaults();
83
	}
84
	
85
	Objects.mixin(JavaScriptFormatting.prototype, /** @lends javascript.JavaScriptFormatting.prototype*/ {
86
		
87
		/**
88
		 * @description Callback from the editor to format the source code
89
		 * @function
90
		 * @public 
91
		 * @memberof javascript.JavaScriptFormatting.prototype
92
		 * @param {orion.edit.EditorContext} editorContext The current editor context
93
		 * @param {Object} context The current selection context
94
		 */
95
		execute: function(editorContext, context) {
96
			var deferred = new Deferred();
97
			if(this.project) {
98
				//TODO make sure we can get the options as set in the formatting preference page Right now only the indent character is customizable
99
				// We should expose all existing options - see defaults above
100
				this.project.getFormattingOptions().then(function(cfg) {
101
					this.format(editorContext, deferred, cfg ? cfg : config.rules);
102
				}.bind(this));
103
			} else {
104
				this.format(editorContext, deferred, config.rules);
105
			}
106
			return deferred;
107
		},
108
	
109
		/**
110
		 * @description Format the given editor
111
		 * @function
112
		 * @private
113
		 * @param {orion.edit.EditorContext} editorContext The given editor context
114
		 * @param {Deferred} deferred the given deferred object
115
		 * @param {Object} configuration the given configuration
116
		 * @since 6.0
117
		 */
118
		format: function(editorContext, deferred, configuration) {
119
			return editorContext.getFileMetadata().then(function(meta) {
120
				return editorContext.getSelection().then(function(selection) {
121
					var start = selection.start;
122
					var end = selection.end;
123
					var files, request;
124
					if (end !== start) {
125
						return editorContext.getText(start, end).then(function(text) {
126
							files = [{type: 'full', name: meta.location, text: text}]; //$NON-NLS-1$
127
							request = {request: 'beautify', args: {meta: {location: meta.location}, files: files, config: configuration, start: start, end: end, contentType: meta.contentType.id}}; //$NON-NLS-1$
128
							this.ternworker.postMessage(
129
								request, 
130
								function(formatted, err) {
131
									if(err) {
132
										deferred.reject();
133
									}
134
									if(formatted && formatted.text) {
135
										deferred.resolve(editorContext.setText(formatted.text, start, end));
136
									} else {
137
										deferred.reject();
138
									}
139
								});
140
							return deferred;
141
						}.bind(this));
142
					}
143
					return editorContext.getText().then(function(text) {
144
						files = [{type: 'full', name: meta.location, text: text}]; //$NON-NLS-1$
145
						request = {request: 'beautify', args: {meta: {location: meta.location}, files: files, config: configuration, contentType: meta.contentType.id}}; //$NON-NLS-1$
146
						this.ternworker.postMessage(
147
							request, 
148
							function(formatted, err) {
149
								if(err) {
150
									deferred.reject();
151
								}
152
								if(formatted && formatted.text) {
153
									deferred.resolve(editorContext.setText(formatted.text));
154
								} else {
155
									deferred.reject();
156
								}
157
							});
158
						return deferred;
159
					}.bind(this));
160
				}.bind(this));
161
			}.bind(this));
162
		},
163
		
164
				/**
165
		 * @description Callback from orion.cm.managedservice
166
		 * @function
167
		 * @public
168
		 * @param {Object} properties The properties that have been changed
169
		 */
170
		updated: function(properties) {
171
			if (!properties) {
172
				return;
173
			}
174
			var oldconfig = properties.pid === 'jsbeautify.config';
175
			var keys = Object.keys(properties);
176
			for(var i = 0; i < keys.length; i++) {
177
				var key = keys[i];
178
				var ruleId = key;
179
				if(oldconfig && config.rules[key] !== config.defaults[key]) {
180
					//don't overwrite a new setting with an old one
181
					continue;
182
				}
183
				config.setOption(ruleId, properties[key]);
184
			}
185
		},
186
	});
187
188
189
	JavaScriptFormatting.prototype.constructor = JavaScriptFormatting;
190
	
191
	return {
192
		JavaScriptFormatting: JavaScriptFormatting
193
	};
194
});
(-)a/bundles/org.eclipse.orion.client.javascript/web/beautifier/lib/beautify-html.js (+995 lines)
Added Link Here
1
/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
2
/*
3
4
  The MIT License (MIT)
5
6
  Copyright (c) 2007-2013 Einar Lielmanis and contributors.
7
8
  Permission is hereby granted, free of charge, to any person
9
  obtaining a copy of this software and associated documentation files
10
  (the "Software"), to deal in the Software without restriction,
11
  including without limitation the rights to use, copy, modify, merge,
12
  publish, distribute, sublicense, and/or sell copies of the Software,
13
  and to permit persons to whom the Software is furnished to do so,
14
  subject to the following conditions:
15
16
  The above copyright notice and this permission notice shall be
17
  included in all copies or substantial portions of the Software.
18
19
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
23
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
24
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
  SOFTWARE.
27
28
29
 Style HTML
30
---------------
31
32
  Written by Nochum Sossonko, (nsossonko@hotmail.com)
33
34
  Based on code initially developed by: Einar Lielmanis, <einar@jsbeautifier.org>
35
    http://jsbeautifier.org/
36
37
  Usage:
38
    style_html(html_source);
39
40
    style_html(html_source, options);
41
42
  The options are:
43
    indent_inner_html (default false)  - indent <head> and <body> sections,
44
    indent_size (default 4)            - indentation size,
45
    indent_char (default space)        - character to indent with,
46
    wrap_line_length (default 250)            -  maximum amount of characters per line (0 = disable)
47
    brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "none"
48
            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, or attempt to keep them where they are.
49
    unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
50
    indent_scripts (default normal)  - "keep"|"separate"|"normal"
51
    preserve_newlines (default true) - whether existing line breaks before elements should be preserved
52
                                        Only works before elements, not inside tags or for text.
53
    max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk
54
    indent_handlebars (default false) - format and indent {{#foo}} and {{/foo}}
55
    end_with_newline (false)          - end with a newline
56
    extra_liners (default [head,body,/html]) -List of tags that should have an extra newline before them.
57
58
    e.g.
59
60
    style_html(html_source, {
61
      'indent_inner_html': false,
62
      'indent_size': 2,
63
      'indent_char': ' ',
64
      'wrap_line_length': 78,
65
      'brace_style': 'expand',
66
      'preserve_newlines': true,
67
      'max_preserve_newlines': 5,
68
      'indent_handlebars': false,
69
      'extra_liners': ['/html']
70
    });
71
*/
72
/*eslint-env amd */
73
define([
74
	"./beautify-js",
75
	"./beautify-css"
76
], function(js_beautify, css_beautify) {
77
78
    // function trim(s) {
79
    //     return s.replace(/^\s+|\s+$/g, '');
80
    // }
81
82
    function ltrim(s) {
83
        return s.replace(/^\s+/g, '');
84
    }
85
86
    function rtrim(s) {
87
        return s.replace(/\s+$/g, '');
88
    }
89
90
    function style_html(html_source, options, js_beautify, css_beautify) {
91
        //Wrapper function to invoke all the necessary constructors and deal with the output.
92
93
        var multi_parser,
94
            indent_inner_html,
95
            indent_size,
96
            indent_character,
97
            wrap_line_length,
98
            brace_style,
99
            unformatted,
100
            preserve_newlines,
101
            max_preserve_newlines,
102
            indent_handlebars,
103
            wrap_attributes,
104
            wrap_attributes_indent_size,
105
            end_with_newline,
106
            extra_liners,
107
            eol;
108
109
        options = options || {};
110
111
        // backwards compatibility to 1.3.4
112
        if ((options.wrap_line_length === undefined || parseInt(options.wrap_line_length, 10) === 0) &&
113
            (options.max_char !== undefined && parseInt(options.max_char, 10) !== 0)) {
114
            options.wrap_line_length = options.max_char;
115
        }
116
117
        indent_inner_html = (options.indent_inner_html === undefined) ? false : options.indent_inner_html;
118
        indent_size = (options.indent_size === undefined) ? 4 : parseInt(options.indent_size, 10);
119
        indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char;
120
        brace_style = (options.brace_style === undefined) ? 'collapse' : options.brace_style;
121
        wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10);
122
        unformatted = options.unformatted || [
123
            // https://www.w3.org/TR/html5/dom.html#phrasing-content
124
            'a', 'abbr', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button', 'canvas', 'cite',
125
            'code', 'data', 'datalist', 'del', 'dfn', 'em', 'embed', 'i', 'iframe', 'img',
126
            'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'meter', 'noscript',
127
            'object', 'output', 'progress', 'q', 'ruby', 's', 'samp', /* 'script', */ 'select', 'small',
128
            'span', 'strong', 'sub', 'sup', 'svg', 'template', 'textarea', 'time', 'u', 'var',
129
            'video', 'wbr', 'text',
130
            // prexisting - not sure of full effect of removing, leaving in
131
            'acronym', 'address', 'big', 'dt', 'ins', 'small', 'strike', 'tt',
132
            'pre',
133
            'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
134
        ];
135
        preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
136
        max_preserve_newlines = preserve_newlines ?
137
            (isNaN(parseInt(options.max_preserve_newlines, 10)) ? 32786 : parseInt(options.max_preserve_newlines, 10)) :
138
            0;
139
        indent_handlebars = (options.indent_handlebars === undefined) ? false : options.indent_handlebars;
140
        wrap_attributes = (options.wrap_attributes === undefined) ? 'auto' : options.wrap_attributes;
141
        wrap_attributes_indent_size = (isNaN(parseInt(options.wrap_attributes_indent_size, 10))) ? indent_size : parseInt(options.wrap_attributes_indent_size, 10);
142
        end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
143
        extra_liners = (typeof options.extra_liners === 'object') && options.extra_liners ?
144
            options.extra_liners.concat() : (typeof options.extra_liners === 'string') ?
145
            options.extra_liners.split(',') : 'head,body,/html'.split(',');
146
        eol = options.eol ? options.eol : '\n';
147
148
        if (options.indent_with_tabs) {
149
            indent_character = '\t';
150
            indent_size = 1;
151
        }
152
153
        eol = eol.replace(/\\r/, '\r').replace(/\\n/, '\n');
154
155
        function Parser() {
156
157
            this.pos = 0; //Parser position
158
            this.token = '';
159
            this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
160
            this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
161
                parent: 'parent1',
162
                parentcount: 1,
163
                parent1: ''
164
            };
165
            this.tag_type = '';
166
            this.token_text = this.last_token = this.last_text = this.token_type = '';
167
            this.newlines = 0;
168
            this.indent_content = indent_inner_html;
169
170
            this.Utils = { //Uilities made available to the various functions
171
                whitespace: "\n\r\t ".split(''),
172
173
                single_token: [
174
                    // HTLM void elements - aka self-closing tags - aka singletons
175
                    // https://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
176
                    'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen',
177
                    'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr',
178
                    // NOTE: Optional tags - are not understood.
179
                    // https://www.w3.org/TR/html5/syntax.html#optional-tags
180
                    // The rules for optional tags are too complex for a simple list
181
                    // Also, the content of these tags should still be indented in many cases.
182
                    // 'li' is a good exmple.
183
184
                    // Doctype and xml elements
185
                    '!doctype', '?xml',
186
                    // ?php tag
187
                    '?php',
188
                    // other tags that were in this list, keeping just in case
189
                    'basefont', 'isindex'
190
                ],
191
                extra_liners: extra_liners, //for tags that need a line of whitespace before them
192
                in_array: function(what, arr) {
193
                    for (var i = 0; i < arr.length; i++) {
194
                        if (what === arr[i]) {
195
                            return true;
196
                        }
197
                    }
198
                    return false;
199
                }
200
            };
201
202
            // Return true if the given text is composed entirely of whitespace.
203
            this.is_whitespace = function(text) {
204
                for (var n = 0; n < text.length; n++) {
205
                    if (!this.Utils.in_array(text.charAt(n), this.Utils.whitespace)) {
206
                        return false;
207
                    }
208
                }
209
                return true;
210
            };
211
212
            this.traverse_whitespace = function() {
213
                var input_char = '';
214
215
                input_char = this.input.charAt(this.pos);
216
                if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
217
                    this.newlines = 0;
218
                    while (this.Utils.in_array(input_char, this.Utils.whitespace)) {
219
                        if (preserve_newlines && input_char === '\n' && this.newlines <= max_preserve_newlines) {
220
                            this.newlines += 1;
221
                        }
222
223
                        this.pos++;
224
                        input_char = this.input.charAt(this.pos);
225
                    }
226
                    return true;
227
                }
228
                return false;
229
            };
230
231
            // Append a space to the given content (string array) or, if we are
232
            // at the wrap_line_length, append a newline/indentation.
233
            // return true if a newline was added, false if a space was added
234
            this.space_or_wrap = function(content) {
235
                if (this.line_char_count >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached
236
                    this.print_newline(false, content);
237
                    this.print_indentation(content);
238
                    return true;
239
                } else {
240
                    this.line_char_count++;
241
                    content.push(' ');
242
                    return false;
243
                }
244
            };
245
246
            this.get_content = function() { //function to capture regular content between tags
247
                var input_char = '',
248
                    content = [];
249
250
                while (this.input.charAt(this.pos) !== '<') {
251
                    if (this.pos >= this.input.length) {
252
                        return content.length ? content.join('') : ['', 'TK_EOF'];
253
                    }
254
255
                    if (this.traverse_whitespace()) {
256
                        this.space_or_wrap(content);
257
                        continue;
258
                    }
259
260
                    if (indent_handlebars) {
261
                        // Handlebars parsing is complicated.
262
                        // {{#foo}} and {{/foo}} are formatted tags.
263
                        // {{something}} should get treated as content, except:
264
                        // {{else}} specifically behaves like {{#if}} and {{/if}}
265
                        var peek3 = this.input.substr(this.pos, 3);
266
                        if (peek3 === '{{#' || peek3 === '{{/') {
267
                            // These are tags and not content.
268
                            break;
269
                        } else if (peek3 === '{{!') {
270
                            return [this.get_tag(), 'TK_TAG_HANDLEBARS_COMMENT'];
271
                        } else if (this.input.substr(this.pos, 2) === '{{') {
272
                            if (this.get_tag(true) === '{{else}}') {
273
                                break;
274
                            }
275
                        }
276
                    }
277
278
                    input_char = this.input.charAt(this.pos);
279
                    this.pos++;
280
                    this.line_char_count++;
281
                    content.push(input_char); //letter at-a-time (or string) inserted to an array
282
                }
283
                return content.length ? content.join('') : '';
284
            };
285
286
            this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify
287
                if (this.pos === this.input.length) {
288
                    return ['', 'TK_EOF'];
289
                }
290
                var content = '';
291
                var reg_match = new RegExp('</' + name + '\\s*>', 'igm');
292
                reg_match.lastIndex = this.pos;
293
                var reg_array = reg_match.exec(this.input);
294
                var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script
295
                if (this.pos < end_script) { //get everything in between the script tags
296
                    content = this.input.substring(this.pos, end_script);
297
                    this.pos = end_script;
298
                }
299
                return content;
300
            };
301
302
            this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object
303
                if (this.tags[tag + 'count']) { //check for the existence of this tag type
304
                    this.tags[tag + 'count']++;
305
                    this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
306
                } else { //otherwise initialize this tag type
307
                    this.tags[tag + 'count'] = 1;
308
                    this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
309
                }
310
                this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
311
                this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
312
            };
313
314
            this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer
315
                if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
316
                    var temp_parent = this.tags.parent; //check to see if it's a closable tag.
317
                    while (temp_parent) { //till we reach '' (the initial value);
318
                        if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
319
                            break;
320
                        }
321
                        temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
322
                    }
323
                    if (temp_parent) { //if we caught something
324
                        this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
325
                        this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
326
                    }
327
                    delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
328
                    delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
329
                    if (this.tags[tag + 'count'] === 1) {
330
                        delete this.tags[tag + 'count'];
331
                    } else {
332
                        this.tags[tag + 'count']--;
333
                    }
334
                }
335
            };
336
337
            this.indent_to_tag = function(tag) {
338
                // Match the indentation level to the last use of this tag, but don't remove it.
339
                if (!this.tags[tag + 'count']) {
340
                    return;
341
                }
342
                var temp_parent = this.tags.parent;
343
                while (temp_parent) {
344
                    if (tag + this.tags[tag + 'count'] === temp_parent) {
345
                        break;
346
                    }
347
                    temp_parent = this.tags[temp_parent + 'parent'];
348
                }
349
                if (temp_parent) {
350
                    this.indent_level = this.tags[tag + this.tags[tag + 'count']];
351
                }
352
            };
353
354
            this.get_tag = function(peek) { //function to get a full tag and parse its type
355
                var input_char = '',
356
                    content = [],
357
                    comment = '',
358
                    space = false,
359
                    first_attr = true,
360
                    tag_start, tag_end,
361
                    tag_start_char,
362
                    orig_pos = this.pos,
363
                    orig_line_char_count = this.line_char_count;
364
365
                peek = peek !== undefined ? peek : false;
366
367
                do {
368
                    if (this.pos >= this.input.length) {
369
                        if (peek) {
370
                            this.pos = orig_pos;
371
                            this.line_char_count = orig_line_char_count;
372
                        }
373
                        return content.length ? content.join('') : ['', 'TK_EOF'];
374
                    }
375
376
                    input_char = this.input.charAt(this.pos);
377
                    this.pos++;
378
379
                    if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
380
                        space = true;
381
                        continue;
382
                    }
383
384
                    if (input_char === "'" || input_char === '"') {
385
                        input_char += this.get_unformatted(input_char);
386
                        space = true;
387
388
                    }
389
390
                    if (input_char === '=') { //no space before =
391
                        space = false;
392
                    }
393
394
                    if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) {
395
                        //no space after = or before >
396
                        var wrapped = this.space_or_wrap(content);
397
                        var indentAttrs = wrapped && input_char !== '/' && wrap_attributes !== 'force';
398
                        space = false;
399
                        if (!first_attr && wrap_attributes === 'force' && input_char !== '/') {
400
                            this.print_newline(false, content);
401
                            this.print_indentation(content);
402
                            indentAttrs = true;
403
                        }
404
                        if (indentAttrs) {
405
                            //indent attributes an auto or forced line-wrap
406
                            for (var count = 0; count < wrap_attributes_indent_size; count++) {
407
                                content.push(indent_character);
408
                            }
409
                        }
410
                        for (var i = 0; i < content.length; i++) {
411
                            if (content[i] === ' ') {
412
                                first_attr = false;
413
                                break;
414
                            }
415
                        }
416
                    }
417
418
                    if (indent_handlebars && tag_start_char === '<') {
419
                        // When inside an angle-bracket tag, put spaces around
420
                        // handlebars not inside of strings.
421
                        if ((input_char + this.input.charAt(this.pos)) === '{{') {
422
                            input_char += this.get_unformatted('}}');
423
                            if (content.length && content[content.length - 1] !== ' ' && content[content.length - 1] !== '<') {
424
                                input_char = ' ' + input_char;
425
                            }
426
                            space = true;
427
                        }
428
                    }
429
430
                    if (input_char === '<' && !tag_start_char) {
431
                        tag_start = this.pos - 1;
432
                        tag_start_char = '<';
433
                    }
434
435
                    if (indent_handlebars && !tag_start_char) {
436
                        if (content.length >= 2 && content[content.length - 1] === '{' && content[content.length - 2] === '{') {
437
                            if (input_char === '#' || input_char === '/' || input_char === '!') {
438
                                tag_start = this.pos - 3;
439
                            } else {
440
                                tag_start = this.pos - 2;
441
                            }
442
                            tag_start_char = '{';
443
                        }
444
                    }
445
446
                    this.line_char_count++;
447
                    content.push(input_char); //inserts character at-a-time (or string)
448
449
                    if (content[1] && (content[1] === '!' || content[1] === '?' || content[1] === '%')) { //if we're in a comment, do something special
450
                        // We treat all comments as literals, even more than preformatted tags
451
                        // we just look for the appropriate close tag
452
                        content = [this.get_comment(tag_start)];
453
                        break;
454
                    }
455
456
                    if (indent_handlebars && content[1] && content[1] === '{' && content[2] && content[2] === '!') { //if we're in a comment, do something special
457
                        // We treat all comments as literals, even more than preformatted tags
458
                        // we just look for the appropriate close tag
459
                        content = [this.get_comment(tag_start)];
460
                        break;
461
                    }
462
463
                    if (indent_handlebars && tag_start_char === '{' && content.length > 2 && content[content.length - 2] === '}' && content[content.length - 1] === '}') {
464
                        break;
465
                    }
466
                } while (input_char !== '>');
467
468
                var tag_complete = content.join('');
469
                var tag_index;
470
                var tag_offset;
471
472
                if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
473
                    tag_index = tag_complete.indexOf(' ');
474
                } else if (tag_complete.charAt(0) === '{') {
475
                    tag_index = tag_complete.indexOf('}');
476
                } else { //otherwise go with the tag ending
477
                    tag_index = tag_complete.indexOf('>');
478
                }
479
                if (tag_complete.charAt(0) === '<' || !indent_handlebars) {
480
                    tag_offset = 1;
481
                } else {
482
                    tag_offset = tag_complete.charAt(2) === '#' ? 3 : 2;
483
                }
484
                var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase();
485
                if (tag_complete.charAt(tag_complete.length - 2) === '/' ||
486
                    this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
487
                    if (!peek) {
488
                        this.tag_type = 'SINGLE';
489
                    }
490
                } else if (indent_handlebars && tag_complete.charAt(0) === '{' && tag_check === 'else') {
491
                    if (!peek) {
492
                        this.indent_to_tag('if');
493
                        this.tag_type = 'HANDLEBARS_ELSE';
494
                        this.indent_content = true;
495
                        this.traverse_whitespace();
496
                    }
497
                } else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
498
                    comment = this.get_unformatted('</' + tag_check + '>', tag_complete); //...delegate to get_unformatted function
499
                    content.push(comment);
500
                    tag_end = this.pos - 1;
501
                    this.tag_type = 'SINGLE';
502
                } else if (tag_check === 'script' &&
503
                    (tag_complete.search('type') === -1 ||
504
                        (tag_complete.search('type') > -1 &&
505
                            tag_complete.search(/\b(text|application)\/(x-)?(javascript|ecmascript|jscript|livescript|(ld\+)?json)/) > -1))) {
506
                    if (!peek) {
507
                        this.record_tag(tag_check);
508
                        this.tag_type = 'SCRIPT';
509
                    }
510
                } else if (tag_check === 'style' &&
511
                    (tag_complete.search('type') === -1 ||
512
                        (tag_complete.search('type') > -1 && tag_complete.search('text/css') > -1))) {
513
                    if (!peek) {
514
                        this.record_tag(tag_check);
515
                        this.tag_type = 'STYLE';
516
                    }
517
                } else if (tag_check.charAt(0) === '!') { //peek for <! comment
518
                    // for comments content is already correct.
519
                    if (!peek) {
520
                        this.tag_type = 'SINGLE';
521
                        this.traverse_whitespace();
522
                    }
523
                } else if (!peek) {
524
                    if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
525
                        this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
526
                        this.tag_type = 'END';
527
                    } else { //otherwise it's a start-tag
528
                        this.record_tag(tag_check); //push it on the tag stack
529
                        if (tag_check.toLowerCase() !== 'html') {
530
                            this.indent_content = true;
531
                        }
532
                        this.tag_type = 'START';
533
                    }
534
535
                    // Allow preserving of newlines after a start or end tag
536
                    if (this.traverse_whitespace()) {
537
                        this.space_or_wrap(content);
538
                    }
539
540
                    if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
541
                        this.print_newline(false, this.output);
542
                        if (this.output.length && this.output[this.output.length - 2] !== '\n') {
543
                            this.print_newline(true, this.output);
544
                        }
545
                    }
546
                }
547
548
                if (peek) {
549
                    this.pos = orig_pos;
550
                    this.line_char_count = orig_line_char_count;
551
                }
552
553
                return content.join(''); //returns fully formatted tag
554
            };
555
556
            this.get_comment = function(start_pos) { //function to return comment content in its entirety
557
                // this is will have very poor perf, but will work for now.
558
                var comment = '',
559
                    delimiter = '>',
560
                    matched = false;
561
562
                this.pos = start_pos;
563
                var input_char = this.input.charAt(this.pos);
564
                this.pos++;
565
566
                while (this.pos <= this.input.length) {
567
                    comment += input_char;
568
569
                    // only need to check for the delimiter if the last chars match
570
                    if (comment.charAt(comment.length - 1) === delimiter.charAt(delimiter.length - 1) &&
571
                        comment.indexOf(delimiter) !== -1) {
572
                        break;
573
                    }
574
575
                    // only need to search for custom delimiter for the first few characters
576
                    if (!matched && comment.length < 10) {
577
                        if (comment.indexOf('<![if') === 0) { //peek for <![if conditional comment
578
                            delimiter = '<![endif]>';
579
                            matched = true;
580
                        } else if (comment.indexOf('<![cdata[') === 0) { //if it's a <[cdata[ comment...
581
                            delimiter = ']]>';
582
                            matched = true;
583
                        } else if (comment.indexOf('<![') === 0) { // some other ![ comment? ...
584
                            delimiter = ']>';
585
                            matched = true;
586
                        } else if (comment.indexOf('<!--') === 0) { // <!-- comment ...
587
                            delimiter = '-->';
588
                            matched = true;
589
                        } else if (comment.indexOf('{{!') === 0) { // {{! handlebars comment
590
                            delimiter = '}}';
591
                            matched = true;
592
                        } else if (comment.indexOf('<?') === 0) { // {{! handlebars comment
593
                            delimiter = '?>';
594
                            matched = true;
595
                        } else if (comment.indexOf('<%') === 0) { // {{! handlebars comment
596
                            delimiter = '%>';
597
                            matched = true;
598
                        }
599
                    }
600
601
                    input_char = this.input.charAt(this.pos);
602
                    this.pos++;
603
                }
604
605
                return comment;
606
            };
607
608
            function tokenMatcher(delimiter) {
609
                var token = '';
610
611
                var add = function(str) {
612
                    var newToken = token + str.toLowerCase();
613
                    token = newToken.length <= delimiter.length ? newToken : newToken.substr(newToken.length - delimiter.length, delimiter.length);
614
                };
615
616
                var doesNotMatch = function() {
617
                    return token.indexOf(delimiter) === -1;
618
                };
619
620
                return {
621
                    add: add,
622
                    doesNotMatch: doesNotMatch
623
                };
624
            }
625
626
            this.get_unformatted = function(delimiter, orig_tag) { //function to return unformatted content in its entirety
627
                if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
628
                    return '';
629
                }
630
                var input_char = '';
631
                var content = '';
632
                var space = true;
633
634
                var delimiterMatcher = tokenMatcher(delimiter);
635
636
                do {
637
638
                    if (this.pos >= this.input.length) {
639
                        return content;
640
                    }
641
642
                    input_char = this.input.charAt(this.pos);
643
                    this.pos++;
644
645
                    if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
646
                        if (!space) {
647
                            this.line_char_count--;
648
                            continue;
649
                        }
650
                        if (input_char === '\n' || input_char === '\r') {
651
                            content += '\n';
652
                            /*  Don't change tab indention for unformatted blocks.  If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array'
653
                for (var i=0; i<this.indent_level; i++) {
654
                  content += this.indent_string;
655
                }
656
                space = false; //...and make sure other indentation is erased
657
                */
658
                            this.line_char_count = 0;
659
                            continue;
660
                        }
661
                    }
662
                    content += input_char;
663
                    delimiterMatcher.add(input_char);
664
                    this.line_char_count++;
665
                    space = true;
666
667
                    if (indent_handlebars && input_char === '{' && content.length && content.charAt(content.length - 2) === '{') {
668
                        // Handlebars expressions in strings should also be unformatted.
669
                        content += this.get_unformatted('}}');
670
                        // Don't consider when stopping for delimiters.
671
                    }
672
                } while (delimiterMatcher.doesNotMatch());
673
674
                return content;
675
            };
676
677
            this.get_token = function() { //initial handler for token-retrieval
678
                var token;
679
680
                if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript
681
                    var type = this.last_token.substr(7);
682
                    token = this.get_contents_to(type);
683
                    if (typeof token !== 'string') {
684
                        return token;
685
                    }
686
                    return [token, 'TK_' + type];
687
                }
688
                if (this.current_mode === 'CONTENT') {
689
                    token = this.get_content();
690
                    if (typeof token !== 'string') {
691
                        return token;
692
                    } else {
693
                        return [token, 'TK_CONTENT'];
694
                    }
695
                }
696
697
                if (this.current_mode === 'TAG') {
698
                    token = this.get_tag();
699
                    if (typeof token !== 'string') {
700
                        return token;
701
                    } else {
702
                        var tag_name_type = 'TK_TAG_' + this.tag_type;
703
                        return [token, tag_name_type];
704
                    }
705
                }
706
            };
707
708
            this.get_full_indent = function(level) {
709
                level = this.indent_level + level || 0;
710
                if (level < 1) {
711
                    return '';
712
                }
713
714
                return Array(level + 1).join(this.indent_string);
715
            };
716
717
            this.is_unformatted = function(tag_check, unformatted) {
718
                //is this an HTML5 block-level link?
719
                if (!this.Utils.in_array(tag_check, unformatted)) {
720
                    return false;
721
                }
722
723
                if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)) {
724
                    return true;
725
                }
726
727
                //at this point we have an  tag; is its first child something we want to remain
728
                //unformatted?
729
                var next_tag = this.get_tag(true /* peek. */ );
730
731
                // test next_tag to see if it is just html tag (no external content)
732
                var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/);
733
734
                // if next_tag comes back but is not an isolated tag, then
735
                // let's treat the 'a' tag as having content
736
                // and respect the unformatted option
737
                if (!tag || this.Utils.in_array(tag, unformatted)) {
738
                    return true;
739
                } else {
740
                    return false;
741
                }
742
            };
743
744
            this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions
745
746
                this.input = js_source || ''; //gets the input for the Parser
747
748
                // HACK: newline parsing inconsistent. This brute force normalizes the input.
749
                this.input = this.input.replace(/\r\n|[\r\u2028\u2029]/g, '\n');
750
751
                this.output = [];
752
                this.indent_character = indent_character;
753
                this.indent_string = '';
754
                this.indent_size = indent_size;
755
                this.brace_style = brace_style;
756
                this.indent_level = 0;
757
                this.wrap_line_length = wrap_line_length;
758
                this.line_char_count = 0; //count to see if wrap_line_length was exceeded
759
760
                for (var i = 0; i < this.indent_size; i++) {
761
                    this.indent_string += this.indent_character;
762
                }
763
764
                this.print_newline = function(force, arr) {
765
                    this.line_char_count = 0;
766
                    if (!arr || !arr.length) {
767
                        return;
768
                    }
769
                    if (force || (arr[arr.length - 1] !== '\n')) { //we might want the extra line
770
                        if ((arr[arr.length - 1] !== '\n')) {
771
                            arr[arr.length - 1] = rtrim(arr[arr.length - 1]);
772
                        }
773
                        arr.push('\n');
774
                    }
775
                };
776
777
                this.print_indentation = function(arr) {
778
                    for (var i = 0; i < this.indent_level; i++) {
779
                        arr.push(this.indent_string);
780
                        this.line_char_count += this.indent_string.length;
781
                    }
782
                };
783
784
                this.print_token = function(text) {
785
                    // Avoid printing initial whitespace.
786
                    if (this.is_whitespace(text) && !this.output.length) {
787
                        return;
788
                    }
789
                    if (text || text !== '') {
790
                        if (this.output.length && this.output[this.output.length - 1] === '\n') {
791
                            this.print_indentation(this.output);
792
                            text = ltrim(text);
793
                        }
794
                    }
795
                    this.print_token_raw(text);
796
                };
797
798
                this.print_token_raw = function(text) {
799
                    // If we are going to print newlines, truncate trailing
800
                    // whitespace, as the newlines will represent the space.
801
                    if (this.newlines > 0) {
802
                        text = rtrim(text);
803
                    }
804
805
                    if (text && text !== '') {
806
                        if (text.length > 1 && text.charAt(text.length - 1) === '\n') {
807
                            // unformatted tags can grab newlines as their last character
808
                            this.output.push(text.slice(0, -1));
809
                            this.print_newline(false, this.output);
810
                        } else {
811
                            this.output.push(text);
812
                        }
813
                    }
814
815
                    for (var n = 0; n < this.newlines; n++) {
816
                        this.print_newline(n > 0, this.output);
817
                    }
818
                    this.newlines = 0;
819
                };
820
821
                this.indent = function() {
822
                    this.indent_level++;
823
                };
824
825
                this.unindent = function() {
826
                    if (this.indent_level > 0) {
827
                        this.indent_level--;
828
                    }
829
                };
830
            };
831
            return this;
832
        }
833
834
        /*_____________________--------------------_____________________*/
835
836
        multi_parser = new Parser(); //wrapping functions Parser
837
        multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values
838
839
        while (true) {
840
            var t = multi_parser.get_token();
841
            multi_parser.token_text = t[0];
842
            multi_parser.token_type = t[1];
843
844
            if (multi_parser.token_type === 'TK_EOF') {
845
                break;
846
            }
847
848
            switch (multi_parser.token_type) {
849
                case 'TK_TAG_START':
850
                    multi_parser.print_newline(false, multi_parser.output);
851
                    multi_parser.print_token(multi_parser.token_text);
852
                    if (multi_parser.indent_content) {
853
                        multi_parser.indent();
854
                        multi_parser.indent_content = false;
855
                    }
856
                    multi_parser.current_mode = 'CONTENT';
857
                    break;
858
                case 'TK_TAG_STYLE':
859
                case 'TK_TAG_SCRIPT':
860
                    multi_parser.print_newline(false, multi_parser.output);
861
                    multi_parser.print_token(multi_parser.token_text);
862
                    multi_parser.current_mode = 'CONTENT';
863
                    break;
864
                case 'TK_TAG_END':
865
                    //Print new line only if the tag has no content and has child
866
                    if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
867
                        var tag_name = multi_parser.token_text.match(/\w+/)[0];
868
                        var tag_extracted_from_last_output = null;
869
                        if (multi_parser.output.length) {
870
                            tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/);
871
                        }
872
                        if (tag_extracted_from_last_output === null ||
873
                            (tag_extracted_from_last_output[1] !== tag_name && !multi_parser.Utils.in_array(tag_extracted_from_last_output[1], unformatted))) {
874
                            multi_parser.print_newline(false, multi_parser.output);
875
                        }
876
                    }
877
                    multi_parser.print_token(multi_parser.token_text);
878
                    multi_parser.current_mode = 'CONTENT';
879
                    break;
880
                case 'TK_TAG_SINGLE':
881
                    // Don't add a newline before elements that should remain unformatted.
882
                    var tag_check = multi_parser.token_text.match(/^\s*<([a-z-]+)/i);
883
                    if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) {
884
                        multi_parser.print_newline(false, multi_parser.output);
885
                    }
886
                    multi_parser.print_token(multi_parser.token_text);
887
                    multi_parser.current_mode = 'CONTENT';
888
                    break;
889
                case 'TK_TAG_HANDLEBARS_ELSE':
890
                    // Don't add a newline if opening {{#if}} tag is on the current line
891
                    var foundIfOnCurrentLine = false;
892
                    for (var lastCheckedOutput = multi_parser.output.length - 1; lastCheckedOutput >= 0; lastCheckedOutput--) {
893
                        if (multi_parser.output[lastCheckedOutput] === '\n') {
894
                            break;
895
                        } else {
896
                            if (multi_parser.output[lastCheckedOutput].match(/{{#if/)) {
897
                                foundIfOnCurrentLine = true;
898
                                break;
899
                            }
900
                        }
901
                    }
902
                    if (!foundIfOnCurrentLine) {
903
                        multi_parser.print_newline(false, multi_parser.output);
904
                    }
905
                    multi_parser.print_token(multi_parser.token_text);
906
                    if (multi_parser.indent_content) {
907
                        multi_parser.indent();
908
                        multi_parser.indent_content = false;
909
                    }
910
                    multi_parser.current_mode = 'CONTENT';
911
                    break;
912
                case 'TK_TAG_HANDLEBARS_COMMENT':
913
                    multi_parser.print_token(multi_parser.token_text);
914
                    multi_parser.current_mode = 'TAG';
915
                    break;
916
                case 'TK_CONTENT':
917
                    multi_parser.print_token(multi_parser.token_text);
918
                    multi_parser.current_mode = 'TAG';
919
                    break;
920
                case 'TK_STYLE':
921
                case 'TK_SCRIPT':
922
                    if (multi_parser.token_text !== '') {
923
                        multi_parser.print_newline(false, multi_parser.output);
924
                        var text = multi_parser.token_text,
925
                            _beautifier,
926
                            script_indent_level = 1;
927
                        if (multi_parser.token_type === 'TK_SCRIPT') {
928
                            _beautifier = typeof js_beautify === 'function' && js_beautify;
929
                        } else if (multi_parser.token_type === 'TK_STYLE') {
930
                            _beautifier = typeof css_beautify === 'function' && css_beautify;
931
                        }
932
933
                        if (options.indent_scripts === "keep") {
934
                            script_indent_level = 0;
935
                        } else if (options.indent_scripts === "separate") {
936
                            script_indent_level = -multi_parser.indent_level;
937
                        }
938
939
                        var indentation = multi_parser.get_full_indent(script_indent_level);
940
                        if (_beautifier) {
941
942
                            // call the Beautifier if avaliable
943
                            var Child_options = function() {
944
                                this.eol = '\n';
945
                            };
946
                            Child_options.prototype = options;
947
                            var child_options = new Child_options();
948
                            text = _beautifier(text.replace(/^\s*/, indentation), child_options);
949
                        } else {
950
                            // simply indent the string otherwise
951
                            var white = text.match(/^\s*/)[0];
952
                            var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
953
                            var reindent = multi_parser.get_full_indent(script_indent_level - _level);
954
                            text = text.replace(/^\s*/, indentation)
955
                                .replace(/\r\n|\r|\n/g, '\n' + reindent)
956
                                .replace(/\s+$/, '');
957
                        }
958
                        if (text) {
959
                            multi_parser.print_token_raw(text);
960
                            multi_parser.print_newline(true, multi_parser.output);
961
                        }
962
                    }
963
                    multi_parser.current_mode = 'TAG';
964
                    break;
965
                default:
966
                    // We should not be getting here but we don't want to drop input on the floor
967
                    // Just output the text and move on
968
                    if (multi_parser.token_text !== '') {
969
                        multi_parser.print_token(multi_parser.token_text);
970
                    }
971
                    break;
972
            }
973
            multi_parser.last_token = multi_parser.token_type;
974
            multi_parser.last_text = multi_parser.token_text;
975
        }
976
        var sweet_code = multi_parser.output.join('').replace(/[\r\n\t ]+$/, '');
977
978
        // establish end_with_newline
979
        if (end_with_newline) {
980
            sweet_code += '\n';
981
        }
982
983
        if (eol !== '\n') {
984
            sweet_code = sweet_code.replace(/[\n]/g, eol);
985
        }
986
987
        return sweet_code;
988
    }
989
990
	return {
991
		html_beautify: function(html_source, options) {
992
			return style_html(html_source, options, js_beautify.js_beautify, css_beautify.css_beautify);
993
		}
994
	};
995
});
(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/plugins/ternWorkerCore.js (-1 / +34 lines)
Lines 508-514 function(Tern, defaultOptions, Deferred, Objects, Serialize, Messages, i18nUtil) Link Here
508
			} else {
508
			} else {
509
		       callback(null, {message: Messages['failedToComputeOccurrencesNoServer']});
509
		       callback(null, {message: Messages['failedToComputeOccurrencesNoServer']});
510
		   }
510
		   }
511
		}
511
		},
512
		/* lint message handler */
513
		'beautify': function(args, callback) {
514
			if(ternserver) {
515
				var query =
516
					{
517
						type: "beautify",  //$NON-NLS-1$
518
						file: args.meta.location,
519
						args: {
520
							config: args.config,
521
							start: args.start,
522
							end: args.end,
523
							contentType: args.contentType
524
						}
525
					};
526
				ternserver.request(
527
					{
528
						query: query,
529
						files: args.files
530
					},
531
					function(error, text) {
532
						if(error) {
533
							callback({request: 'beautify', error: error.message, message: Messages['failedToFormat']}); //$NON-NLS-1$
534
						} else if(text) {
535
							callback({request: 'beautify', text: text}); //$NON-NLS-1$
536
						} else {
537
							callback({request: 'beautify', text: ""}); //$NON-NLS-1$
538
						}
539
					}
540
				);
541
			} else {
542
				callback(null, {message: Messages['failedToFormatNoServer']});
543
			}
544
		},
512
	};
545
	};
513
546
514
	var ternID = 0;
547
	var ternID = 0;
(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/ternPlugins/beautifier.js (+62 lines)
Added Link Here
1
/*******************************************************************************
2
 * @license
3
 * Copyright (c) 2016 IBM Corporation and others.
4
 * All rights reserved. This program and the accompanying materials are made 
5
 * available under the terms of the Eclipse Public License v1.0 
6
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
7
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
8
 *
9
 * Contributors:
10
 *	 IBM Corporation - Allow original requirejs plugin to find files in Orion workspace
11
 *******************************************************************************/
12
/* eslint-disable missing-nls */
13
/*eslint-env node, amd*/
14
/*globals tern tern */
15
define([
16
	"tern/lib/tern",
17
	"beautifier/beautifier"
18
], function(tern, Beautifier) {
19
20
	tern.registerPlugin("beautifier", /* @callback */ function(server, options) {
21
		return {
22
			//don't need any passes yet
23
		};
24
	});
25
26
	tern.defineQueryType("beautify", {
27
		takesFile: true,
28
		/**
29
		 * @callback
30
		 */
31
		run: function(server, query, file) {
32
			return format(query, file);
33
		}
34
	});
35
36
	/**
37
	 * @description Format the code using the right beautifier
38
	 * @param {Object} query The original Tern query object
39
	 * @param {Object} file The file object from Tern 
40
	 */
41
	function format(query, file) {
42
		// we should format using the right beautifier based on the file extension
43
		var args = query.args;
44
		var text = file.text;
45
		var contentType = "";
46
		if (args) {
47
			if (args.start && args.end) {
48
				text = file.text.substring(args.start, args.end);
49
			}
50
			contentType = args.contentType;
51
		}
52
		switch(contentType) {
53
			case "text/html" :
54
				return Beautifier.html_beautify(text, query.args.config);
55
			case "text/css" :
56
				return Beautifier.css_beautify(text, query.args.config);
57
			case "application/javascript" :
58
				return Beautifier.js_beautify(text, query.args.config);
59
		}
60
		return text;
61
	}
62
});
(-)a/bundles/org.eclipse.orion.client.javascript/web/beautifier/lib/beautify-js.js (+2254 lines)
Added Link Here
1
/*eslint-env amd, node*/
2
/*
3
4
  The MIT License (MIT)
5
6
  Copyright (c) 2007-2013 Einar Lielmanis and contributors.
7
8
  Permission is hereby granted, free of charge, to any person
9
  obtaining a copy of this software and associated documentation files
10
  (the "Software"), to deal in the Software without restriction,
11
  including without limitation the rights to use, copy, modify, merge,
12
  publish, distribute, sublicense, and/or sell copies of the Software,
13
  and to permit persons to whom the Software is furnished to do so,
14
  subject to the following conditions:
15
16
  The above copyright notice and this permission notice shall be
17
  included in all copies or substantial portions of the Software.
18
19
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
23
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
24
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
  SOFTWARE.
27
28
 JS Beautifier
29
---------------
30
31
32
  Written by Einar Lielmanis, <einar@jsbeautifier.org>
33
      http://jsbeautifier.org/
34
35
  Originally converted to javascript by Vital, <vital76@gmail.com>
36
  "End braces on own line" added by Chris J. Shull, <chrisjshull@gmail.com>
37
  Parsing improvements for brace-less statements by Liam Newman <bitwiseman@gmail.com>
38
39
40
  Usage:
41
    js_beautify(js_source_text);
42
    js_beautify(js_source_text, options);
43
44
  The options are:
45
    indent_size (default 4)          - indentation size,
46
    indent_char (default space)      - character to indent with,
47
    preserve_newlines (default true) - whether existing line breaks should be preserved,
48
    max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk,
49
50
    jslint_happy (default false) - if true, then jslint-stricter mode is enforced.
51
52
            jslint_happy        !jslint_happy
53
            ---------------------------------
54
            function ()         function()
55
56
            switch () {         switch() {
57
            case 1:               case 1:
58
              break;                break;
59
            }                   }
60
61
    space_after_anon_function (default false) - should the space before an anonymous function's parens be added, "function()" vs "function ()",
62
          NOTE: This option is overriden by jslint_happy (i.e. if jslint_happy is true, space_after_anon_function is true by design)
63
64
    brace_style (default "collapse") - "collapse-preserve-inline" | "collapse" | "expand" | "end-expand" | "none"
65
            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, or attempt to keep them where they are.
66
67
    space_before_conditional (default true) - should the space before conditional statement be added, "if(true)" vs "if (true)",
68
69
    unescape_strings (default false) - should printable characters in strings encoded in \xNN notation be unescaped, "example" vs "\x65\x78\x61\x6d\x70\x6c\x65"
70
71
    wrap_line_length (default unlimited) - lines should wrap at next opportunity after this number of characters.
72
          NOTE: This is not a hard limit. Lines will continue until a point where a newline would
73
                be preserved if it were present.
74
75
    end_with_newline (default false)  - end output with a newline
76
77
78
    e.g
79
80
    js_beautify(js_source_text, {
81
      'indent_size': 1,
82
      'indent_char': '\t'
83
    });
84
85
*/
86
define([
87
	"acorn/dist/acorn"
88
], function(acorn) {
89
90
91
	// Object.values polyfill found here:
92
	// http://tokenposts.blogspot.com.au/2012/04/javascript-objectkeys-browser.html
93
	if (!Object.values) {
94
	    Object.values = function(o) {
95
	        if (o !== Object(o)) {
96
	            throw new TypeError('Object.values called on a non-object');
97
	        }
98
	        var k = [],
99
	            p;
100
	        for (p in o) {
101
	            if (Object.prototype.hasOwnProperty.call(o, p)) {
102
	                k.push(o[p]);
103
	            }
104
	        }
105
	        return k;
106
	    };
107
	}
108
109
110
	function js_beautify(js_source_text, options) {
111
112
        function in_array(what, arr) {
113
            for (var i = 0; i < arr.length; i += 1) {
114
                if (arr[i] === what) {
115
                    return true;
116
                }
117
            }
118
            return false;
119
        }
120
121
        function trim(s) {
122
            return s.replace(/^\s+|\s+$/g, '');
123
        }
124
125
        function ltrim(s) {
126
            return s.replace(/^\s+/g, '');
127
        }
128
129
        // function rtrim(s) {
130
        //     return s.replace(/\s+$/g, '');
131
        // }
132
133
        function sanitizeOperatorPosition(opPosition) {
134
            opPosition = opPosition || OPERATOR_POSITION.before_newline;
135
136
            var validPositionValues = Object.values(OPERATOR_POSITION);
137
138
            if (!in_array(opPosition, validPositionValues)) {
139
                throw new Error("Invalid Option Value: The option 'operator_position' must be one of the following values\n" +
140
                    validPositionValues +
141
                    "\nYou passed in: '" + opPosition + "'");
142
            }
143
144
            return opPosition;
145
        }
146
147
        var OPERATOR_POSITION = {
148
            before_newline: 'before-newline',
149
            after_newline: 'after-newline',
150
            preserve_newline: 'preserve-newline',
151
        };
152
153
        var OPERATOR_POSITION_BEFORE_OR_PRESERVE = [OPERATOR_POSITION.before_newline, OPERATOR_POSITION.preserve_newline];
154
155
        var MODE = {
156
            BlockStatement: 'BlockStatement', // 'BLOCK'
157
            Statement: 'Statement', // 'STATEMENT'
158
            ObjectLiteral: 'ObjectLiteral', // 'OBJECT',
159
            ArrayLiteral: 'ArrayLiteral', //'[EXPRESSION]',
160
            ForInitializer: 'ForInitializer', //'(FOR-EXPRESSION)',
161
            Conditional: 'Conditional', //'(COND-EXPRESSION)',
162
            Expression: 'Expression' //'(EXPRESSION)'
163
        };
164
165
        function Beautifier(js_source_text, options) {
166
            "use strict";
167
            var output;
168
            var tokens = [],
169
                token_pos;
170
            var Tokenizer;
171
            var current_token;
172
            var last_type, last_last_text, indent_string;
173
            var flags, previous_flags, flag_store;
174
            var prefix;
175
176
            var handlers, opt;
177
            var baseIndentString = '';
178
179
            handlers = {
180
                'TK_START_EXPR': handle_start_expr,
181
                'TK_END_EXPR': handle_end_expr,
182
                'TK_START_BLOCK': handle_start_block,
183
                'TK_END_BLOCK': handle_end_block,
184
                'TK_WORD': handle_word,
185
                'TK_RESERVED': handle_word,
186
                'TK_SEMICOLON': handle_semicolon,
187
                'TK_STRING': handle_string,
188
                'TK_EQUALS': handle_equals,
189
                'TK_OPERATOR': handle_operator,
190
                'TK_COMMA': handle_comma,
191
                'TK_BLOCK_COMMENT': handle_block_comment,
192
                'TK_COMMENT': handle_comment,
193
                'TK_DOT': handle_dot,
194
                'TK_UNKNOWN': handle_unknown,
195
                'TK_EOF': handle_eof
196
            };
197
198
            function create_flags(flags_base, mode) {
199
                var next_indent_level = 0;
200
                if (flags_base) {
201
                    next_indent_level = flags_base.indentation_level;
202
                    if (!output.just_added_newline() &&
203
                        flags_base.line_indent_level > next_indent_level) {
204
                        next_indent_level = flags_base.line_indent_level;
205
                    }
206
                }
207
208
                var next_flags = {
209
                    mode: mode,
210
                    parent: flags_base,
211
                    last_text: flags_base ? flags_base.last_text : '', // last token text
212
                    last_word: flags_base ? flags_base.last_word : '', // last 'TK_WORD' passed
213
                    declaration_statement: false,
214
                    declaration_assignment: false,
215
                    multiline_frame: false,
216
                    inline_frame: false,
217
                    if_block: false,
218
                    else_block: false,
219
                    do_block: false,
220
                    do_while: false,
221
                    import_block: false,
222
                    in_case_statement: false, // switch(..){ INSIDE HERE }
223
                    in_case: false, // we're on the exact line with "case 0:"
224
                    case_body: false, // the indented case-action block
225
                    indentation_level: next_indent_level,
226
                    line_indent_level: flags_base ? flags_base.line_indent_level : next_indent_level,
227
                    start_line_index: output.get_line_number(),
228
                    ternary_depth: 0
229
                };
230
                return next_flags;
231
            }
232
233
            // Some interpreters have unexpected results with foo = baz || bar;
234
            options = options ? options : {};
235
            opt = {};
236
237
            // compatibility
238
            if (options.braces_on_own_line !== undefined) { //graceful handling of deprecated option
239
                opt.brace_style = options.braces_on_own_line ? "expand" : "collapse";
240
            }
241
            opt.brace_style = options.brace_style ? options.brace_style : (opt.brace_style ? opt.brace_style : "collapse");
242
243
            // graceful handling of deprecated option
244
            if (opt.brace_style === "expand-strict") {
245
                opt.brace_style = "expand";
246
            }
247
248
            opt.indent_size = options.indent_size ? parseInt(options.indent_size, 10) : 4;
249
            opt.indent_char = options.indent_char ? options.indent_char : ' ';
250
            opt.eol = options.eol ? options.eol : 'auto';
251
            opt.preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
252
            opt.break_chained_methods = (options.break_chained_methods === undefined) ? false : options.break_chained_methods;
253
            opt.max_preserve_newlines = (options.max_preserve_newlines === undefined) ? 0 : parseInt(options.max_preserve_newlines, 10);
254
            opt.space_in_paren = (options.space_in_paren === undefined) ? false : options.space_in_paren;
255
            opt.space_in_empty_paren = (options.space_in_empty_paren === undefined) ? false : options.space_in_empty_paren;
256
            opt.jslint_happy = (options.jslint_happy === undefined) ? false : options.jslint_happy;
257
            opt.space_after_anon_function = (options.space_after_anon_function === undefined) ? false : options.space_after_anon_function;
258
            opt.keep_array_indentation = (options.keep_array_indentation === undefined) ? false : options.keep_array_indentation;
259
            opt.space_before_conditional = (options.space_before_conditional === undefined) ? true : options.space_before_conditional;
260
            opt.unescape_strings = (options.unescape_strings === undefined) ? false : options.unescape_strings;
261
            opt.wrap_line_length = (options.wrap_line_length === undefined) ? 0 : parseInt(options.wrap_line_length, 10);
262
            opt.e4x = (options.e4x === undefined) ? false : options.e4x;
263
            opt.end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
264
            opt.comma_first = (options.comma_first === undefined) ? false : options.comma_first;
265
            opt.operator_position = sanitizeOperatorPosition(options.operator_position);
266
267
            // For testing of beautify ignore:start directive
268
            opt.test_output_raw = (options.test_output_raw === undefined) ? false : options.test_output_raw;
269
270
            // force opt.space_after_anon_function to true if opt.jslint_happy
271
            if (opt.jslint_happy) {
272
                opt.space_after_anon_function = true;
273
            }
274
275
            if (options.indent_with_tabs) {
276
                opt.indent_char = '\t';
277
                opt.indent_size = 1;
278
            }
279
280
            if (opt.eol === 'auto') {
281
                opt.eol = '\n';
282
                if (js_source_text && acorn.lineBreak.test(js_source_text || '')) {
283
                    opt.eol = js_source_text.match(acorn.lineBreak)[0];
284
                }
285
            }
286
287
            opt.eol = opt.eol.replace(/\\r/, '\r').replace(/\\n/, '\n');
288
289
            //----------------------------------
290
            indent_string = '';
291
            while (opt.indent_size > 0) {
292
                indent_string += opt.indent_char;
293
                opt.indent_size -= 1;
294
            }
295
296
            var preindent_index = 0;
297
            if (js_source_text && js_source_text.length) {
298
                while ((js_source_text.charAt(preindent_index) === ' ' ||
299
                        js_source_text.charAt(preindent_index) === '\t')) {
300
                    baseIndentString += js_source_text.charAt(preindent_index);
301
                    preindent_index += 1;
302
                }
303
                js_source_text = js_source_text.substring(preindent_index);
304
            }
305
306
            last_type = 'TK_START_BLOCK'; // last token type
307
            last_last_text = ''; // pre-last token text
308
            output = new Output(indent_string, baseIndentString);
309
310
            // If testing the ignore directive, start with output disable set to true
311
            output.raw = opt.test_output_raw;
312
313
314
            // Stack of parsing/formatting states, including MODE.
315
            // We tokenize, parse, and output in an almost purely a forward-only stream of token input
316
            // and formatted output.  This makes the beautifier less accurate than full parsers
317
            // but also far more tolerant of syntax errors.
318
            //
319
            // For example, the default mode is MODE.BlockStatement. If we see a '{' we push a new frame of type
320
            // MODE.BlockStatement on the the stack, even though it could be object literal.  If we later
321
            // encounter a ":", we'll switch to to MODE.ObjectLiteral.  If we then see a ";",
322
            // most full parsers would die, but the beautifier gracefully falls back to
323
            // MODE.BlockStatement and continues on.
324
            flag_store = [];
325
            set_mode(MODE.BlockStatement);
326
327
            this.beautify = function() {
328
329
                /*jshint onevar:true */
330
                var local_token, sweet_code;
331
                Tokenizer = new tokenizer(js_source_text, opt, indent_string);
332
                tokens = Tokenizer.tokenize();
333
                token_pos = 0;
334
335
                function get_local_token() {
336
                    local_token = get_token();
337
                    return local_token;
338
                }
339
340
                while (get_local_token()) {
341
                    for (var i = 0; i < local_token.comments_before.length; i++) {
342
                        // The cleanest handling of inline comments is to treat them as though they aren't there.
343
                        // Just continue formatting and the behavior should be logical.
344
                        // Also ignore unknown tokens.  Again, this should result in better behavior.
345
                        handle_token(local_token.comments_before[i]);
346
                    }
347
                    handle_token(local_token);
348
349
                    last_last_text = flags.last_text;
350
                    last_type = local_token.type;
351
                    flags.last_text = local_token.text;
352
353
                    token_pos += 1;
354
                }
355
356
                sweet_code = output.get_code();
357
                if (opt.end_with_newline) {
358
                    sweet_code += '\n';
359
                }
360
361
                if (opt.eol !== '\n') {
362
                    sweet_code = sweet_code.replace(/[\n]/g, opt.eol);
363
                }
364
365
                return sweet_code;
366
            };
367
368
            function handle_token(local_token) {
369
                var newlines = local_token.newlines;
370
                var keep_whitespace = opt.keep_array_indentation && is_array(flags.mode);
371
372
                if (keep_whitespace) {
373
                    for (var i = 0; i < newlines; i += 1) {
374
                        print_newline(i > 0);
375
                    }
376
                } else {
377
                    if (opt.max_preserve_newlines && newlines > opt.max_preserve_newlines) {
378
                        newlines = opt.max_preserve_newlines;
379
                    }
380
381
                    if (opt.preserve_newlines) {
382
                        if (local_token.newlines > 1) {
383
                            print_newline();
384
                            for (var j = 1; j < newlines; j += 1) {
385
                                print_newline(true);
386
                            }
387
                        }
388
                    }
389
                }
390
391
                current_token = local_token;
392
                handlers[current_token.type]();
393
            }
394
395
            // we could use just string.split, but
396
            // IE doesn't like returning empty strings
397
            function split_linebreaks(s) {
398
                //return s.split(/\x0d\x0a|\x0a/);
399
400
                s = s.replace(acorn.allLineBreaks, '\n');
401
                var out = [],
402
                    idx = s.indexOf("\n");
403
                while (idx !== -1) {
404
                    out.push(s.substring(0, idx));
405
                    s = s.substring(idx + 1);
406
                    idx = s.indexOf("\n");
407
                }
408
                if (s.length) {
409
                    out.push(s);
410
                }
411
                return out;
412
            }
413
414
            var newline_restricted_tokens = ['break', 'contiue', 'return', 'throw'];
415
416
            function allow_wrap_or_preserved_newline(force_linewrap) {
417
                force_linewrap = (force_linewrap === undefined) ? false : force_linewrap;
418
419
                // Never wrap the first token on a line
420
                if (output.just_added_newline()) {
421
                    return;
422
                }
423
424
                var shouldPreserveOrForce = (opt.preserve_newlines && current_token.wanted_newline) || force_linewrap;
425
                var operatorLogicApplies = in_array(flags.last_text, Tokenizer.positionable_operators) || in_array(current_token.text, Tokenizer.positionable_operators);
426
427
                if (operatorLogicApplies) {
428
                    var shouldPrintOperatorNewline = (
429
                            in_array(flags.last_text, Tokenizer.positionable_operators) &&
430
                            in_array(opt.operator_position, OPERATOR_POSITION_BEFORE_OR_PRESERVE)
431
                        ) ||
432
                        in_array(current_token.text, Tokenizer.positionable_operators);
433
                    shouldPreserveOrForce = shouldPreserveOrForce && shouldPrintOperatorNewline;
434
                }
435
436
                if (shouldPreserveOrForce) {
437
                    print_newline(false, true);
438
                } else if (opt.wrap_line_length) {
439
                    if (last_type === 'TK_RESERVED' && in_array(flags.last_text, newline_restricted_tokens)) {
440
                        // These tokens should never have a newline inserted
441
                        // between them and the following expression.
442
                        return;
443
                    }
444
                    var proposed_line_length = output.current_line.get_character_count() + current_token.text.length +
445
                        (output.space_before_token ? 1 : 0);
446
                    if (proposed_line_length >= opt.wrap_line_length) {
447
                        print_newline(false, true);
448
                    }
449
                }
450
            }
451
452
            function print_newline(force_newline, preserve_statement_flags) {
453
                if (!preserve_statement_flags) {
454
                    if (flags.last_text !== ';' && flags.last_text !== ',' && flags.last_text !== '=' && last_type !== 'TK_OPERATOR') {
455
                        while (flags.mode === MODE.Statement && !flags.if_block && !flags.do_block) {
456
                            restore_mode();
457
                        }
458
                    }
459
                }
460
461
                if (output.add_new_line(force_newline)) {
462
                    flags.multiline_frame = true;
463
                }
464
            }
465
466
            function print_token_line_indentation() {
467
                if (output.just_added_newline()) {
468
                    if (opt.keep_array_indentation && is_array(flags.mode) && current_token.wanted_newline) {
469
                        output.current_line.push(current_token.whitespace_before);
470
                        output.space_before_token = false;
471
                    } else if (output.set_indent(flags.indentation_level)) {
472
                        flags.line_indent_level = flags.indentation_level;
473
                    }
474
                }
475
            }
476
477
            function print_token(printable_token) {
478
                if (output.raw) {
479
                    output.add_raw_token(current_token);
480
                    return;
481
                }
482
483
                if (opt.comma_first && last_type === 'TK_COMMA' &&
484
                    output.just_added_newline()) {
485
                    if (output.previous_line.last() === ',') {
486
                        var popped = output.previous_line.pop();
487
                        // if the comma was already at the start of the line,
488
                        // pull back onto that line and reprint the indentation
489
                        if (output.previous_line.is_empty()) {
490
                            output.previous_line.push(popped);
491
                            output.trim(true);
492
                            output.current_line.pop();
493
                            output.trim();
494
                        }
495
496
                        // add the comma in front of the next token
497
                        print_token_line_indentation();
498
                        output.add_token(',');
499
                        output.space_before_token = true;
500
                    }
501
                }
502
503
                printable_token = printable_token || current_token.text;
504
                print_token_line_indentation();
505
                output.add_token(printable_token);
506
            }
507
508
            function indent() {
509
                flags.indentation_level += 1;
510
            }
511
512
            function deindent() {
513
                if (flags.indentation_level > 0 &&
514
                    ((!flags.parent) || flags.indentation_level > flags.parent.indentation_level)) {
515
                    flags.indentation_level -= 1;
516
517
                }
518
            }
519
520
            function set_mode(mode) {
521
                if (flags) {
522
                    flag_store.push(flags);
523
                    previous_flags = flags;
524
                } else {
525
                    previous_flags = create_flags(null, mode);
526
                }
527
528
                flags = create_flags(previous_flags, mode);
529
            }
530
531
            function is_array(mode) {
532
                return mode === MODE.ArrayLiteral;
533
            }
534
535
            function is_expression(mode) {
536
                return in_array(mode, [MODE.Expression, MODE.ForInitializer, MODE.Conditional]);
537
            }
538
539
            function restore_mode() {
540
                if (flag_store.length > 0) {
541
                    previous_flags = flags;
542
                    flags = flag_store.pop();
543
                    if (previous_flags.mode === MODE.Statement) {
544
                        output.remove_redundant_indentation(previous_flags);
545
                    }
546
                }
547
            }
548
549
            function start_of_object_property() {
550
                return flags.parent.mode === MODE.ObjectLiteral && flags.mode === MODE.Statement && (
551
                    (flags.last_text === ':' && flags.ternary_depth === 0) || (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['get', 'set'])));
552
            }
553
554
            function start_of_statement() {
555
                if (
556
                    (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['var', 'let', 'const']) && current_token.type === 'TK_WORD') ||
557
                    (last_type === 'TK_RESERVED' && flags.last_text === 'do') ||
558
                    (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['return', 'throw']) && !current_token.wanted_newline) ||
559
                    (last_type === 'TK_RESERVED' && flags.last_text === 'else' && !(current_token.type === 'TK_RESERVED' && current_token.text === 'if')) ||
560
                    (last_type === 'TK_END_EXPR' && (previous_flags.mode === MODE.ForInitializer || previous_flags.mode === MODE.Conditional)) ||
561
                    (last_type === 'TK_WORD' && flags.mode === MODE.BlockStatement &&
562
                        !flags.in_case &&
563
                        !(current_token.text === '--' || current_token.text === '++') &&
564
                        last_last_text !== 'function' &&
565
                        current_token.type !== 'TK_WORD' && current_token.type !== 'TK_RESERVED') ||
566
                    (flags.mode === MODE.ObjectLiteral && (
567
                        (flags.last_text === ':' && flags.ternary_depth === 0) || (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['get', 'set']))))
568
                ) {
569
570
                    set_mode(MODE.Statement);
571
                    indent();
572
573
                    if (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['var', 'let', 'const']) && current_token.type === 'TK_WORD') {
574
                        flags.declaration_statement = true;
575
                    }
576
577
                    // Issue #276:
578
                    // If starting a new statement with [if, for, while, do], push to a new line.
579
                    // if (a) if (b) if(c) d(); else e(); else f();
580
                    if (!start_of_object_property()) {
581
                        allow_wrap_or_preserved_newline(
582
                            current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['do', 'for', 'if', 'while']));
583
                    }
584
585
                    return true;
586
                }
587
                return false;
588
            }
589
590
            function all_lines_start_with(lines, c) {
591
                for (var i = 0; i < lines.length; i++) {
592
                    var line = trim(lines[i]);
593
                    if (line.charAt(0) !== c) {
594
                        return false;
595
                    }
596
                }
597
                return true;
598
            }
599
600
            function each_line_matches_indent(lines, indent) {
601
                var i = 0,
602
                    len = lines.length,
603
                    line;
604
                for (; i < len; i++) {
605
                    line = lines[i];
606
                    // allow empty lines to pass through
607
                    if (line && line.indexOf(indent) !== 0) {
608
                        return false;
609
                    }
610
                }
611
                return true;
612
            }
613
614
            function is_special_word(word) {
615
                return in_array(word, ['case', 'return', 'do', 'if', 'throw', 'else']);
616
            }
617
618
            function get_token(offset) {
619
                var index = token_pos + (offset || 0);
620
                return (index < 0 || index >= tokens.length) ? null : tokens[index];
621
            }
622
623
            function handle_start_expr() {
624
                if (start_of_statement()) {
625
                    // The conditional starts the statement if appropriate.
626
                }
627
628
                var next_mode = MODE.Expression;
629
                if (current_token.text === '[') {
630
631
                    if (last_type === 'TK_WORD' || flags.last_text === ')') {
632
                        // this is array index specifier, break immediately
633
                        // a[x], fn()[x]
634
                        if (last_type === 'TK_RESERVED' && in_array(flags.last_text, Tokenizer.line_starters)) {
635
                            output.space_before_token = true;
636
                        }
637
                        set_mode(next_mode);
638
                        print_token();
639
                        indent();
640
                        if (opt.space_in_paren) {
641
                            output.space_before_token = true;
642
                        }
643
                        return;
644
                    }
645
646
                    next_mode = MODE.ArrayLiteral;
647
                    if (is_array(flags.mode)) {
648
                        if (flags.last_text === '[' ||
649
                            (flags.last_text === ',' && (last_last_text === ']' || last_last_text === '}'))) {
650
                            // ], [ goes to new line
651
                            // }, [ goes to new line
652
                            if (!opt.keep_array_indentation) {
653
                                print_newline();
654
                            }
655
                        }
656
                    }
657
658
                } else {
659
                    if (last_type === 'TK_RESERVED' && flags.last_text === 'for') {
660
                        next_mode = MODE.ForInitializer;
661
                    } else if (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['if', 'while'])) {
662
                        next_mode = MODE.Conditional;
663
                    } else {
664
                        // next_mode = MODE.Expression;
665
                    }
666
                }
667
668
                if (flags.last_text === ';' || last_type === 'TK_START_BLOCK') {
669
                    print_newline();
670
                } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || flags.last_text === '.') {
671
                    // TODO: Consider whether forcing this is required.  Review failing tests when removed.
672
                    allow_wrap_or_preserved_newline(current_token.wanted_newline);
673
                    // do nothing on (( and )( and ][ and ]( and .(
674
                } else if (!(last_type === 'TK_RESERVED' && current_token.text === '(') && last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
675
                    output.space_before_token = true;
676
                } else if ((last_type === 'TK_RESERVED' && (flags.last_word === 'function' || flags.last_word === 'typeof')) ||
677
                    (flags.last_text === '*' && last_last_text === 'function')) {
678
                    // function() vs function ()
679
                    if (opt.space_after_anon_function) {
680
                        output.space_before_token = true;
681
                    }
682
                } else if (last_type === 'TK_RESERVED' && (in_array(flags.last_text, Tokenizer.line_starters) || flags.last_text === 'catch')) {
683
                    if (opt.space_before_conditional) {
684
                        output.space_before_token = true;
685
                    }
686
                }
687
688
                // Should be a space between await and an IIFE
689
                if (current_token.text === '(' && last_type === 'TK_RESERVED' && flags.last_word === 'await') {
690
                    output.space_before_token = true;
691
                }
692
693
                // Support of this kind of newline preservation.
694
                // a = (b &&
695
                //     (c || d));
696
                if (current_token.text === '(') {
697
                    if (last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
698
                        if (!start_of_object_property()) {
699
                            allow_wrap_or_preserved_newline();
700
                        }
701
                    }
702
                }
703
704
                // Support preserving wrapped arrow function expressions
705
                // a.b('c',
706
                //     () => d.e
707
                // )
708
                if (current_token.text === '(' && last_type !== 'TK_WORD' && last_type !== 'TK_RESERVED') {
709
                    allow_wrap_or_preserved_newline();
710
                }
711
712
                set_mode(next_mode);
713
                print_token();
714
                if (opt.space_in_paren) {
715
                    output.space_before_token = true;
716
                }
717
718
                // In all cases, if we newline while inside an expression it should be indented.
719
                indent();
720
            }
721
722
            function handle_end_expr() {
723
                // statements inside expressions are not valid syntax, but...
724
                // statements must all be closed when their container closes
725
                while (flags.mode === MODE.Statement) {
726
                    restore_mode();
727
                }
728
729
                if (flags.multiline_frame) {
730
                    allow_wrap_or_preserved_newline(current_token.text === ']' && is_array(flags.mode) && !opt.keep_array_indentation);
731
                }
732
733
                if (opt.space_in_paren) {
734
                    if (last_type === 'TK_START_EXPR' && !opt.space_in_empty_paren) {
735
                        // () [] no inner space in empty parens like these, ever, ref #320
736
                        output.trim();
737
                        output.space_before_token = false;
738
                    } else {
739
                        output.space_before_token = true;
740
                    }
741
                }
742
                if (current_token.text === ']' && opt.keep_array_indentation) {
743
                    print_token();
744
                    restore_mode();
745
                } else {
746
                    restore_mode();
747
                    print_token();
748
                }
749
                output.remove_redundant_indentation(previous_flags);
750
751
                // do {} while () // no statement required after
752
                if (flags.do_while && previous_flags.mode === MODE.Conditional) {
753
                    previous_flags.mode = MODE.Expression;
754
                    flags.do_block = false;
755
                    flags.do_while = false;
756
757
                }
758
            }
759
760
            function handle_start_block() {
761
                // Check if this is should be treated as a ObjectLiteral
762
                var next_token = get_token(1);
763
                var second_token = get_token(2);
764
                if (second_token && (
765
                        (in_array(second_token.text, [':', ',']) && in_array(next_token.type, ['TK_STRING', 'TK_WORD', 'TK_RESERVED'])) ||
766
                        (in_array(next_token.text, ['get', 'set']) && in_array(second_token.type, ['TK_WORD', 'TK_RESERVED']))
767
                    )) {
768
                    // We don't support TypeScript,but we didn't break it for a very long time.
769
                    // We'll try to keep not breaking it.
770
                    if (!in_array(last_last_text, ['class', 'interface'])) {
771
                        set_mode(MODE.ObjectLiteral);
772
                    } else {
773
                        set_mode(MODE.BlockStatement);
774
                    }
775
                } else if (last_type === 'TK_OPERATOR' && flags.last_text === '=>') {
776
                    // arrow function: (param1, paramN) => { statements }
777
                    set_mode(MODE.BlockStatement);
778
                } else if (in_array(last_type, ['TK_EQUALS', 'TK_START_EXPR', 'TK_COMMA', 'TK_OPERATOR']) ||
779
                    (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['return', 'throw', 'import']))
780
                ) {
781
                    // Detecting shorthand function syntax is difficult by scanning forward,
782
                    //     so check the surrounding context.
783
                    // If the block is being returned, imported, passed as arg,
784
                    //     assigned with = or assigned in a nested object, treat as an ObjectLiteral.
785
                    set_mode(MODE.ObjectLiteral);
786
                } else {
787
                    set_mode(MODE.BlockStatement);
788
                }
789
790
                var empty_braces = !next_token.comments_before.length && next_token.text === '}';
791
                var empty_anonymous_function = empty_braces && flags.last_word === 'function' &&
792
                    last_type === 'TK_END_EXPR';
793
794
795
                if (opt.brace_style === "expand" ||
796
                    (opt.brace_style === "none" && current_token.wanted_newline)) {
797
                    if (last_type !== 'TK_OPERATOR' &&
798
                        (empty_anonymous_function ||
799
                            last_type === 'TK_EQUALS' ||
800
                            (last_type === 'TK_RESERVED' && is_special_word(flags.last_text) && flags.last_text !== 'else'))) {
801
                        output.space_before_token = true;
802
                    } else {
803
                        print_newline(false, true);
804
                    }
805
                } else { // collapse
806
                    if (opt.brace_style === 'collapse-preserve-inline') {
807
                        // search forward for a newline wanted inside this block
808
                        var index = 0;
809
                        var check_token = null;
810
                        flags.inline_frame = true;
811
                        do {
812
                            index += 1;
813
                            check_token = get_token(index);
814
                            if (check_token.wanted_newline) {
815
                                flags.inline_frame = false;
816
                                break;
817
                            }
818
                        } while (check_token.type !== 'TK_EOF' &&
819
                            !(check_token.type === 'TK_END_BLOCK' && check_token.opened === current_token));
820
                    }
821
822
                    if (is_array(previous_flags.mode) && (last_type === 'TK_START_EXPR' || last_type === 'TK_COMMA')) {
823
                        // if we're preserving inline,
824
                        // allow newline between comma and next brace.
825
                        if (last_type === 'TK_COMMA' || opt.space_in_paren) {
826
                            output.space_before_token = true;
827
                        }
828
829
                        if (opt.brace_style === 'collapse-preserve-inline' &&
830
                            (last_type === 'TK_COMMA' || (last_type === 'TK_START_EXPR' && flags.inline_frame))) {
831
                            allow_wrap_or_preserved_newline();
832
                            previous_flags.multiline_frame = previous_flags.multiline_frame || flags.multiline_frame;
833
                            flags.multiline_frame = false;
834
                        }
835
                    } else if (last_type !== 'TK_OPERATOR' && last_type !== 'TK_START_EXPR') {
836
                        if (last_type === 'TK_START_BLOCK') {
837
                            print_newline();
838
                        } else {
839
                            output.space_before_token = true;
840
                        }
841
                    }
842
                }
843
                print_token();
844
                indent();
845
            }
846
847
            function handle_end_block() {
848
                // statements must all be closed when their container closes
849
                while (flags.mode === MODE.Statement) {
850
                    restore_mode();
851
                }
852
                var empty_braces = last_type === 'TK_START_BLOCK';
853
854
                if (opt.brace_style === "expand") {
855
                    if (!empty_braces) {
856
                        print_newline();
857
                    }
858
                } else {
859
                    // skip {}
860
                    if (!empty_braces) {
861
                        if (flags.inline_frame) {
862
                            output.space_before_token = true;
863
                        } else if (is_array(flags.mode) && opt.keep_array_indentation) {
864
                            // we REALLY need a newline here, but newliner would skip that
865
                            opt.keep_array_indentation = false;
866
                            print_newline();
867
                            opt.keep_array_indentation = true;
868
869
                        } else {
870
                            print_newline();
871
                        }
872
                    }
873
                }
874
                restore_mode();
875
                print_token();
876
            }
877
878
            function handle_word() {
879
                if (current_token.type === 'TK_RESERVED') {
880
                    if (in_array(current_token.text, ['set', 'get']) && flags.mode !== MODE.ObjectLiteral) {
881
                        current_token.type = 'TK_WORD';
882
                    } else if (in_array(current_token.text, ['as', 'from']) && !flags.import_block) {
883
                        current_token.type = 'TK_WORD';
884
                    } else if (flags.mode === MODE.ObjectLiteral) {
885
                        var next_token = get_token(1);
886
                        if (next_token.text === ':') {
887
                            current_token.type = 'TK_WORD';
888
                        }
889
                    }
890
                }
891
892
                if (start_of_statement()) {
893
                    // The conditional starts the statement if appropriate.
894
                } else if (current_token.wanted_newline && !is_expression(flags.mode) &&
895
                    (last_type !== 'TK_OPERATOR' || (flags.last_text === '--' || flags.last_text === '++')) &&
896
                    last_type !== 'TK_EQUALS' &&
897
                    (opt.preserve_newlines || !(last_type === 'TK_RESERVED' && in_array(flags.last_text, ['var', 'let', 'const', 'set', 'get'])))) {
898
899
                    print_newline();
900
                }
901
902
                if (flags.do_block && !flags.do_while) {
903
                    if (current_token.type === 'TK_RESERVED' && current_token.text === 'while') {
904
                        // do {} ## while ()
905
                        output.space_before_token = true;
906
                        print_token();
907
                        output.space_before_token = true;
908
                        flags.do_while = true;
909
                        return;
910
                    } else {
911
                        // do {} should always have while as the next word.
912
                        // if we don't see the expected while, recover
913
                        print_newline();
914
                        flags.do_block = false;
915
                    }
916
                }
917
918
                // if may be followed by else, or not
919
                // Bare/inline ifs are tricky
920
                // Need to unwind the modes correctly: if (a) if (b) c(); else d(); else e();
921
                if (flags.if_block) {
922
                    if (!flags.else_block && (current_token.type === 'TK_RESERVED' && current_token.text === 'else')) {
923
                        flags.else_block = true;
924
                    } else {
925
                        while (flags.mode === MODE.Statement) {
926
                            restore_mode();
927
                        }
928
                        flags.if_block = false;
929
                        flags.else_block = false;
930
                    }
931
                }
932
933
                if (current_token.type === 'TK_RESERVED' && (current_token.text === 'case' || (current_token.text === 'default' && flags.in_case_statement))) {
934
                    print_newline();
935
                    if (flags.case_body || opt.jslint_happy) {
936
                        // switch cases following one another
937
                        deindent();
938
                        flags.case_body = false;
939
                    }
940
                    print_token();
941
                    flags.in_case = true;
942
                    flags.in_case_statement = true;
943
                    return;
944
                }
945
946
                if (current_token.type === 'TK_RESERVED' && current_token.text === 'function') {
947
                    if (in_array(flags.last_text, ['}', ';']) || (output.just_added_newline() && !in_array(flags.last_text, ['[', '{', ':', '=', ',']))) {
948
                        // make sure there is a nice clean space of at least one blank line
949
                        // before a new function definition
950
                        if (!output.just_added_blankline() && !current_token.comments_before.length) {
951
                            print_newline();
952
                            print_newline(true);
953
                        }
954
                    }
955
                    if (last_type === 'TK_RESERVED' || last_type === 'TK_WORD') {
956
                        if (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['get', 'set', 'new', 'return', 'export', 'async'])) {
957
                            output.space_before_token = true;
958
                        } else if (last_type === 'TK_RESERVED' && flags.last_text === 'default' && last_last_text === 'export') {
959
                            output.space_before_token = true;
960
                        } else {
961
                            print_newline();
962
                        }
963
                    } else if (last_type === 'TK_OPERATOR' || flags.last_text === '=') {
964
                        // foo = function
965
                        output.space_before_token = true;
966
                    } else if (!flags.multiline_frame && (is_expression(flags.mode) || is_array(flags.mode))) {
967
                        // (function
968
                    } else {
969
                        print_newline();
970
                    }
971
                }
972
973
                if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
974
                    if (!start_of_object_property()) {
975
                        allow_wrap_or_preserved_newline();
976
                    }
977
                }
978
979
                if (current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['function', 'get', 'set'])) {
980
                    print_token();
981
                    flags.last_word = current_token.text;
982
                    return;
983
                }
984
985
                prefix = 'NONE';
986
987
                if (last_type === 'TK_END_BLOCK') {
988
989
                    if (!(current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['else', 'catch', 'finally', 'from']))) {
990
                        prefix = 'NEWLINE';
991
                    } else {
992
                        if (opt.brace_style === "expand" ||
993
                            opt.brace_style === "end-expand" ||
994
                            (opt.brace_style === "none" && current_token.wanted_newline)) {
995
                            prefix = 'NEWLINE';
996
                        } else {
997
                            prefix = 'SPACE';
998
                            output.space_before_token = true;
999
                        }
1000
                    }
1001
                } else if (last_type === 'TK_SEMICOLON' && flags.mode === MODE.BlockStatement) {
1002
                    // TODO: Should this be for STATEMENT as well?
1003
                    prefix = 'NEWLINE';
1004
                } else if (last_type === 'TK_SEMICOLON' && is_expression(flags.mode)) {
1005
                    prefix = 'SPACE';
1006
                } else if (last_type === 'TK_STRING') {
1007
                    prefix = 'NEWLINE';
1008
                } else if (last_type === 'TK_RESERVED' || last_type === 'TK_WORD' ||
1009
                    (flags.last_text === '*' && last_last_text === 'function')) {
1010
                    prefix = 'SPACE';
1011
                } else if (last_type === 'TK_START_BLOCK') {
1012
                    if (flags.inline_frame) {
1013
                        prefix = 'SPACE';
1014
                    } else {
1015
                        prefix = 'NEWLINE';
1016
                    }
1017
                } else if (last_type === 'TK_END_EXPR') {
1018
                    output.space_before_token = true;
1019
                    prefix = 'NEWLINE';
1020
                }
1021
1022
                if (current_token.type === 'TK_RESERVED' && in_array(current_token.text, Tokenizer.line_starters) && flags.last_text !== ')') {
1023
                    if (flags.last_text === 'else' || flags.last_text === 'export') {
1024
                        prefix = 'SPACE';
1025
                    } else {
1026
                        prefix = 'NEWLINE';
1027
                    }
1028
1029
                }
1030
1031
                if (current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['else', 'catch', 'finally'])) {
1032
                    if (!(last_type === 'TK_END_BLOCK' && previous_flags.mode === MODE.BlockStatement) ||
1033
                        opt.brace_style === "expand" ||
1034
                        opt.brace_style === "end-expand" ||
1035
                        (opt.brace_style === "none" && current_token.wanted_newline)) {
1036
                        print_newline();
1037
                    } else {
1038
                        output.trim(true);
1039
                        var line = output.current_line;
1040
                        // If we trimmed and there's something other than a close block before us
1041
                        // put a newline back in.  Handles '} // comment' scenario.
1042
                        if (line.last() !== '}') {
1043
                            print_newline();
1044
                        }
1045
                        output.space_before_token = true;
1046
                    }
1047
                } else if (prefix === 'NEWLINE') {
1048
                    if (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) {
1049
                        // no newline between 'return nnn'
1050
                        output.space_before_token = true;
1051
                    } else if (last_type !== 'TK_END_EXPR') {
1052
                        if ((last_type !== 'TK_START_EXPR' || !(current_token.type === 'TK_RESERVED' && in_array(current_token.text, ['var', 'let', 'const']))) && flags.last_text !== ':') {
1053
                            // no need to force newline on 'var': for (var x = 0...)
1054
                            if (current_token.type === 'TK_RESERVED' && current_token.text === 'if' && flags.last_text === 'else') {
1055
                                // no newline for } else if {
1056
                                output.space_before_token = true;
1057
                            } else {
1058
                                print_newline();
1059
                            }
1060
                        }
1061
                    } else if (current_token.type === 'TK_RESERVED' && in_array(current_token.text, Tokenizer.line_starters) && flags.last_text !== ')') {
1062
                        print_newline();
1063
                    }
1064
                } else if (flags.multiline_frame && is_array(flags.mode) && flags.last_text === ',' && last_last_text === '}') {
1065
                    print_newline(); // }, in lists get a newline treatment
1066
                } else if (prefix === 'SPACE') {
1067
                    output.space_before_token = true;
1068
                }
1069
                print_token();
1070
                flags.last_word = current_token.text;
1071
1072
                if (current_token.type === 'TK_RESERVED') {
1073
                    if (current_token.text === 'do') {
1074
                        flags.do_block = true;
1075
                    } else if (current_token.text === 'if') {
1076
                        flags.if_block = true;
1077
                    } else if (current_token.text === 'import') {
1078
                        flags.import_block = true;
1079
                    } else if (flags.import_block && current_token.type === 'TK_RESERVED' && current_token.text === 'from') {
1080
                        flags.import_block = false;
1081
                    }
1082
                }
1083
            }
1084
1085
            function handle_semicolon() {
1086
                if (start_of_statement()) {
1087
                    // The conditional starts the statement if appropriate.
1088
                    // Semicolon can be the start (and end) of a statement
1089
                    output.space_before_token = false;
1090
                }
1091
                while (flags.mode === MODE.Statement && !flags.if_block && !flags.do_block) {
1092
                    restore_mode();
1093
                }
1094
1095
                // hacky but effective for the moment
1096
                if (flags.import_block) {
1097
                    flags.import_block = false;
1098
                }
1099
                print_token();
1100
            }
1101
1102
            function handle_string() {
1103
                if (start_of_statement()) {
1104
                    // The conditional starts the statement if appropriate.
1105
                    // One difference - strings want at least a space before
1106
                    output.space_before_token = true;
1107
                } else if (last_type === 'TK_RESERVED' || last_type === 'TK_WORD' || flags.inline_frame) {
1108
                    output.space_before_token = true;
1109
                } else if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
1110
                    if (!start_of_object_property()) {
1111
                        allow_wrap_or_preserved_newline();
1112
                    }
1113
                } else {
1114
                    print_newline();
1115
                }
1116
                print_token();
1117
            }
1118
1119
            function handle_equals() {
1120
                if (start_of_statement()) {
1121
                    // The conditional starts the statement if appropriate.
1122
                }
1123
1124
                if (flags.declaration_statement) {
1125
                    // just got an '=' in a var-line, different formatting/line-breaking, etc will now be done
1126
                    flags.declaration_assignment = true;
1127
                }
1128
                output.space_before_token = true;
1129
                print_token();
1130
                output.space_before_token = true;
1131
            }
1132
1133
            function handle_comma() {
1134
                print_token();
1135
                output.space_before_token = true;
1136
                if (flags.declaration_statement) {
1137
                    if (is_expression(flags.parent.mode)) {
1138
                        // do not break on comma, for(var a = 1, b = 2)
1139
                        flags.declaration_assignment = false;
1140
                    }
1141
1142
                    if (flags.declaration_assignment) {
1143
                        flags.declaration_assignment = false;
1144
                        print_newline(false, true);
1145
                    } else if (opt.comma_first) {
1146
                        // for comma-first, we want to allow a newline before the comma
1147
                        // to turn into a newline after the comma, which we will fixup later
1148
                        allow_wrap_or_preserved_newline();
1149
                    }
1150
                } else if (flags.mode === MODE.ObjectLiteral ||
1151
                    (flags.mode === MODE.Statement && flags.parent.mode === MODE.ObjectLiteral)) {
1152
                    if (flags.mode === MODE.Statement) {
1153
                        restore_mode();
1154
                    }
1155
1156
                    if (!flags.inline_frame) {
1157
                        print_newline();
1158
                    }
1159
                } else if (opt.comma_first) {
1160
                    // EXPR or DO_BLOCK
1161
                    // for comma-first, we want to allow a newline before the comma
1162
                    // to turn into a newline after the comma, which we will fixup later
1163
                    allow_wrap_or_preserved_newline();
1164
                }
1165
            }
1166
1167
            function handle_operator() {
1168
                if (start_of_statement()) {
1169
                    // The conditional starts the statement if appropriate.
1170
                }
1171
1172
                if (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) {
1173
                    // "return" had a special handling in TK_WORD. Now we need to return the favor
1174
                    output.space_before_token = true;
1175
                    print_token();
1176
                    return;
1177
                }
1178
1179
                // hack for actionscript's import .*;
1180
                if (current_token.text === '*' && last_type === 'TK_DOT') {
1181
                    print_token();
1182
                    return;
1183
                }
1184
1185
                if (current_token.text === '::') {
1186
                    // no spaces around exotic namespacing syntax operator
1187
                    print_token();
1188
                    return;
1189
                }
1190
1191
                // Allow line wrapping between operators when operator_position is
1192
                //   set to before or preserve
1193
                if (last_type === 'TK_OPERATOR' && in_array(opt.operator_position, OPERATOR_POSITION_BEFORE_OR_PRESERVE)) {
1194
                    allow_wrap_or_preserved_newline();
1195
                }
1196
1197
                if (current_token.text === ':' && flags.in_case) {
1198
                    flags.case_body = true;
1199
                    indent();
1200
                    print_token();
1201
                    print_newline();
1202
                    flags.in_case = false;
1203
                    return;
1204
                }
1205
1206
                var space_before = true;
1207
                var space_after = true;
1208
                var in_ternary = false;
1209
                var isGeneratorAsterisk = current_token.text === '*' && last_type === 'TK_RESERVED' && flags.last_text === 'function';
1210
                var isUnary = in_array(current_token.text, ['-', '+']) && (
1211
                    in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) ||
1212
                    in_array(flags.last_text, Tokenizer.line_starters) ||
1213
                    flags.last_text === ','
1214
                );
1215
1216
                if (current_token.text === ':') {
1217
                    if (flags.ternary_depth === 0) {
1218
                        // Colon is invalid javascript outside of ternary and object, but do our best to guess what was meant.
1219
                        space_before = false;
1220
                    } else {
1221
                        flags.ternary_depth -= 1;
1222
                        in_ternary = true;
1223
                    }
1224
                } else if (current_token.text === '?') {
1225
                    flags.ternary_depth += 1;
1226
                }
1227
1228
                // let's handle the operator_position option prior to any conflicting logic
1229
                if (!isUnary && !isGeneratorAsterisk && opt.preserve_newlines && in_array(current_token.text, Tokenizer.positionable_operators)) {
1230
                    var isColon = current_token.text === ':';
1231
                    var isTernaryColon = (isColon && in_ternary);
1232
                    var isOtherColon = (isColon && !in_ternary);
1233
1234
                    switch (opt.operator_position) {
1235
                        case OPERATOR_POSITION.before_newline:
1236
                            // if the current token is : and it's not a ternary statement then we set space_before to false
1237
                            output.space_before_token = !isOtherColon;
1238
1239
                            print_token();
1240
1241
                            if (!isColon || isTernaryColon) {
1242
                                allow_wrap_or_preserved_newline();
1243
                            }
1244
1245
                            output.space_before_token = true;
1246
                            return;
1247
1248
                        case OPERATOR_POSITION.after_newline:
1249
                            // if the current token is anything but colon, or (via deduction) it's a colon and in a ternary statement,
1250
                            //   then print a newline.
1251
1252
                            output.space_before_token = true;
1253
1254
                            if (!isColon || isTernaryColon) {
1255
                                if (get_token(1).wanted_newline) {
1256
                                    print_newline(false, true);
1257
                                } else {
1258
                                    allow_wrap_or_preserved_newline();
1259
                                }
1260
                            } else {
1261
                                output.space_before_token = false;
1262
                            }
1263
1264
                            print_token();
1265
1266
                            output.space_before_token = true;
1267
                            return;
1268
1269
                        case OPERATOR_POSITION.preserve_newline:
1270
                            if (!isOtherColon) {
1271
                                allow_wrap_or_preserved_newline();
1272
                            }
1273
1274
                            // if we just added a newline, or the current token is : and it's not a ternary statement,
1275
                            //   then we set space_before to false
1276
                            space_before = !(output.just_added_newline() || isOtherColon);
1277
1278
                            output.space_before_token = space_before;
1279
                            print_token();
1280
                            output.space_before_token = true;
1281
                            return;
1282
                    }
1283
                }
1284
1285
                if (in_array(current_token.text, ['--', '++', '!', '~']) || isUnary) {
1286
                    // unary operators (and binary +/- pretending to be unary) special cases
1287
1288
                    space_before = false;
1289
                    space_after = false;
1290
1291
                    // http://www.ecma-international.org/ecma-262/5.1/#sec-7.9.1
1292
                    // if there is a newline between -- or ++ and anything else we should preserve it.
1293
                    if (current_token.wanted_newline && (current_token.text === '--' || current_token.text === '++')) {
1294
                        print_newline(false, true);
1295
                    }
1296
1297
                    if (flags.last_text === ';' && is_expression(flags.mode)) {
1298
                        // for (;; ++i)
1299
                        //        ^^^
1300
                        space_before = true;
1301
                    }
1302
1303
                    if (last_type === 'TK_RESERVED') {
1304
                        space_before = true;
1305
                    } else if (last_type === 'TK_END_EXPR') {
1306
                        space_before = !(flags.last_text === ']' && (current_token.text === '--' || current_token.text === '++'));
1307
                    } else if (last_type === 'TK_OPERATOR') {
1308
                        // a++ + ++b;
1309
                        // a - -b
1310
                        space_before = in_array(current_token.text, ['--', '-', '++', '+']) && in_array(flags.last_text, ['--', '-', '++', '+']);
1311
                        // + and - are not unary when preceeded by -- or ++ operator
1312
                        // a-- + b
1313
                        // a * +b
1314
                        // a - -b
1315
                        if (in_array(current_token.text, ['+', '-']) && in_array(flags.last_text, ['--', '++'])) {
1316
                            space_after = true;
1317
                        }
1318
                    }
1319
1320
1321
                    if (((flags.mode === MODE.BlockStatement && !flags.inline_frame) || flags.mode === MODE.Statement) &&
1322
                        (flags.last_text === '{' || flags.last_text === ';')) {
1323
                        // { foo; --i }
1324
                        // foo(); --bar;
1325
                        print_newline();
1326
                    }
1327
                } else if (isGeneratorAsterisk) {
1328
                    space_before = false;
1329
                    space_after = false;
1330
                }
1331
                output.space_before_token = output.space_before_token || space_before;
1332
                print_token();
1333
                output.space_before_token = space_after;
1334
            }
1335
1336
            function handle_block_comment() {
1337
                if (output.raw) {
1338
                    output.add_raw_token(current_token);
1339
                    if (current_token.directives && current_token.directives.preserve === 'end') {
1340
                        // If we're testing the raw output behavior, do not allow a directive to turn it off.
1341
                        output.raw = opt.test_output_raw;
1342
                    }
1343
                    return;
1344
                }
1345
1346
                if (current_token.directives) {
1347
                    print_newline(false, true);
1348
                    print_token();
1349
                    if (current_token.directives.preserve === 'start') {
1350
                        output.raw = true;
1351
                    }
1352
                    print_newline(false, true);
1353
                    return;
1354
                }
1355
1356
                // inline block
1357
                if (!acorn.isNewLine(current_token.text) && !current_token.wanted_newline) {
1358
                    output.space_before_token = true;
1359
                    print_token();
1360
                    output.space_before_token = true;
1361
                    return;
1362
                }
1363
1364
                var lines = split_linebreaks(current_token.text);
1365
                var j; // iterator for this case
1366
                var javadoc = false;
1367
                var starless = false;
1368
                var lastIndent = current_token.whitespace_before;
1369
                var lastIndentLength = lastIndent.length;
1370
1371
                // block comment starts with a new line
1372
                print_newline(false, true);
1373
                if (lines.length > 1) {
1374
                    javadoc = all_lines_start_with(lines.slice(1), '*');
1375
                    starless = each_line_matches_indent(lines.slice(1), lastIndent);
1376
                }
1377
1378
                // first line always indented
1379
                print_token(lines[0]);
1380
                for (j = 1; j < lines.length; j++) {
1381
                    print_newline(false, true);
1382
                    if (javadoc) {
1383
                        // javadoc: reformat and re-indent
1384
                        print_token(' ' + ltrim(lines[j]));
1385
                    } else if (starless && lines[j].length > lastIndentLength) {
1386
                        // starless: re-indent non-empty content, avoiding trim
1387
                        print_token(lines[j].substring(lastIndentLength));
1388
                    } else {
1389
                        // normal comments output raw
1390
                        output.add_token(lines[j]);
1391
                    }
1392
                }
1393
1394
                // for comments of more than one line, make sure there's a new line after
1395
                print_newline(false, true);
1396
            }
1397
1398
            function handle_comment() {
1399
                if (current_token.wanted_newline) {
1400
                    print_newline(false, true);
1401
                } else {
1402
                    output.trim(true);
1403
                }
1404
1405
                output.space_before_token = true;
1406
                print_token();
1407
                print_newline(false, true);
1408
            }
1409
1410
            function handle_dot() {
1411
                if (start_of_statement()) {
1412
                    // The conditional starts the statement if appropriate.
1413
                }
1414
1415
                if (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) {
1416
                    output.space_before_token = true;
1417
                } else {
1418
                    // allow preserved newlines before dots in general
1419
                    // force newlines on dots after close paren when break_chained - for bar().baz()
1420
                    allow_wrap_or_preserved_newline(flags.last_text === ')' && opt.break_chained_methods);
1421
                }
1422
1423
                print_token();
1424
            }
1425
1426
            function handle_unknown() {
1427
                print_token();
1428
1429
                if (current_token.text[current_token.text.length - 1] === '\n') {
1430
                    print_newline();
1431
                }
1432
            }
1433
1434
            function handle_eof() {
1435
                // Unwind any open statements
1436
                while (flags.mode === MODE.Statement) {
1437
                    restore_mode();
1438
                }
1439
            }
1440
        }
1441
1442
1443
        function OutputLine(parent) {
1444
            var _character_count = 0;
1445
            // use indent_count as a marker for lines that have preserved indentation
1446
            var _indent_count = -1;
1447
1448
            var _items = [];
1449
            var _empty = true;
1450
1451
            this.set_indent = function(level) {
1452
                _character_count = parent.baseIndentLength + level * parent.indent_length;
1453
                _indent_count = level;
1454
            };
1455
1456
            this.get_character_count = function() {
1457
                return _character_count;
1458
            };
1459
1460
            this.is_empty = function() {
1461
                return _empty;
1462
            };
1463
1464
            this.last = function() {
1465
                if (!this._empty) {
1466
                    return _items[_items.length - 1];
1467
                } else {
1468
                    return null;
1469
                }
1470
            };
1471
1472
            this.push = function(input) {
1473
                _items.push(input);
1474
                _character_count += input.length;
1475
                _empty = false;
1476
            };
1477
1478
            this.pop = function() {
1479
                var item = null;
1480
                if (!_empty) {
1481
                    item = _items.pop();
1482
                    _character_count -= item.length;
1483
                    _empty = _items.length === 0;
1484
                }
1485
                return item;
1486
            };
1487
1488
            this.remove_indent = function() {
1489
                if (_indent_count > 0) {
1490
                    _indent_count -= 1;
1491
                    _character_count -= parent.indent_length;
1492
                }
1493
            };
1494
1495
            this.trim = function() {
1496
                while (this.last() === ' ') {
1497
                    _items.pop();
1498
                    _character_count -= 1;
1499
                }
1500
                _empty = _items.length === 0;
1501
            };
1502
1503
            this.toString = function() {
1504
                var result = '';
1505
                if (!this._empty) {
1506
                    if (_indent_count >= 0) {
1507
                        result = parent.indent_cache[_indent_count];
1508
                    }
1509
                    result += _items.join('');
1510
                }
1511
                return result;
1512
            };
1513
        }
1514
1515
        function Output(indent_string, baseIndentString) {
1516
            baseIndentString = baseIndentString || '';
1517
            this.indent_cache = [baseIndentString];
1518
            this.baseIndentLength = baseIndentString.length;
1519
            this.indent_length = indent_string.length;
1520
            this.raw = false;
1521
1522
            var lines = [];
1523
            this.baseIndentString = baseIndentString;
1524
            this.indent_string = indent_string;
1525
            this.previous_line = null;
1526
            this.current_line = null;
1527
            this.space_before_token = false;
1528
1529
            this.add_outputline = function() {
1530
                this.previous_line = this.current_line;
1531
                this.current_line = new OutputLine(this);
1532
                lines.push(this.current_line);
1533
            };
1534
1535
            // initialize
1536
            this.add_outputline();
1537
1538
1539
            this.get_line_number = function() {
1540
                return lines.length;
1541
            };
1542
1543
            // Using object instead of string to allow for later expansion of info about each line
1544
            this.add_new_line = function(force_newline) {
1545
                if (this.get_line_number() === 1 && this.just_added_newline()) {
1546
                    return false; // no newline on start of file
1547
                }
1548
1549
                if (force_newline || !this.just_added_newline()) {
1550
                    if (!this.raw) {
1551
                        this.add_outputline();
1552
                    }
1553
                    return true;
1554
                }
1555
1556
                return false;
1557
            };
1558
1559
            this.get_code = function() {
1560
                var sweet_code = lines.join('\n').replace(/[\r\n\t ]+$/, '');
1561
                return sweet_code;
1562
            };
1563
1564
            this.set_indent = function(level) {
1565
                // Never indent your first output indent at the start of the file
1566
                if (lines.length > 1) {
1567
                    while (level >= this.indent_cache.length) {
1568
                        this.indent_cache.push(this.indent_cache[this.indent_cache.length - 1] + this.indent_string);
1569
                    }
1570
1571
                    this.current_line.set_indent(level);
1572
                    return true;
1573
                }
1574
                this.current_line.set_indent(0);
1575
                return false;
1576
            };
1577
1578
            this.add_raw_token = function(token) {
1579
                for (var x = 0; x < token.newlines; x++) {
1580
                    this.add_outputline();
1581
                }
1582
                this.current_line.push(token.whitespace_before);
1583
                this.current_line.push(token.text);
1584
                this.space_before_token = false;
1585
            };
1586
1587
            this.add_token = function(printable_token) {
1588
                this.add_space_before_token();
1589
                this.current_line.push(printable_token);
1590
            };
1591
1592
            this.add_space_before_token = function() {
1593
                if (this.space_before_token && !this.just_added_newline()) {
1594
                    this.current_line.push(' ');
1595
                }
1596
                this.space_before_token = false;
1597
            };
1598
1599
            this.remove_redundant_indentation = function(frame) {
1600
                // This implementation is effective but has some issues:
1601
                //     - can cause line wrap to happen too soon due to indent removal
1602
                //           after wrap points are calculated
1603
                // These issues are minor compared to ugly indentation.
1604
1605
                if (frame.multiline_frame ||
1606
                    frame.mode === MODE.ForInitializer ||
1607
                    frame.mode === MODE.Conditional) {
1608
                    return;
1609
                }
1610
1611
                // remove one indent from each line inside this section
1612
                var index = frame.start_line_index;
1613
1614
                var output_length = lines.length;
1615
                while (index < output_length) {
1616
                    lines[index].remove_indent();
1617
                    index++;
1618
                }
1619
            };
1620
1621
            this.trim = function(eat_newlines) {
1622
                eat_newlines = (eat_newlines === undefined) ? false : eat_newlines;
1623
1624
                this.current_line.trim(indent_string, baseIndentString);
1625
1626
                while (eat_newlines && lines.length > 1 &&
1627
                    this.current_line.is_empty()) {
1628
                    lines.pop();
1629
                    this.current_line = lines[lines.length - 1];
1630
                    this.current_line.trim();
1631
                }
1632
1633
                this.previous_line = lines.length > 1 ? lines[lines.length - 2] : null;
1634
            };
1635
1636
            this.just_added_newline = function() {
1637
                return this.current_line.is_empty();
1638
            };
1639
1640
            this.just_added_blankline = function() {
1641
                if (this.just_added_newline()) {
1642
                    if (lines.length === 1) {
1643
                        return true; // start of the file and newline = blank
1644
                    }
1645
1646
                    var line = lines[lines.length - 2];
1647
                    return line.is_empty();
1648
                }
1649
                return false;
1650
            };
1651
        }
1652
1653
1654
        var Token = function(type, text, newlines, whitespace_before, parent) {
1655
            this.type = type;
1656
            this.text = text;
1657
            this.comments_before = [];
1658
            this.newlines = newlines || 0;
1659
            this.wanted_newline = newlines > 0;
1660
            this.whitespace_before = whitespace_before || '';
1661
            this.parent = parent || null;
1662
            this.opened = null;
1663
            this.directives = null;
1664
        };
1665
1666
        function tokenizer(input, opts) {
1667
1668
            var whitespace = "\n\r\t ".split('');
1669
            var digit = /[0-9]/;
1670
            var digit_bin = /[01]/;
1671
            var digit_oct = /[01234567]/;
1672
            var digit_hex = /[0123456789abcdefABCDEF]/;
1673
1674
            this.positionable_operators = '!= !== % & && * ** + - / : < << <= == === > >= >> >>> ? ^ | ||'.split(' ');
1675
            var punct = this.positionable_operators.concat(
1676
                // non-positionable operators - these do not follow operator position settings
1677
                '! %= &= *= **= ++ += , -- -= /= :: <<= = => >>= >>>= ^= |= ~'.split(' '));
1678
1679
            // words which should always start on new line.
1680
            this.line_starters = 'continue,try,throw,return,var,let,const,if,switch,case,default,for,while,break,function,import,export'.split(',');
1681
            var reserved_words = this.line_starters.concat(['do', 'in', 'else', 'get', 'set', 'new', 'catch', 'finally', 'typeof', 'yield', 'async', 'await', 'from', 'as']);
1682
1683
            //  /* ... */ comment ends with nearest */ or end of file
1684
            var block_comment_pattern = /([\s\S]*?)((?:\*\/)|$)/g;
1685
1686
            // comment ends just before nearest linefeed or end of file
1687
            var comment_pattern = /([^\n\r\u2028\u2029]*)/g;
1688
1689
            var directives_block_pattern = /\/\* beautify( \w+[:]\w+)+ \*\//g;
1690
            var directive_pattern = / (\w+)[:](\w+)/g;
1691
            var directives_end_ignore_pattern = /([\s\S]*?)((?:\/\*\sbeautify\signore:end\s\*\/)|$)/g;
1692
1693
            var template_pattern = /((<\?php|<\?=)[\s\S]*?\?>)|(<%[\s\S]*?%>)/g;
1694
1695
            var n_newlines, whitespace_before_token, in_html_comment, tokens, parser_pos;
1696
            var input_length;
1697
1698
            this.tokenize = function() {
1699
                // cache the source's length.
1700
                input_length = input.length;
1701
                parser_pos = 0;
1702
                in_html_comment = false;
1703
                tokens = [];
1704
1705
                var next, last;
1706
                var token_values;
1707
                var open = null;
1708
                var open_stack = [];
1709
                var comments = [];
1710
1711
                while (!(last && last.type === 'TK_EOF')) {
1712
                    token_values = tokenize_next();
1713
                    next = new Token(token_values[1], token_values[0], n_newlines, whitespace_before_token);
1714
                    while (next.type === 'TK_COMMENT' || next.type === 'TK_BLOCK_COMMENT' || next.type === 'TK_UNKNOWN') {
1715
                        if (next.type === 'TK_BLOCK_COMMENT') {
1716
                            next.directives = token_values[2];
1717
                        }
1718
                        comments.push(next);
1719
                        token_values = tokenize_next();
1720
                        next = new Token(token_values[1], token_values[0], n_newlines, whitespace_before_token);
1721
                    }
1722
1723
                    if (comments.length) {
1724
                        next.comments_before = comments;
1725
                        comments = [];
1726
                    }
1727
1728
                    if (next.type === 'TK_START_BLOCK' || next.type === 'TK_START_EXPR') {
1729
                        next.parent = last;
1730
                        open_stack.push(open);
1731
                        open = next;
1732
                    } else if ((next.type === 'TK_END_BLOCK' || next.type === 'TK_END_EXPR') &&
1733
                        (open && (
1734
                            (next.text === ']' && open.text === '[') ||
1735
                            (next.text === ')' && open.text === '(') ||
1736
                            (next.text === '}' && open.text === '{')))) {
1737
                        next.parent = open.parent;
1738
                        next.opened = open;
1739
1740
                        open = open_stack.pop();
1741
                    }
1742
1743
                    tokens.push(next);
1744
                    last = next;
1745
                }
1746
1747
                return tokens;
1748
            };
1749
1750
            function get_directives(text) {
1751
                if (!text.match(directives_block_pattern)) {
1752
                    return null;
1753
                }
1754
1755
                var directives = {};
1756
                directive_pattern.lastIndex = 0;
1757
                var directive_match = directive_pattern.exec(text);
1758
1759
                while (directive_match) {
1760
                    directives[directive_match[1]] = directive_match[2];
1761
                    directive_match = directive_pattern.exec(text);
1762
                }
1763
1764
                return directives;
1765
            }
1766
1767
            function tokenize_next() {
1768
                var resulting_string;
1769
                var whitespace_on_this_line = [];
1770
1771
                n_newlines = 0;
1772
                whitespace_before_token = '';
1773
1774
                if (parser_pos >= input_length) {
1775
                    return ['', 'TK_EOF'];
1776
                }
1777
1778
                var last_token;
1779
                if (tokens.length) {
1780
                    last_token = tokens[tokens.length - 1];
1781
                } else {
1782
                    // For the sake of tokenizing we can pretend that there was on open brace to start
1783
                    last_token = new Token('TK_START_BLOCK', '{');
1784
                }
1785
1786
1787
                var c = input.charAt(parser_pos);
1788
                parser_pos += 1;
1789
1790
                while (in_array(c, whitespace)) {
1791
1792
                    if (acorn.isNewLine(c)) {
1793
                        if (!(c === '\n' && input.charAt(parser_pos - 2) === '\r')) {
1794
                            n_newlines += 1;
1795
                            whitespace_on_this_line = [];
1796
                        }
1797
                    } else {
1798
                        whitespace_on_this_line.push(c);
1799
                    }
1800
1801
                    if (parser_pos >= input_length) {
1802
                        return ['', 'TK_EOF'];
1803
                    }
1804
1805
                    c = input.charAt(parser_pos);
1806
                    parser_pos += 1;
1807
                }
1808
1809
                if (whitespace_on_this_line.length) {
1810
                    whitespace_before_token = whitespace_on_this_line.join('');
1811
                }
1812
1813
                if (digit.test(c) || (c === '.' && digit.test(input.charAt(parser_pos)))) {
1814
                    var allow_decimal = true;
1815
                    var allow_e = true;
1816
                    var local_digit = digit;
1817
1818
                    if (c === '0' && parser_pos < input_length && /[XxOoBb]/.test(input.charAt(parser_pos))) {
1819
                        // switch to hex/oct/bin number, no decimal or e, just hex/oct/bin digits
1820
                        allow_decimal = false;
1821
                        allow_e = false;
1822
                        if (/[Bb]/.test(input.charAt(parser_pos))) {
1823
                            local_digit = digit_bin;
1824
                        } else if (/[Oo]/.test(input.charAt(parser_pos))) {
1825
                            local_digit = digit_oct;
1826
                        } else {
1827
                            local_digit = digit_hex;
1828
                        }
1829
                        c += input.charAt(parser_pos);
1830
                        parser_pos += 1;
1831
                    } else if (c === '.') {
1832
                        // Already have a decimal for this literal, don't allow another
1833
                        allow_decimal = false;
1834
                    } else {
1835
                        // we know this first loop will run.  It keeps the logic simpler.
1836
                        c = '';
1837
                        parser_pos -= 1;
1838
                    }
1839
1840
                    // Add the digits
1841
                    while (parser_pos < input_length && local_digit.test(input.charAt(parser_pos))) {
1842
                        c += input.charAt(parser_pos);
1843
                        parser_pos += 1;
1844
1845
                        if (allow_decimal && parser_pos < input_length && input.charAt(parser_pos) === '.') {
1846
                            c += input.charAt(parser_pos);
1847
                            parser_pos += 1;
1848
                            allow_decimal = false;
1849
                        } else if (allow_e && parser_pos < input_length && /[Ee]/.test(input.charAt(parser_pos))) {
1850
                            c += input.charAt(parser_pos);
1851
                            parser_pos += 1;
1852
1853
                            if (parser_pos < input_length && /[+-]/.test(input.charAt(parser_pos))) {
1854
                                c += input.charAt(parser_pos);
1855
                                parser_pos += 1;
1856
                            }
1857
1858
                            allow_e = false;
1859
                            allow_decimal = false;
1860
                        }
1861
                    }
1862
1863
                    return [c, 'TK_WORD'];
1864
                }
1865
1866
                if (acorn.isIdentifierStart(input.charCodeAt(parser_pos - 1))) {
1867
                    if (parser_pos < input_length) {
1868
                        while (acorn.isIdentifierChar(input.charCodeAt(parser_pos))) {
1869
                            c += input.charAt(parser_pos);
1870
                            parser_pos += 1;
1871
                            if (parser_pos === input_length) {
1872
                                break;
1873
                            }
1874
                        }
1875
                    }
1876
1877
                    if (!(last_token.type === 'TK_DOT' ||
1878
                            (last_token.type === 'TK_RESERVED' && in_array(last_token.text, ['set', 'get']))) &&
1879
                        in_array(c, reserved_words)) {
1880
                        if (c === 'in') { // hack for 'in' operator
1881
                            return [c, 'TK_OPERATOR'];
1882
                        }
1883
                        return [c, 'TK_RESERVED'];
1884
                    }
1885
1886
                    return [c, 'TK_WORD'];
1887
                }
1888
1889
                if (c === '(' || c === '[') {
1890
                    return [c, 'TK_START_EXPR'];
1891
                }
1892
1893
                if (c === ')' || c === ']') {
1894
                    return [c, 'TK_END_EXPR'];
1895
                }
1896
1897
                if (c === '{') {
1898
                    return [c, 'TK_START_BLOCK'];
1899
                }
1900
1901
                if (c === '}') {
1902
                    return [c, 'TK_END_BLOCK'];
1903
                }
1904
1905
                if (c === ';') {
1906
                    return [c, 'TK_SEMICOLON'];
1907
                }
1908
1909
                if (c === '/') {
1910
                    var comment = '';
1911
                    var comment_match;
1912
                    // peek for comment /* ... */
1913
                    if (input.charAt(parser_pos) === '*') {
1914
                        parser_pos += 1;
1915
                        block_comment_pattern.lastIndex = parser_pos;
1916
                        comment_match = block_comment_pattern.exec(input);
1917
                        comment = '/*' + comment_match[0];
1918
                        parser_pos += comment_match[0].length;
1919
                        var directives = get_directives(comment);
1920
                        if (directives && directives.ignore === 'start') {
1921
                            directives_end_ignore_pattern.lastIndex = parser_pos;
1922
                            comment_match = directives_end_ignore_pattern.exec(input);
1923
                            comment += comment_match[0];
1924
                            parser_pos += comment_match[0].length;
1925
                        }
1926
                        comment = comment.replace(acorn.allLineBreaks, '\n');
1927
                        return [comment, 'TK_BLOCK_COMMENT', directives];
1928
                    }
1929
                    // peek for comment // ...
1930
                    if (input.charAt(parser_pos) === '/') {
1931
                        parser_pos += 1;
1932
                        comment_pattern.lastIndex = parser_pos;
1933
                        comment_match = comment_pattern.exec(input);
1934
                        comment = '//' + comment_match[0];
1935
                        parser_pos += comment_match[0].length;
1936
                        return [comment, 'TK_COMMENT'];
1937
                    }
1938
1939
                }
1940
1941
                var startXmlRegExp = /^<([-a-zA-Z:0-9_.]+|{.+?}|!\[CDATA\[[\s\S]*?\]\])(\s+{.+?}|\s+[-a-zA-Z:0-9_.]+|\s+[-a-zA-Z:0-9_.]+\s*=\s*('[^']*'|"[^"]*"|{.+?}))*\s*(\/?)\s*>/;
1942
1943
                if (c === '`' || c === "'" || c === '"' || // string
1944
                    (
1945
                        (c === '/') || // regexp
1946
                        (opts.e4x && c === "<" && input.slice(parser_pos - 1).match(startXmlRegExp)) // xml
1947
                    ) && ( // regex and xml can only appear in specific locations during parsing
1948
                        (last_token.type === 'TK_RESERVED' && in_array(last_token.text, ['return', 'case', 'throw', 'else', 'do', 'typeof', 'yield'])) ||
1949
                        (last_token.type === 'TK_END_EXPR' && last_token.text === ')' &&
1950
                            last_token.parent && last_token.parent.type === 'TK_RESERVED' && in_array(last_token.parent.text, ['if', 'while', 'for'])) ||
1951
                        (in_array(last_token.type, ['TK_COMMENT', 'TK_START_EXPR', 'TK_START_BLOCK',
1952
                            'TK_END_BLOCK', 'TK_OPERATOR', 'TK_EQUALS', 'TK_EOF', 'TK_SEMICOLON', 'TK_COMMA'
1953
                        ]))
1954
                    )) {
1955
1956
                    var sep = c,
1957
                        esc = false,
1958
                        has_char_escapes = false;
1959
1960
                    resulting_string = c;
1961
1962
                    if (sep === '/') {
1963
                        //
1964
                        // handle regexp
1965
                        //
1966
                        var in_char_class = false;
1967
                        while (parser_pos < input_length &&
1968
                            ((esc || in_char_class || input.charAt(parser_pos) !== sep) &&
1969
                                !acorn.isNewLine(input.charAt(parser_pos)))) {
1970
                            resulting_string += input.charAt(parser_pos);
1971
                            if (!esc) {
1972
                                esc = input.charAt(parser_pos) === '\\';
1973
                                if (input.charAt(parser_pos) === '[') {
1974
                                    in_char_class = true;
1975
                                } else if (input.charAt(parser_pos) === ']') {
1976
                                    in_char_class = false;
1977
                                }
1978
                            } else {
1979
                                esc = false;
1980
                            }
1981
                            parser_pos += 1;
1982
                        }
1983
                    } else if (opts.e4x && sep === '<') {
1984
                        //
1985
                        // handle e4x xml literals
1986
                        //
1987
1988
                        var xmlRegExp = /<(\/?)([-a-zA-Z:0-9_.]+|{.+?}|!\[CDATA\[[\s\S]*?\]\])(\s+{.+?}|\s+[-a-zA-Z:0-9_.]+|\s+[-a-zA-Z:0-9_.]+\s*=\s*('[^']*'|"[^"]*"|{.+?}))*\s*(\/?)\s*>/g;
1989
                        var xmlStr = input.slice(parser_pos - 1);
1990
                        var match = xmlRegExp.exec(xmlStr);
1991
                        if (match && match.index === 0) {
1992
                            var rootTag = match[2];
1993
                            var depth = 0;
1994
                            while (match) {
1995
                                var isEndTag = !!match[1];
1996
                                var tagName = match[2];
1997
                                var isSingletonTag = (!!match[match.length - 1]) || (tagName.slice(0, 8) === "![CDATA[");
1998
                                if (tagName === rootTag && !isSingletonTag) {
1999
                                    if (isEndTag) {
2000
                                        --depth;
2001
                                    } else {
2002
                                        ++depth;
2003
                                    }
2004
                                }
2005
                                if (depth <= 0) {
2006
                                    break;
2007
                                }
2008
                                match = xmlRegExp.exec(xmlStr);
2009
                            }
2010
                            var xmlLength = match ? match.index + match[0].length : xmlStr.length;
2011
                            xmlStr = xmlStr.slice(0, xmlLength);
2012
                            parser_pos += xmlLength - 1;
2013
                            xmlStr = xmlStr.replace(acorn.allLineBreaks, '\n');
2014
                            return [xmlStr, "TK_STRING"];
2015
                        }
2016
                    } else {
2017
                        //
2018
                        // handle string
2019
                        //
2020
                        var parse_string = function(delimiter, allow_unescaped_newlines, start_sub) {
2021
                            // Template strings can travers lines without escape characters.
2022
                            // Other strings cannot
2023
                            var current_char;
2024
                            while (parser_pos < input_length) {
2025
                                current_char = input.charAt(parser_pos);
2026
                                if (!(esc || (current_char !== delimiter &&
2027
                                        (allow_unescaped_newlines || !acorn.isNewLine(current_char))))) {
2028
                                    break;
2029
                                }
2030
2031
                                // Handle \r\n linebreaks after escapes or in template strings
2032
                                if ((esc || allow_unescaped_newlines) && acorn.isNewLine(current_char)) {
2033
                                    if (current_char === '\r' && input.charAt(parser_pos + 1) === '\n') {
2034
                                        parser_pos += 1;
2035
                                        current_char = input.charAt(parser_pos);
2036
                                    }
2037
                                    resulting_string += '\n';
2038
                                } else {
2039
                                    resulting_string += current_char;
2040
                                }
2041
                                if (esc) {
2042
                                    if (current_char === 'x' || current_char === 'u') {
2043
                                        has_char_escapes = true;
2044
                                    }
2045
                                    esc = false;
2046
                                } else {
2047
                                    esc = current_char === '\\';
2048
                                }
2049
2050
                                parser_pos += 1;
2051
2052
                                if (start_sub && resulting_string.indexOf(start_sub, resulting_string.length - start_sub.length) !== -1) {
2053
                                    if (delimiter === '`') {
2054
                                        parse_string('}', allow_unescaped_newlines, '`');
2055
                                    } else {
2056
                                        parse_string('`', allow_unescaped_newlines, '${');
2057
                                    }
2058
                                }
2059
                            }
2060
                        };
2061
2062
                        if (sep === '`') {
2063
                            parse_string('`', true, '${');
2064
                        } else {
2065
                            parse_string(sep);
2066
                        }
2067
                    }
2068
2069
                    if (has_char_escapes && opts.unescape_strings) {
2070
                        resulting_string = unescape_string(resulting_string);
2071
                    }
2072
2073
                    if (parser_pos < input_length && input.charAt(parser_pos) === sep) {
2074
                        resulting_string += sep;
2075
                        parser_pos += 1;
2076
2077
                        if (sep === '/') {
2078
                            // regexps may have modifiers /regexp/MOD , so fetch those, too
2079
                            // Only [gim] are valid, but if the user puts in garbage, do what we can to take it.
2080
                            while (parser_pos < input_length && acorn.isIdentifierStart(input.charCodeAt(parser_pos))) {
2081
                                resulting_string += input.charAt(parser_pos);
2082
                                parser_pos += 1;
2083
                            }
2084
                        }
2085
                    }
2086
                    return [resulting_string, 'TK_STRING'];
2087
                }
2088
2089
                if (c === '#') {
2090
2091
                    if (tokens.length === 0 && input.charAt(parser_pos) === '!') {
2092
                        // shebang
2093
                        resulting_string = c;
2094
                        while (parser_pos < input_length && c !== '\n') {
2095
                            c = input.charAt(parser_pos);
2096
                            resulting_string += c;
2097
                            parser_pos += 1;
2098
                        }
2099
                        return [trim(resulting_string) + '\n', 'TK_UNKNOWN'];
2100
                    }
2101
2102
2103
2104
                    // Spidermonkey-specific sharp variables for circular references
2105
                    // https://developer.mozilla.org/En/Sharp_variables_in_JavaScript
2106
                    // http://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935
2107
                    var sharp = '#';
2108
                    if (parser_pos < input_length && digit.test(input.charAt(parser_pos))) {
2109
                        do {
2110
                            c = input.charAt(parser_pos);
2111
                            sharp += c;
2112
                            parser_pos += 1;
2113
                        } while (parser_pos < input_length && c !== '#' && c !== '=');
2114
                        if (c === '#') {
2115
                            //
2116
                        } else if (input.charAt(parser_pos) === '[' && input.charAt(parser_pos + 1) === ']') {
2117
                            sharp += '[]';
2118
                            parser_pos += 2;
2119
                        } else if (input.charAt(parser_pos) === '{' && input.charAt(parser_pos + 1) === '}') {
2120
                            sharp += '{}';
2121
                            parser_pos += 2;
2122
                        }
2123
                        return [sharp, 'TK_WORD'];
2124
                    }
2125
                }
2126
2127
                if (c === '<' && (input.charAt(parser_pos) === '?' || input.charAt(parser_pos) === '%')) {
2128
                    template_pattern.lastIndex = parser_pos - 1;
2129
                    var template_match = template_pattern.exec(input);
2130
                    if (template_match) {
2131
                        c = template_match[0];
2132
                        parser_pos += c.length - 1;
2133
                        c = c.replace(acorn.allLineBreaks, '\n');
2134
                        return [c, 'TK_STRING'];
2135
                    }
2136
                }
2137
2138
                if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '<!--') {
2139
                    parser_pos += 3;
2140
                    c = '<!--';
2141
                    while (!acorn.isNewLine(input.charAt(parser_pos)) && parser_pos < input_length) {
2142
                        c += input.charAt(parser_pos);
2143
                        parser_pos++;
2144
                    }
2145
                    in_html_comment = true;
2146
                    return [c, 'TK_COMMENT'];
2147
                }
2148
2149
                if (c === '-' && in_html_comment && input.substring(parser_pos - 1, parser_pos + 2) === '-->') {
2150
                    in_html_comment = false;
2151
                    parser_pos += 2;
2152
                    return ['-->', 'TK_COMMENT'];
2153
                }
2154
2155
                if (c === '.') {
2156
                    return [c, 'TK_DOT'];
2157
                }
2158
2159
                if (in_array(c, punct)) {
2160
                    while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) {
2161
                        c += input.charAt(parser_pos);
2162
                        parser_pos += 1;
2163
                        if (parser_pos >= input_length) {
2164
                            break;
2165
                        }
2166
                    }
2167
2168
                    if (c === ',') {
2169
                        return [c, 'TK_COMMA'];
2170
                    } else if (c === '=') {
2171
                        return [c, 'TK_EQUALS'];
2172
                    } else {
2173
                        return [c, 'TK_OPERATOR'];
2174
                    }
2175
                }
2176
2177
                return [c, 'TK_UNKNOWN'];
2178
            }
2179
2180
2181
            function unescape_string(s) {
2182
                var esc = false,
2183
                    out = '',
2184
                    pos = 0,
2185
                    s_hex = '',
2186
                    escaped = 0,
2187
                    c;
2188
2189
                while (esc || pos < s.length) {
2190
2191
                    c = s.charAt(pos);
2192
                    pos++;
2193
2194
                    if (esc) {
2195
                        esc = false;
2196
                        if (c === 'x') {
2197
                            // simple hex-escape \x24
2198
                            s_hex = s.substr(pos, 2);
2199
                            pos += 2;
2200
                        } else if (c === 'u') {
2201
                            // unicode-escape, \u2134
2202
                            s_hex = s.substr(pos, 4);
2203
                            pos += 4;
2204
                        } else {
2205
                            // some common escape, e.g \n
2206
                            out += '\\' + c;
2207
                            continue;
2208
                        }
2209
                        if (!s_hex.match(/^[0123456789abcdefABCDEF]+$/)) {
2210
                            // some weird escaping, bail out,
2211
                            // leaving whole string intact
2212
                            return s;
2213
                        }
2214
2215
                        escaped = parseInt(s_hex, 16);
2216
2217
                        if (escaped >= 0x00 && escaped < 0x20) {
2218
                            // leave 0x00...0x1f escaped
2219
                            if (c === 'x') {
2220
                                out += '\\x' + s_hex;
2221
                            } else {
2222
                                out += '\\u' + s_hex;
2223
                            }
2224
                            continue;
2225
                        } else if (escaped === 0x22 || escaped === 0x27 || escaped === 0x5c) {
2226
                            // single-quote, apostrophe, backslash - escape these
2227
                            out += '\\' + String.fromCharCode(escaped);
2228
                        } else if (c === 'x' && escaped > 0x7e && escaped <= 0xff) {
2229
                            // we bail out on \x7f..\xff,
2230
                            // leaving whole string escaped,
2231
                            // as it's probably completely binary
2232
                            return s;
2233
                        } else {
2234
                            out += String.fromCharCode(escaped);
2235
                        }
2236
                    } else if (c === '\\') {
2237
                        esc = true;
2238
                    } else {
2239
                        out += c;
2240
                    }
2241
                }
2242
                return out;
2243
            }
2244
        }
2245
2246
        var beautifier = new Beautifier(js_source_text, options);
2247
        return beautifier.beautify();
2248
2249
    }
2250
2251
	return {
2252
		js_beautify: js_beautify
2253
	};
2254
});
(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/nls/root/messages.js (+64 lines)
Lines 281-287 define({ Link Here
281
	'astPluginDescription': 'Provides AST (acorn) for Tern',
281
	'astPluginDescription': 'Provides AST (acorn) for Tern',
282
	'templatesPlugin': 'Orion code templates',
282
	'templatesPlugin': 'Orion code templates',
283
	'templatesPluginDescription': 'Provides a variety of code templates for JavaScript in Orion.',
283
	'templatesPluginDescription': 'Provides a variety of code templates for JavaScript in Orion.',
284
	'formatCommandName' : 'Formatting',
285
	'formatting' : 'Formatting',
286
	'beautifierPluginName' : 'JSBeautify plugin for Tern',
287
	'beautifierPluginDescription' : 'Provides formatting for Tern',
284
	
288
	
289
	// Common formatting options
290
	'commonFormattingOptions' : 'Common Formatting Options',
291
	'indent_size' : 'Indention size:',
292
	'indent_char' : 'Indentation character:',
293
	'eol' : 'Charater(s) to use as line terminators:',
294
	'end_with_newline' : 'End ouput with newline:',
295
	'indentation_unix' : 'Unix',
296
	'indentation_mac' : 'Mac',
297
	'indentation_windows' : 'Windows',
298
	'identation_space' : 'space',
299
	'indentation_tab' : 'tab',
300
301
	// JS formatting options
302
	'jsFormattingOptions' : 'Formatting Options for Javascript',
303
	'indent_level': 'Initial indentation level:',
304
	'before_newline' : 'Before new line',
305
	'after_newline' : 'After new line',
306
	'preserve_newline' : 'Preserve new line',
307
	'collapse_preserve_inline' : 'Collapse Preserve inline',
308
	'collapse' : 'Collapse',
309
	'expand' : 'Expand',
310
	'end_expand' : 'End expand',
311
	'none' : 'None',
312
	'preserve_new_lines' : 'Preserve line-breaks:',
313
	'max_preserve_new_lines' : 'Number of line-breaks to be preserved in one chunk:',
314
	'space_in_paren' : 'Add padding space within paren:',
315
	'space_in_empty_paren' : 'Add padding space in empty paren:',
316
	'space_after_anon_function' : "Add a space before an anonymous function's parens",
317
	'brace_style' : 'Brace Style:',
318
	'break_chained_methods' : 'Break chained method calls across subsequent lines:',
319
	'keep_array_indentation' : 'Preserve array indentation:',
320
	'space_before_conditional' : 'Space before condition:',
321
	'unescape_strings' : 'Decode printable characters encoded in xNN notation:',
322
	'wrap_line_length' : 'Wrap lines at next opportunity after N characters: (0 for unlimited)',
323
	'e4x' : 'Pass E4X xml literal through untouched:',
324
	'comma_first' : 'Put commas at the beginning of new line instead of end:',
325
	'operator_position' : 'Position for operator:',
326
	
327
	// CSS formatting options
328
	'cssFormattingOptions' : 'Formatting Options for CSS',
329
	'selector_separator_newline' : 'Add a newline between multiple selectors:',
330
	'newline_between_rules' : 'Add a newline between CSS rules:',
331
	'space_around_selector_separator' : 'Ensure space around selector separators:',
332
	
333
	// HTML formatting options
334
	'htmlFormattingOptions' : 'Formatting Options for HTML',
335
	'indent_inner_html' : 'Indent <head> and <body> sections:',
336
	'indent_handlebars' : 'Format and indent {{#foo}} and {{/foo}}:',
337
	'wrap_attributes' : 'Wrap attributes to new lines:',
338
	'wrap_attributes_indent_size' : 'Indent wrapped attributes to after N characters:',
339
	'extra_liners' : 'List of tags that should have an extra newline before them (separate with commas):',
340
	'normal' : 'normal',
341
	'keep' : 'keep',
342
	'separate' : 'separate',
343
	'indent_scripts' : 'Indent scripts:',
344
	'auto' : 'auto',
345
	'force' : 'force',
346
285
	// Other messages
347
	// Other messages
286
	'unknownError': 'An unknown error occurred.',
348
	'unknownError': 'An unknown error occurred.',
287
	'failedDeleteRequest': 'Failed to delete file from Tern: ${0}',
349
	'failedDeleteRequest': 'Failed to delete file from Tern: ${0}',
Lines 318-323 define({ Link Here
318
	'serverNotStarted': 'The server has not been started. Request: \'${0}\'',
380
	'serverNotStarted': 'The server has not been started. Request: \'${0}\'',
319
	'failedToComputeProblems': 'Failed to compute ESLint problems/markers',
381
	'failedToComputeProblems': 'Failed to compute ESLint problems/markers',
320
	'failedToComputeOutline': 'Failed to compute outline',
382
	'failedToComputeOutline': 'Failed to compute outline',
383
	'failedToFormat' : 'Failed to format',
384
	'failedToFormatNoServer' : 'Failed to format, server not started',
321
	
385
	
322
	//Templates
386
	//Templates
323
	'eslintRuleEnableDisable': 'Enable or disable ESLint rule using the ```ruleid:0/1/2``` form.\n\nExample use:\n\n>```/* eslint semi:1, no-console:0, no-redeclare:2 */```',
387
	'eslintRuleEnableDisable': 'Enable or disable ESLint rule using the ```ruleid:0/1/2``` form.\n\nExample use:\n\n>```/* eslint semi:1, no-console:0, no-redeclare:2 */```',
(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/plugins/ternDefaults.js (-2 / +8 lines)
Lines 43-49 define([ Link Here
43
	"javascript/ternPlugins/redis",
43
	"javascript/ternPlugins/redis",
44
	"javascript/ternPlugins/refs",
44
	"javascript/ternPlugins/refs",
45
	"javascript/ternPlugins/templates",
45
	"javascript/ternPlugins/templates",
46
	"javascript/ternPlugins/quickfixes"
46
	"javascript/ternPlugins/quickfixes",
47
	"javascript/ternPlugins/beautifier"
47
], function(Messages, ecma5, ecma6, browser, chai) {
48
], function(Messages, ecma5, ecma6, browser, chai) {
48
	
49
	
49
	var defs = [ecma5, ecma6, browser, chai];
50
	var defs = [ecma5, ecma6, browser, chai];
Lines 111-117 define([ Link Here
111
				"name": Messages["templatesPlugin"],
112
				"name": Messages["templatesPlugin"],
112
				"description": Messages["templatesPluginDescription"],
113
				"description": Messages["templatesPluginDescription"],
113
				"version": "1.0"
114
				"version": "1.0"
114
			}
115
			},
116
			"beautifier": {
117
				"name": Messages["beautifierPluginName"],
118
				"description": Messages["beautifierPluginDescription"],
119
				"version": "1.0"
120
			},
115
		},
121
		},
116
		optional: {
122
		optional: {
117
			"amqp": {
123
			"amqp": {
(-)a/bundles/org.eclipse.orion.client.ui/web/orion/settings/nls/root/messages.js (-2 / +2 lines)
Lines 1-6 Link Here
1
/*******************************************************************************
1
/*******************************************************************************
2
 * @license
2
 * @license
3
 * Copyright (c) 2012 IBM Corporation and others.
3
 * Copyright (c) 2012, 2016 IBM Corporation and others.
4
 * All rights reserved. This program and the accompanying materials are made
4
 * All rights reserved. This program and the accompanying materials are made
5
 * available under the terms of the Eclipse Public License v1.0
5
 * available under the terms of the Eclipse Public License v1.0
6
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
6
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution
Lines 283-287 define({//Default message bundle Link Here
283
    "editorTheme selection background": "Selection background",
283
    "editorTheme selection background": "Selection background",
284
    'customizeTheme': 'Custom Style...',
284
    'customizeTheme': 'Custom Style...',
285
    'moreEditorSettings': 'Editor Settings...',
285
    'moreEditorSettings': 'Editor Settings...',
286
    'JavascriptSettingWarning' : '${0} Warning: Global settings for ${2} are overriden by the settings defined in: ${1}'
286
    'SettingWarning' : '${0} Warning: Global settings for \'${2}\' are overriden by the settings defined in: ${1}'
287
});
287
});
(-)a/bundles/org.eclipse.orion.client.ui/web/orion/settings/ui/PluginSettings.js (-2 / +49 lines)
Lines 449-454 define([ Link Here
449
			}.bind(this));
449
			}.bind(this));
450
		}.bind(this));
450
		}.bind(this));
451
	}
451
	}
452
	
453
	function getFormattingSettings(fileClient, projectPath, callback) {
454
		var jsbeautifyrc = projectPath + SettingsList.prototype.JSBEAUTIFYRC;
455
		return fileClient.read(jsbeautifyrc, false, false, {readIfExists: true}).then(function(contents) {
456
			if (contents !== null) {
457
				callback({contents: contents, name: SettingsList.prototype.JSBEAUTIFYRC, location: jsbeautifyrc});
458
				return;
459
			}
460
		}.bind(this));
461
	}
452
462
453
	SettingsList.prototype = {
463
	SettingsList.prototype = {
454
		/**
464
		/**
Lines 480-485 define([ Link Here
480
		 * The package.json file name
490
		 * The package.json file name
481
		 */
491
		 */
482
		PACKAGE_JSON : 'package.json',
492
		PACKAGE_JSON : 'package.json',
493
		/**
494
		 * The .jsbeautifyrc file name
495
		 * @see https://github.com/beautify-web/js-beautify/blob/master/README.md
496
		 */
497
		JSBEAUTIFYRC : '.jsbeautifyrc',
483
		
498
		
484
		_makeSection: function(parent, sectionId, title, hasMultipleSections) {
499
		_makeSection: function(parent, sectionId, title, hasMultipleSections) {
485
			var that = this;
500
			var that = this;
Lines 537-543 define([ Link Here
537
								if (!this.destroyed) {
552
								if (!this.destroyed) {
538
									var infoText = document.createElement("div"); //$NON-NLS-0$
553
									var infoText = document.createElement("div"); //$NON-NLS-0$
539
									infoText.classList.add("setting-info"); //$NON-NLS-0$
554
									infoText.classList.add("setting-info"); //$NON-NLS-0$
540
									infoText.textContent = messages.JavascriptSettingWarning;
555
									infoText.textContent = messages.SettingWarning;
541
									var icon = document.createElement("span"); //$NON-NLS-0$
556
									var icon = document.createElement("span"); //$NON-NLS-0$
542
									icon.classList.add("core-sprite-warning"); //$NON-NLS-0$
557
									icon.classList.add("core-sprite-warning"); //$NON-NLS-0$
543
									icon.classList.add("icon-inline"); //$NON-NLS-0$
558
									icon.classList.add("icon-inline"); //$NON-NLS-0$
Lines 559-565 define([ Link Here
559
					}
574
					}
560
					break;
575
					break;
561
				}
576
				}
562
			}
577
				case "formatting" : {
578
					if (pageParams.resource && pageParams.resource.length !== 0) {
579
						// resource name starts with /file/ and ends with '/'
580
						projectName = pageParams.resource.substring(6, pageParams.resource.length - 1);
581
						getFormattingSettings(
582
							this.fileClient,
583
							pageParams.resource,
584
							function(file) {
585
								if (!this.destroyed) {
586
									var infoText = document.createElement("div"); //$NON-NLS-0$
587
									infoText.classList.add("setting-info"); //$NON-NLS-0$
588
									infoText.textContent = messages.SettingWarning;
589
									var icon = document.createElement("span"); //$NON-NLS-0$
590
									icon.classList.add("core-sprite-warning"); //$NON-NLS-0$
591
									icon.classList.add("icon-inline"); //$NON-NLS-0$
592
									icon.classList.add("imageSprite"); //$NON-NLS-0$
593
									var link = document.createElement("a"); //$NON-NLS-0$
594
									link.href = editTemplate.expand({resource: file.location});
595
									link.appendChild(document.createTextNode(file.name));
596
									var projectText = document.createElement("span"); //$NON-NLS-0$
597
									projectText.textContent = projectName;
598
									lib.processDOMNodes(infoText, [icon, link, projectText]);
599
									if (parent.firstChild) {
600
										parent.insertBefore(infoText, parent.firstChild);
601
									} else {
602
										parent.appendChild(infoText);
603
									}
604
									return true;
605
								}
606
							}.bind(this));
607
					}
608
				}
609
		}
563
			for (var i=0; i<settings.length; i++) {
610
			for (var i=0; i<settings.length; i++) {
564
				var setting = settings[i];
611
				var setting = settings[i];
565
				var sectionId = 'settings.section.'; //$NON-NLS-1$
612
				var sectionId = 'settings.section.'; //$NON-NLS-1$
(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/javascriptProject.js (-12 / +37 lines)
Lines 20-55 define([ Link Here
20
				fileName === project.ESLINTRC_JSON || fileName === project.PACKAGE_JSON) {
20
				fileName === project.ESLINTRC_JSON || fileName === project.PACKAGE_JSON) {
21
				delete project.map.eslint;
21
				delete project.map.eslint;
22
			}
22
			}
23
			if (fileName === project.JSBEAUTIFYRC) {
24
				delete project.map.formatting;
25
			}
23
		},
26
		},
24
		/**
27
		/**
25
		 * @callback
28
		 * @callback
26
		 */
29
		 */
27
		onModified: function onModified(project, qualifiedName, fileName) {
30
		onModified: function onModified(project, qualifiedName, fileName) {
28
			this._update(project, fileName);			
31
			this._update(project, fileName);
29
		},
32
		},
30
		/**
33
		/**
31
		 * @callback
34
		 * @callback
32
		 */
35
		 */
33
		onDeleted: function onDeleted(project, qualifiedName, fileName) {
36
		onDeleted: function onDeleted(project, qualifiedName, fileName) {
34
			this._update(project, fileName);			
37
			this._update(project, fileName);
35
		},
38
		},
36
		/**
39
		/**
37
		 * @callback
40
		 * @callback
38
		 */
41
		 */
39
		onCreated: function onCreated(project, qualifiedName, fileName) {
42
		onCreated: function onCreated(project, qualifiedName, fileName) {
40
			this._update(project, fileName);			
43
			this._update(project, fileName);
41
		},
44
		},
42
		/**
45
		/**
43
		 * @callback
46
		 * @callback
44
		 */
47
		 */
45
		onMoved: function onMoved(project, qualifiedName, fileName, toQualified, toName) {
48
		onMoved: function onMoved(project, qualifiedName, fileName, toQualified, toName) {
46
			this._update(project, fileName);			
49
			this._update(project, fileName);
47
		},
50
		},
48
		/**
51
		/**
49
		 * @callback
52
		 * @callback
50
		 */
53
		 */
51
		onProjectChanged: function onProjectChanged(project, evnt, projectName) {
54
		onProjectChanged: function onProjectChanged(project, evnt, projectName) {
52
			delete project.map.eslint;
55
			delete project.map.eslint;
56
			delete project.map.formatting;
53
		}
57
		}
54
	};
58
	};
55
	
59
	
Lines 114-120 define([ Link Here
114
	 * The node_modules folder name
118
	 * The node_modules folder name
115
	 */
119
	 */
116
	JavaScriptProject.prototype.NODE_MODULES = 'node_modules';
120
	JavaScriptProject.prototype.NODE_MODULES = 'node_modules';
117
	
121
	/**
122
	 * The .jsbeautifyrc file name
123
	 * @see https://github.com/beautify-web/js-beautify/blob/master/README.md
124
	 */
125
	JavaScriptProject.prototype.JSBEAUTIFYRC = '.jsbeautifyrc';
126
118
	/**
127
	/**
119
	 * @description Adds a handler for the given file name to the mapping of handlers
128
	 * @description Adds a handler for the given file name to the mapping of handlers
120
	 * @function
129
	 * @function
Lines 154-160 define([ Link Here
154
						this.ecma = v.ecmaVersion;
163
						this.ecma = v.ecmaVersion;
155
					}
164
					}
156
				} catch(err) {
165
				} catch(err) {
157
					this.ecma = 6;					
166
					this.ecma = 6;
158
				}
167
				}
159
			}
168
			}
160
			return this.ecma;
169
			return this.ecma;
Lines 269-285 define([ Link Here
269
		//TODO support loading YML and YAML files
278
		//TODO support loading YML and YAML files
270
		var vals;
279
		var vals;
271
		return this.getFile(this.ESLINTRC_JS).then(function(file) {
280
		return this.getFile(this.ESLINTRC_JS).then(function(file) {
272
			vals = readAndMap(this.map, file);
281
			vals = readAndMap(this.map, file, "eslint");
273
			if(vals) {
282
			if(vals) {
274
				return vals;
283
				return vals;
275
			} 
284
			} 
276
			return this.getFile(this.ESLINTRC_JSON).then(function(file) {
285
			return this.getFile(this.ESLINTRC_JSON).then(function(file) {
277
				vals = readAndMap(this.map, file);
286
				vals = readAndMap(this.map, file, "eslint");
278
				if(vals) {
287
				if(vals) {
279
					return vals;
288
					return vals;
280
				}
289
				}
281
				return this.getFile(this.ESLINTRC).then(function(file) {
290
				return this.getFile(this.ESLINTRC).then(function(file) {
282
					vals = readAndMap(this.map, file);
291
					vals = readAndMap(this.map, file, "eslint");
283
					if(vals) {
292
					if(vals) {
284
						return vals;
293
						return vals;
285
					}
294
					}
Lines 298-310 define([ Link Here
298
		}.bind(this));
307
		}.bind(this));
299
	};
308
	};
300
	
309
	
301
	function readAndMap(map, file) {
310
	/**
311
	 * @name JavaScriptProject.prototype.getFormattingOptions
312
	 * @description Returns project-specific formatting options (if any)
313
	 * @function
314
	 * @returns {Deferred} A deferred that will resolve to the project-specific formatting options or null
315
	 * @see https://github.com/beautify-web/js-beautify
316
	 */
317
	JavaScriptProject.prototype.getFormattingOptions = function getESlintOptions() {
318
		if(this.map.formatting) {
319
			return new Deferred().resolve(this.map.formatting);
320
		}
321
		return this.getFile(this.JSBEAUTIFYRC).then(function(file) {
322
			return readAndMap(this.map, file, "formatting");
323
		}.bind(this));
324
	};
325
326
	function readAndMap(map, file, key) {
302
		if(file && file.contents) {
327
		if(file && file.contents) {
303
			try {
328
			try {
304
				var vals = JSON.parse(file.contents);
329
				var vals = JSON.parse(file.contents);
305
				if(Object.keys(vals).length > 0) {
330
				if(Object.keys(vals).length > 0) {
306
					map.eslint = vals;
331
					map[key]= vals;
307
					return map.eslint;
332
					return map[key];
308
				}
333
				}
309
			} catch(e) {
334
			} catch(e) {
310
				//fall through and return null
335
				//fall through and return null
(-)a/bundles/org.eclipse.orion.client.javascript/web/javascript/plugins/javascriptPlugin.js (-6 / +293 lines)
Lines 21-26 define([ Link Here
21
'javascript/scriptResolver',
21
'javascript/scriptResolver',
22
'javascript/astManager',
22
'javascript/astManager',
23
'javascript/quickFixes',
23
'javascript/quickFixes',
24
'javascript/formatting',
24
'javascript/javascriptProject',
25
'javascript/javascriptProject',
25
'javascript/contentAssist/ternAssist',
26
'javascript/contentAssist/ternAssist',
26
'javascript/contentAssist/ternProjectAssist',
27
'javascript/contentAssist/ternProjectAssist',
Lines 45-51 define([ Link Here
45
'i18n!javascript/nls/messages',
46
'i18n!javascript/nls/messages',
46
'orion/i18nUtil',
47
'orion/i18nUtil',
47
'orion/URL-shim'
48
'orion/URL-shim'
48
], function(PluginProvider, mServiceRegistry, Deferred, ScriptResolver, ASTManager, QuickFixes, JavaScriptProject, TernAssist, TernProjectAssist,
49
], function(PluginProvider, mServiceRegistry, Deferred, ScriptResolver, ASTManager, QuickFixes, Formatting, JavaScriptProject, TernAssist, TernProjectAssist,
49
			EslintValidator, TernProjectValidator, Occurrences, Hover, Outliner, CUProvider, TernProjectManager, Util, Logger, GenerateDocCommand, OpenDeclCommand, OpenImplCommand,
50
			EslintValidator, TernProjectValidator, Occurrences, Hover, Outliner, CUProvider, TernProjectManager, Util, Logger, GenerateDocCommand, OpenDeclCommand, OpenImplCommand,
50
			RenameCommand, RefsCommand, mJS, mJSON, mJSONSchema, mEJS, javascriptMessages, i18nUtil) {
51
			RenameCommand, RefsCommand, mJS, mJSON, mJSONSchema, mEJS, javascriptMessages, i18nUtil) {
51
52
Lines 69-75 define([ Link Here
69
    		               }, {id: "application/json", //$NON-NLS-1$
70
    		               }, {id: "application/json", //$NON-NLS-1$
70
    		            	   "extends": "text/plain", //$NON-NLS-1$ //$NON-NLS-1$
71
    		            	   "extends": "text/plain", //$NON-NLS-1$ //$NON-NLS-1$
71
    		            	   name: "JSON", //$NON-NLS-1$
72
    		            	   name: "JSON", //$NON-NLS-1$
72
    		            	   extension: ["json", "pref", "tern-project", "eslintrc"], //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
73
    		            	   extension: ["json", "pref", "tern-project", "eslintrc", "jsbeautifyrc"], //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
73
    		            	   imageClass: "file-sprite-javascript modelDecorationSprite" //$NON-NLS-1$
74
    		            	   imageClass: "file-sprite-javascript modelDecorationSprite" //$NON-NLS-1$
74
    		               }, {id: "application/x-ejs", //$NON-NLS-1$
75
    		               }, {id: "application/x-ejs", //$NON-NLS-1$
75
    		            	   "extends": "text/plain", //$NON-NLS-1$ //$NON-NLS-1$
76
    		            	   "extends": "text/plain", //$NON-NLS-1$ //$NON-NLS-1$
Lines 680-686 define([ Link Here
680
                    ]
681
                    ]
681
    			}
682
    			}
682
    	);
683
    	);
683
    	
684
684
    	provider.registerServiceProvider("orion.edit.command",  //$NON-NLS-1$
685
    	provider.registerServiceProvider("orion.edit.command",  //$NON-NLS-1$
685
    			quickFixComputer,
686
    			quickFixComputer,
686
    			{
687
    			{
Lines 1496-1502 define([ Link Here
1496
				 	        	   name: javascriptMessages['prefBestPractices'],
1497
				 	        	   name: javascriptMessages['prefBestPractices'],
1497
				 	        	   tags: "validation javascript js eslint".split(" "),  //$NON-NLS-1$  //$NON-NLS-1$
1498
				 	        	   tags: "validation javascript js eslint".split(" "),  //$NON-NLS-1$  //$NON-NLS-1$
1498
				 	        	   category: 'javascript', //$NON-NLS-1$
1499
				 	        	   category: 'javascript', //$NON-NLS-1$
1499
 				 	        	   categoryLabel: javascriptMessages['javascriptValidation'],
1500
				 	        	   categoryLabel: javascriptMessages['javascriptValidation'],
1500
				 	        	   properties: [
1501
				 	        	   properties: [
1501
				 	        	   				{
1502
				 	        	   				{
1502
			 	        	                		id: "no-eq-null",  //$NON-NLS-1$
1503
			 	        	                		id: "no-eq-null",  //$NON-NLS-1$
Lines 1753-1761 define([ Link Here
1753
				 	        	                	defaultValue: warning,
1754
				 	        	                	defaultValue: warning,
1754
				 	        	                	options: severities
1755
				 	        	                	options: severities
1755
				 	        	                }
1756
				 	        	                }
1756
]
1757
										]
1757
				 	        	}]
1758
				 	        	}]
1758
    			});
1759
			});
1760
1761
		var beautifier = new Formatting.JavaScriptFormatting(ternWorker, jsProject);
1762
		provider.registerServiceProvider("orion.edit.command",
1763
			beautifier,
1764
			{
1765
				name: javascriptMessages["formatCommandName"],
1766
				contentType: ["application/javascript", "text/html", "text/css"],
1767
				id: "format",
1768
				key : [ 'F', false, true, !Util.isMac, Util.isMac]  //$NON-NLS-1$ Alt-Shift-F
1769
			}
1770
		);
1771
		// register preferences for formatting when modified (updated call)
1772
		provider.registerService("orion.cm.managedservice", beautifier, {pid: 'jsbeautify.config.common'}); //$NON-NLS-1$ //$NON-NLS-2$
1773
		provider.registerService("orion.cm.managedservice", beautifier, {pid: 'jsbeautify.config.js'}); //$NON-NLS-1$ //$NON-NLS-2$
1774
		provider.registerService("orion.cm.managedservice", beautifier, {pid: 'jsbeautify.config.html'}); //$NON-NLS-1$ //$NON-NLS-2$
1775
		provider.registerService("orion.cm.managedservice", beautifier, {pid: 'jsbeautify.config.css'}); //$NON-NLS-1$ //$NON-NLS-2$
1776
1777
		var unix = "\n";
1778
		var mac = "\r";
1779
		var windows = "\n\r";
1780
		var eof_characters = [
1781
			{label: javascriptMessages.indentation_unix,  value: unix},
1782
			{label: javascriptMessages.indentation_mac, value: mac},
1783
			{label: javascriptMessages.indentation_windows, value: windows}
1784
		];
1785
1786
		var space = ' ';
1787
		var tab= '\t';
1788
		var indentation_characters = [
1789
			{label: javascriptMessages.identation_space,  value: space},
1790
			{label: javascriptMessages.indentation_tab,  value: tab},
1791
		];
1792
		
1793
		var before_newline = 'before-newline';
1794
		var after_newline = 'after-newline';
1795
		var preserve_newline = 'preserve-newline';
1796
		var operator_positions = [
1797
			{ label: javascriptMessages.before_newline, value: before_newline},
1798
			{ label: javascriptMessages.after_newline, value: after_newline},
1799
			{ label: javascriptMessages.preserve_newline, value: preserve_newline},
1800
		];
1801
		
1802
		var collapse_preserve_inline = 'collapse-preserve-inline';
1803
		var collapse = 'collapse';
1804
		var expand = 'expand';
1805
		var end_expand = 'end-expand';
1806
		var none = 'none';
1807
		var brace_styles = [
1808
			{ label: javascriptMessages.collapse_preserve_inline , value: collapse_preserve_inline},
1809
			{ label: javascriptMessages.collapse , value: collapse},
1810
			{ label: javascriptMessages.expand , value: expand},
1811
			{ label: javascriptMessages.end_expand , value: end_expand},
1812
			{ label: javascriptMessages.none , value: none},
1813
		];
1814
		
1815
		var keep = 'keep';
1816
		var separate = 'separate';
1817
		var normal = 'normal';
1818
		var indent_scripts_values = [
1819
			{ label: javascriptMessages.normal, value: normal},
1820
			{ label: javascriptMessages.keep, value: keep},
1821
			{ label: javascriptMessages.separate, value: separate},
1822
		];
1823
		var auto = 'auto';
1824
		var force = 'force';
1825
		var wrap_attributes_values = [
1826
			{ label: javascriptMessages.auto, value: auto},
1827
			{ label: javascriptMessages.force, value: force},
1828
		];
1829
		provider.registerServiceProvider("orion.core.setting", {}, {
1830
			settings: [
1831
				{
1832
					pid: 'jsbeautify.config.common',
1833
					order: 1,
1834
					name: javascriptMessages['commonFormattingOptions'],
1835
					tags: 'beautify javascript js jsbeautify formatting'.split(' '),
1836
					category: 'formatting',
1837
					categoryLabel: javascriptMessages['formatting'],
1838
					properties: [
1839
						{
1840
							id: 'indent_size',  //$NON-NLS-1$
1841
							name: javascriptMessages['indent_size'],
1842
							type: 'number', //$NON-NLS-1$
1843
							defaultValue: 4
1844
						},
1845
						{
1846
							id: 'indent_char',  //$NON-NLS-1$
1847
							name: javascriptMessages['indent_char'],
1848
							type: 'string', //$NON-NLS-1$
1849
							defaultValue: space,
1850
							options: indentation_characters
1851
						},
1852
						{
1853
							id: 'eol',  //$NON-NLS-1$
1854
							name: javascriptMessages['eol'],
1855
							type: 'string', //$NON-NLS-1$
1856
							defaultValue: unix,
1857
							options: eof_characters
1858
						},
1859
						{
1860
							id: 'end_with_newline',  //$NON-NLS-1$
1861
							name: javascriptMessages['end_with_newline'],
1862
							type: 'boolean', //$NON-NLS-1$
1863
							defaultValue: false
1864
						},
1865
						{
1866
							id: 'preserve_new_lines',  //$NON-NLS-1$
1867
							name: javascriptMessages['preserve_new_lines'],
1868
							type: 'boolean', //$NON-NLS-1$
1869
							defaultValue: true
1870
						},
1871
						{
1872
							id: 'max_preserve_new_lines',  //$NON-NLS-1$
1873
							name: javascriptMessages['max_preserve_new_lines'],
1874
							type: 'number', //$NON-NLS-1$
1875
							defaultValue: 10
1876
						},
1877
						{
1878
							id: 'brace_style',  //$NON-NLS-1$
1879
							name: javascriptMessages['brace_style'],
1880
							type: 'boolean', //$NON-NLS-1$
1881
							defaultValue: collapse,
1882
							options: brace_styles
1883
						},
1884
						{
1885
							id: 'wrap_line_length',  //$NON-NLS-1$
1886
							name: javascriptMessages['wrap_line_length'],
1887
							type: 'number', //$NON-NLS-1$
1888
							defaultValue: 0
1889
						},
1890
					]
1891
				},
1892
				{
1893
					pid: 'jsbeautify.config.js',
1894
					order: 2,
1895
					name: javascriptMessages['jsFormattingOptions'],
1896
					tags: 'beautify javascript js jsbeautify formatting'.split(' '),
1897
					category: 'formatting',
1898
					categoryLabel: javascriptMessages['formatting'],
1899
					properties: [
1900
						{
1901
							id: 'indent_level',  //$NON-NLS-1$
1902
							name: javascriptMessages['indent_level'],
1903
							type: 'number', //$NON-NLS-1$
1904
							defaultValue: 0
1905
						},
1906
						{
1907
							id: 'space_in_paren',  //$NON-NLS-1$
1908
							name: javascriptMessages['space_in_paren'],
1909
							type: 'boolean', //$NON-NLS-1$
1910
							defaultValue: false
1911
						},
1912
						{
1913
							id: 'space_in_empty_paren',  //$NON-NLS-1$
1914
							name: javascriptMessages['space_in_empty_paren'],
1915
							type: 'boolean', //$NON-NLS-1$
1916
							defaultValue: false
1917
						},
1918
						{
1919
							id: 'space_after_anon_function',  //$NON-NLS-1$
1920
							name: javascriptMessages['space_after_anon_function'],
1921
							type: 'boolean', //$NON-NLS-1$
1922
							defaultValue: false
1923
						},
1924
						{
1925
							id: 'break_chained_methods',  //$NON-NLS-1$
1926
							name: javascriptMessages['break_chained_methods'],
1927
							type: 'boolean', //$NON-NLS-1$
1928
							defaultValue: false
1929
						},
1930
						{
1931
							id: 'keep_array_indentation',  //$NON-NLS-1$
1932
							name: javascriptMessages['keep_array_indentation'],
1933
							type: 'boolean', //$NON-NLS-1$
1934
							defaultValue: false
1935
						},
1936
						{
1937
							id: 'space_before_conditional',  //$NON-NLS-1$
1938
							name: javascriptMessages['space_before_conditional'],
1939
							type: 'boolean', //$NON-NLS-1$
1940
							defaultValue: true
1941
						},
1942
						{
1943
							id: 'unescape_strings',  //$NON-NLS-1$
1944
							name: javascriptMessages['unescape_strings'],
1945
							type: 'boolean', //$NON-NLS-1$
1946
							defaultValue: false
1947
						},
1948
						{
1949
							id: 'e4x',  //$NON-NLS-1$
1950
							name: javascriptMessages['e4x'],
1951
							type: 'boolean', //$NON-NLS-1$
1952
							defaultValue: false
1953
						},
1954
						{
1955
							id: 'comma_first',  //$NON-NLS-1$
1956
							name: javascriptMessages['comma_first'],
1957
							type: 'boolean', //$NON-NLS-1$
1958
							defaultValue: false
1959
						},
1960
						{
1961
							id: 'operator_position',  //$NON-NLS-1$
1962
							name: javascriptMessages['operator_position'],
1963
							type: 'string', //$NON-NLS-1$
1964
							defaultValue: before_newline,
1965
							options: operator_positions
1966
						},
1967
					]
1968
				},
1969
				{
1970
					pid: 'jsbeautify.config.html',
1971
					order: 3,
1972
					name: javascriptMessages['htmlFormattingOptions'],
1973
					tags: 'beautify javascript js jsbeautify formatting'.split(' '),
1974
					category: 'formatting',
1975
					categoryLabel: javascriptMessages['formatting'],
1976
					properties: [
1977
						{
1978
							id: 'indent_inner_html',  //$NON-NLS-1$
1979
							name: javascriptMessages['indent_inner_html'],
1980
							type: 'boolean', //$NON-NLS-1$
1981
							defaultValue: false
1982
						},
1983
						{
1984
							id: 'indent_handlebars',  //$NON-NLS-1$
1985
							name: javascriptMessages['indent_handlebars'],
1986
							type: 'boolean', //$NON-NLS-1$
1987
							defaultValue: false
1988
						},
1989
						{
1990
							id: 'wrap_attributes',  //$NON-NLS-1$
1991
							name: javascriptMessages['wrap_attributes'],
1992
							type: 'string', //$NON-NLS-1$
1993
							defaultValue: 'auto',
1994
							options: wrap_attributes_values
1995
						},
1996
						{
1997
							id: 'wrap_attributes_indent_size',  //$NON-NLS-1$
1998
							name: javascriptMessages['wrap_attributes_indent_size'],
1999
							type: 'number', //$NON-NLS-1$
2000
							defaultValue: 1
2001
						},
2002
						{
2003
							id: 'extra_liners',  //$NON-NLS-1$
2004
							name: javascriptMessages['extra_liners'],
2005
							type: 'string', //$NON-NLS-1$
2006
							defaultValue: 'head,body,html'
2007
						},
2008
						{
2009
							id: 'indent-scripts',  //$NON-NLS-1$
2010
							name: javascriptMessages['indent_scripts'],
2011
							type: 'string', //$NON-NLS-1$
2012
							defaultValue: normal,
2013
							options: indent_scripts_values
2014
						}
2015
					]
2016
				},
2017
				{
2018
					pid: 'jsbeautify.config.css',
2019
					order: 4,
2020
					name: javascriptMessages['cssFormattingOptions'],
2021
					tags: 'beautify javascript js jsbeautify formatting'.split(' '),
2022
					category: 'formatting',
2023
					categoryLabel: javascriptMessages['formatting'],
2024
					properties: [
2025
						{
2026
							id: 'selector_separator_newline',  //$NON-NLS-1$
2027
							name: javascriptMessages['selector_separator_newline'],
2028
							type: 'boolean', //$NON-NLS-1$
2029
							defaultValue: true
2030
						},
2031
						{
2032
							id: 'newline_between_rules',  //$NON-NLS-1$
2033
							name: javascriptMessages['newline_between_rules'],
2034
							type: 'boolean', //$NON-NLS-1$
2035
							defaultValue: true
2036
						},
2037
						{
2038
							id: 'space_around_selector_separator',  //$NON-NLS-1$
2039
							name: javascriptMessages['space_around_selector_separator'],
2040
							type: 'boolean', //$NON-NLS-1$
2041
							defaultValue: false
2042
						}
2043
					]
2044
				}]
2045
		});
1759
2046
1760
    	/**
2047
    	/**
1761
    	 * Register syntax styling for js, json and json schema content
2048
    	 * Register syntax styling for js, json and json schema content

Return to bug 496437