@herb-tools/formatter 0.6.0 → 0.7.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.
package/dist/index.esm.js CHANGED
@@ -1,3 +1,70 @@
1
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
2
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
3
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
4
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
5
+ function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
6
+ const dedent = createDedent({});
7
+ function createDedent(options) {
8
+ dedent.withOptions = newOptions => createDedent(_objectSpread(_objectSpread({}, options), newOptions));
9
+ return dedent;
10
+ function dedent(strings, ...values) {
11
+ const raw = typeof strings === "string" ? [strings] : strings.raw;
12
+ const {
13
+ escapeSpecialCharacters = Array.isArray(strings),
14
+ trimWhitespace = true
15
+ } = options;
16
+
17
+ // first, perform interpolation
18
+ let result = "";
19
+ for (let i = 0; i < raw.length; i++) {
20
+ let next = raw[i];
21
+ if (escapeSpecialCharacters) {
22
+ // handle escaped newlines, backticks, and interpolation characters
23
+ next = next.replace(/\\\n[ \t]*/g, "").replace(/\\`/g, "`").replace(/\\\$/g, "$").replace(/\\\{/g, "{");
24
+ }
25
+ result += next;
26
+ if (i < values.length) {
27
+ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
28
+ result += values[i];
29
+ }
30
+ }
31
+
32
+ // now strip indentation
33
+ const lines = result.split("\n");
34
+ let mindent = null;
35
+ for (const l of lines) {
36
+ const m = l.match(/^(\s+)\S+/);
37
+ if (m) {
38
+ const indent = m[1].length;
39
+ if (!mindent) {
40
+ // this is the first indented line
41
+ mindent = indent;
42
+ } else {
43
+ mindent = Math.min(mindent, indent);
44
+ }
45
+ }
46
+ }
47
+ if (mindent !== null) {
48
+ const m = mindent; // appease TypeScript
49
+ result = lines
50
+ // https://github.com/typescript-eslint/typescript-eslint/issues/7140
51
+ // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
52
+ .map(l => l[0] === " " || l[0] === "\t" ? l.slice(m) : l).join("\n");
53
+ }
54
+
55
+ // dedent eats leading and trailing whitespace too
56
+ if (trimWhitespace) {
57
+ result = result.trim();
58
+ }
59
+
60
+ // handle escaped newlines at the end to ensure they don't get stripped too
61
+ if (escapeSpecialCharacters) {
62
+ result = result.replace(/\\n/g, "\n");
63
+ }
64
+ return result;
65
+ }
66
+ }
67
+
1
68
  class Position {
2
69
  line;
3
70
  column;
@@ -129,7 +196,7 @@ class Token {
129
196
  }
130
197
 
131
198
  // NOTE: This file is generated by the templates/template.rb script and should not
132
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.6.0/templates/javascript/packages/core/src/errors.ts.erb
199
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.7.0/templates/javascript/packages/core/src/errors.ts.erb
133
200
  class HerbError {
134
201
  type;
135
202
  message;
@@ -579,7 +646,7 @@ function convertToUTF8(string) {
579
646
  }
580
647
 
581
648
  // NOTE: This file is generated by the templates/template.rb script and should not
582
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.6.0/templates/javascript/packages/core/src/nodes.ts.erb
649
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.7.0/templates/javascript/packages/core/src/nodes.ts.erb
583
650
  class Node {
584
651
  type;
585
652
  location;
@@ -587,6 +654,9 @@ class Node {
587
654
  static from(node) {
588
655
  return fromSerializedNode(node);
589
656
  }
657
+ static get type() {
658
+ throw new Error("AST_NODE");
659
+ }
590
660
  constructor(type, location, errors) {
591
661
  this.type = type;
592
662
  this.location = location;
@@ -602,6 +672,12 @@ class Node {
602
672
  inspect() {
603
673
  return this.treeInspect(0);
604
674
  }
675
+ is(nodeClass) {
676
+ return this.type === nodeClass.type;
677
+ }
678
+ isOfType(type) {
679
+ return this.type === type;
680
+ }
605
681
  get isSingleLine() {
606
682
  return this.location.start.line === this.location.end.line;
607
683
  }
@@ -610,7 +686,7 @@ class Node {
610
686
  return "∅\n";
611
687
  if (array.length === 0)
612
688
  return "[]\n";
613
- let output = `(${array.length} item${array.length == 1 ? "" : "s"})\n`;
689
+ let output = `(${array.length} item${array.length === 1 ? "" : "s"})\n`;
614
690
  array.forEach((item, index) => {
615
691
  const isLast = index === array.length - 1;
616
692
  if (item instanceof Node || item instanceof HerbError) {
@@ -634,7 +710,7 @@ class Node {
634
710
  .treeInspect()
635
711
  .trimStart()
636
712
  .split("\n")
637
- .map((line, index) => index == 0 ? line.trimStart() : `${prefix}${prefix2}${line}`)
713
+ .map((line, index) => index === 0 ? line.trimStart() : `${prefix}${prefix2}${line}`)
638
714
  .join("\n")
639
715
  .trimStart();
640
716
  output += `\n`;
@@ -643,6 +719,9 @@ class Node {
643
719
  }
644
720
  class DocumentNode extends Node {
645
721
  children;
722
+ static get type() {
723
+ return "AST_DOCUMENT_NODE";
724
+ }
646
725
  static from(data) {
647
726
  return new DocumentNode({
648
727
  type: data.type,
@@ -689,6 +768,9 @@ class DocumentNode extends Node {
689
768
  }
690
769
  class LiteralNode extends Node {
691
770
  content;
771
+ static get type() {
772
+ return "AST_LITERAL_NODE";
773
+ }
692
774
  static from(data) {
693
775
  return new LiteralNode({
694
776
  type: data.type,
@@ -736,6 +818,9 @@ class HTMLOpenTagNode extends Node {
736
818
  tag_closing;
737
819
  children;
738
820
  is_void;
821
+ static get type() {
822
+ return "AST_HTML_OPEN_TAG_NODE";
823
+ }
739
824
  static from(data) {
740
825
  return new HTMLOpenTagNode({
741
826
  type: data.type,
@@ -801,6 +886,9 @@ class HTMLCloseTagNode extends Node {
801
886
  tag_name;
802
887
  children;
803
888
  tag_closing;
889
+ static get type() {
890
+ return "AST_HTML_CLOSE_TAG_NODE";
891
+ }
804
892
  static from(data) {
805
893
  return new HTMLCloseTagNode({
806
894
  type: data.type,
@@ -863,6 +951,10 @@ class HTMLElementNode extends Node {
863
951
  body;
864
952
  close_tag;
865
953
  is_void;
954
+ source;
955
+ static get type() {
956
+ return "AST_HTML_ELEMENT_NODE";
957
+ }
866
958
  static from(data) {
867
959
  return new HTMLElementNode({
868
960
  type: data.type,
@@ -873,6 +965,7 @@ class HTMLElementNode extends Node {
873
965
  body: (data.body || []).map(node => fromSerializedNode(node)),
874
966
  close_tag: data.close_tag ? fromSerializedNode((data.close_tag)) : null,
875
967
  is_void: data.is_void,
968
+ source: data.source,
876
969
  });
877
970
  }
878
971
  constructor(props) {
@@ -882,6 +975,7 @@ class HTMLElementNode extends Node {
882
975
  this.body = props.body;
883
976
  this.close_tag = props.close_tag;
884
977
  this.is_void = props.is_void;
978
+ this.source = props.source;
885
979
  }
886
980
  accept(visitor) {
887
981
  visitor.visitHTMLElementNode(this);
@@ -913,6 +1007,7 @@ class HTMLElementNode extends Node {
913
1007
  body: this.body.map(node => node.toJSON()),
914
1008
  close_tag: this.close_tag ? this.close_tag.toJSON() : null,
915
1009
  is_void: this.is_void,
1010
+ source: this.source,
916
1011
  };
917
1012
  }
918
1013
  treeInspect() {
@@ -923,7 +1018,8 @@ class HTMLElementNode extends Node {
923
1018
  output += `├── tag_name: ${this.tag_name ? this.tag_name.treeInspect() : "∅"}\n`;
924
1019
  output += `├── body: ${this.inspectArray(this.body, "│ ")}`;
925
1020
  output += `├── close_tag: ${this.inspectNode(this.close_tag, "│ ")}`;
926
- output += `└── is_void: ${typeof this.is_void === 'boolean' ? String(this.is_void) : "∅"}\n`;
1021
+ output += `├── is_void: ${typeof this.is_void === 'boolean' ? String(this.is_void) : "∅"}\n`;
1022
+ output += `└── source: ${this.source ? JSON.stringify(this.source) : "∅"}\n`;
927
1023
  return output;
928
1024
  }
929
1025
  }
@@ -932,6 +1028,9 @@ class HTMLAttributeValueNode extends Node {
932
1028
  children;
933
1029
  close_quote;
934
1030
  quoted;
1031
+ static get type() {
1032
+ return "AST_HTML_ATTRIBUTE_VALUE_NODE";
1033
+ }
935
1034
  static from(data) {
936
1035
  return new HTMLAttributeValueNode({
937
1036
  type: data.type,
@@ -990,6 +1089,9 @@ class HTMLAttributeValueNode extends Node {
990
1089
  }
991
1090
  class HTMLAttributeNameNode extends Node {
992
1091
  children;
1092
+ static get type() {
1093
+ return "AST_HTML_ATTRIBUTE_NAME_NODE";
1094
+ }
993
1095
  static from(data) {
994
1096
  return new HTMLAttributeNameNode({
995
1097
  type: data.type,
@@ -1038,6 +1140,9 @@ class HTMLAttributeNode extends Node {
1038
1140
  name;
1039
1141
  equals;
1040
1142
  value;
1143
+ static get type() {
1144
+ return "AST_HTML_ATTRIBUTE_NODE";
1145
+ }
1041
1146
  static from(data) {
1042
1147
  return new HTMLAttributeNode({
1043
1148
  type: data.type,
@@ -1094,6 +1199,9 @@ class HTMLAttributeNode extends Node {
1094
1199
  }
1095
1200
  class HTMLTextNode extends Node {
1096
1201
  content;
1202
+ static get type() {
1203
+ return "AST_HTML_TEXT_NODE";
1204
+ }
1097
1205
  static from(data) {
1098
1206
  return new HTMLTextNode({
1099
1207
  type: data.type,
@@ -1139,6 +1247,9 @@ class HTMLCommentNode extends Node {
1139
1247
  comment_start;
1140
1248
  children;
1141
1249
  comment_end;
1250
+ static get type() {
1251
+ return "AST_HTML_COMMENT_NODE";
1252
+ }
1142
1253
  static from(data) {
1143
1254
  return new HTMLCommentNode({
1144
1255
  type: data.type,
@@ -1195,6 +1306,9 @@ class HTMLDoctypeNode extends Node {
1195
1306
  tag_opening;
1196
1307
  children;
1197
1308
  tag_closing;
1309
+ static get type() {
1310
+ return "AST_HTML_DOCTYPE_NODE";
1311
+ }
1198
1312
  static from(data) {
1199
1313
  return new HTMLDoctypeNode({
1200
1314
  type: data.type,
@@ -1251,6 +1365,9 @@ class XMLDeclarationNode extends Node {
1251
1365
  tag_opening;
1252
1366
  children;
1253
1367
  tag_closing;
1368
+ static get type() {
1369
+ return "AST_XML_DECLARATION_NODE";
1370
+ }
1254
1371
  static from(data) {
1255
1372
  return new XMLDeclarationNode({
1256
1373
  type: data.type,
@@ -1307,6 +1424,9 @@ class CDATANode extends Node {
1307
1424
  tag_opening;
1308
1425
  children;
1309
1426
  tag_closing;
1427
+ static get type() {
1428
+ return "AST_CDATA_NODE";
1429
+ }
1310
1430
  static from(data) {
1311
1431
  return new CDATANode({
1312
1432
  type: data.type,
@@ -1361,6 +1481,9 @@ class CDATANode extends Node {
1361
1481
  }
1362
1482
  class WhitespaceNode extends Node {
1363
1483
  value;
1484
+ static get type() {
1485
+ return "AST_WHITESPACE_NODE";
1486
+ }
1364
1487
  static from(data) {
1365
1488
  return new WhitespaceNode({
1366
1489
  type: data.type,
@@ -1409,6 +1532,9 @@ class ERBContentNode extends Node {
1409
1532
  // no-op for analyzed_ruby
1410
1533
  parsed;
1411
1534
  valid;
1535
+ static get type() {
1536
+ return "AST_ERB_CONTENT_NODE";
1537
+ }
1412
1538
  static from(data) {
1413
1539
  return new ERBContentNode({
1414
1540
  type: data.type,
@@ -1474,6 +1600,9 @@ class ERBEndNode extends Node {
1474
1600
  tag_opening;
1475
1601
  content;
1476
1602
  tag_closing;
1603
+ static get type() {
1604
+ return "AST_ERB_END_NODE";
1605
+ }
1477
1606
  static from(data) {
1478
1607
  return new ERBEndNode({
1479
1608
  type: data.type,
@@ -1528,6 +1657,9 @@ class ERBElseNode extends Node {
1528
1657
  content;
1529
1658
  tag_closing;
1530
1659
  statements;
1660
+ static get type() {
1661
+ return "AST_ERB_ELSE_NODE";
1662
+ }
1531
1663
  static from(data) {
1532
1664
  return new ERBElseNode({
1533
1665
  type: data.type,
@@ -1591,6 +1723,9 @@ class ERBIfNode extends Node {
1591
1723
  statements;
1592
1724
  subsequent;
1593
1725
  end_node;
1726
+ static get type() {
1727
+ return "AST_ERB_IF_NODE";
1728
+ }
1594
1729
  static from(data) {
1595
1730
  return new ERBIfNode({
1596
1731
  type: data.type,
@@ -1665,6 +1800,9 @@ class ERBBlockNode extends Node {
1665
1800
  tag_closing;
1666
1801
  body;
1667
1802
  end_node;
1803
+ static get type() {
1804
+ return "AST_ERB_BLOCK_NODE";
1805
+ }
1668
1806
  static from(data) {
1669
1807
  return new ERBBlockNode({
1670
1808
  type: data.type,
@@ -1732,6 +1870,9 @@ class ERBWhenNode extends Node {
1732
1870
  content;
1733
1871
  tag_closing;
1734
1872
  statements;
1873
+ static get type() {
1874
+ return "AST_ERB_WHEN_NODE";
1875
+ }
1735
1876
  static from(data) {
1736
1877
  return new ERBWhenNode({
1737
1878
  type: data.type,
@@ -1796,6 +1937,9 @@ class ERBCaseNode extends Node {
1796
1937
  conditions;
1797
1938
  else_clause;
1798
1939
  end_node;
1940
+ static get type() {
1941
+ return "AST_ERB_CASE_NODE";
1942
+ }
1799
1943
  static from(data) {
1800
1944
  return new ERBCaseNode({
1801
1945
  type: data.type,
@@ -1878,6 +2022,9 @@ class ERBCaseMatchNode extends Node {
1878
2022
  conditions;
1879
2023
  else_clause;
1880
2024
  end_node;
2025
+ static get type() {
2026
+ return "AST_ERB_CASE_MATCH_NODE";
2027
+ }
1881
2028
  static from(data) {
1882
2029
  return new ERBCaseMatchNode({
1883
2030
  type: data.type,
@@ -1958,6 +2105,9 @@ class ERBWhileNode extends Node {
1958
2105
  tag_closing;
1959
2106
  statements;
1960
2107
  end_node;
2108
+ static get type() {
2109
+ return "AST_ERB_WHILE_NODE";
2110
+ }
1961
2111
  static from(data) {
1962
2112
  return new ERBWhileNode({
1963
2113
  type: data.type,
@@ -2026,6 +2176,9 @@ class ERBUntilNode extends Node {
2026
2176
  tag_closing;
2027
2177
  statements;
2028
2178
  end_node;
2179
+ static get type() {
2180
+ return "AST_ERB_UNTIL_NODE";
2181
+ }
2029
2182
  static from(data) {
2030
2183
  return new ERBUntilNode({
2031
2184
  type: data.type,
@@ -2094,6 +2247,9 @@ class ERBForNode extends Node {
2094
2247
  tag_closing;
2095
2248
  statements;
2096
2249
  end_node;
2250
+ static get type() {
2251
+ return "AST_ERB_FOR_NODE";
2252
+ }
2097
2253
  static from(data) {
2098
2254
  return new ERBForNode({
2099
2255
  type: data.type,
@@ -2162,6 +2318,9 @@ class ERBRescueNode extends Node {
2162
2318
  tag_closing;
2163
2319
  statements;
2164
2320
  subsequent;
2321
+ static get type() {
2322
+ return "AST_ERB_RESCUE_NODE";
2323
+ }
2165
2324
  static from(data) {
2166
2325
  return new ERBRescueNode({
2167
2326
  type: data.type,
@@ -2229,6 +2388,9 @@ class ERBEnsureNode extends Node {
2229
2388
  content;
2230
2389
  tag_closing;
2231
2390
  statements;
2391
+ static get type() {
2392
+ return "AST_ERB_ENSURE_NODE";
2393
+ }
2232
2394
  static from(data) {
2233
2395
  return new ERBEnsureNode({
2234
2396
  type: data.type,
@@ -2294,6 +2456,9 @@ class ERBBeginNode extends Node {
2294
2456
  else_clause;
2295
2457
  ensure_clause;
2296
2458
  end_node;
2459
+ static get type() {
2460
+ return "AST_ERB_BEGIN_NODE";
2461
+ }
2297
2462
  static from(data) {
2298
2463
  return new ERBBeginNode({
2299
2464
  type: data.type,
@@ -2381,6 +2546,9 @@ class ERBUnlessNode extends Node {
2381
2546
  statements;
2382
2547
  else_clause;
2383
2548
  end_node;
2549
+ static get type() {
2550
+ return "AST_ERB_UNLESS_NODE";
2551
+ }
2384
2552
  static from(data) {
2385
2553
  return new ERBUnlessNode({
2386
2554
  type: data.type,
@@ -2453,6 +2621,9 @@ class ERBYieldNode extends Node {
2453
2621
  tag_opening;
2454
2622
  content;
2455
2623
  tag_closing;
2624
+ static get type() {
2625
+ return "AST_ERB_YIELD_NODE";
2626
+ }
2456
2627
  static from(data) {
2457
2628
  return new ERBYieldNode({
2458
2629
  type: data.type,
@@ -2507,6 +2678,9 @@ class ERBInNode extends Node {
2507
2678
  content;
2508
2679
  tag_closing;
2509
2680
  statements;
2681
+ static get type() {
2682
+ return "AST_ERB_IN_NODE";
2683
+ }
2510
2684
  static from(data) {
2511
2685
  return new ERBInNode({
2512
2686
  type: data.type,
@@ -2706,7 +2880,7 @@ class ParseResult extends Result {
2706
2880
  }
2707
2881
 
2708
2882
  // NOTE: This file is generated by the templates/template.rb script and should not
2709
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.6.0/templates/javascript/packages/core/src/node-type-guards.ts.erb
2883
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.7.0/templates/javascript/packages/core/src/node-type-guards.ts.erb
2710
2884
  /**
2711
2885
  * Type guard functions for AST nodes.
2712
2886
  * These functions provide type checking by combining both instanceof
@@ -2717,187 +2891,187 @@ class ParseResult extends Result {
2717
2891
  * Checks if a node is a DocumentNode
2718
2892
  */
2719
2893
  function isDocumentNode(node) {
2720
- return node instanceof DocumentNode || node.type === "AST_DOCUMENT_NODE";
2894
+ return node instanceof DocumentNode || node.type === "AST_DOCUMENT_NODE" || node.constructor.type === "AST_DOCUMENT_NODE";
2721
2895
  }
2722
2896
  /**
2723
2897
  * Checks if a node is a LiteralNode
2724
2898
  */
2725
2899
  function isLiteralNode(node) {
2726
- return node instanceof LiteralNode || node.type === "AST_LITERAL_NODE";
2900
+ return node instanceof LiteralNode || node.type === "AST_LITERAL_NODE" || node.constructor.type === "AST_LITERAL_NODE";
2727
2901
  }
2728
2902
  /**
2729
2903
  * Checks if a node is a HTMLOpenTagNode
2730
2904
  */
2731
2905
  function isHTMLOpenTagNode(node) {
2732
- return node instanceof HTMLOpenTagNode || node.type === "AST_HTML_OPEN_TAG_NODE";
2906
+ return node instanceof HTMLOpenTagNode || node.type === "AST_HTML_OPEN_TAG_NODE" || node.constructor.type === "AST_HTML_OPEN_TAG_NODE";
2733
2907
  }
2734
2908
  /**
2735
2909
  * Checks if a node is a HTMLCloseTagNode
2736
2910
  */
2737
2911
  function isHTMLCloseTagNode(node) {
2738
- return node instanceof HTMLCloseTagNode || node.type === "AST_HTML_CLOSE_TAG_NODE";
2912
+ return node instanceof HTMLCloseTagNode || node.type === "AST_HTML_CLOSE_TAG_NODE" || node.constructor.type === "AST_HTML_CLOSE_TAG_NODE";
2739
2913
  }
2740
2914
  /**
2741
2915
  * Checks if a node is a HTMLElementNode
2742
2916
  */
2743
2917
  function isHTMLElementNode(node) {
2744
- return node instanceof HTMLElementNode || node.type === "AST_HTML_ELEMENT_NODE";
2918
+ return node instanceof HTMLElementNode || node.type === "AST_HTML_ELEMENT_NODE" || node.constructor.type === "AST_HTML_ELEMENT_NODE";
2745
2919
  }
2746
2920
  /**
2747
2921
  * Checks if a node is a HTMLAttributeValueNode
2748
2922
  */
2749
2923
  function isHTMLAttributeValueNode(node) {
2750
- return node instanceof HTMLAttributeValueNode || node.type === "AST_HTML_ATTRIBUTE_VALUE_NODE";
2924
+ return node instanceof HTMLAttributeValueNode || node.type === "AST_HTML_ATTRIBUTE_VALUE_NODE" || node.constructor.type === "AST_HTML_ATTRIBUTE_VALUE_NODE";
2751
2925
  }
2752
2926
  /**
2753
2927
  * Checks if a node is a HTMLAttributeNameNode
2754
2928
  */
2755
2929
  function isHTMLAttributeNameNode(node) {
2756
- return node instanceof HTMLAttributeNameNode || node.type === "AST_HTML_ATTRIBUTE_NAME_NODE";
2930
+ return node instanceof HTMLAttributeNameNode || node.type === "AST_HTML_ATTRIBUTE_NAME_NODE" || node.constructor.type === "AST_HTML_ATTRIBUTE_NAME_NODE";
2757
2931
  }
2758
2932
  /**
2759
2933
  * Checks if a node is a HTMLAttributeNode
2760
2934
  */
2761
2935
  function isHTMLAttributeNode(node) {
2762
- return node instanceof HTMLAttributeNode || node.type === "AST_HTML_ATTRIBUTE_NODE";
2936
+ return node instanceof HTMLAttributeNode || node.type === "AST_HTML_ATTRIBUTE_NODE" || node.constructor.type === "AST_HTML_ATTRIBUTE_NODE";
2763
2937
  }
2764
2938
  /**
2765
2939
  * Checks if a node is a HTMLTextNode
2766
2940
  */
2767
2941
  function isHTMLTextNode(node) {
2768
- return node instanceof HTMLTextNode || node.type === "AST_HTML_TEXT_NODE";
2942
+ return node instanceof HTMLTextNode || node.type === "AST_HTML_TEXT_NODE" || node.constructor.type === "AST_HTML_TEXT_NODE";
2769
2943
  }
2770
2944
  /**
2771
2945
  * Checks if a node is a HTMLCommentNode
2772
2946
  */
2773
2947
  function isHTMLCommentNode(node) {
2774
- return node instanceof HTMLCommentNode || node.type === "AST_HTML_COMMENT_NODE";
2948
+ return node instanceof HTMLCommentNode || node.type === "AST_HTML_COMMENT_NODE" || node.constructor.type === "AST_HTML_COMMENT_NODE";
2775
2949
  }
2776
2950
  /**
2777
2951
  * Checks if a node is a HTMLDoctypeNode
2778
2952
  */
2779
2953
  function isHTMLDoctypeNode(node) {
2780
- return node instanceof HTMLDoctypeNode || node.type === "AST_HTML_DOCTYPE_NODE";
2954
+ return node instanceof HTMLDoctypeNode || node.type === "AST_HTML_DOCTYPE_NODE" || node.constructor.type === "AST_HTML_DOCTYPE_NODE";
2781
2955
  }
2782
2956
  /**
2783
2957
  * Checks if a node is a XMLDeclarationNode
2784
2958
  */
2785
2959
  function isXMLDeclarationNode(node) {
2786
- return node instanceof XMLDeclarationNode || node.type === "AST_XML_DECLARATION_NODE";
2960
+ return node instanceof XMLDeclarationNode || node.type === "AST_XML_DECLARATION_NODE" || node.constructor.type === "AST_XML_DECLARATION_NODE";
2787
2961
  }
2788
2962
  /**
2789
2963
  * Checks if a node is a CDATANode
2790
2964
  */
2791
2965
  function isCDATANode(node) {
2792
- return node instanceof CDATANode || node.type === "AST_CDATA_NODE";
2966
+ return node instanceof CDATANode || node.type === "AST_CDATA_NODE" || node.constructor.type === "AST_CDATA_NODE";
2793
2967
  }
2794
2968
  /**
2795
2969
  * Checks if a node is a WhitespaceNode
2796
2970
  */
2797
2971
  function isWhitespaceNode(node) {
2798
- return node instanceof WhitespaceNode || node.type === "AST_WHITESPACE_NODE";
2972
+ return node instanceof WhitespaceNode || node.type === "AST_WHITESPACE_NODE" || node.constructor.type === "AST_WHITESPACE_NODE";
2799
2973
  }
2800
2974
  /**
2801
2975
  * Checks if a node is a ERBContentNode
2802
2976
  */
2803
2977
  function isERBContentNode(node) {
2804
- return node instanceof ERBContentNode || node.type === "AST_ERB_CONTENT_NODE";
2978
+ return node instanceof ERBContentNode || node.type === "AST_ERB_CONTENT_NODE" || node.constructor.type === "AST_ERB_CONTENT_NODE";
2805
2979
  }
2806
2980
  /**
2807
2981
  * Checks if a node is a ERBEndNode
2808
2982
  */
2809
2983
  function isERBEndNode(node) {
2810
- return node instanceof ERBEndNode || node.type === "AST_ERB_END_NODE";
2984
+ return node instanceof ERBEndNode || node.type === "AST_ERB_END_NODE" || node.constructor.type === "AST_ERB_END_NODE";
2811
2985
  }
2812
2986
  /**
2813
2987
  * Checks if a node is a ERBElseNode
2814
2988
  */
2815
2989
  function isERBElseNode(node) {
2816
- return node instanceof ERBElseNode || node.type === "AST_ERB_ELSE_NODE";
2990
+ return node instanceof ERBElseNode || node.type === "AST_ERB_ELSE_NODE" || node.constructor.type === "AST_ERB_ELSE_NODE";
2817
2991
  }
2818
2992
  /**
2819
2993
  * Checks if a node is a ERBIfNode
2820
2994
  */
2821
2995
  function isERBIfNode(node) {
2822
- return node instanceof ERBIfNode || node.type === "AST_ERB_IF_NODE";
2996
+ return node instanceof ERBIfNode || node.type === "AST_ERB_IF_NODE" || node.constructor.type === "AST_ERB_IF_NODE";
2823
2997
  }
2824
2998
  /**
2825
2999
  * Checks if a node is a ERBBlockNode
2826
3000
  */
2827
3001
  function isERBBlockNode(node) {
2828
- return node instanceof ERBBlockNode || node.type === "AST_ERB_BLOCK_NODE";
3002
+ return node instanceof ERBBlockNode || node.type === "AST_ERB_BLOCK_NODE" || node.constructor.type === "AST_ERB_BLOCK_NODE";
2829
3003
  }
2830
3004
  /**
2831
3005
  * Checks if a node is a ERBWhenNode
2832
3006
  */
2833
3007
  function isERBWhenNode(node) {
2834
- return node instanceof ERBWhenNode || node.type === "AST_ERB_WHEN_NODE";
3008
+ return node instanceof ERBWhenNode || node.type === "AST_ERB_WHEN_NODE" || node.constructor.type === "AST_ERB_WHEN_NODE";
2835
3009
  }
2836
3010
  /**
2837
3011
  * Checks if a node is a ERBCaseNode
2838
3012
  */
2839
3013
  function isERBCaseNode(node) {
2840
- return node instanceof ERBCaseNode || node.type === "AST_ERB_CASE_NODE";
3014
+ return node instanceof ERBCaseNode || node.type === "AST_ERB_CASE_NODE" || node.constructor.type === "AST_ERB_CASE_NODE";
2841
3015
  }
2842
3016
  /**
2843
3017
  * Checks if a node is a ERBCaseMatchNode
2844
3018
  */
2845
3019
  function isERBCaseMatchNode(node) {
2846
- return node instanceof ERBCaseMatchNode || node.type === "AST_ERB_CASE_MATCH_NODE";
3020
+ return node instanceof ERBCaseMatchNode || node.type === "AST_ERB_CASE_MATCH_NODE" || node.constructor.type === "AST_ERB_CASE_MATCH_NODE";
2847
3021
  }
2848
3022
  /**
2849
3023
  * Checks if a node is a ERBWhileNode
2850
3024
  */
2851
3025
  function isERBWhileNode(node) {
2852
- return node instanceof ERBWhileNode || node.type === "AST_ERB_WHILE_NODE";
3026
+ return node instanceof ERBWhileNode || node.type === "AST_ERB_WHILE_NODE" || node.constructor.type === "AST_ERB_WHILE_NODE";
2853
3027
  }
2854
3028
  /**
2855
3029
  * Checks if a node is a ERBUntilNode
2856
3030
  */
2857
3031
  function isERBUntilNode(node) {
2858
- return node instanceof ERBUntilNode || node.type === "AST_ERB_UNTIL_NODE";
3032
+ return node instanceof ERBUntilNode || node.type === "AST_ERB_UNTIL_NODE" || node.constructor.type === "AST_ERB_UNTIL_NODE";
2859
3033
  }
2860
3034
  /**
2861
3035
  * Checks if a node is a ERBForNode
2862
3036
  */
2863
3037
  function isERBForNode(node) {
2864
- return node instanceof ERBForNode || node.type === "AST_ERB_FOR_NODE";
3038
+ return node instanceof ERBForNode || node.type === "AST_ERB_FOR_NODE" || node.constructor.type === "AST_ERB_FOR_NODE";
2865
3039
  }
2866
3040
  /**
2867
3041
  * Checks if a node is a ERBRescueNode
2868
3042
  */
2869
3043
  function isERBRescueNode(node) {
2870
- return node instanceof ERBRescueNode || node.type === "AST_ERB_RESCUE_NODE";
3044
+ return node instanceof ERBRescueNode || node.type === "AST_ERB_RESCUE_NODE" || node.constructor.type === "AST_ERB_RESCUE_NODE";
2871
3045
  }
2872
3046
  /**
2873
3047
  * Checks if a node is a ERBEnsureNode
2874
3048
  */
2875
3049
  function isERBEnsureNode(node) {
2876
- return node instanceof ERBEnsureNode || node.type === "AST_ERB_ENSURE_NODE";
3050
+ return node instanceof ERBEnsureNode || node.type === "AST_ERB_ENSURE_NODE" || node.constructor.type === "AST_ERB_ENSURE_NODE";
2877
3051
  }
2878
3052
  /**
2879
3053
  * Checks if a node is a ERBBeginNode
2880
3054
  */
2881
3055
  function isERBBeginNode(node) {
2882
- return node instanceof ERBBeginNode || node.type === "AST_ERB_BEGIN_NODE";
3056
+ return node instanceof ERBBeginNode || node.type === "AST_ERB_BEGIN_NODE" || node.constructor.type === "AST_ERB_BEGIN_NODE";
2883
3057
  }
2884
3058
  /**
2885
3059
  * Checks if a node is a ERBUnlessNode
2886
3060
  */
2887
3061
  function isERBUnlessNode(node) {
2888
- return node instanceof ERBUnlessNode || node.type === "AST_ERB_UNLESS_NODE";
3062
+ return node instanceof ERBUnlessNode || node.type === "AST_ERB_UNLESS_NODE" || node.constructor.type === "AST_ERB_UNLESS_NODE";
2889
3063
  }
2890
3064
  /**
2891
3065
  * Checks if a node is a ERBYieldNode
2892
3066
  */
2893
3067
  function isERBYieldNode(node) {
2894
- return node instanceof ERBYieldNode || node.type === "AST_ERB_YIELD_NODE";
3068
+ return node instanceof ERBYieldNode || node.type === "AST_ERB_YIELD_NODE" || node.constructor.type === "AST_ERB_YIELD_NODE";
2895
3069
  }
2896
3070
  /**
2897
3071
  * Checks if a node is a ERBInNode
2898
3072
  */
2899
3073
  function isERBInNode(node) {
2900
- return node instanceof ERBInNode || node.type === "AST_ERB_IN_NODE";
3074
+ return node instanceof ERBInNode || node.type === "AST_ERB_IN_NODE" || node.constructor.type === "AST_ERB_IN_NODE";
2901
3075
  }
2902
3076
  /**
2903
3077
  * Checks if a node is any ERB node type
@@ -3066,6 +3240,8 @@ function filterNodes(nodes, ...types) {
3066
3240
  return nodes.filter(node => isAnyOf(node, ...types));
3067
3241
  }
3068
3242
  function isNode(node, type) {
3243
+ if (!node)
3244
+ return false;
3069
3245
  if (typeof type === 'string') {
3070
3246
  const guard = AST_TYPE_GUARDS.get(type);
3071
3247
  return guard ? guard(node) : false;
@@ -3089,7 +3265,11 @@ function isParseResult(object) {
3089
3265
  * Checks if a node is an ERB output node (generates content: <%= %> or <%== %>)
3090
3266
  */
3091
3267
  function isERBOutputNode(node) {
3092
- return isNode(node, ERBContentNode) && ["<%=", "<%=="].includes(node.tag_opening?.value);
3268
+ if (!isNode(node, ERBContentNode))
3269
+ return false;
3270
+ if (!node.tag_opening?.value)
3271
+ return false;
3272
+ return ["<%=", "<%=="].includes(node.tag_opening?.value);
3093
3273
  }
3094
3274
  /**
3095
3275
  * Checks if a node is a non-output ERB node (control flow: <% %>)
@@ -3147,9 +3327,47 @@ function getTagName(node) {
3147
3327
  function isCommentNode(node) {
3148
3328
  return isNode(node, HTMLCommentNode) || (isERBNode(node) && !isERBControlFlowNode(node));
3149
3329
  }
3330
+ /**
3331
+ * Compares two positions to determine if the first comes before the second
3332
+ * Returns true if pos1 comes before pos2 in source order
3333
+ * @param inclusive - If true, returns true when positions are equal
3334
+ */
3335
+ function isPositionBefore(position1, position2, inclusive = false) {
3336
+ if (position1.line < position2.line)
3337
+ return true;
3338
+ if (position1.line > position2.line)
3339
+ return false;
3340
+ return inclusive ? position1.column <= position2.column : position1.column < position2.column;
3341
+ }
3342
+ /**
3343
+ * Compares two positions to determine if the first comes after the second
3344
+ * Returns true if pos1 comes after pos2 in source order
3345
+ * @param inclusive - If true, returns true when positions are equal
3346
+ */
3347
+ function isPositionAfter(position1, position2, inclusive = false) {
3348
+ if (position1.line > position2.line)
3349
+ return true;
3350
+ if (position1.line < position2.line)
3351
+ return false;
3352
+ return inclusive ? position1.column >= position2.column : position1.column > position2.column;
3353
+ }
3354
+ /**
3355
+ * Gets nodes that end before the specified position
3356
+ * @param inclusive - If true, includes nodes that end exactly at the position (default: false, matching half-open interval semantics)
3357
+ */
3358
+ function getNodesBeforePosition(nodes, position, inclusive = false) {
3359
+ return nodes.filter(node => node.location && isPositionBefore(node.location.end, position, inclusive));
3360
+ }
3361
+ /**
3362
+ * Gets nodes that start after the specified position
3363
+ * @param inclusive - If true, includes nodes that start exactly at the position (default: true, matching typical boundary behavior)
3364
+ */
3365
+ function getNodesAfterPosition(nodes, position, inclusive = true) {
3366
+ return nodes.filter(node => node.location && isPositionAfter(node.location.start, position, inclusive));
3367
+ }
3150
3368
 
3151
3369
  // NOTE: This file is generated by the templates/template.rb script and should not
3152
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.6.0/templates/javascript/packages/core/src/visitor.ts.erb
3370
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.7.0/templates/javascript/packages/core/src/visitor.ts.erb
3153
3371
  class Visitor {
3154
3372
  visit(node) {
3155
3373
  if (!node)
@@ -3378,9 +3596,16 @@ class Printer extends Visitor {
3378
3596
  * @throws {Error} When node has parse errors and ignoreErrors is false
3379
3597
  */
3380
3598
  print(input, options = DEFAULT_PRINT_OPTIONS) {
3599
+ if (!input)
3600
+ return "";
3381
3601
  if (isToken(input)) {
3382
3602
  return input.value;
3383
3603
  }
3604
+ if (Array.isArray(input)) {
3605
+ this.context.reset();
3606
+ input.forEach(node => this.visit(node));
3607
+ return this.context.getOutput();
3608
+ }
3384
3609
  const node = isParseResult(input) ? input.value : input;
3385
3610
  if (options.ignoreErrors === false && node.recursiveErrors().length > 0) {
3386
3611
  throw new Error(`Cannot print the node (${node.type}) since it or any of its children has parse errors. Either pass in a valid Node or call \`print()\` using \`print(node, { ignoreErrors: true })\``);
@@ -3389,11 +3614,23 @@ class Printer extends Visitor {
3389
3614
  this.visit(node);
3390
3615
  return this.context.getOutput();
3391
3616
  }
3392
- visitDocumentNode(node) {
3393
- this.visitChildNodes(node);
3617
+ write(content) {
3618
+ this.context.write(content);
3394
3619
  }
3620
+ }
3621
+
3622
+ /**
3623
+ * IdentityPrinter - Provides lossless reconstruction of the original source
3624
+ *
3625
+ * This printer aims to reconstruct the original input as faithfully as possible,
3626
+ * preserving all whitespace, formatting, and structure. It's useful for:
3627
+ * - Testing parser accuracy (input should equal output)
3628
+ * - Baseline printing before applying transformations
3629
+ * - Verifying AST round-trip fidelity
3630
+ */
3631
+ class IdentityPrinter extends Printer {
3395
3632
  visitLiteralNode(node) {
3396
- this.context.write(node.content);
3633
+ this.write(node.content);
3397
3634
  }
3398
3635
  visitHTMLTextNode(node) {
3399
3636
  this.write(node.content);
@@ -3405,25 +3642,32 @@ class Printer extends Visitor {
3405
3642
  }
3406
3643
  visitHTMLOpenTagNode(node) {
3407
3644
  if (node.tag_opening) {
3408
- this.context.write(node.tag_opening.value);
3645
+ this.write(node.tag_opening.value);
3409
3646
  }
3410
3647
  if (node.tag_name) {
3411
- this.context.write(node.tag_name.value);
3648
+ this.write(node.tag_name.value);
3412
3649
  }
3413
3650
  this.visitChildNodes(node);
3414
3651
  if (node.tag_closing) {
3415
- this.context.write(node.tag_closing.value);
3652
+ this.write(node.tag_closing.value);
3416
3653
  }
3417
3654
  }
3418
3655
  visitHTMLCloseTagNode(node) {
3419
3656
  if (node.tag_opening) {
3420
- this.context.write(node.tag_opening.value);
3657
+ this.write(node.tag_opening.value);
3421
3658
  }
3422
3659
  if (node.tag_name) {
3423
- this.context.write(node.tag_name.value);
3660
+ const before = getNodesBeforePosition(node.children, node.tag_name.location.start, true);
3661
+ const after = getNodesAfterPosition(node.children, node.tag_name.location.end);
3662
+ this.visitAll(before);
3663
+ this.write(node.tag_name.value);
3664
+ this.visitAll(after);
3665
+ }
3666
+ else {
3667
+ this.visitAll(node.children);
3424
3668
  }
3425
3669
  if (node.tag_closing) {
3426
- this.context.write(node.tag_closing.value);
3670
+ this.write(node.tag_closing.value);
3427
3671
  }
3428
3672
  }
3429
3673
  visitHTMLElementNode(node) {
@@ -3449,7 +3693,7 @@ class Printer extends Visitor {
3449
3693
  this.visit(node.name);
3450
3694
  }
3451
3695
  if (node.equals) {
3452
- this.context.write(node.equals.value);
3696
+ this.write(node.equals.value);
3453
3697
  }
3454
3698
  if (node.equals && node.value) {
3455
3699
  this.visit(node.value);
@@ -3460,47 +3704,47 @@ class Printer extends Visitor {
3460
3704
  }
3461
3705
  visitHTMLAttributeValueNode(node) {
3462
3706
  if (node.quoted && node.open_quote) {
3463
- this.context.write(node.open_quote.value);
3707
+ this.write(node.open_quote.value);
3464
3708
  }
3465
3709
  this.visitChildNodes(node);
3466
3710
  if (node.quoted && node.close_quote) {
3467
- this.context.write(node.close_quote.value);
3711
+ this.write(node.close_quote.value);
3468
3712
  }
3469
3713
  }
3470
3714
  visitHTMLCommentNode(node) {
3471
3715
  if (node.comment_start) {
3472
- this.context.write(node.comment_start.value);
3716
+ this.write(node.comment_start.value);
3473
3717
  }
3474
3718
  this.visitChildNodes(node);
3475
3719
  if (node.comment_end) {
3476
- this.context.write(node.comment_end.value);
3720
+ this.write(node.comment_end.value);
3477
3721
  }
3478
3722
  }
3479
3723
  visitHTMLDoctypeNode(node) {
3480
3724
  if (node.tag_opening) {
3481
- this.context.write(node.tag_opening.value);
3725
+ this.write(node.tag_opening.value);
3482
3726
  }
3483
3727
  this.visitChildNodes(node);
3484
3728
  if (node.tag_closing) {
3485
- this.context.write(node.tag_closing.value);
3729
+ this.write(node.tag_closing.value);
3486
3730
  }
3487
3731
  }
3488
3732
  visitXMLDeclarationNode(node) {
3489
3733
  if (node.tag_opening) {
3490
- this.context.write(node.tag_opening.value);
3734
+ this.write(node.tag_opening.value);
3491
3735
  }
3492
3736
  this.visitChildNodes(node);
3493
3737
  if (node.tag_closing) {
3494
- this.context.write(node.tag_closing.value);
3738
+ this.write(node.tag_closing.value);
3495
3739
  }
3496
3740
  }
3497
3741
  visitCDATANode(node) {
3498
3742
  if (node.tag_opening) {
3499
- this.context.write(node.tag_opening.value);
3743
+ this.write(node.tag_opening.value);
3500
3744
  }
3501
3745
  this.visitChildNodes(node);
3502
3746
  if (node.tag_closing) {
3503
- this.context.write(node.tag_closing.value);
3747
+ this.write(node.tag_closing.value);
3504
3748
  }
3505
3749
  }
3506
3750
  visitERBContentNode(node) {
@@ -3658,31 +3902,19 @@ class Printer extends Visitor {
3658
3902
  */
3659
3903
  printERBNode(node) {
3660
3904
  if (node.tag_opening) {
3661
- this.context.write(node.tag_opening.value);
3905
+ this.write(node.tag_opening.value);
3662
3906
  }
3663
3907
  if (node.content) {
3664
- this.context.write(node.content.value);
3908
+ this.write(node.content.value);
3665
3909
  }
3666
3910
  if (node.tag_closing) {
3667
- this.context.write(node.tag_closing.value);
3911
+ this.write(node.tag_closing.value);
3668
3912
  }
3669
3913
  }
3670
- write(content) {
3671
- this.context.write(content);
3672
- }
3673
3914
  }
3674
3915
 
3675
- /**
3676
- * IdentityPrinter - Provides lossless reconstruction of the original source
3677
- *
3678
- * This printer aims to reconstruct the original input as faithfully as possible,
3679
- * preserving all whitespace, formatting, and structure. It's useful for:
3680
- * - Testing parser accuracy (input should equal output)
3681
- * - Baseline printing before applying transformations
3682
- * - Verifying AST round-trip fidelity
3683
- */
3684
- class IdentityPrinter extends Printer {
3685
- }
3916
+ ({
3917
+ ...DEFAULT_PRINT_OPTIONS});
3686
3918
 
3687
3919
  // TODO: we can probably expand this list with more tags/attributes
3688
3920
  const FORMATTABLE_ATTRIBUTES = {
@@ -3719,12 +3951,16 @@ class FormatPrinter extends Printer {
3719
3951
  'samp', 'small', 'span', 'strong', 'sub', 'sup',
3720
3952
  'tt', 'var', 'del', 'ins', 'mark', 's', 'u', 'time', 'wbr'
3721
3953
  ]);
3954
+ static CONTENT_PRESERVING_ELEMENTS = new Set([
3955
+ 'script', 'style', 'pre', 'textarea'
3956
+ ]);
3722
3957
  static SPACEABLE_CONTAINERS = new Set([
3723
3958
  'div', 'section', 'article', 'main', 'header', 'footer', 'aside',
3724
3959
  'figure', 'details', 'summary', 'dialog', 'fieldset'
3725
3960
  ]);
3726
3961
  static TIGHT_GROUP_PARENTS = new Set([
3727
- 'ul', 'ol', 'nav', 'select', 'datalist', 'optgroup', 'tr', 'thead', 'tbody', 'tfoot'
3962
+ 'ul', 'ol', 'nav', 'select', 'datalist', 'optgroup', 'tr', 'thead',
3963
+ 'tbody', 'tfoot'
3728
3964
  ]);
3729
3965
  static TIGHT_GROUP_CHILDREN = new Set([
3730
3966
  'li', 'option', 'td', 'th', 'dt', 'dd'
@@ -3822,6 +4058,13 @@ class FormatPrinter extends Printer {
3822
4058
  push(line) {
3823
4059
  this.lines.push(line);
3824
4060
  }
4061
+ /**
4062
+ * @deprecated refactor to use @herb-tools/printer infrastructre (or rework printer use push and this.lines)
4063
+ */
4064
+ pushWithIndent(line) {
4065
+ const indent = line.trim() === "" ? "" : this.indent;
4066
+ this.push(indent + line);
4067
+ }
3825
4068
  withIndent(callback) {
3826
4069
  this.indentLevel++;
3827
4070
  const result = callback();
@@ -4022,20 +4265,14 @@ class FormatPrinter extends Printer {
4022
4265
  return false;
4023
4266
  }
4024
4267
  getAttributeValue(attribute) {
4025
- if (attribute.value && isNode(attribute.value, HTMLAttributeValueNode)) {
4026
- const content = attribute.value.children.map(child => {
4027
- if (isNode(child, HTMLTextNode)) {
4028
- return child.content;
4029
- }
4030
- return IdentityPrinter.print(child);
4031
- }).join('');
4032
- return content;
4268
+ if (isNode(attribute.value, HTMLAttributeValueNode)) {
4269
+ return attribute.value.children.map(child => isNode(child, HTMLTextNode) ? child.content : IdentityPrinter.print(child)).join('');
4033
4270
  }
4034
4271
  return '';
4035
4272
  }
4036
4273
  hasMultilineAttributes(attributes) {
4037
4274
  return attributes.some(attribute => {
4038
- if (attribute.value && isNode(attribute.value, HTMLAttributeValueNode)) {
4275
+ if (isNode(attribute.value, HTMLAttributeValueNode)) {
4039
4276
  const content = getCombinedStringFromNodes(attribute.value.children);
4040
4277
  if (/\r?\n/.test(content)) {
4041
4278
  const name = attribute.name ? getCombinedAttributeName(attribute.name) : "";
@@ -4123,11 +4360,11 @@ class FormatPrinter extends Printer {
4123
4360
  * Render multiline attributes for a tag
4124
4361
  */
4125
4362
  renderMultilineAttributes(tagName, allChildren = [], isSelfClosing = false) {
4126
- this.push(this.indent + `<${tagName}`);
4363
+ this.pushWithIndent(`<${tagName}`);
4127
4364
  this.withIndent(() => {
4128
4365
  allChildren.forEach(child => {
4129
4366
  if (isNode(child, HTMLAttributeNode)) {
4130
- this.push(this.indent + this.renderAttribute(child));
4367
+ this.pushWithIndent(this.renderAttribute(child));
4131
4368
  }
4132
4369
  else if (!isNode(child, WhitespaceNode)) {
4133
4370
  this.visit(child);
@@ -4135,10 +4372,10 @@ class FormatPrinter extends Printer {
4135
4372
  });
4136
4373
  });
4137
4374
  if (isSelfClosing) {
4138
- this.push(this.indent + "/>");
4375
+ this.pushWithIndent("/>");
4139
4376
  }
4140
4377
  else {
4141
- this.push(this.indent + ">");
4378
+ this.pushWithIndent(">");
4142
4379
  }
4143
4380
  }
4144
4381
  /**
@@ -4202,6 +4439,10 @@ class FormatPrinter extends Printer {
4202
4439
  this.elementStack.pop();
4203
4440
  }
4204
4441
  visitHTMLElementBody(body, element) {
4442
+ if (this.isContentPreserving(element)) {
4443
+ element.body.map(child => this.pushToLastLine(IdentityPrinter.print(child)));
4444
+ return;
4445
+ }
4205
4446
  const analysis = this.elementFormattingAnalysis.get(element);
4206
4447
  const hasTextFlow = this.isInTextFlowContext(null, body);
4207
4448
  const children = this.filterSignificantChildren(body, hasTextFlow);
@@ -4327,7 +4568,7 @@ class FormatPrinter extends Printer {
4327
4568
  this.pushToLastLine(closingTag);
4328
4569
  }
4329
4570
  else {
4330
- this.push(this.indent + closingTag);
4571
+ this.pushWithIndent(closingTag);
4331
4572
  }
4332
4573
  }
4333
4574
  visitHTMLTextNode(node) {
@@ -4359,13 +4600,13 @@ class FormatPrinter extends Printer {
4359
4600
  lines.forEach(line => this.push(line));
4360
4601
  }
4361
4602
  visitHTMLAttributeNode(node) {
4362
- this.push(this.indent + this.renderAttribute(node));
4603
+ this.pushWithIndent(this.renderAttribute(node));
4363
4604
  }
4364
4605
  visitHTMLAttributeNameNode(node) {
4365
- this.push(this.indent + getCombinedAttributeName(node));
4606
+ this.pushWithIndent(getCombinedAttributeName(node));
4366
4607
  }
4367
4608
  visitHTMLAttributeValueNode(node) {
4368
- this.push(this.indent + IdentityPrinter.print(node));
4609
+ this.pushWithIndent(IdentityPrinter.print(node));
4369
4610
  }
4370
4611
  // TODO: rework
4371
4612
  visitHTMLCommentNode(node) {
@@ -4420,37 +4661,40 @@ class FormatPrinter extends Printer {
4420
4661
  else {
4421
4662
  inner = "";
4422
4663
  }
4423
- this.push(this.indent + open + inner + close);
4664
+ this.pushWithIndent(open + inner + close);
4424
4665
  }
4425
4666
  visitERBCommentNode(node) {
4426
- const open = node.tag_opening?.value ?? "";
4427
- const close = node.tag_closing?.value ?? "";
4428
- let inner;
4429
- if (node.content && node.content.value) {
4430
- const rawInner = node.content.value;
4431
- const lines = rawInner.split("\n");
4432
- if (lines.length > 2) {
4433
- const childIndent = this.indent + " ".repeat(this.indentWidth);
4434
- const innerLines = lines.slice(1, -1).map(line => childIndent + line.trim());
4435
- inner = "\n" + innerLines.join("\n") + "\n";
4436
- }
4437
- else {
4438
- inner = ` ${rawInner.trim()} `;
4439
- }
4667
+ const open = node.tag_opening?.value || "<%#";
4668
+ const content = node?.content?.value || "";
4669
+ const close = node.tag_closing?.value || "%>";
4670
+ const contentLines = content.split("\n");
4671
+ const contentTrimmedLines = content.trim().split("\n");
4672
+ if (contentLines.length === 1 && contentTrimmedLines.length === 1) {
4673
+ const startsWithSpace = content[0] === " ";
4674
+ const before = startsWithSpace ? "" : " ";
4675
+ this.pushWithIndent(open + before + content.trimEnd() + ' ' + close);
4676
+ return;
4440
4677
  }
4441
- else {
4442
- inner = "";
4678
+ if (contentTrimmedLines.length === 1) {
4679
+ this.pushWithIndent(open + ' ' + content.trim() + ' ' + close);
4680
+ return;
4443
4681
  }
4444
- this.push(this.indent + open + inner + close);
4682
+ const firstLineEmpty = contentLines[0].trim() === "";
4683
+ const dedentedContent = dedent(firstLineEmpty ? content : content.trimStart());
4684
+ this.pushWithIndent(open);
4685
+ this.withIndent(() => {
4686
+ dedentedContent.split("\n").forEach(line => this.pushWithIndent(line));
4687
+ });
4688
+ this.pushWithIndent(close);
4445
4689
  }
4446
4690
  visitHTMLDoctypeNode(node) {
4447
- this.push(this.indent + IdentityPrinter.print(node));
4691
+ this.pushWithIndent(IdentityPrinter.print(node));
4448
4692
  }
4449
4693
  visitXMLDeclarationNode(node) {
4450
- this.push(this.indent + IdentityPrinter.print(node));
4694
+ this.pushWithIndent(IdentityPrinter.print(node));
4451
4695
  }
4452
4696
  visitCDATANode(node) {
4453
- this.push(this.indent + IdentityPrinter.print(node));
4697
+ this.pushWithIndent(IdentityPrinter.print(node));
4454
4698
  }
4455
4699
  visitERBContentNode(node) {
4456
4700
  // TODO: this feels hacky
@@ -4666,8 +4910,11 @@ class FormatPrinter extends Printer {
4666
4910
  * Determines if the close tag should be rendered inline (usually follows content decision)
4667
4911
  */
4668
4912
  shouldRenderCloseTagInline(node, elementContentInline) {
4669
- const isSelfClosing = node.open_tag?.tag_closing?.value === "/>";
4670
- if (isSelfClosing || node.is_void)
4913
+ if (node.is_void)
4914
+ return true;
4915
+ if (node.open_tag?.tag_closing?.value === "/>")
4916
+ return true;
4917
+ if (this.isContentPreserving(node))
4671
4918
  return true;
4672
4919
  const children = this.filterSignificantChildren(node.body, this.isInTextFlowContext(null, node.body));
4673
4920
  if (children.length === 0)
@@ -4726,7 +4973,7 @@ class FormatPrinter extends Printer {
4726
4973
  }
4727
4974
  else {
4728
4975
  if (currentLineContent.trim()) {
4729
- this.push(this.indent + currentLineContent.trim());
4976
+ this.pushWithIndent(currentLineContent.trim());
4730
4977
  currentLineContent = "";
4731
4978
  }
4732
4979
  this.visit(child);
@@ -4734,7 +4981,7 @@ class FormatPrinter extends Printer {
4734
4981
  }
4735
4982
  else {
4736
4983
  if (currentLineContent.trim()) {
4737
- this.push(this.indent + currentLineContent.trim());
4984
+ this.pushWithIndent(currentLineContent.trim());
4738
4985
  currentLineContent = "";
4739
4986
  }
4740
4987
  this.visit(child);
@@ -4764,7 +5011,7 @@ class FormatPrinter extends Printer {
4764
5011
  }
4765
5012
  else {
4766
5013
  if (currentLineContent.trim()) {
4767
- this.push(this.indent + currentLineContent.trim());
5014
+ this.pushWithIndent(currentLineContent.trim());
4768
5015
  currentLineContent = "";
4769
5016
  }
4770
5017
  this.visit(child);
@@ -4845,7 +5092,7 @@ class FormatPrinter extends Printer {
4845
5092
  const equals = attribute.equals?.value ?? "";
4846
5093
  this.currentAttributeName = name;
4847
5094
  let value = "";
4848
- if (attribute.value && isNode(attribute.value, HTMLAttributeValueNode)) {
5095
+ if (isNode(attribute.value, HTMLAttributeValueNode)) {
4849
5096
  const attributeValue = attribute.value;
4850
5097
  let open_quote = attributeValue.open_quote?.value ?? "";
4851
5098
  let close_quote = attributeValue.close_quote?.value ?? "";
@@ -5094,6 +5341,10 @@ class FormatPrinter extends Printer {
5094
5341
  }
5095
5342
  return content.replace(/\s+/g, ' ').trim();
5096
5343
  }
5344
+ isContentPreserving(element) {
5345
+ const tagName = getTagName(element);
5346
+ return FormatPrinter.CONTENT_PRESERVING_ELEMENTS.has(tagName);
5347
+ }
5097
5348
  }
5098
5349
 
5099
5350
  /**