js-beautify 1.14.2 → 1.14.5

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.
@@ -1090,6 +1090,10 @@ function Beautifier(source_text, options) {
1090
1090
  "@supports": true,
1091
1091
  "@document": true
1092
1092
  };
1093
+ this.NON_SEMICOLON_NEWLINE_PROPERTY = [
1094
+ "grid-template-areas",
1095
+ "grid-template"
1096
+ ];
1093
1097
 
1094
1098
  }
1095
1099
 
@@ -1214,7 +1218,9 @@ Beautifier.prototype.beautify = function() {
1214
1218
  var enteringConditionalGroup = false;
1215
1219
  var insideAtExtend = false;
1216
1220
  var insideAtImport = false;
1221
+ var insideScssMap = false;
1217
1222
  var topCharacter = this._ch;
1223
+ var insideNonSemiColonValues = false;
1218
1224
  var whitespace;
1219
1225
  var isAfterSpace;
1220
1226
  var previous_ch;
@@ -1266,7 +1272,7 @@ Beautifier.prototype.beautify = function() {
1266
1272
 
1267
1273
  // Ensures any new lines following the comment are preserved
1268
1274
  this.eatWhitespace(true);
1269
- } else if (this._ch === '@') {
1275
+ } else if (this._ch === '@' || this._ch === '$') {
1270
1276
  this.preserveSingleSpace(isAfterSpace);
1271
1277
 
1272
1278
  // deal with less propery mixins @{...}
@@ -1337,7 +1343,12 @@ Beautifier.prototype.beautify = function() {
1337
1343
  this.indent();
1338
1344
  this._output.set_indent(this._indentLevel);
1339
1345
  } else {
1340
- this.indent();
1346
+ // inside mixin and first param is object
1347
+ if (previous_ch === '(') {
1348
+ this._output.space_before_token = false;
1349
+ } else if (previous_ch !== ',') {
1350
+ this.indent();
1351
+ }
1341
1352
  this.print_string(this._ch);
1342
1353
  }
1343
1354
 
@@ -1369,7 +1380,21 @@ Beautifier.prototype.beautify = function() {
1369
1380
  this._output.add_new_line(true);
1370
1381
  }
1371
1382
  }
1383
+ if (this._input.peek() === ')') {
1384
+ this._output.trim(true);
1385
+ if (this._options.brace_style === "expand") {
1386
+ this._output.add_new_line(true);
1387
+ }
1388
+ }
1372
1389
  } else if (this._ch === ":") {
1390
+
1391
+ for (var i = 0; i < this.NON_SEMICOLON_NEWLINE_PROPERTY.length; i++) {
1392
+ if (this._input.lookBack(this.NON_SEMICOLON_NEWLINE_PROPERTY[i])) {
1393
+ insideNonSemiColonValues = true;
1394
+ break;
1395
+ }
1396
+ }
1397
+
1373
1398
  if ((insideRule || enteringConditionalGroup) && !(this._input.lookBack("&") || this.foundNestedPseudoClass()) && !this._input.lookBack("(") && !insideAtExtend && parenLevel === 0) {
1374
1399
  // 'property: value' delimiter
1375
1400
  // which could be in a conditional group query
@@ -1398,10 +1423,12 @@ Beautifier.prototype.beautify = function() {
1398
1423
  }
1399
1424
  }
1400
1425
  } else if (this._ch === '"' || this._ch === '\'') {
1401
- this.preserveSingleSpace(isAfterSpace);
1426
+ var preserveQuoteSpace = previous_ch === '"' || previous_ch === '\'';
1427
+ this.preserveSingleSpace(preserveQuoteSpace || isAfterSpace);
1402
1428
  this.print_string(this._ch + this.eatString(this._ch));
1403
1429
  this.eatWhitespace(true);
1404
1430
  } else if (this._ch === ';') {
1431
+ insideNonSemiColonValues = false;
1405
1432
  if (parenLevel === 0) {
1406
1433
  if (insidePropertyValue) {
1407
1434
  this.outdent();
@@ -1441,22 +1468,39 @@ Beautifier.prototype.beautify = function() {
1441
1468
  }
1442
1469
  }
1443
1470
  } else {
1444
- this.preserveSingleSpace(isAfterSpace);
1471
+ var space_needed = false;
1472
+ if (this._input.lookBack("with")) {
1473
+ // look back is not an accurate solution, we need tokens to confirm without whitespaces
1474
+ space_needed = true;
1475
+ }
1476
+ this.preserveSingleSpace(isAfterSpace || space_needed);
1445
1477
  this.print_string(this._ch);
1446
- this.eatWhitespace();
1447
- parenLevel++;
1448
- this.indent();
1478
+
1479
+ // handle scss/sass map
1480
+ if (insidePropertyValue && previous_ch === "$" && this._options.selector_separator_newline) {
1481
+ this._output.add_new_line();
1482
+ insideScssMap = true;
1483
+ } else {
1484
+ this.eatWhitespace();
1485
+ parenLevel++;
1486
+ this.indent();
1487
+ }
1449
1488
  }
1450
1489
  } else if (this._ch === ')') {
1451
1490
  if (parenLevel) {
1452
1491
  parenLevel--;
1453
1492
  this.outdent();
1454
1493
  }
1494
+ if (insideScssMap && this._input.peek() === ";" && this._options.selector_separator_newline) {
1495
+ insideScssMap = false;
1496
+ this.outdent();
1497
+ this._output.add_new_line();
1498
+ }
1455
1499
  this.print_string(this._ch);
1456
1500
  } else if (this._ch === ',') {
1457
1501
  this.print_string(this._ch);
1458
1502
  this.eatWhitespace(true);
1459
- if (this._options.selector_separator_newline && !insidePropertyValue && parenLevel === 0 && !insideAtImport && !insideAtExtend) {
1503
+ if (this._options.selector_separator_newline && (!insidePropertyValue || insideScssMap) && parenLevel === 0 && !insideAtImport && !insideAtExtend) {
1460
1504
  this._output.add_new_line();
1461
1505
  } else {
1462
1506
  this._output.space_before_token = true;
@@ -1487,11 +1531,16 @@ Beautifier.prototype.beautify = function() {
1487
1531
  this._ch = '';
1488
1532
  }
1489
1533
  } else if (this._ch === '!' && !this._input.lookBack("\\")) { // !important
1490
- this.print_string(' ');
1534
+ this._output.space_before_token = true;
1491
1535
  this.print_string(this._ch);
1492
1536
  } else {
1493
- this.preserveSingleSpace(isAfterSpace);
1537
+ var preserveAfterSpace = previous_ch === '"' || previous_ch === '\'';
1538
+ this.preserveSingleSpace(preserveAfterSpace || isAfterSpace);
1494
1539
  this.print_string(this._ch);
1540
+
1541
+ if (!this._output.just_added_newline() && this._input.peek() === '\n' && insideNonSemiColonValues) {
1542
+ this._output.add_new_line();
1543
+ }
1495
1544
  }
1496
1545
  }
1497
1546
 
@@ -2355,14 +2355,19 @@ var TagOpenParserToken = function(parent, raw_token) {
2355
2355
  tag_check_match = raw_token.text.match(/^<([^\s>]*)/);
2356
2356
  this.tag_check = tag_check_match ? tag_check_match[1] : '';
2357
2357
  } else {
2358
- tag_check_match = raw_token.text.match(/^{{(?:[\^]|#\*?)?([^\s}]+)/);
2358
+ tag_check_match = raw_token.text.match(/^{{~?(?:[\^]|#\*?)?([^\s}]+)/);
2359
2359
  this.tag_check = tag_check_match ? tag_check_match[1] : '';
2360
2360
 
2361
- // handle "{{#> myPartial}}
2362
- if (raw_token.text === '{{#>' && this.tag_check === '>' && raw_token.next !== null) {
2363
- this.tag_check = raw_token.next.text.split(' ')[0];
2361
+ // handle "{{#> myPartial}}" or "{{~#> myPartial}}"
2362
+ if ((raw_token.text.startsWith('{{#>') || raw_token.text.startsWith('{{~#>')) && this.tag_check[0] === '>') {
2363
+ if (this.tag_check === '>' && raw_token.next !== null) {
2364
+ this.tag_check = raw_token.next.text.split(' ')[0];
2365
+ } else {
2366
+ this.tag_check = raw_token.text.split('>')[1];
2367
+ }
2364
2368
  }
2365
2369
  }
2370
+
2366
2371
  this.tag_check = this.tag_check.toLowerCase();
2367
2372
 
2368
2373
  if (raw_token.type === TOKEN.COMMENT) {
@@ -2374,9 +2379,17 @@ var TagOpenParserToken = function(parent, raw_token) {
2374
2379
  this.is_end_tag = !this.is_start_tag ||
2375
2380
  (raw_token.closed && raw_token.closed.text === '/>');
2376
2381
 
2382
+ // if whitespace handler ~ included (i.e. {{~#if true}}), handlebars tags start at pos 3 not pos 2
2383
+ var handlebar_starts = 2;
2384
+ if (this.tag_start_char === '{' && this.text.length >= 3) {
2385
+ if (this.text.charAt(2) === '~') {
2386
+ handlebar_starts = 3;
2387
+ }
2388
+ }
2389
+
2377
2390
  // handlebars tags that don't start with # or ^ are single_tags, and so also start and end.
2378
2391
  this.is_end_tag = this.is_end_tag ||
2379
- (this.tag_start_char === '{' && (this.text.length < 3 || (/[^#\^]/.test(this.text.charAt(2)))));
2392
+ (this.tag_start_char === '{' && (this.text.length < 3 || (/[^#\^]/.test(this.text.charAt(handlebar_starts)))));
2380
2393
  }
2381
2394
  };
2382
2395
 
@@ -330,6 +330,7 @@ Beautifier.prototype.create_flags = function(flags_base, mode) {
330
330
  inline_frame: false,
331
331
  if_block: false,
332
332
  else_block: false,
333
+ class_start_block: false, // class A { INSIDE HERE } or class B extends C { INSIDE HERE }
333
334
  do_block: false,
334
335
  do_while: false,
335
336
  import_block: false,
@@ -742,6 +743,8 @@ Beautifier.prototype.handle_start_expr = function(current_token) {
742
743
  (peek_back_two.text === '*' && (peek_back_three.text === '{' || peek_back_three.text === ','))) {
743
744
  this._output.space_before_token = true;
744
745
  }
746
+ } else if (this._flags.parent && this._flags.parent.class_start_block) {
747
+ this._output.space_before_token = true;
745
748
  }
746
749
  }
747
750
  } else {
@@ -836,10 +839,10 @@ Beautifier.prototype.handle_start_block = function(current_token) {
836
839
  )) {
837
840
  // We don't support TypeScript,but we didn't break it for a very long time.
838
841
  // We'll try to keep not breaking it.
839
- if (!in_array(this._last_last_text, ['class', 'interface'])) {
840
- this.set_mode(MODE.ObjectLiteral);
841
- } else {
842
+ if (in_array(this._last_last_text, ['class', 'interface']) && !in_array(second_token.text, [':', ','])) {
842
843
  this.set_mode(MODE.BlockStatement);
844
+ } else {
845
+ this.set_mode(MODE.ObjectLiteral);
843
846
  }
844
847
  } else if (this._flags.last_token.type === TOKEN.OPERATOR && this._flags.last_token.text === '=>') {
845
848
  // arrow function: (param1, paramN) => { statements }
@@ -856,6 +859,12 @@ Beautifier.prototype.handle_start_block = function(current_token) {
856
859
  this.set_mode(MODE.BlockStatement);
857
860
  }
858
861
 
862
+ if (this._flags.last_token) {
863
+ if (reserved_array(this._flags.last_token.previous, ['class', 'extends'])) {
864
+ this._flags.class_start_block = true;
865
+ }
866
+ }
867
+
859
868
  var empty_braces = !next_token.comments_before && next_token.text === '}';
860
869
  var empty_anonymous_function = empty_braces && this._flags.last_word === 'function' &&
861
870
  this._flags.last_token.type === TOKEN.END_EXPR;
@@ -955,7 +964,7 @@ Beautifier.prototype.handle_word = function(current_token) {
955
964
  if (current_token.type === TOKEN.RESERVED) {
956
965
  if (in_array(current_token.text, ['set', 'get']) && this._flags.mode !== MODE.ObjectLiteral) {
957
966
  current_token.type = TOKEN.WORD;
958
- } else if (current_token.text === 'import' && this._tokens.peek().text === '(') {
967
+ } else if (current_token.text === 'import' && in_array(this._tokens.peek().text, ['(', '.'])) {
959
968
  current_token.type = TOKEN.WORD;
960
969
  } else if (in_array(current_token.text, ['as', 'from']) && !this._flags.import_block) {
961
970
  current_token.type = TOKEN.WORD;
@@ -1296,13 +1305,6 @@ Beautifier.prototype.handle_operator = function(current_token) {
1296
1305
  this.handle_whitespace_and_comments(current_token, preserve_statement_flags);
1297
1306
  }
1298
1307
 
1299
- if (reserved_array(this._flags.last_token, special_words)) {
1300
- // "return" had a special handling in TK_WORD. Now we need to return the favor
1301
- this._output.space_before_token = true;
1302
- this.print_token(current_token);
1303
- return;
1304
- }
1305
-
1306
1308
  // hack for actionscript's import .*;
1307
1309
  if (current_token.text === '*' && this._flags.last_token.type === TOKEN.DOT) {
1308
1310
  this.print_token(current_token);
@@ -1430,7 +1432,11 @@ Beautifier.prototype.handle_operator = function(current_token) {
1430
1432
  // http://www.ecma-international.org/ecma-262/5.1/#sec-7.9.1
1431
1433
  // if there is a newline between -- or ++ and anything else we should preserve it.
1432
1434
  if (current_token.newlines && (current_token.text === '--' || current_token.text === '++' || current_token.text === '~')) {
1433
- this.print_newline(false, true);
1435
+ var new_line_needed = reserved_array(this._flags.last_token, special_words) && current_token.newlines;
1436
+ if (new_line_needed && (this._previous_flags.if_block || this._previous_flags.else_block)) {
1437
+ this.restore_mode();
1438
+ }
1439
+ this.print_newline(new_line_needed, true);
1434
1440
  }
1435
1441
 
1436
1442
  if (this._flags.last_token.text === ';' && is_expression(this._flags.mode)) {
@@ -1570,6 +1576,10 @@ Beautifier.prototype.handle_dot = function(current_token) {
1570
1576
  this.handle_whitespace_and_comments(current_token, true);
1571
1577
  }
1572
1578
 
1579
+ if (this._flags.last_token.text.match('^[0-9]+$')) {
1580
+ this._output.space_before_token = true;
1581
+ }
1582
+
1573
1583
  if (reserved_array(this._flags.last_token, special_words)) {
1574
1584
  this._output.space_before_token = false;
1575
1585
  } else {
@@ -2533,7 +2543,7 @@ var digit = /[0-9]/;
2533
2543
  var dot_pattern = /[^\d\.]/;
2534
2544
 
2535
2545
  var positionable_operators = (
2536
- ">>> === !== " +
2546
+ ">>> === !== &&= ??= ||= " +
2537
2547
  "<< && >= ** != == <= >> || ?? |> " +
2538
2548
  "< / - + > : & % ? ^ | *").split(' ');
2539
2549
 
@@ -2541,7 +2551,7 @@ var positionable_operators = (
2541
2551
  // Also, you must update possitionable operators separately from punct
2542
2552
  var punct =
2543
2553
  ">>>= " +
2544
- "... >>= <<= === >>> !== **= " +
2554
+ "... >>= <<= === >>> !== **= &&= ??= ||= " +
2545
2555
  "=> ^= :: /= << <= == && -= >= >> != -- += ** || ?? ++ %= &= *= |= |> " +
2546
2556
  "= ! ? > < : / ^ - + * & % ~ |";
2547
2557
 
@@ -2554,7 +2564,7 @@ var punct_pattern = new RegExp(punct);
2554
2564
 
2555
2565
  // words which should always start on new line.
2556
2566
  var line_starters = 'continue,try,throw,return,var,let,const,if,switch,case,default,for,while,break,function,import,export'.split(',');
2557
- var reserved_words = line_starters.concat(['do', 'in', 'of', 'else', 'get', 'set', 'new', 'catch', 'finally', 'typeof', 'yield', 'async', 'await', 'from', 'as']);
2567
+ var reserved_words = line_starters.concat(['do', 'in', 'of', 'else', 'get', 'set', 'new', 'catch', 'finally', 'typeof', 'yield', 'async', 'await', 'from', 'as', 'class', 'extends']);
2558
2568
  var reserved_word_pattern = new RegExp('^(?:' + reserved_words.join('|') + ')$');
2559
2569
 
2560
2570
  // var template_pattern = /(?:(?:<\?php|<\?=)[\s\S]*?\?>)|(?:<%[\s\S]*?%>)/g;
@@ -2645,7 +2655,8 @@ Tokenizer.prototype._read_word = function(previous_token) {
2645
2655
  if (!(previous_token.type === TOKEN.DOT ||
2646
2656
  (previous_token.type === TOKEN.RESERVED && (previous_token.text === 'set' || previous_token.text === 'get'))) &&
2647
2657
  reserved_word_pattern.test(resulting_string)) {
2648
- if (resulting_string === 'in' || resulting_string === 'of') { // hack for 'in' and 'of' operators
2658
+ if ((resulting_string === 'in' || resulting_string === 'of') &&
2659
+ (previous_token.type === TOKEN.WORD || previous_token.type === TOKEN.STRING)) { // hack for 'in' and 'of' operators
2649
2660
  return this._create_token(TOKEN.OPERATOR, resulting_string);
2650
2661
  }
2651
2662
  return this._create_token(TOKEN.RESERVED, resulting_string);
@@ -67,6 +67,10 @@ function Beautifier(source_text, options) {
67
67
  "@supports": true,
68
68
  "@document": true
69
69
  };
70
+ this.NON_SEMICOLON_NEWLINE_PROPERTY = [
71
+ "grid-template-areas",
72
+ "grid-template"
73
+ ];
70
74
 
71
75
  }
72
76
 
@@ -191,7 +195,9 @@ Beautifier.prototype.beautify = function() {
191
195
  var enteringConditionalGroup = false;
192
196
  var insideAtExtend = false;
193
197
  var insideAtImport = false;
198
+ var insideScssMap = false;
194
199
  var topCharacter = this._ch;
200
+ var insideNonSemiColonValues = false;
195
201
  var whitespace;
196
202
  var isAfterSpace;
197
203
  var previous_ch;
@@ -243,7 +249,7 @@ Beautifier.prototype.beautify = function() {
243
249
 
244
250
  // Ensures any new lines following the comment are preserved
245
251
  this.eatWhitespace(true);
246
- } else if (this._ch === '@') {
252
+ } else if (this._ch === '@' || this._ch === '$') {
247
253
  this.preserveSingleSpace(isAfterSpace);
248
254
 
249
255
  // deal with less propery mixins @{...}
@@ -314,7 +320,12 @@ Beautifier.prototype.beautify = function() {
314
320
  this.indent();
315
321
  this._output.set_indent(this._indentLevel);
316
322
  } else {
317
- this.indent();
323
+ // inside mixin and first param is object
324
+ if (previous_ch === '(') {
325
+ this._output.space_before_token = false;
326
+ } else if (previous_ch !== ',') {
327
+ this.indent();
328
+ }
318
329
  this.print_string(this._ch);
319
330
  }
320
331
 
@@ -346,7 +357,21 @@ Beautifier.prototype.beautify = function() {
346
357
  this._output.add_new_line(true);
347
358
  }
348
359
  }
360
+ if (this._input.peek() === ')') {
361
+ this._output.trim(true);
362
+ if (this._options.brace_style === "expand") {
363
+ this._output.add_new_line(true);
364
+ }
365
+ }
349
366
  } else if (this._ch === ":") {
367
+
368
+ for (var i = 0; i < this.NON_SEMICOLON_NEWLINE_PROPERTY.length; i++) {
369
+ if (this._input.lookBack(this.NON_SEMICOLON_NEWLINE_PROPERTY[i])) {
370
+ insideNonSemiColonValues = true;
371
+ break;
372
+ }
373
+ }
374
+
350
375
  if ((insideRule || enteringConditionalGroup) && !(this._input.lookBack("&") || this.foundNestedPseudoClass()) && !this._input.lookBack("(") && !insideAtExtend && parenLevel === 0) {
351
376
  // 'property: value' delimiter
352
377
  // which could be in a conditional group query
@@ -375,10 +400,12 @@ Beautifier.prototype.beautify = function() {
375
400
  }
376
401
  }
377
402
  } else if (this._ch === '"' || this._ch === '\'') {
378
- this.preserveSingleSpace(isAfterSpace);
403
+ var preserveQuoteSpace = previous_ch === '"' || previous_ch === '\'';
404
+ this.preserveSingleSpace(preserveQuoteSpace || isAfterSpace);
379
405
  this.print_string(this._ch + this.eatString(this._ch));
380
406
  this.eatWhitespace(true);
381
407
  } else if (this._ch === ';') {
408
+ insideNonSemiColonValues = false;
382
409
  if (parenLevel === 0) {
383
410
  if (insidePropertyValue) {
384
411
  this.outdent();
@@ -418,22 +445,39 @@ Beautifier.prototype.beautify = function() {
418
445
  }
419
446
  }
420
447
  } else {
421
- this.preserveSingleSpace(isAfterSpace);
448
+ var space_needed = false;
449
+ if (this._input.lookBack("with")) {
450
+ // look back is not an accurate solution, we need tokens to confirm without whitespaces
451
+ space_needed = true;
452
+ }
453
+ this.preserveSingleSpace(isAfterSpace || space_needed);
422
454
  this.print_string(this._ch);
423
- this.eatWhitespace();
424
- parenLevel++;
425
- this.indent();
455
+
456
+ // handle scss/sass map
457
+ if (insidePropertyValue && previous_ch === "$" && this._options.selector_separator_newline) {
458
+ this._output.add_new_line();
459
+ insideScssMap = true;
460
+ } else {
461
+ this.eatWhitespace();
462
+ parenLevel++;
463
+ this.indent();
464
+ }
426
465
  }
427
466
  } else if (this._ch === ')') {
428
467
  if (parenLevel) {
429
468
  parenLevel--;
430
469
  this.outdent();
431
470
  }
471
+ if (insideScssMap && this._input.peek() === ";" && this._options.selector_separator_newline) {
472
+ insideScssMap = false;
473
+ this.outdent();
474
+ this._output.add_new_line();
475
+ }
432
476
  this.print_string(this._ch);
433
477
  } else if (this._ch === ',') {
434
478
  this.print_string(this._ch);
435
479
  this.eatWhitespace(true);
436
- if (this._options.selector_separator_newline && !insidePropertyValue && parenLevel === 0 && !insideAtImport && !insideAtExtend) {
480
+ if (this._options.selector_separator_newline && (!insidePropertyValue || insideScssMap) && parenLevel === 0 && !insideAtImport && !insideAtExtend) {
437
481
  this._output.add_new_line();
438
482
  } else {
439
483
  this._output.space_before_token = true;
@@ -464,11 +508,16 @@ Beautifier.prototype.beautify = function() {
464
508
  this._ch = '';
465
509
  }
466
510
  } else if (this._ch === '!' && !this._input.lookBack("\\")) { // !important
467
- this.print_string(' ');
511
+ this._output.space_before_token = true;
468
512
  this.print_string(this._ch);
469
513
  } else {
470
- this.preserveSingleSpace(isAfterSpace);
514
+ var preserveAfterSpace = previous_ch === '"' || previous_ch === '\'';
515
+ this.preserveSingleSpace(preserveAfterSpace || isAfterSpace);
471
516
  this.print_string(this._ch);
517
+
518
+ if (!this._output.just_added_newline() && this._input.peek() === '\n' && insideNonSemiColonValues) {
519
+ this._output.add_new_line();
520
+ }
472
521
  }
473
522
  }
474
523
 
@@ -607,14 +607,19 @@ var TagOpenParserToken = function(parent, raw_token) {
607
607
  tag_check_match = raw_token.text.match(/^<([^\s>]*)/);
608
608
  this.tag_check = tag_check_match ? tag_check_match[1] : '';
609
609
  } else {
610
- tag_check_match = raw_token.text.match(/^{{(?:[\^]|#\*?)?([^\s}]+)/);
610
+ tag_check_match = raw_token.text.match(/^{{~?(?:[\^]|#\*?)?([^\s}]+)/);
611
611
  this.tag_check = tag_check_match ? tag_check_match[1] : '';
612
612
 
613
- // handle "{{#> myPartial}}
614
- if (raw_token.text === '{{#>' && this.tag_check === '>' && raw_token.next !== null) {
615
- this.tag_check = raw_token.next.text.split(' ')[0];
613
+ // handle "{{#> myPartial}}" or "{{~#> myPartial}}"
614
+ if ((raw_token.text.startsWith('{{#>') || raw_token.text.startsWith('{{~#>')) && this.tag_check[0] === '>') {
615
+ if (this.tag_check === '>' && raw_token.next !== null) {
616
+ this.tag_check = raw_token.next.text.split(' ')[0];
617
+ } else {
618
+ this.tag_check = raw_token.text.split('>')[1];
619
+ }
616
620
  }
617
621
  }
622
+
618
623
  this.tag_check = this.tag_check.toLowerCase();
619
624
 
620
625
  if (raw_token.type === TOKEN.COMMENT) {
@@ -626,9 +631,17 @@ var TagOpenParserToken = function(parent, raw_token) {
626
631
  this.is_end_tag = !this.is_start_tag ||
627
632
  (raw_token.closed && raw_token.closed.text === '/>');
628
633
 
634
+ // if whitespace handler ~ included (i.e. {{~#if true}}), handlebars tags start at pos 3 not pos 2
635
+ var handlebar_starts = 2;
636
+ if (this.tag_start_char === '{' && this.text.length >= 3) {
637
+ if (this.text.charAt(2) === '~') {
638
+ handlebar_starts = 3;
639
+ }
640
+ }
641
+
629
642
  // handlebars tags that don't start with # or ^ are single_tags, and so also start and end.
630
643
  this.is_end_tag = this.is_end_tag ||
631
- (this.tag_start_char === '{' && (this.text.length < 3 || (/[^#\^]/.test(this.text.charAt(2)))));
644
+ (this.tag_start_char === '{' && (this.text.length < 3 || (/[^#\^]/.test(this.text.charAt(handlebar_starts)))));
632
645
  }
633
646
  };
634
647
 
@@ -185,6 +185,7 @@ Beautifier.prototype.create_flags = function(flags_base, mode) {
185
185
  inline_frame: false,
186
186
  if_block: false,
187
187
  else_block: false,
188
+ class_start_block: false, // class A { INSIDE HERE } or class B extends C { INSIDE HERE }
188
189
  do_block: false,
189
190
  do_while: false,
190
191
  import_block: false,
@@ -597,6 +598,8 @@ Beautifier.prototype.handle_start_expr = function(current_token) {
597
598
  (peek_back_two.text === '*' && (peek_back_three.text === '{' || peek_back_three.text === ','))) {
598
599
  this._output.space_before_token = true;
599
600
  }
601
+ } else if (this._flags.parent && this._flags.parent.class_start_block) {
602
+ this._output.space_before_token = true;
600
603
  }
601
604
  }
602
605
  } else {
@@ -691,10 +694,10 @@ Beautifier.prototype.handle_start_block = function(current_token) {
691
694
  )) {
692
695
  // We don't support TypeScript,but we didn't break it for a very long time.
693
696
  // We'll try to keep not breaking it.
694
- if (!in_array(this._last_last_text, ['class', 'interface'])) {
695
- this.set_mode(MODE.ObjectLiteral);
696
- } else {
697
+ if (in_array(this._last_last_text, ['class', 'interface']) && !in_array(second_token.text, [':', ','])) {
697
698
  this.set_mode(MODE.BlockStatement);
699
+ } else {
700
+ this.set_mode(MODE.ObjectLiteral);
698
701
  }
699
702
  } else if (this._flags.last_token.type === TOKEN.OPERATOR && this._flags.last_token.text === '=>') {
700
703
  // arrow function: (param1, paramN) => { statements }
@@ -711,6 +714,12 @@ Beautifier.prototype.handle_start_block = function(current_token) {
711
714
  this.set_mode(MODE.BlockStatement);
712
715
  }
713
716
 
717
+ if (this._flags.last_token) {
718
+ if (reserved_array(this._flags.last_token.previous, ['class', 'extends'])) {
719
+ this._flags.class_start_block = true;
720
+ }
721
+ }
722
+
714
723
  var empty_braces = !next_token.comments_before && next_token.text === '}';
715
724
  var empty_anonymous_function = empty_braces && this._flags.last_word === 'function' &&
716
725
  this._flags.last_token.type === TOKEN.END_EXPR;
@@ -810,7 +819,7 @@ Beautifier.prototype.handle_word = function(current_token) {
810
819
  if (current_token.type === TOKEN.RESERVED) {
811
820
  if (in_array(current_token.text, ['set', 'get']) && this._flags.mode !== MODE.ObjectLiteral) {
812
821
  current_token.type = TOKEN.WORD;
813
- } else if (current_token.text === 'import' && this._tokens.peek().text === '(') {
822
+ } else if (current_token.text === 'import' && in_array(this._tokens.peek().text, ['(', '.'])) {
814
823
  current_token.type = TOKEN.WORD;
815
824
  } else if (in_array(current_token.text, ['as', 'from']) && !this._flags.import_block) {
816
825
  current_token.type = TOKEN.WORD;
@@ -1151,13 +1160,6 @@ Beautifier.prototype.handle_operator = function(current_token) {
1151
1160
  this.handle_whitespace_and_comments(current_token, preserve_statement_flags);
1152
1161
  }
1153
1162
 
1154
- if (reserved_array(this._flags.last_token, special_words)) {
1155
- // "return" had a special handling in TK_WORD. Now we need to return the favor
1156
- this._output.space_before_token = true;
1157
- this.print_token(current_token);
1158
- return;
1159
- }
1160
-
1161
1163
  // hack for actionscript's import .*;
1162
1164
  if (current_token.text === '*' && this._flags.last_token.type === TOKEN.DOT) {
1163
1165
  this.print_token(current_token);
@@ -1285,7 +1287,11 @@ Beautifier.prototype.handle_operator = function(current_token) {
1285
1287
  // http://www.ecma-international.org/ecma-262/5.1/#sec-7.9.1
1286
1288
  // if there is a newline between -- or ++ and anything else we should preserve it.
1287
1289
  if (current_token.newlines && (current_token.text === '--' || current_token.text === '++' || current_token.text === '~')) {
1288
- this.print_newline(false, true);
1290
+ var new_line_needed = reserved_array(this._flags.last_token, special_words) && current_token.newlines;
1291
+ if (new_line_needed && (this._previous_flags.if_block || this._previous_flags.else_block)) {
1292
+ this.restore_mode();
1293
+ }
1294
+ this.print_newline(new_line_needed, true);
1289
1295
  }
1290
1296
 
1291
1297
  if (this._flags.last_token.text === ';' && is_expression(this._flags.mode)) {
@@ -1425,6 +1431,10 @@ Beautifier.prototype.handle_dot = function(current_token) {
1425
1431
  this.handle_whitespace_and_comments(current_token, true);
1426
1432
  }
1427
1433
 
1434
+ if (this._flags.last_token.text.match('^[0-9]+$')) {
1435
+ this._output.space_before_token = true;
1436
+ }
1437
+
1428
1438
  if (reserved_array(this._flags.last_token, special_words)) {
1429
1439
  this._output.space_before_token = false;
1430
1440
  } else {
@@ -74,7 +74,7 @@ var digit = /[0-9]/;
74
74
  var dot_pattern = /[^\d\.]/;
75
75
 
76
76
  var positionable_operators = (
77
- ">>> === !== " +
77
+ ">>> === !== &&= ??= ||= " +
78
78
  "<< && >= ** != == <= >> || ?? |> " +
79
79
  "< / - + > : & % ? ^ | *").split(' ');
80
80
 
@@ -82,7 +82,7 @@ var positionable_operators = (
82
82
  // Also, you must update possitionable operators separately from punct
83
83
  var punct =
84
84
  ">>>= " +
85
- "... >>= <<= === >>> !== **= " +
85
+ "... >>= <<= === >>> !== **= &&= ??= ||= " +
86
86
  "=> ^= :: /= << <= == && -= >= >> != -- += ** || ?? ++ %= &= *= |= |> " +
87
87
  "= ! ? > < : / ^ - + * & % ~ |";
88
88
 
@@ -95,7 +95,7 @@ var punct_pattern = new RegExp(punct);
95
95
 
96
96
  // words which should always start on new line.
97
97
  var line_starters = 'continue,try,throw,return,var,let,const,if,switch,case,default,for,while,break,function,import,export'.split(',');
98
- var reserved_words = line_starters.concat(['do', 'in', 'of', 'else', 'get', 'set', 'new', 'catch', 'finally', 'typeof', 'yield', 'async', 'await', 'from', 'as']);
98
+ var reserved_words = line_starters.concat(['do', 'in', 'of', 'else', 'get', 'set', 'new', 'catch', 'finally', 'typeof', 'yield', 'async', 'await', 'from', 'as', 'class', 'extends']);
99
99
  var reserved_word_pattern = new RegExp('^(?:' + reserved_words.join('|') + ')$');
100
100
 
101
101
  // var template_pattern = /(?:(?:<\?php|<\?=)[\s\S]*?\?>)|(?:<%[\s\S]*?%>)/g;
@@ -186,7 +186,8 @@ Tokenizer.prototype._read_word = function(previous_token) {
186
186
  if (!(previous_token.type === TOKEN.DOT ||
187
187
  (previous_token.type === TOKEN.RESERVED && (previous_token.text === 'set' || previous_token.text === 'get'))) &&
188
188
  reserved_word_pattern.test(resulting_string)) {
189
- if (resulting_string === 'in' || resulting_string === 'of') { // hack for 'in' and 'of' operators
189
+ if ((resulting_string === 'in' || resulting_string === 'of') &&
190
+ (previous_token.type === TOKEN.WORD || previous_token.type === TOKEN.STRING)) { // hack for 'in' and 'of' operators
190
191
  return this._create_token(TOKEN.OPERATOR, resulting_string);
191
192
  }
192
193
  return this._create_token(TOKEN.RESERVED, resulting_string);