@herb-tools/formatter 0.6.0 → 0.6.1

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.6.1/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.6.1/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
  }
@@ -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,9 @@ class HTMLElementNode extends Node {
865
953
  body;
866
954
  close_tag;
867
955
  is_void;
956
+ static get type() {
957
+ return "AST_HTML_ELEMENT_NODE";
958
+ }
868
959
  static from(data) {
869
960
  return new HTMLElementNode({
870
961
  type: data.type,
@@ -934,6 +1025,9 @@ class HTMLAttributeValueNode extends Node {
934
1025
  children;
935
1026
  close_quote;
936
1027
  quoted;
1028
+ static get type() {
1029
+ return "AST_HTML_ATTRIBUTE_VALUE_NODE";
1030
+ }
937
1031
  static from(data) {
938
1032
  return new HTMLAttributeValueNode({
939
1033
  type: data.type,
@@ -992,6 +1086,9 @@ class HTMLAttributeValueNode extends Node {
992
1086
  }
993
1087
  class HTMLAttributeNameNode extends Node {
994
1088
  children;
1089
+ static get type() {
1090
+ return "AST_HTML_ATTRIBUTE_NAME_NODE";
1091
+ }
995
1092
  static from(data) {
996
1093
  return new HTMLAttributeNameNode({
997
1094
  type: data.type,
@@ -1040,6 +1137,9 @@ class HTMLAttributeNode extends Node {
1040
1137
  name;
1041
1138
  equals;
1042
1139
  value;
1140
+ static get type() {
1141
+ return "AST_HTML_ATTRIBUTE_NODE";
1142
+ }
1043
1143
  static from(data) {
1044
1144
  return new HTMLAttributeNode({
1045
1145
  type: data.type,
@@ -1096,6 +1196,9 @@ class HTMLAttributeNode extends Node {
1096
1196
  }
1097
1197
  class HTMLTextNode extends Node {
1098
1198
  content;
1199
+ static get type() {
1200
+ return "AST_HTML_TEXT_NODE";
1201
+ }
1099
1202
  static from(data) {
1100
1203
  return new HTMLTextNode({
1101
1204
  type: data.type,
@@ -1141,6 +1244,9 @@ class HTMLCommentNode extends Node {
1141
1244
  comment_start;
1142
1245
  children;
1143
1246
  comment_end;
1247
+ static get type() {
1248
+ return "AST_HTML_COMMENT_NODE";
1249
+ }
1144
1250
  static from(data) {
1145
1251
  return new HTMLCommentNode({
1146
1252
  type: data.type,
@@ -1197,6 +1303,9 @@ class HTMLDoctypeNode extends Node {
1197
1303
  tag_opening;
1198
1304
  children;
1199
1305
  tag_closing;
1306
+ static get type() {
1307
+ return "AST_HTML_DOCTYPE_NODE";
1308
+ }
1200
1309
  static from(data) {
1201
1310
  return new HTMLDoctypeNode({
1202
1311
  type: data.type,
@@ -1253,6 +1362,9 @@ class XMLDeclarationNode extends Node {
1253
1362
  tag_opening;
1254
1363
  children;
1255
1364
  tag_closing;
1365
+ static get type() {
1366
+ return "AST_XML_DECLARATION_NODE";
1367
+ }
1256
1368
  static from(data) {
1257
1369
  return new XMLDeclarationNode({
1258
1370
  type: data.type,
@@ -1309,6 +1421,9 @@ class CDATANode extends Node {
1309
1421
  tag_opening;
1310
1422
  children;
1311
1423
  tag_closing;
1424
+ static get type() {
1425
+ return "AST_CDATA_NODE";
1426
+ }
1312
1427
  static from(data) {
1313
1428
  return new CDATANode({
1314
1429
  type: data.type,
@@ -1363,6 +1478,9 @@ class CDATANode extends Node {
1363
1478
  }
1364
1479
  class WhitespaceNode extends Node {
1365
1480
  value;
1481
+ static get type() {
1482
+ return "AST_WHITESPACE_NODE";
1483
+ }
1366
1484
  static from(data) {
1367
1485
  return new WhitespaceNode({
1368
1486
  type: data.type,
@@ -1411,6 +1529,9 @@ class ERBContentNode extends Node {
1411
1529
  // no-op for analyzed_ruby
1412
1530
  parsed;
1413
1531
  valid;
1532
+ static get type() {
1533
+ return "AST_ERB_CONTENT_NODE";
1534
+ }
1414
1535
  static from(data) {
1415
1536
  return new ERBContentNode({
1416
1537
  type: data.type,
@@ -1476,6 +1597,9 @@ class ERBEndNode extends Node {
1476
1597
  tag_opening;
1477
1598
  content;
1478
1599
  tag_closing;
1600
+ static get type() {
1601
+ return "AST_ERB_END_NODE";
1602
+ }
1479
1603
  static from(data) {
1480
1604
  return new ERBEndNode({
1481
1605
  type: data.type,
@@ -1530,6 +1654,9 @@ class ERBElseNode extends Node {
1530
1654
  content;
1531
1655
  tag_closing;
1532
1656
  statements;
1657
+ static get type() {
1658
+ return "AST_ERB_ELSE_NODE";
1659
+ }
1533
1660
  static from(data) {
1534
1661
  return new ERBElseNode({
1535
1662
  type: data.type,
@@ -1593,6 +1720,9 @@ class ERBIfNode extends Node {
1593
1720
  statements;
1594
1721
  subsequent;
1595
1722
  end_node;
1723
+ static get type() {
1724
+ return "AST_ERB_IF_NODE";
1725
+ }
1596
1726
  static from(data) {
1597
1727
  return new ERBIfNode({
1598
1728
  type: data.type,
@@ -1667,6 +1797,9 @@ class ERBBlockNode extends Node {
1667
1797
  tag_closing;
1668
1798
  body;
1669
1799
  end_node;
1800
+ static get type() {
1801
+ return "AST_ERB_BLOCK_NODE";
1802
+ }
1670
1803
  static from(data) {
1671
1804
  return new ERBBlockNode({
1672
1805
  type: data.type,
@@ -1734,6 +1867,9 @@ class ERBWhenNode extends Node {
1734
1867
  content;
1735
1868
  tag_closing;
1736
1869
  statements;
1870
+ static get type() {
1871
+ return "AST_ERB_WHEN_NODE";
1872
+ }
1737
1873
  static from(data) {
1738
1874
  return new ERBWhenNode({
1739
1875
  type: data.type,
@@ -1798,6 +1934,9 @@ class ERBCaseNode extends Node {
1798
1934
  conditions;
1799
1935
  else_clause;
1800
1936
  end_node;
1937
+ static get type() {
1938
+ return "AST_ERB_CASE_NODE";
1939
+ }
1801
1940
  static from(data) {
1802
1941
  return new ERBCaseNode({
1803
1942
  type: data.type,
@@ -1880,6 +2019,9 @@ class ERBCaseMatchNode extends Node {
1880
2019
  conditions;
1881
2020
  else_clause;
1882
2021
  end_node;
2022
+ static get type() {
2023
+ return "AST_ERB_CASE_MATCH_NODE";
2024
+ }
1883
2025
  static from(data) {
1884
2026
  return new ERBCaseMatchNode({
1885
2027
  type: data.type,
@@ -1960,6 +2102,9 @@ class ERBWhileNode extends Node {
1960
2102
  tag_closing;
1961
2103
  statements;
1962
2104
  end_node;
2105
+ static get type() {
2106
+ return "AST_ERB_WHILE_NODE";
2107
+ }
1963
2108
  static from(data) {
1964
2109
  return new ERBWhileNode({
1965
2110
  type: data.type,
@@ -2028,6 +2173,9 @@ class ERBUntilNode extends Node {
2028
2173
  tag_closing;
2029
2174
  statements;
2030
2175
  end_node;
2176
+ static get type() {
2177
+ return "AST_ERB_UNTIL_NODE";
2178
+ }
2031
2179
  static from(data) {
2032
2180
  return new ERBUntilNode({
2033
2181
  type: data.type,
@@ -2096,6 +2244,9 @@ class ERBForNode extends Node {
2096
2244
  tag_closing;
2097
2245
  statements;
2098
2246
  end_node;
2247
+ static get type() {
2248
+ return "AST_ERB_FOR_NODE";
2249
+ }
2099
2250
  static from(data) {
2100
2251
  return new ERBForNode({
2101
2252
  type: data.type,
@@ -2164,6 +2315,9 @@ class ERBRescueNode extends Node {
2164
2315
  tag_closing;
2165
2316
  statements;
2166
2317
  subsequent;
2318
+ static get type() {
2319
+ return "AST_ERB_RESCUE_NODE";
2320
+ }
2167
2321
  static from(data) {
2168
2322
  return new ERBRescueNode({
2169
2323
  type: data.type,
@@ -2231,6 +2385,9 @@ class ERBEnsureNode extends Node {
2231
2385
  content;
2232
2386
  tag_closing;
2233
2387
  statements;
2388
+ static get type() {
2389
+ return "AST_ERB_ENSURE_NODE";
2390
+ }
2234
2391
  static from(data) {
2235
2392
  return new ERBEnsureNode({
2236
2393
  type: data.type,
@@ -2296,6 +2453,9 @@ class ERBBeginNode extends Node {
2296
2453
  else_clause;
2297
2454
  ensure_clause;
2298
2455
  end_node;
2456
+ static get type() {
2457
+ return "AST_ERB_BEGIN_NODE";
2458
+ }
2299
2459
  static from(data) {
2300
2460
  return new ERBBeginNode({
2301
2461
  type: data.type,
@@ -2383,6 +2543,9 @@ class ERBUnlessNode extends Node {
2383
2543
  statements;
2384
2544
  else_clause;
2385
2545
  end_node;
2546
+ static get type() {
2547
+ return "AST_ERB_UNLESS_NODE";
2548
+ }
2386
2549
  static from(data) {
2387
2550
  return new ERBUnlessNode({
2388
2551
  type: data.type,
@@ -2455,6 +2618,9 @@ class ERBYieldNode extends Node {
2455
2618
  tag_opening;
2456
2619
  content;
2457
2620
  tag_closing;
2621
+ static get type() {
2622
+ return "AST_ERB_YIELD_NODE";
2623
+ }
2458
2624
  static from(data) {
2459
2625
  return new ERBYieldNode({
2460
2626
  type: data.type,
@@ -2509,6 +2675,9 @@ class ERBInNode extends Node {
2509
2675
  content;
2510
2676
  tag_closing;
2511
2677
  statements;
2678
+ static get type() {
2679
+ return "AST_ERB_IN_NODE";
2680
+ }
2512
2681
  static from(data) {
2513
2682
  return new ERBInNode({
2514
2683
  type: data.type,
@@ -2708,7 +2877,7 @@ class ParseResult extends Result {
2708
2877
  }
2709
2878
 
2710
2879
  // 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
2880
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.6.1/templates/javascript/packages/core/src/node-type-guards.ts.erb
2712
2881
  /**
2713
2882
  * Type guard functions for AST nodes.
2714
2883
  * These functions provide type checking by combining both instanceof
@@ -2719,187 +2888,187 @@ class ParseResult extends Result {
2719
2888
  * Checks if a node is a DocumentNode
2720
2889
  */
2721
2890
  function isDocumentNode(node) {
2722
- return node instanceof DocumentNode || node.type === "AST_DOCUMENT_NODE";
2891
+ return node instanceof DocumentNode || node.type === "AST_DOCUMENT_NODE" || node.constructor.type === "AST_DOCUMENT_NODE";
2723
2892
  }
2724
2893
  /**
2725
2894
  * Checks if a node is a LiteralNode
2726
2895
  */
2727
2896
  function isLiteralNode(node) {
2728
- return node instanceof LiteralNode || node.type === "AST_LITERAL_NODE";
2897
+ return node instanceof LiteralNode || node.type === "AST_LITERAL_NODE" || node.constructor.type === "AST_LITERAL_NODE";
2729
2898
  }
2730
2899
  /**
2731
2900
  * Checks if a node is a HTMLOpenTagNode
2732
2901
  */
2733
2902
  function isHTMLOpenTagNode(node) {
2734
- return node instanceof HTMLOpenTagNode || node.type === "AST_HTML_OPEN_TAG_NODE";
2903
+ return node instanceof HTMLOpenTagNode || node.type === "AST_HTML_OPEN_TAG_NODE" || node.constructor.type === "AST_HTML_OPEN_TAG_NODE";
2735
2904
  }
2736
2905
  /**
2737
2906
  * Checks if a node is a HTMLCloseTagNode
2738
2907
  */
2739
2908
  function isHTMLCloseTagNode(node) {
2740
- return node instanceof HTMLCloseTagNode || node.type === "AST_HTML_CLOSE_TAG_NODE";
2909
+ return node instanceof HTMLCloseTagNode || node.type === "AST_HTML_CLOSE_TAG_NODE" || node.constructor.type === "AST_HTML_CLOSE_TAG_NODE";
2741
2910
  }
2742
2911
  /**
2743
2912
  * Checks if a node is a HTMLElementNode
2744
2913
  */
2745
2914
  function isHTMLElementNode(node) {
2746
- return node instanceof HTMLElementNode || node.type === "AST_HTML_ELEMENT_NODE";
2915
+ return node instanceof HTMLElementNode || node.type === "AST_HTML_ELEMENT_NODE" || node.constructor.type === "AST_HTML_ELEMENT_NODE";
2747
2916
  }
2748
2917
  /**
2749
2918
  * Checks if a node is a HTMLAttributeValueNode
2750
2919
  */
2751
2920
  function isHTMLAttributeValueNode(node) {
2752
- return node instanceof HTMLAttributeValueNode || node.type === "AST_HTML_ATTRIBUTE_VALUE_NODE";
2921
+ return node instanceof HTMLAttributeValueNode || node.type === "AST_HTML_ATTRIBUTE_VALUE_NODE" || node.constructor.type === "AST_HTML_ATTRIBUTE_VALUE_NODE";
2753
2922
  }
2754
2923
  /**
2755
2924
  * Checks if a node is a HTMLAttributeNameNode
2756
2925
  */
2757
2926
  function isHTMLAttributeNameNode(node) {
2758
- return node instanceof HTMLAttributeNameNode || node.type === "AST_HTML_ATTRIBUTE_NAME_NODE";
2927
+ return node instanceof HTMLAttributeNameNode || node.type === "AST_HTML_ATTRIBUTE_NAME_NODE" || node.constructor.type === "AST_HTML_ATTRIBUTE_NAME_NODE";
2759
2928
  }
2760
2929
  /**
2761
2930
  * Checks if a node is a HTMLAttributeNode
2762
2931
  */
2763
2932
  function isHTMLAttributeNode(node) {
2764
- return node instanceof HTMLAttributeNode || node.type === "AST_HTML_ATTRIBUTE_NODE";
2933
+ return node instanceof HTMLAttributeNode || node.type === "AST_HTML_ATTRIBUTE_NODE" || node.constructor.type === "AST_HTML_ATTRIBUTE_NODE";
2765
2934
  }
2766
2935
  /**
2767
2936
  * Checks if a node is a HTMLTextNode
2768
2937
  */
2769
2938
  function isHTMLTextNode(node) {
2770
- return node instanceof HTMLTextNode || node.type === "AST_HTML_TEXT_NODE";
2939
+ return node instanceof HTMLTextNode || node.type === "AST_HTML_TEXT_NODE" || node.constructor.type === "AST_HTML_TEXT_NODE";
2771
2940
  }
2772
2941
  /**
2773
2942
  * Checks if a node is a HTMLCommentNode
2774
2943
  */
2775
2944
  function isHTMLCommentNode(node) {
2776
- return node instanceof HTMLCommentNode || node.type === "AST_HTML_COMMENT_NODE";
2945
+ return node instanceof HTMLCommentNode || node.type === "AST_HTML_COMMENT_NODE" || node.constructor.type === "AST_HTML_COMMENT_NODE";
2777
2946
  }
2778
2947
  /**
2779
2948
  * Checks if a node is a HTMLDoctypeNode
2780
2949
  */
2781
2950
  function isHTMLDoctypeNode(node) {
2782
- return node instanceof HTMLDoctypeNode || node.type === "AST_HTML_DOCTYPE_NODE";
2951
+ return node instanceof HTMLDoctypeNode || node.type === "AST_HTML_DOCTYPE_NODE" || node.constructor.type === "AST_HTML_DOCTYPE_NODE";
2783
2952
  }
2784
2953
  /**
2785
2954
  * Checks if a node is a XMLDeclarationNode
2786
2955
  */
2787
2956
  function isXMLDeclarationNode(node) {
2788
- return node instanceof XMLDeclarationNode || node.type === "AST_XML_DECLARATION_NODE";
2957
+ return node instanceof XMLDeclarationNode || node.type === "AST_XML_DECLARATION_NODE" || node.constructor.type === "AST_XML_DECLARATION_NODE";
2789
2958
  }
2790
2959
  /**
2791
2960
  * Checks if a node is a CDATANode
2792
2961
  */
2793
2962
  function isCDATANode(node) {
2794
- return node instanceof CDATANode || node.type === "AST_CDATA_NODE";
2963
+ return node instanceof CDATANode || node.type === "AST_CDATA_NODE" || node.constructor.type === "AST_CDATA_NODE";
2795
2964
  }
2796
2965
  /**
2797
2966
  * Checks if a node is a WhitespaceNode
2798
2967
  */
2799
2968
  function isWhitespaceNode(node) {
2800
- return node instanceof WhitespaceNode || node.type === "AST_WHITESPACE_NODE";
2969
+ return node instanceof WhitespaceNode || node.type === "AST_WHITESPACE_NODE" || node.constructor.type === "AST_WHITESPACE_NODE";
2801
2970
  }
2802
2971
  /**
2803
2972
  * Checks if a node is a ERBContentNode
2804
2973
  */
2805
2974
  function isERBContentNode(node) {
2806
- return node instanceof ERBContentNode || node.type === "AST_ERB_CONTENT_NODE";
2975
+ return node instanceof ERBContentNode || node.type === "AST_ERB_CONTENT_NODE" || node.constructor.type === "AST_ERB_CONTENT_NODE";
2807
2976
  }
2808
2977
  /**
2809
2978
  * Checks if a node is a ERBEndNode
2810
2979
  */
2811
2980
  function isERBEndNode(node) {
2812
- return node instanceof ERBEndNode || node.type === "AST_ERB_END_NODE";
2981
+ return node instanceof ERBEndNode || node.type === "AST_ERB_END_NODE" || node.constructor.type === "AST_ERB_END_NODE";
2813
2982
  }
2814
2983
  /**
2815
2984
  * Checks if a node is a ERBElseNode
2816
2985
  */
2817
2986
  function isERBElseNode(node) {
2818
- return node instanceof ERBElseNode || node.type === "AST_ERB_ELSE_NODE";
2987
+ return node instanceof ERBElseNode || node.type === "AST_ERB_ELSE_NODE" || node.constructor.type === "AST_ERB_ELSE_NODE";
2819
2988
  }
2820
2989
  /**
2821
2990
  * Checks if a node is a ERBIfNode
2822
2991
  */
2823
2992
  function isERBIfNode(node) {
2824
- return node instanceof ERBIfNode || node.type === "AST_ERB_IF_NODE";
2993
+ return node instanceof ERBIfNode || node.type === "AST_ERB_IF_NODE" || node.constructor.type === "AST_ERB_IF_NODE";
2825
2994
  }
2826
2995
  /**
2827
2996
  * Checks if a node is a ERBBlockNode
2828
2997
  */
2829
2998
  function isERBBlockNode(node) {
2830
- return node instanceof ERBBlockNode || node.type === "AST_ERB_BLOCK_NODE";
2999
+ return node instanceof ERBBlockNode || node.type === "AST_ERB_BLOCK_NODE" || node.constructor.type === "AST_ERB_BLOCK_NODE";
2831
3000
  }
2832
3001
  /**
2833
3002
  * Checks if a node is a ERBWhenNode
2834
3003
  */
2835
3004
  function isERBWhenNode(node) {
2836
- return node instanceof ERBWhenNode || node.type === "AST_ERB_WHEN_NODE";
3005
+ return node instanceof ERBWhenNode || node.type === "AST_ERB_WHEN_NODE" || node.constructor.type === "AST_ERB_WHEN_NODE";
2837
3006
  }
2838
3007
  /**
2839
3008
  * Checks if a node is a ERBCaseNode
2840
3009
  */
2841
3010
  function isERBCaseNode(node) {
2842
- return node instanceof ERBCaseNode || node.type === "AST_ERB_CASE_NODE";
3011
+ return node instanceof ERBCaseNode || node.type === "AST_ERB_CASE_NODE" || node.constructor.type === "AST_ERB_CASE_NODE";
2843
3012
  }
2844
3013
  /**
2845
3014
  * Checks if a node is a ERBCaseMatchNode
2846
3015
  */
2847
3016
  function isERBCaseMatchNode(node) {
2848
- return node instanceof ERBCaseMatchNode || node.type === "AST_ERB_CASE_MATCH_NODE";
3017
+ return node instanceof ERBCaseMatchNode || node.type === "AST_ERB_CASE_MATCH_NODE" || node.constructor.type === "AST_ERB_CASE_MATCH_NODE";
2849
3018
  }
2850
3019
  /**
2851
3020
  * Checks if a node is a ERBWhileNode
2852
3021
  */
2853
3022
  function isERBWhileNode(node) {
2854
- return node instanceof ERBWhileNode || node.type === "AST_ERB_WHILE_NODE";
3023
+ return node instanceof ERBWhileNode || node.type === "AST_ERB_WHILE_NODE" || node.constructor.type === "AST_ERB_WHILE_NODE";
2855
3024
  }
2856
3025
  /**
2857
3026
  * Checks if a node is a ERBUntilNode
2858
3027
  */
2859
3028
  function isERBUntilNode(node) {
2860
- return node instanceof ERBUntilNode || node.type === "AST_ERB_UNTIL_NODE";
3029
+ return node instanceof ERBUntilNode || node.type === "AST_ERB_UNTIL_NODE" || node.constructor.type === "AST_ERB_UNTIL_NODE";
2861
3030
  }
2862
3031
  /**
2863
3032
  * Checks if a node is a ERBForNode
2864
3033
  */
2865
3034
  function isERBForNode(node) {
2866
- return node instanceof ERBForNode || node.type === "AST_ERB_FOR_NODE";
3035
+ return node instanceof ERBForNode || node.type === "AST_ERB_FOR_NODE" || node.constructor.type === "AST_ERB_FOR_NODE";
2867
3036
  }
2868
3037
  /**
2869
3038
  * Checks if a node is a ERBRescueNode
2870
3039
  */
2871
3040
  function isERBRescueNode(node) {
2872
- return node instanceof ERBRescueNode || node.type === "AST_ERB_RESCUE_NODE";
3041
+ return node instanceof ERBRescueNode || node.type === "AST_ERB_RESCUE_NODE" || node.constructor.type === "AST_ERB_RESCUE_NODE";
2873
3042
  }
2874
3043
  /**
2875
3044
  * Checks if a node is a ERBEnsureNode
2876
3045
  */
2877
3046
  function isERBEnsureNode(node) {
2878
- return node instanceof ERBEnsureNode || node.type === "AST_ERB_ENSURE_NODE";
3047
+ return node instanceof ERBEnsureNode || node.type === "AST_ERB_ENSURE_NODE" || node.constructor.type === "AST_ERB_ENSURE_NODE";
2879
3048
  }
2880
3049
  /**
2881
3050
  * Checks if a node is a ERBBeginNode
2882
3051
  */
2883
3052
  function isERBBeginNode(node) {
2884
- return node instanceof ERBBeginNode || node.type === "AST_ERB_BEGIN_NODE";
3053
+ return node instanceof ERBBeginNode || node.type === "AST_ERB_BEGIN_NODE" || node.constructor.type === "AST_ERB_BEGIN_NODE";
2885
3054
  }
2886
3055
  /**
2887
3056
  * Checks if a node is a ERBUnlessNode
2888
3057
  */
2889
3058
  function isERBUnlessNode(node) {
2890
- return node instanceof ERBUnlessNode || node.type === "AST_ERB_UNLESS_NODE";
3059
+ return node instanceof ERBUnlessNode || node.type === "AST_ERB_UNLESS_NODE" || node.constructor.type === "AST_ERB_UNLESS_NODE";
2891
3060
  }
2892
3061
  /**
2893
3062
  * Checks if a node is a ERBYieldNode
2894
3063
  */
2895
3064
  function isERBYieldNode(node) {
2896
- return node instanceof ERBYieldNode || node.type === "AST_ERB_YIELD_NODE";
3065
+ return node instanceof ERBYieldNode || node.type === "AST_ERB_YIELD_NODE" || node.constructor.type === "AST_ERB_YIELD_NODE";
2897
3066
  }
2898
3067
  /**
2899
3068
  * Checks if a node is a ERBInNode
2900
3069
  */
2901
3070
  function isERBInNode(node) {
2902
- return node instanceof ERBInNode || node.type === "AST_ERB_IN_NODE";
3071
+ return node instanceof ERBInNode || node.type === "AST_ERB_IN_NODE" || node.constructor.type === "AST_ERB_IN_NODE";
2903
3072
  }
2904
3073
  /**
2905
3074
  * Checks if a node is any ERB node type
@@ -3068,6 +3237,8 @@ function filterNodes(nodes, ...types) {
3068
3237
  return nodes.filter(node => isAnyOf(node, ...types));
3069
3238
  }
3070
3239
  function isNode(node, type) {
3240
+ if (!node)
3241
+ return false;
3071
3242
  if (typeof type === 'string') {
3072
3243
  const guard = AST_TYPE_GUARDS.get(type);
3073
3244
  return guard ? guard(node) : false;
@@ -3149,9 +3320,47 @@ function getTagName(node) {
3149
3320
  function isCommentNode(node) {
3150
3321
  return isNode(node, HTMLCommentNode) || (isERBNode(node) && !isERBControlFlowNode(node));
3151
3322
  }
3323
+ /**
3324
+ * Compares two positions to determine if the first comes before the second
3325
+ * Returns true if pos1 comes before pos2 in source order
3326
+ * @param inclusive - If true, returns true when positions are equal
3327
+ */
3328
+ function isPositionBefore(position1, position2, inclusive = false) {
3329
+ if (position1.line < position2.line)
3330
+ return true;
3331
+ if (position1.line > position2.line)
3332
+ return false;
3333
+ return inclusive ? position1.column <= position2.column : position1.column < position2.column;
3334
+ }
3335
+ /**
3336
+ * Compares two positions to determine if the first comes after the second
3337
+ * Returns true if pos1 comes after pos2 in source order
3338
+ * @param inclusive - If true, returns true when positions are equal
3339
+ */
3340
+ function isPositionAfter(position1, position2, inclusive = false) {
3341
+ if (position1.line > position2.line)
3342
+ return true;
3343
+ if (position1.line < position2.line)
3344
+ return false;
3345
+ return inclusive ? position1.column >= position2.column : position1.column > position2.column;
3346
+ }
3347
+ /**
3348
+ * Gets nodes that end before the specified position
3349
+ * @param inclusive - If true, includes nodes that end exactly at the position (default: false, matching half-open interval semantics)
3350
+ */
3351
+ function getNodesBeforePosition(nodes, position, inclusive = false) {
3352
+ return nodes.filter(node => node.location && isPositionBefore(node.location.end, position, inclusive));
3353
+ }
3354
+ /**
3355
+ * Gets nodes that start after the specified position
3356
+ * @param inclusive - If true, includes nodes that start exactly at the position (default: true, matching typical boundary behavior)
3357
+ */
3358
+ function getNodesAfterPosition(nodes, position, inclusive = true) {
3359
+ return nodes.filter(node => node.location && isPositionAfter(node.location.start, position, inclusive));
3360
+ }
3152
3361
 
3153
3362
  // 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
3363
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.6.1/templates/javascript/packages/core/src/visitor.ts.erb
3155
3364
  class Visitor {
3156
3365
  visit(node) {
3157
3366
  if (!node)
@@ -3383,6 +3592,11 @@ class Printer extends Visitor {
3383
3592
  if (isToken(input)) {
3384
3593
  return input.value;
3385
3594
  }
3595
+ if (Array.isArray(input)) {
3596
+ this.context.reset();
3597
+ input.forEach(node => this.visit(node));
3598
+ return this.context.getOutput();
3599
+ }
3386
3600
  const node = isParseResult(input) ? input.value : input;
3387
3601
  if (options.ignoreErrors === false && node.recursiveErrors().length > 0) {
3388
3602
  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 +3605,23 @@ class Printer extends Visitor {
3391
3605
  this.visit(node);
3392
3606
  return this.context.getOutput();
3393
3607
  }
3394
- visitDocumentNode(node) {
3395
- this.visitChildNodes(node);
3608
+ write(content) {
3609
+ this.context.write(content);
3396
3610
  }
3611
+ }
3612
+
3613
+ /**
3614
+ * IdentityPrinter - Provides lossless reconstruction of the original source
3615
+ *
3616
+ * This printer aims to reconstruct the original input as faithfully as possible,
3617
+ * preserving all whitespace, formatting, and structure. It's useful for:
3618
+ * - Testing parser accuracy (input should equal output)
3619
+ * - Baseline printing before applying transformations
3620
+ * - Verifying AST round-trip fidelity
3621
+ */
3622
+ class IdentityPrinter extends Printer {
3397
3623
  visitLiteralNode(node) {
3398
- this.context.write(node.content);
3624
+ this.write(node.content);
3399
3625
  }
3400
3626
  visitHTMLTextNode(node) {
3401
3627
  this.write(node.content);
@@ -3407,25 +3633,32 @@ class Printer extends Visitor {
3407
3633
  }
3408
3634
  visitHTMLOpenTagNode(node) {
3409
3635
  if (node.tag_opening) {
3410
- this.context.write(node.tag_opening.value);
3636
+ this.write(node.tag_opening.value);
3411
3637
  }
3412
3638
  if (node.tag_name) {
3413
- this.context.write(node.tag_name.value);
3639
+ this.write(node.tag_name.value);
3414
3640
  }
3415
3641
  this.visitChildNodes(node);
3416
3642
  if (node.tag_closing) {
3417
- this.context.write(node.tag_closing.value);
3643
+ this.write(node.tag_closing.value);
3418
3644
  }
3419
3645
  }
3420
3646
  visitHTMLCloseTagNode(node) {
3421
3647
  if (node.tag_opening) {
3422
- this.context.write(node.tag_opening.value);
3648
+ this.write(node.tag_opening.value);
3423
3649
  }
3424
3650
  if (node.tag_name) {
3425
- this.context.write(node.tag_name.value);
3651
+ const before = getNodesBeforePosition(node.children, node.tag_name.location.start, true);
3652
+ const after = getNodesAfterPosition(node.children, node.tag_name.location.end);
3653
+ this.visitAll(before);
3654
+ this.write(node.tag_name.value);
3655
+ this.visitAll(after);
3656
+ }
3657
+ else {
3658
+ this.visitAll(node.children);
3426
3659
  }
3427
3660
  if (node.tag_closing) {
3428
- this.context.write(node.tag_closing.value);
3661
+ this.write(node.tag_closing.value);
3429
3662
  }
3430
3663
  }
3431
3664
  visitHTMLElementNode(node) {
@@ -3451,7 +3684,7 @@ class Printer extends Visitor {
3451
3684
  this.visit(node.name);
3452
3685
  }
3453
3686
  if (node.equals) {
3454
- this.context.write(node.equals.value);
3687
+ this.write(node.equals.value);
3455
3688
  }
3456
3689
  if (node.equals && node.value) {
3457
3690
  this.visit(node.value);
@@ -3462,47 +3695,47 @@ class Printer extends Visitor {
3462
3695
  }
3463
3696
  visitHTMLAttributeValueNode(node) {
3464
3697
  if (node.quoted && node.open_quote) {
3465
- this.context.write(node.open_quote.value);
3698
+ this.write(node.open_quote.value);
3466
3699
  }
3467
3700
  this.visitChildNodes(node);
3468
3701
  if (node.quoted && node.close_quote) {
3469
- this.context.write(node.close_quote.value);
3702
+ this.write(node.close_quote.value);
3470
3703
  }
3471
3704
  }
3472
3705
  visitHTMLCommentNode(node) {
3473
3706
  if (node.comment_start) {
3474
- this.context.write(node.comment_start.value);
3707
+ this.write(node.comment_start.value);
3475
3708
  }
3476
3709
  this.visitChildNodes(node);
3477
3710
  if (node.comment_end) {
3478
- this.context.write(node.comment_end.value);
3711
+ this.write(node.comment_end.value);
3479
3712
  }
3480
3713
  }
3481
3714
  visitHTMLDoctypeNode(node) {
3482
3715
  if (node.tag_opening) {
3483
- this.context.write(node.tag_opening.value);
3716
+ this.write(node.tag_opening.value);
3484
3717
  }
3485
3718
  this.visitChildNodes(node);
3486
3719
  if (node.tag_closing) {
3487
- this.context.write(node.tag_closing.value);
3720
+ this.write(node.tag_closing.value);
3488
3721
  }
3489
3722
  }
3490
3723
  visitXMLDeclarationNode(node) {
3491
3724
  if (node.tag_opening) {
3492
- this.context.write(node.tag_opening.value);
3725
+ this.write(node.tag_opening.value);
3493
3726
  }
3494
3727
  this.visitChildNodes(node);
3495
3728
  if (node.tag_closing) {
3496
- this.context.write(node.tag_closing.value);
3729
+ this.write(node.tag_closing.value);
3497
3730
  }
3498
3731
  }
3499
3732
  visitCDATANode(node) {
3500
3733
  if (node.tag_opening) {
3501
- this.context.write(node.tag_opening.value);
3734
+ this.write(node.tag_opening.value);
3502
3735
  }
3503
3736
  this.visitChildNodes(node);
3504
3737
  if (node.tag_closing) {
3505
- this.context.write(node.tag_closing.value);
3738
+ this.write(node.tag_closing.value);
3506
3739
  }
3507
3740
  }
3508
3741
  visitERBContentNode(node) {
@@ -3660,31 +3893,19 @@ class Printer extends Visitor {
3660
3893
  */
3661
3894
  printERBNode(node) {
3662
3895
  if (node.tag_opening) {
3663
- this.context.write(node.tag_opening.value);
3896
+ this.write(node.tag_opening.value);
3664
3897
  }
3665
3898
  if (node.content) {
3666
- this.context.write(node.content.value);
3899
+ this.write(node.content.value);
3667
3900
  }
3668
3901
  if (node.tag_closing) {
3669
- this.context.write(node.tag_closing.value);
3902
+ this.write(node.tag_closing.value);
3670
3903
  }
3671
3904
  }
3672
- write(content) {
3673
- this.context.write(content);
3674
- }
3675
3905
  }
3676
3906
 
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
- }
3907
+ ({
3908
+ ...DEFAULT_PRINT_OPTIONS});
3688
3909
 
3689
3910
  // TODO: we can probably expand this list with more tags/attributes
3690
3911
  const FORMATTABLE_ATTRIBUTES = {
@@ -3721,12 +3942,16 @@ class FormatPrinter extends Printer {
3721
3942
  'samp', 'small', 'span', 'strong', 'sub', 'sup',
3722
3943
  'tt', 'var', 'del', 'ins', 'mark', 's', 'u', 'time', 'wbr'
3723
3944
  ]);
3945
+ static CONTENT_PRESERVING_ELEMENTS = new Set([
3946
+ 'script', 'style', 'pre', 'textarea'
3947
+ ]);
3724
3948
  static SPACEABLE_CONTAINERS = new Set([
3725
3949
  'div', 'section', 'article', 'main', 'header', 'footer', 'aside',
3726
3950
  'figure', 'details', 'summary', 'dialog', 'fieldset'
3727
3951
  ]);
3728
3952
  static TIGHT_GROUP_PARENTS = new Set([
3729
- 'ul', 'ol', 'nav', 'select', 'datalist', 'optgroup', 'tr', 'thead', 'tbody', 'tfoot'
3953
+ 'ul', 'ol', 'nav', 'select', 'datalist', 'optgroup', 'tr', 'thead',
3954
+ 'tbody', 'tfoot'
3730
3955
  ]);
3731
3956
  static TIGHT_GROUP_CHILDREN = new Set([
3732
3957
  'li', 'option', 'td', 'th', 'dt', 'dd'
@@ -3824,6 +4049,13 @@ class FormatPrinter extends Printer {
3824
4049
  push(line) {
3825
4050
  this.lines.push(line);
3826
4051
  }
4052
+ /**
4053
+ * @deprecated refactor to use @herb-tools/printer infrastructre (or rework printer use push and this.lines)
4054
+ */
4055
+ pushWithIndent(line) {
4056
+ const indent = line.trim() === "" ? "" : this.indent;
4057
+ this.push(indent + line);
4058
+ }
3827
4059
  withIndent(callback) {
3828
4060
  this.indentLevel++;
3829
4061
  const result = callback();
@@ -4024,20 +4256,14 @@ class FormatPrinter extends Printer {
4024
4256
  return false;
4025
4257
  }
4026
4258
  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;
4259
+ if (isNode(attribute.value, HTMLAttributeValueNode)) {
4260
+ return attribute.value.children.map(child => isNode(child, HTMLTextNode) ? child.content : IdentityPrinter.print(child)).join('');
4035
4261
  }
4036
4262
  return '';
4037
4263
  }
4038
4264
  hasMultilineAttributes(attributes) {
4039
4265
  return attributes.some(attribute => {
4040
- if (attribute.value && isNode(attribute.value, HTMLAttributeValueNode)) {
4266
+ if (isNode(attribute.value, HTMLAttributeValueNode)) {
4041
4267
  const content = getCombinedStringFromNodes(attribute.value.children);
4042
4268
  if (/\r?\n/.test(content)) {
4043
4269
  const name = attribute.name ? getCombinedAttributeName(attribute.name) : "";
@@ -4125,11 +4351,11 @@ class FormatPrinter extends Printer {
4125
4351
  * Render multiline attributes for a tag
4126
4352
  */
4127
4353
  renderMultilineAttributes(tagName, allChildren = [], isSelfClosing = false) {
4128
- this.push(this.indent + `<${tagName}`);
4354
+ this.pushWithIndent(`<${tagName}`);
4129
4355
  this.withIndent(() => {
4130
4356
  allChildren.forEach(child => {
4131
4357
  if (isNode(child, HTMLAttributeNode)) {
4132
- this.push(this.indent + this.renderAttribute(child));
4358
+ this.pushWithIndent(this.renderAttribute(child));
4133
4359
  }
4134
4360
  else if (!isNode(child, WhitespaceNode)) {
4135
4361
  this.visit(child);
@@ -4137,10 +4363,10 @@ class FormatPrinter extends Printer {
4137
4363
  });
4138
4364
  });
4139
4365
  if (isSelfClosing) {
4140
- this.push(this.indent + "/>");
4366
+ this.pushWithIndent("/>");
4141
4367
  }
4142
4368
  else {
4143
- this.push(this.indent + ">");
4369
+ this.pushWithIndent(">");
4144
4370
  }
4145
4371
  }
4146
4372
  /**
@@ -4204,6 +4430,10 @@ class FormatPrinter extends Printer {
4204
4430
  this.elementStack.pop();
4205
4431
  }
4206
4432
  visitHTMLElementBody(body, element) {
4433
+ if (this.isContentPreserving(element)) {
4434
+ element.body.map(child => this.pushToLastLine(IdentityPrinter.print(child)));
4435
+ return;
4436
+ }
4207
4437
  const analysis = this.elementFormattingAnalysis.get(element);
4208
4438
  const hasTextFlow = this.isInTextFlowContext(null, body);
4209
4439
  const children = this.filterSignificantChildren(body, hasTextFlow);
@@ -4329,7 +4559,7 @@ class FormatPrinter extends Printer {
4329
4559
  this.pushToLastLine(closingTag);
4330
4560
  }
4331
4561
  else {
4332
- this.push(this.indent + closingTag);
4562
+ this.pushWithIndent(closingTag);
4333
4563
  }
4334
4564
  }
4335
4565
  visitHTMLTextNode(node) {
@@ -4361,13 +4591,13 @@ class FormatPrinter extends Printer {
4361
4591
  lines.forEach(line => this.push(line));
4362
4592
  }
4363
4593
  visitHTMLAttributeNode(node) {
4364
- this.push(this.indent + this.renderAttribute(node));
4594
+ this.pushWithIndent(this.renderAttribute(node));
4365
4595
  }
4366
4596
  visitHTMLAttributeNameNode(node) {
4367
- this.push(this.indent + getCombinedAttributeName(node));
4597
+ this.pushWithIndent(getCombinedAttributeName(node));
4368
4598
  }
4369
4599
  visitHTMLAttributeValueNode(node) {
4370
- this.push(this.indent + IdentityPrinter.print(node));
4600
+ this.pushWithIndent(IdentityPrinter.print(node));
4371
4601
  }
4372
4602
  // TODO: rework
4373
4603
  visitHTMLCommentNode(node) {
@@ -4422,37 +4652,40 @@ class FormatPrinter extends Printer {
4422
4652
  else {
4423
4653
  inner = "";
4424
4654
  }
4425
- this.push(this.indent + open + inner + close);
4655
+ this.pushWithIndent(open + inner + close);
4426
4656
  }
4427
4657
  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
- }
4658
+ const open = node.tag_opening?.value || "<%#";
4659
+ const content = node?.content?.value || "";
4660
+ const close = node.tag_closing?.value || "%>";
4661
+ const contentLines = content.split("\n");
4662
+ const contentTrimmedLines = content.trim().split("\n");
4663
+ if (contentLines.length === 1 && contentTrimmedLines.length === 1) {
4664
+ const startsWithSpace = content[0] === " ";
4665
+ const before = startsWithSpace ? "" : " ";
4666
+ this.pushWithIndent(open + before + content.trimEnd() + ' ' + close);
4667
+ return;
4442
4668
  }
4443
- else {
4444
- inner = "";
4669
+ if (contentTrimmedLines.length === 1) {
4670
+ this.pushWithIndent(open + ' ' + content.trim() + ' ' + close);
4671
+ return;
4445
4672
  }
4446
- this.push(this.indent + open + inner + close);
4673
+ const firstLineEmpty = contentLines[0].trim() === "";
4674
+ const dedentedContent = dedent(firstLineEmpty ? content : content.trimStart());
4675
+ this.pushWithIndent(open);
4676
+ this.withIndent(() => {
4677
+ dedentedContent.split("\n").forEach(line => this.pushWithIndent(line));
4678
+ });
4679
+ this.pushWithIndent(close);
4447
4680
  }
4448
4681
  visitHTMLDoctypeNode(node) {
4449
- this.push(this.indent + IdentityPrinter.print(node));
4682
+ this.pushWithIndent(IdentityPrinter.print(node));
4450
4683
  }
4451
4684
  visitXMLDeclarationNode(node) {
4452
- this.push(this.indent + IdentityPrinter.print(node));
4685
+ this.pushWithIndent(IdentityPrinter.print(node));
4453
4686
  }
4454
4687
  visitCDATANode(node) {
4455
- this.push(this.indent + IdentityPrinter.print(node));
4688
+ this.pushWithIndent(IdentityPrinter.print(node));
4456
4689
  }
4457
4690
  visitERBContentNode(node) {
4458
4691
  // TODO: this feels hacky
@@ -4668,8 +4901,11 @@ class FormatPrinter extends Printer {
4668
4901
  * Determines if the close tag should be rendered inline (usually follows content decision)
4669
4902
  */
4670
4903
  shouldRenderCloseTagInline(node, elementContentInline) {
4671
- const isSelfClosing = node.open_tag?.tag_closing?.value === "/>";
4672
- if (isSelfClosing || node.is_void)
4904
+ if (node.is_void)
4905
+ return true;
4906
+ if (node.open_tag?.tag_closing?.value === "/>")
4907
+ return true;
4908
+ if (this.isContentPreserving(node))
4673
4909
  return true;
4674
4910
  const children = this.filterSignificantChildren(node.body, this.isInTextFlowContext(null, node.body));
4675
4911
  if (children.length === 0)
@@ -4728,7 +4964,7 @@ class FormatPrinter extends Printer {
4728
4964
  }
4729
4965
  else {
4730
4966
  if (currentLineContent.trim()) {
4731
- this.push(this.indent + currentLineContent.trim());
4967
+ this.pushWithIndent(currentLineContent.trim());
4732
4968
  currentLineContent = "";
4733
4969
  }
4734
4970
  this.visit(child);
@@ -4736,7 +4972,7 @@ class FormatPrinter extends Printer {
4736
4972
  }
4737
4973
  else {
4738
4974
  if (currentLineContent.trim()) {
4739
- this.push(this.indent + currentLineContent.trim());
4975
+ this.pushWithIndent(currentLineContent.trim());
4740
4976
  currentLineContent = "";
4741
4977
  }
4742
4978
  this.visit(child);
@@ -4766,7 +5002,7 @@ class FormatPrinter extends Printer {
4766
5002
  }
4767
5003
  else {
4768
5004
  if (currentLineContent.trim()) {
4769
- this.push(this.indent + currentLineContent.trim());
5005
+ this.pushWithIndent(currentLineContent.trim());
4770
5006
  currentLineContent = "";
4771
5007
  }
4772
5008
  this.visit(child);
@@ -4847,7 +5083,7 @@ class FormatPrinter extends Printer {
4847
5083
  const equals = attribute.equals?.value ?? "";
4848
5084
  this.currentAttributeName = name;
4849
5085
  let value = "";
4850
- if (attribute.value && isNode(attribute.value, HTMLAttributeValueNode)) {
5086
+ if (isNode(attribute.value, HTMLAttributeValueNode)) {
4851
5087
  const attributeValue = attribute.value;
4852
5088
  let open_quote = attributeValue.open_quote?.value ?? "";
4853
5089
  let close_quote = attributeValue.close_quote?.value ?? "";
@@ -5096,6 +5332,10 @@ class FormatPrinter extends Printer {
5096
5332
  }
5097
5333
  return content.replace(/\s+/g, ' ').trim();
5098
5334
  }
5335
+ isContentPreserving(element) {
5336
+ const tagName = getTagName(element);
5337
+ return FormatPrinter.CONTENT_PRESERVING_ELEMENTS.has(tagName);
5338
+ }
5099
5339
  }
5100
5340
 
5101
5341
  /**