js-beautify 1.10.2 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1404,23 +1404,34 @@ Beautifier.prototype.beautify = function() {
1404
1404
  insidePropertyValue = false;
1405
1405
  this.outdent();
1406
1406
  }
1407
- this.indent();
1408
- this._output.space_before_token = true;
1409
- this.print_string(this._ch);
1410
1407
 
1411
1408
  // when entering conditional groups, only rulesets are allowed
1412
1409
  if (enteringConditionalGroup) {
1413
1410
  enteringConditionalGroup = false;
1414
- insideRule = (this._indentLevel > this._nestedLevel);
1411
+ insideRule = (this._indentLevel >= this._nestedLevel);
1415
1412
  } else {
1416
1413
  // otherwise, declarations are also allowed
1417
- insideRule = (this._indentLevel >= this._nestedLevel);
1414
+ insideRule = (this._indentLevel >= this._nestedLevel - 1);
1418
1415
  }
1419
1416
  if (this._options.newline_between_rules && insideRule) {
1420
1417
  if (this._output.previous_line && this._output.previous_line.item(-1) !== '{') {
1421
1418
  this._output.ensure_empty_line_above('/', ',');
1422
1419
  }
1423
1420
  }
1421
+
1422
+ this._output.space_before_token = true;
1423
+
1424
+ // The difference in print_string and indent order is necessary to indent the '{' correctly
1425
+ if (this._options.brace_style === 'expand') {
1426
+ this._output.add_new_line();
1427
+ this.print_string(this._ch);
1428
+ this.indent();
1429
+ this._output.set_indent(this._indentLevel);
1430
+ } else {
1431
+ this.indent();
1432
+ this.print_string(this._ch);
1433
+ }
1434
+
1424
1435
  this.eatWhitespace(true);
1425
1436
  this._output.add_new_line();
1426
1437
  } else if (this._ch === '}') {
@@ -1628,6 +1639,16 @@ function Options(options) {
1628
1639
  var space_around_selector_separator = this._get_boolean('space_around_selector_separator');
1629
1640
  this.space_around_combinator = this._get_boolean('space_around_combinator') || space_around_selector_separator;
1630
1641
 
1642
+ var brace_style_split = this._get_selection_list('brace_style', ['collapse', 'expand', 'end-expand', 'none', 'preserve-inline']);
1643
+ this.brace_style = 'collapse';
1644
+ for (var bs = 0; bs < brace_style_split.length; bs++) {
1645
+ if (brace_style_split[bs] !== 'expand') {
1646
+ // default to collapse, as only collapse|expand is implemented for now
1647
+ this.brace_style = 'collapse';
1648
+ } else {
1649
+ this.brace_style = brace_style_split[bs];
1650
+ }
1651
+ }
1631
1652
  }
1632
1653
  Options.prototype = new BaseOptions();
1633
1654
 
@@ -1983,7 +1983,7 @@ var get_custom_beautifier_name = function(tag_check, raw_token) {
1983
1983
  // For those without a type attribute use default;
1984
1984
  if (typeAttribute.search('text/css') > -1) {
1985
1985
  result = 'css';
1986
- } else if (typeAttribute.search(/(text|application|dojo)\/(x-)?(javascript|ecmascript|jscript|livescript|(ld\+)?json|method|aspect)/) > -1) {
1986
+ } else if (typeAttribute.search(/module|((text|application|dojo)\/(x-)?(javascript|ecmascript|jscript|livescript|(ld\+)?json|method|aspect))/) > -1) {
1987
1987
  result = 'javascript';
1988
1988
  } else if (typeAttribute.search(/(text|application|dojo)\/(x-)?(html)/) > -1) {
1989
1989
  result = 'html';
@@ -2370,10 +2370,12 @@ Beautifier.prototype._handle_tag_open = function(printer, raw_token, last_tag_to
2370
2370
  var parser_token = this._get_tag_open_token(raw_token);
2371
2371
 
2372
2372
  if ((last_tag_token.is_unformatted || last_tag_token.is_content_unformatted) &&
2373
+ !last_tag_token.is_empty_element &&
2373
2374
  raw_token.type === TOKEN.TAG_OPEN && raw_token.text.indexOf('</') === 0) {
2374
2375
  // End element tags for unformatted or content_unformatted elements
2375
2376
  // are printed raw to keep any newlines inside them exactly the same.
2376
2377
  printer.add_raw_token(raw_token);
2378
+ parser_token.start_tag_token = this._tag_stack.try_pop(parser_token.tag_name);
2377
2379
  } else {
2378
2380
  printer.traverse_whitespace(raw_token);
2379
2381
  this._set_tag_position(printer, raw_token, parser_token, last_tag_token, last_token);
@@ -2429,8 +2431,13 @@ var TagOpenParserToken = function(parent, raw_token) {
2429
2431
  tag_check_match = raw_token.text.match(/^<([^\s>]*)/);
2430
2432
  this.tag_check = tag_check_match ? tag_check_match[1] : '';
2431
2433
  } else {
2432
- tag_check_match = raw_token.text.match(/^{{[#\^]?([^\s}]+)/);
2434
+ tag_check_match = raw_token.text.match(/^{{(?:[\^]|#\*?)?([^\s}]+)/);
2433
2435
  this.tag_check = tag_check_match ? tag_check_match[1] : '';
2436
+
2437
+ // handle "{{#> myPartial}}
2438
+ if (raw_token.text === '{{#>' && this.tag_check === '>' && raw_token.next !== null) {
2439
+ this.tag_check = raw_token.next.text;
2440
+ }
2434
2441
  }
2435
2442
  this.tag_check = this.tag_check.toLowerCase();
2436
2443
 
@@ -2477,12 +2484,8 @@ Beautifier.prototype._set_tag_position = function(printer, raw_token, parser_tok
2477
2484
  // and do an ending needed
2478
2485
  if (this._do_optional_end_element(parser_token)) {
2479
2486
  if (!parser_token.is_inline_element) {
2480
- if (parser_token.parent) {
2481
- parser_token.parent.multiline_content = true;
2482
- }
2483
2487
  printer.print_newline(false);
2484
2488
  }
2485
-
2486
2489
  }
2487
2490
 
2488
2491
  this._tag_stack.record_tag(parser_token); //push it on the tag stack
@@ -2519,21 +2522,28 @@ Beautifier.prototype._set_tag_position = function(printer, raw_token, parser_tok
2519
2522
  if (parser_token.tag_name === '!--' && last_token.type === TOKEN.TAG_CLOSE &&
2520
2523
  last_tag_token.is_end_tag && parser_token.text.indexOf('\n') === -1) {
2521
2524
  //Do nothing. Leave comments on same line.
2522
- } else if (!parser_token.is_inline_element && !parser_token.is_unformatted) {
2523
- printer.print_newline(false);
2524
- }
2525
- } else if (parser_token.is_unformatted || parser_token.is_content_unformatted) {
2526
- if (!parser_token.is_inline_element && !parser_token.is_unformatted) {
2527
- printer.print_newline(false);
2525
+ } else {
2526
+ if (!(parser_token.is_inline_element || parser_token.is_unformatted)) {
2527
+ printer.print_newline(false);
2528
+ }
2529
+ this._calcluate_parent_multiline(printer, parser_token);
2528
2530
  }
2529
2531
  } else if (parser_token.is_end_tag) { //this tag is a double tag so check for tag-ending
2530
- if ((parser_token.start_tag_token && parser_token.start_tag_token.multiline_content) ||
2531
- !(parser_token.is_inline_element ||
2532
- (last_tag_token.is_inline_element) ||
2533
- (last_token.type === TOKEN.TAG_CLOSE &&
2534
- parser_token.start_tag_token === last_tag_token) ||
2535
- (last_token.type === 'TK_CONTENT')
2536
- )) {
2532
+ var do_end_expand = false;
2533
+
2534
+ // deciding whether a block is multiline should not be this hard
2535
+ do_end_expand = parser_token.start_tag_token && parser_token.start_tag_token.multiline_content;
2536
+ do_end_expand = do_end_expand || (!parser_token.is_inline_element &&
2537
+ !(last_tag_token.is_inline_element || last_tag_token.is_unformatted) &&
2538
+ !(last_token.type === TOKEN.TAG_CLOSE && parser_token.start_tag_token === last_tag_token) &&
2539
+ last_token.type !== 'TK_CONTENT'
2540
+ );
2541
+
2542
+ if (parser_token.is_content_unformatted || parser_token.is_unformatted) {
2543
+ do_end_expand = false;
2544
+ }
2545
+
2546
+ if (do_end_expand) {
2537
2547
  printer.print_newline(false);
2538
2548
  }
2539
2549
  } else { // it's a start-tag
@@ -2549,17 +2559,25 @@ Beautifier.prototype._set_tag_position = function(printer, raw_token, parser_tok
2549
2559
  }
2550
2560
  }
2551
2561
 
2552
- if (!parser_token.is_inline_element && last_token.type !== 'TK_CONTENT') {
2553
- if (parser_token.parent) {
2554
- parser_token.parent.multiline_content = true;
2555
- }
2562
+ if (!(parser_token.is_inline_element || parser_token.is_unformatted) &&
2563
+ (last_token.type !== 'TK_CONTENT' || parser_token.is_content_unformatted)) {
2556
2564
  printer.print_newline(false);
2557
2565
  }
2566
+
2567
+ this._calcluate_parent_multiline(printer, parser_token);
2568
+ }
2569
+ };
2570
+
2571
+ Beautifier.prototype._calcluate_parent_multiline = function(printer, parser_token) {
2572
+ if (parser_token.parent && printer._output.just_added_newline() &&
2573
+ !((parser_token.is_inline_element || parser_token.is_unformatted) && parser_token.parent.is_inline_element)) {
2574
+ parser_token.parent.multiline_content = true;
2558
2575
  }
2559
2576
  };
2560
2577
 
2561
2578
  //To be used for <p> tag special case:
2562
- //var p_closers = ['address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'main', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul'];
2579
+ var p_closers = ['address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'main', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul'];
2580
+ var p_parent_excludes = ['a', 'audio', 'del', 'ins', 'map', 'noscript', 'video'];
2563
2581
 
2564
2582
  Beautifier.prototype._do_optional_end_element = function(parser_token) {
2565
2583
  var result = null;
@@ -2570,7 +2588,9 @@ Beautifier.prototype._do_optional_end_element = function(parser_token) {
2570
2588
  if (parser_token.is_empty_element || !parser_token.is_start_tag || !parser_token.parent) {
2571
2589
  return;
2572
2590
 
2573
- } else if (parser_token.tag_name === 'body') {
2591
+ }
2592
+
2593
+ if (parser_token.tag_name === 'body') {
2574
2594
  // A head element’s end tag may be omitted if the head element is not immediately followed by a space character or a comment.
2575
2595
  result = result || this._tag_stack.try_pop('head');
2576
2596
 
@@ -2587,11 +2607,16 @@ Beautifier.prototype._do_optional_end_element = function(parser_token) {
2587
2607
  result = result || this._tag_stack.try_pop('dt', ['dl']);
2588
2608
  result = result || this._tag_stack.try_pop('dd', ['dl']);
2589
2609
 
2590
- //} else if (p_closers.indexOf(parser_token.tag_name) !== -1) {
2591
- //TODO: THIS IS A BUG FARM. We are not putting this into 1.8.0 as it is likely to blow up.
2592
- //A p element’s end tag may be omitted if the p element is immediately followed by an address, article, aside, blockquote, details, div, dl, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hr, main, nav, ol, p, pre, section, table, or ul element, or if there is no more content in the parent element and the parent element is an HTML element that is not an a, audio, del, ins, map, noscript, or video element, or an autonomous custom element.
2593
- //result = result || this._tag_stack.try_pop('p', ['body']);
2594
2610
 
2611
+ } else if (parser_token.parent.tag_name === 'p' && p_closers.indexOf(parser_token.tag_name) !== -1) {
2612
+ // IMPORTANT: this else-if works because p_closers has no overlap with any other element we look for in this method
2613
+ // check for the parent element is an HTML element that is not an <a>, <audio>, <del>, <ins>, <map>, <noscript>, or <video> element, or an autonomous custom element.
2614
+ // To do this right, this needs to be coded as an inclusion of the inverse of the exclusion above.
2615
+ // But to start with (if we ignore "autonomous custom elements") the exclusion would be fine.
2616
+ var p_parent = parser_token.parent.parent;
2617
+ if (!p_parent || p_parent_excludes.indexOf(p_parent.tag_name) === -1) {
2618
+ result = result || this._tag_stack.try_pop('p');
2619
+ }
2595
2620
  } else if (parser_token.tag_name === 'rp' || parser_token.tag_name === 'rt') {
2596
2621
  // An rt element’s end tag may be omitted if the rt element is immediately followed by an rt or rp element, or if there is no more content in the parent element.
2597
2622
  // An rp element’s end tag may be omitted if the rp element is immediately followed by an rt or rp element, or if there is no more content in the parent element.
@@ -2832,7 +2857,7 @@ var Tokenizer = function(input_string, options) {
2832
2857
  word: templatable_reader.until(/[\n\r\t <]/),
2833
2858
  single_quote: templatable_reader.until_after(/'/),
2834
2859
  double_quote: templatable_reader.until_after(/"/),
2835
- attribute: templatable_reader.until(/[\n\r\t =\/>]/),
2860
+ attribute: templatable_reader.until(/[\n\r\t =>]|\/>/),
2836
2861
  element_name: templatable_reader.until(/[\n\r\t >\/]/),
2837
2862
 
2838
2863
  handlebars_comment: pattern_reader.starting_with(/{{!--/).until_after(/--}}/),
@@ -2891,8 +2916,8 @@ Tokenizer.prototype._get_next_token = function(previous_token, open_token) { //
2891
2916
 
2892
2917
  token = token || this._read_open_handlebars(c, open_token);
2893
2918
  token = token || this._read_attribute(c, previous_token, open_token);
2894
- token = token || this._read_raw_content(c, previous_token, open_token);
2895
2919
  token = token || this._read_close(c, open_token);
2920
+ token = token || this._read_raw_content(c, previous_token, open_token);
2896
2921
  token = token || this._read_content_word(c);
2897
2922
  token = token || this._read_comment_or_cdata(c);
2898
2923
  token = token || this._read_processing(c);
@@ -3055,7 +3080,9 @@ Tokenizer.prototype._read_raw_content = function(c, previous_token, open_token)
3055
3080
  var resulting_string = '';
3056
3081
  if (open_token && open_token.text[0] === '{') {
3057
3082
  resulting_string = this.__patterns.handlebars_raw_close.read();
3058
- } else if (previous_token.type === TOKEN.TAG_CLOSE && (previous_token.opened.text[0] === '<')) {
3083
+ } else if (previous_token.type === TOKEN.TAG_CLOSE &&
3084
+ previous_token.opened.text[0] === '<' && previous_token.text[0] !== '/') {
3085
+ // ^^ empty tag has no content
3059
3086
  var tag_name = previous_token.opened.text.substr(1).toLowerCase();
3060
3087
  if (tag_name === 'script' || tag_name === 'style') {
3061
3088
  // Script and style tags are allowed to have comments wrapping their content
@@ -3067,6 +3094,7 @@ Tokenizer.prototype._read_raw_content = function(c, previous_token, open_token)
3067
3094
  }
3068
3095
  resulting_string = this._input.readUntil(new RegExp('</' + tag_name + '[\\n\\r\\t ]*?>', 'ig'));
3069
3096
  } else if (this._is_content_unformatted(tag_name)) {
3097
+
3070
3098
  resulting_string = this._input.readUntil(new RegExp('</' + tag_name + '[\\n\\r\\t ]*?>', 'ig'));
3071
3099
  }
3072
3100
  }
@@ -2197,11 +2197,11 @@ module.exports.Token = Token;
2197
2197
 
2198
2198
  // acorn used char codes to squeeze the last bit of performance out
2199
2199
  // Beautifier is okay without that, so we're using regex
2200
- // permit $ (36) and @ (64). @ is used in ES7 decorators.
2200
+ // permit # (23), $ (36), and @ (64). @ is used in ES7 decorators.
2201
2201
  // 65 through 91 are uppercase letters.
2202
2202
  // permit _ (95).
2203
2203
  // 97 through 123 are lowercase letters.
2204
- var baseASCIIidentifierStartChars = "\\x24\\x40\\x41-\\x5a\\x5f\\x61-\\x7a";
2204
+ var baseASCIIidentifierStartChars = "\\x23\\x24\\x40\\x41-\\x5a\\x5f\\x61-\\x7a";
2205
2205
 
2206
2206
  // inside an identifier @ is not allowed but 0-9 are.
2207
2207
  var baseASCIIidentifierChars = "\\x24\\x30-\\x39\\x41-\\x5a\\x5f\\x61-\\x7a";
@@ -2619,7 +2619,7 @@ var dot_pattern = /[^\d\.]/;
2619
2619
 
2620
2620
  var positionable_operators = (
2621
2621
  ">>> === !== " +
2622
- "<< && >= ** != == <= >> || " +
2622
+ "<< && >= ** != == <= >> || ?? |> " +
2623
2623
  "< / - + > : & % ? ^ | *").split(' ');
2624
2624
 
2625
2625
  // IMPORTANT: this must be sorted longest to shortest or tokenizing many not work.
@@ -2627,10 +2627,12 @@ var positionable_operators = (
2627
2627
  var punct =
2628
2628
  ">>>= " +
2629
2629
  "... >>= <<= === >>> !== **= " +
2630
- "=> ^= :: /= << <= == && -= >= >> != -- += ** || ++ %= &= *= |= " +
2630
+ "=> ^= :: /= << <= == && -= >= >> != -- += ** || ?? ++ %= &= *= |= |> " +
2631
2631
  "= ! ? > < : / ^ - + * & % ~ |";
2632
2632
 
2633
2633
  punct = punct.replace(/[-[\]{}()*+?.,\\^$|#]/g, "\\$&");
2634
+ // ?. but not if followed by a number
2635
+ punct = '\\?\\.(?!\\d) ' + punct;
2634
2636
  punct = punct.replace(/ /g, '|');
2635
2637
 
2636
2638
  var punct_pattern = new RegExp(punct);
@@ -2707,13 +2709,13 @@ Tokenizer.prototype._get_next_token = function(previous_token, open_token) { //
2707
2709
  return this._create_token(TOKEN.EOF, '');
2708
2710
  }
2709
2711
 
2712
+ token = token || this._read_non_javascript(c);
2710
2713
  token = token || this._read_string(c);
2711
2714
  token = token || this._read_word(previous_token);
2712
2715
  token = token || this._read_singles(c);
2713
2716
  token = token || this._read_comment(c);
2714
2717
  token = token || this._read_regexp(c, previous_token);
2715
2718
  token = token || this._read_xml(c, previous_token);
2716
- token = token || this._read_non_javascript(c);
2717
2719
  token = token || this._read_punctuation();
2718
2720
  token = token || this._create_token(TOKEN.UNKNOWN, this._input.next());
2719
2721
 
@@ -2772,6 +2774,8 @@ Tokenizer.prototype._read_punctuation = function() {
2772
2774
  if (resulting_string !== '') {
2773
2775
  if (resulting_string === '=') {
2774
2776
  return this._create_token(TOKEN.EQUALS, resulting_string);
2777
+ } else if (resulting_string === '?.') {
2778
+ return this._create_token(TOKEN.DOT, resulting_string);
2775
2779
  } else {
2776
2780
  return this._create_token(TOKEN.OPERATOR, resulting_string);
2777
2781
  }
package/js/lib/cli.js CHANGED
@@ -392,6 +392,7 @@ function usage(err) {
392
392
  msg.push(' --unformatted_content_delimiter Keep text content together between this string [""]');
393
393
  break;
394
394
  case "css":
395
+ msg.push(' -b, --brace-style [collapse|expand] ["collapse"]');
395
396
  msg.push(' -L, --selector-separator-newline Add a newline between multiple selectors.');
396
397
  msg.push(' -N, --newline-between-rules Add a newline between CSS rules.');
397
398
  }
@@ -100,6 +100,14 @@ var P_A_C_K_E_R = {
100
100
  t.expect(pk1, unpk1);
101
101
  t.expect(pk2, unpk2);
102
102
  t.expect(pk3, unpk3);
103
+ t.expect("function test (){alert ('This is a test!')}; " +
104
+ "eval(function(p,a,c,k,e,r){e=String;if(!''.replace(/^/,String))" +
105
+ "{while(c--)r[c]=k[c]||c;k=[function(e){return r[e]}];e=function" +
106
+ "(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp(" +
107
+ "'\\b'+e(c)+'\\b','g'),k[c]);return p}('0 2=\\\'{Íâ–+›ï;ã†Ù¥#\\\'',3,3," +
108
+ "'var||a'.split('|'),0,{}))",
109
+ "function test (){alert ('This is a test!')}; var a='{Íâ–+›ï;ã†Ù¥#'");
110
+
103
111
 
104
112
  var filler = '\nfiller\n';
105
113
  t.expect(filler + pk1 + "\n" + pk_broken + filler + pk2 + filler, filler + unpk1 + "\n" + pk_broken + filler + unpk2 + filler);
package/js/src/cli.js CHANGED
@@ -392,6 +392,7 @@ function usage(err) {
392
392
  msg.push(' --unformatted_content_delimiter Keep text content together between this string [""]');
393
393
  break;
394
394
  case "css":
395
+ msg.push(' -b, --brace-style [collapse|expand] ["collapse"]');
395
396
  msg.push(' -L, --selector-separator-newline Add a newline between multiple selectors.');
396
397
  msg.push(' -N, --newline-between-rules Add a newline between CSS rules.');
397
398
  }
@@ -291,23 +291,34 @@ Beautifier.prototype.beautify = function() {
291
291
  insidePropertyValue = false;
292
292
  this.outdent();
293
293
  }
294
- this.indent();
295
- this._output.space_before_token = true;
296
- this.print_string(this._ch);
297
294
 
298
295
  // when entering conditional groups, only rulesets are allowed
299
296
  if (enteringConditionalGroup) {
300
297
  enteringConditionalGroup = false;
301
- insideRule = (this._indentLevel > this._nestedLevel);
298
+ insideRule = (this._indentLevel >= this._nestedLevel);
302
299
  } else {
303
300
  // otherwise, declarations are also allowed
304
- insideRule = (this._indentLevel >= this._nestedLevel);
301
+ insideRule = (this._indentLevel >= this._nestedLevel - 1);
305
302
  }
306
303
  if (this._options.newline_between_rules && insideRule) {
307
304
  if (this._output.previous_line && this._output.previous_line.item(-1) !== '{') {
308
305
  this._output.ensure_empty_line_above('/', ',');
309
306
  }
310
307
  }
308
+
309
+ this._output.space_before_token = true;
310
+
311
+ // The difference in print_string and indent order is necessary to indent the '{' correctly
312
+ if (this._options.brace_style === 'expand') {
313
+ this._output.add_new_line();
314
+ this.print_string(this._ch);
315
+ this.indent();
316
+ this._output.set_indent(this._indentLevel);
317
+ } else {
318
+ this.indent();
319
+ this.print_string(this._ch);
320
+ }
321
+
311
322
  this.eatWhitespace(true);
312
323
  this._output.add_new_line();
313
324
  } else if (this._ch === '}') {
@@ -38,6 +38,16 @@ function Options(options) {
38
38
  var space_around_selector_separator = this._get_boolean('space_around_selector_separator');
39
39
  this.space_around_combinator = this._get_boolean('space_around_combinator') || space_around_selector_separator;
40
40
 
41
+ var brace_style_split = this._get_selection_list('brace_style', ['collapse', 'expand', 'end-expand', 'none', 'preserve-inline']);
42
+ this.brace_style = 'collapse';
43
+ for (var bs = 0; bs < brace_style_split.length; bs++) {
44
+ if (brace_style_split[bs] !== 'expand') {
45
+ // default to collapse, as only collapse|expand is implemented for now
46
+ this.brace_style = 'collapse';
47
+ } else {
48
+ this.brace_style = brace_style_split[bs];
49
+ }
50
+ }
41
51
  }
42
52
  Options.prototype = new BaseOptions();
43
53
 
@@ -159,7 +159,7 @@ var get_custom_beautifier_name = function(tag_check, raw_token) {
159
159
  // For those without a type attribute use default;
160
160
  if (typeAttribute.search('text/css') > -1) {
161
161
  result = 'css';
162
- } else if (typeAttribute.search(/(text|application|dojo)\/(x-)?(javascript|ecmascript|jscript|livescript|(ld\+)?json|method|aspect)/) > -1) {
162
+ } else if (typeAttribute.search(/module|((text|application|dojo)\/(x-)?(javascript|ecmascript|jscript|livescript|(ld\+)?json|method|aspect))/) > -1) {
163
163
  result = 'javascript';
164
164
  } else if (typeAttribute.search(/(text|application|dojo)\/(x-)?(html)/) > -1) {
165
165
  result = 'html';
@@ -546,10 +546,12 @@ Beautifier.prototype._handle_tag_open = function(printer, raw_token, last_tag_to
546
546
  var parser_token = this._get_tag_open_token(raw_token);
547
547
 
548
548
  if ((last_tag_token.is_unformatted || last_tag_token.is_content_unformatted) &&
549
+ !last_tag_token.is_empty_element &&
549
550
  raw_token.type === TOKEN.TAG_OPEN && raw_token.text.indexOf('</') === 0) {
550
551
  // End element tags for unformatted or content_unformatted elements
551
552
  // are printed raw to keep any newlines inside them exactly the same.
552
553
  printer.add_raw_token(raw_token);
554
+ parser_token.start_tag_token = this._tag_stack.try_pop(parser_token.tag_name);
553
555
  } else {
554
556
  printer.traverse_whitespace(raw_token);
555
557
  this._set_tag_position(printer, raw_token, parser_token, last_tag_token, last_token);
@@ -605,8 +607,13 @@ var TagOpenParserToken = function(parent, raw_token) {
605
607
  tag_check_match = raw_token.text.match(/^<([^\s>]*)/);
606
608
  this.tag_check = tag_check_match ? tag_check_match[1] : '';
607
609
  } else {
608
- tag_check_match = raw_token.text.match(/^{{[#\^]?([^\s}]+)/);
610
+ tag_check_match = raw_token.text.match(/^{{(?:[\^]|#\*?)?([^\s}]+)/);
609
611
  this.tag_check = tag_check_match ? tag_check_match[1] : '';
612
+
613
+ // handle "{{#> myPartial}}
614
+ if (raw_token.text === '{{#>' && this.tag_check === '>' && raw_token.next !== null) {
615
+ this.tag_check = raw_token.next.text;
616
+ }
610
617
  }
611
618
  this.tag_check = this.tag_check.toLowerCase();
612
619
 
@@ -653,12 +660,8 @@ Beautifier.prototype._set_tag_position = function(printer, raw_token, parser_tok
653
660
  // and do an ending needed
654
661
  if (this._do_optional_end_element(parser_token)) {
655
662
  if (!parser_token.is_inline_element) {
656
- if (parser_token.parent) {
657
- parser_token.parent.multiline_content = true;
658
- }
659
663
  printer.print_newline(false);
660
664
  }
661
-
662
665
  }
663
666
 
664
667
  this._tag_stack.record_tag(parser_token); //push it on the tag stack
@@ -695,21 +698,28 @@ Beautifier.prototype._set_tag_position = function(printer, raw_token, parser_tok
695
698
  if (parser_token.tag_name === '!--' && last_token.type === TOKEN.TAG_CLOSE &&
696
699
  last_tag_token.is_end_tag && parser_token.text.indexOf('\n') === -1) {
697
700
  //Do nothing. Leave comments on same line.
698
- } else if (!parser_token.is_inline_element && !parser_token.is_unformatted) {
699
- printer.print_newline(false);
700
- }
701
- } else if (parser_token.is_unformatted || parser_token.is_content_unformatted) {
702
- if (!parser_token.is_inline_element && !parser_token.is_unformatted) {
703
- printer.print_newline(false);
701
+ } else {
702
+ if (!(parser_token.is_inline_element || parser_token.is_unformatted)) {
703
+ printer.print_newline(false);
704
+ }
705
+ this._calcluate_parent_multiline(printer, parser_token);
704
706
  }
705
707
  } else if (parser_token.is_end_tag) { //this tag is a double tag so check for tag-ending
706
- if ((parser_token.start_tag_token && parser_token.start_tag_token.multiline_content) ||
707
- !(parser_token.is_inline_element ||
708
- (last_tag_token.is_inline_element) ||
709
- (last_token.type === TOKEN.TAG_CLOSE &&
710
- parser_token.start_tag_token === last_tag_token) ||
711
- (last_token.type === 'TK_CONTENT')
712
- )) {
708
+ var do_end_expand = false;
709
+
710
+ // deciding whether a block is multiline should not be this hard
711
+ do_end_expand = parser_token.start_tag_token && parser_token.start_tag_token.multiline_content;
712
+ do_end_expand = do_end_expand || (!parser_token.is_inline_element &&
713
+ !(last_tag_token.is_inline_element || last_tag_token.is_unformatted) &&
714
+ !(last_token.type === TOKEN.TAG_CLOSE && parser_token.start_tag_token === last_tag_token) &&
715
+ last_token.type !== 'TK_CONTENT'
716
+ );
717
+
718
+ if (parser_token.is_content_unformatted || parser_token.is_unformatted) {
719
+ do_end_expand = false;
720
+ }
721
+
722
+ if (do_end_expand) {
713
723
  printer.print_newline(false);
714
724
  }
715
725
  } else { // it's a start-tag
@@ -725,17 +735,25 @@ Beautifier.prototype._set_tag_position = function(printer, raw_token, parser_tok
725
735
  }
726
736
  }
727
737
 
728
- if (!parser_token.is_inline_element && last_token.type !== 'TK_CONTENT') {
729
- if (parser_token.parent) {
730
- parser_token.parent.multiline_content = true;
731
- }
738
+ if (!(parser_token.is_inline_element || parser_token.is_unformatted) &&
739
+ (last_token.type !== 'TK_CONTENT' || parser_token.is_content_unformatted)) {
732
740
  printer.print_newline(false);
733
741
  }
742
+
743
+ this._calcluate_parent_multiline(printer, parser_token);
744
+ }
745
+ };
746
+
747
+ Beautifier.prototype._calcluate_parent_multiline = function(printer, parser_token) {
748
+ if (parser_token.parent && printer._output.just_added_newline() &&
749
+ !((parser_token.is_inline_element || parser_token.is_unformatted) && parser_token.parent.is_inline_element)) {
750
+ parser_token.parent.multiline_content = true;
734
751
  }
735
752
  };
736
753
 
737
754
  //To be used for <p> tag special case:
738
- //var p_closers = ['address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'main', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul'];
755
+ var p_closers = ['address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'main', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul'];
756
+ var p_parent_excludes = ['a', 'audio', 'del', 'ins', 'map', 'noscript', 'video'];
739
757
 
740
758
  Beautifier.prototype._do_optional_end_element = function(parser_token) {
741
759
  var result = null;
@@ -746,7 +764,9 @@ Beautifier.prototype._do_optional_end_element = function(parser_token) {
746
764
  if (parser_token.is_empty_element || !parser_token.is_start_tag || !parser_token.parent) {
747
765
  return;
748
766
 
749
- } else if (parser_token.tag_name === 'body') {
767
+ }
768
+
769
+ if (parser_token.tag_name === 'body') {
750
770
  // A head element’s end tag may be omitted if the head element is not immediately followed by a space character or a comment.
751
771
  result = result || this._tag_stack.try_pop('head');
752
772
 
@@ -763,11 +783,16 @@ Beautifier.prototype._do_optional_end_element = function(parser_token) {
763
783
  result = result || this._tag_stack.try_pop('dt', ['dl']);
764
784
  result = result || this._tag_stack.try_pop('dd', ['dl']);
765
785
 
766
- //} else if (p_closers.indexOf(parser_token.tag_name) !== -1) {
767
- //TODO: THIS IS A BUG FARM. We are not putting this into 1.8.0 as it is likely to blow up.
768
- //A p element’s end tag may be omitted if the p element is immediately followed by an address, article, aside, blockquote, details, div, dl, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hr, main, nav, ol, p, pre, section, table, or ul element, or if there is no more content in the parent element and the parent element is an HTML element that is not an a, audio, del, ins, map, noscript, or video element, or an autonomous custom element.
769
- //result = result || this._tag_stack.try_pop('p', ['body']);
770
786
 
787
+ } else if (parser_token.parent.tag_name === 'p' && p_closers.indexOf(parser_token.tag_name) !== -1) {
788
+ // IMPORTANT: this else-if works because p_closers has no overlap with any other element we look for in this method
789
+ // check for the parent element is an HTML element that is not an <a>, <audio>, <del>, <ins>, <map>, <noscript>, or <video> element, or an autonomous custom element.
790
+ // To do this right, this needs to be coded as an inclusion of the inverse of the exclusion above.
791
+ // But to start with (if we ignore "autonomous custom elements") the exclusion would be fine.
792
+ var p_parent = parser_token.parent.parent;
793
+ if (!p_parent || p_parent_excludes.indexOf(p_parent.tag_name) === -1) {
794
+ result = result || this._tag_stack.try_pop('p');
795
+ }
771
796
  } else if (parser_token.tag_name === 'rp' || parser_token.tag_name === 'rt') {
772
797
  // An rt element’s end tag may be omitted if the rt element is immediately followed by an rt or rp element, or if there is no more content in the parent element.
773
798
  // An rp element’s end tag may be omitted if the rp element is immediately followed by an rt or rp element, or if there is no more content in the parent element.
@@ -63,7 +63,7 @@ var Tokenizer = function(input_string, options) {
63
63
  word: templatable_reader.until(/[\n\r\t <]/),
64
64
  single_quote: templatable_reader.until_after(/'/),
65
65
  double_quote: templatable_reader.until_after(/"/),
66
- attribute: templatable_reader.until(/[\n\r\t =\/>]/),
66
+ attribute: templatable_reader.until(/[\n\r\t =>]|\/>/),
67
67
  element_name: templatable_reader.until(/[\n\r\t >\/]/),
68
68
 
69
69
  handlebars_comment: pattern_reader.starting_with(/{{!--/).until_after(/--}}/),
@@ -122,8 +122,8 @@ Tokenizer.prototype._get_next_token = function(previous_token, open_token) { //
122
122
 
123
123
  token = token || this._read_open_handlebars(c, open_token);
124
124
  token = token || this._read_attribute(c, previous_token, open_token);
125
- token = token || this._read_raw_content(c, previous_token, open_token);
126
125
  token = token || this._read_close(c, open_token);
126
+ token = token || this._read_raw_content(c, previous_token, open_token);
127
127
  token = token || this._read_content_word(c);
128
128
  token = token || this._read_comment_or_cdata(c);
129
129
  token = token || this._read_processing(c);
@@ -286,7 +286,9 @@ Tokenizer.prototype._read_raw_content = function(c, previous_token, open_token)
286
286
  var resulting_string = '';
287
287
  if (open_token && open_token.text[0] === '{') {
288
288
  resulting_string = this.__patterns.handlebars_raw_close.read();
289
- } else if (previous_token.type === TOKEN.TAG_CLOSE && (previous_token.opened.text[0] === '<')) {
289
+ } else if (previous_token.type === TOKEN.TAG_CLOSE &&
290
+ previous_token.opened.text[0] === '<' && previous_token.text[0] !== '/') {
291
+ // ^^ empty tag has no content
290
292
  var tag_name = previous_token.opened.text.substr(1).toLowerCase();
291
293
  if (tag_name === 'script' || tag_name === 'style') {
292
294
  // Script and style tags are allowed to have comments wrapping their content
@@ -298,6 +300,7 @@ Tokenizer.prototype._read_raw_content = function(c, previous_token, open_token)
298
300
  }
299
301
  resulting_string = this._input.readUntil(new RegExp('</' + tag_name + '[\\n\\r\\t ]*?>', 'ig'));
300
302
  } else if (this._is_content_unformatted(tag_name)) {
303
+
301
304
  resulting_string = this._input.readUntil(new RegExp('</' + tag_name + '[\\n\\r\\t ]*?>', 'ig'));
302
305
  }
303
306
  }
@@ -17,11 +17,11 @@
17
17
 
18
18
  // acorn used char codes to squeeze the last bit of performance out
19
19
  // Beautifier is okay without that, so we're using regex
20
- // permit $ (36) and @ (64). @ is used in ES7 decorators.
20
+ // permit # (23), $ (36), and @ (64). @ is used in ES7 decorators.
21
21
  // 65 through 91 are uppercase letters.
22
22
  // permit _ (95).
23
23
  // 97 through 123 are lowercase letters.
24
- var baseASCIIidentifierStartChars = "\\x24\\x40\\x41-\\x5a\\x5f\\x61-\\x7a";
24
+ var baseASCIIidentifierStartChars = "\\x23\\x24\\x40\\x41-\\x5a\\x5f\\x61-\\x7a";
25
25
 
26
26
  // inside an identifier @ is not allowed but 0-9 are.
27
27
  var baseASCIIidentifierChars = "\\x24\\x30-\\x39\\x41-\\x5a\\x5f\\x61-\\x7a";