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