@herb-tools/formatter 0.8.6 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -64,6 +64,18 @@ function createDedent(options) {
64
64
  if (escapeSpecialCharacters) {
65
65
  result = result.replace(/\\n/g, "\n");
66
66
  }
67
+
68
+ // Workaround for Bun issue with Unicode characters
69
+ // https://github.com/oven-sh/bun/issues/8745
70
+ if (typeof Bun !== "undefined") {
71
+ result = result.replace(
72
+ // Matches e.g. \\u{1f60a} or \\u5F1F
73
+ /\\u(?:\{([\da-fA-F]{1,6})\}|([\da-fA-F]{4}))/g, (_, braced, unbraced) => {
74
+ var _ref;
75
+ const hex = (_ref = braced !== null && braced !== void 0 ? braced : unbraced) !== null && _ref !== void 0 ? _ref : "";
76
+ return String.fromCodePoint(parseInt(hex, 16));
77
+ });
78
+ }
67
79
  return result;
68
80
  }
69
81
  }
@@ -241,7 +253,7 @@ class Token {
241
253
  }
242
254
 
243
255
  // NOTE: This file is generated by the templates/template.rb script and should not
244
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.6/templates/javascript/packages/core/src/errors.ts.erb
256
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.8/templates/javascript/packages/core/src/errors.ts.erb
245
257
  class HerbError {
246
258
  type;
247
259
  message;
@@ -806,7 +818,7 @@ function convertToUTF8(string) {
806
818
  }
807
819
 
808
820
  // NOTE: This file is generated by the templates/template.rb script and should not
809
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.6/templates/javascript/packages/core/src/nodes.ts.erb
821
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.8/templates/javascript/packages/core/src/nodes.ts.erb
810
822
  class Node {
811
823
  type;
812
824
  location;
@@ -1880,6 +1892,7 @@ class ERBIfNode extends Node {
1880
1892
  tag_opening;
1881
1893
  content;
1882
1894
  tag_closing;
1895
+ then_keyword;
1883
1896
  statements;
1884
1897
  subsequent;
1885
1898
  end_node;
@@ -1894,6 +1907,7 @@ class ERBIfNode extends Node {
1894
1907
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
1895
1908
  content: data.content ? Token.from(data.content) : null,
1896
1909
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
1910
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
1897
1911
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
1898
1912
  subsequent: data.subsequent ? fromSerializedNode((data.subsequent)) : null,
1899
1913
  end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
@@ -1904,6 +1918,7 @@ class ERBIfNode extends Node {
1904
1918
  this.tag_opening = props.tag_opening;
1905
1919
  this.content = props.content;
1906
1920
  this.tag_closing = props.tag_closing;
1921
+ this.then_keyword = props.then_keyword;
1907
1922
  this.statements = props.statements;
1908
1923
  this.subsequent = props.subsequent;
1909
1924
  this.end_node = props.end_node;
@@ -1936,6 +1951,7 @@ class ERBIfNode extends Node {
1936
1951
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
1937
1952
  content: this.content ? this.content.toJSON() : null,
1938
1953
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
1954
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
1939
1955
  statements: this.statements.map(node => node.toJSON()),
1940
1956
  subsequent: this.subsequent ? this.subsequent.toJSON() : null,
1941
1957
  end_node: this.end_node ? this.end_node.toJSON() : null,
@@ -1948,6 +1964,7 @@ class ERBIfNode extends Node {
1948
1964
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
1949
1965
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
1950
1966
  output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
1967
+ output += `├── then_keyword: ${this.then_keyword ? "(location: " + this.then_keyword.treeInspect() + ")" : "∅"}\n`;
1951
1968
  output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
1952
1969
  output += `├── subsequent: ${this.inspectNode(this.subsequent, "│ ")}`;
1953
1970
  output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
@@ -2029,6 +2046,7 @@ class ERBWhenNode extends Node {
2029
2046
  tag_opening;
2030
2047
  content;
2031
2048
  tag_closing;
2049
+ then_keyword;
2032
2050
  statements;
2033
2051
  static get type() {
2034
2052
  return "AST_ERB_WHEN_NODE";
@@ -2041,6 +2059,7 @@ class ERBWhenNode extends Node {
2041
2059
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2042
2060
  content: data.content ? Token.from(data.content) : null,
2043
2061
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2062
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2044
2063
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2045
2064
  });
2046
2065
  }
@@ -2049,6 +2068,7 @@ class ERBWhenNode extends Node {
2049
2068
  this.tag_opening = props.tag_opening;
2050
2069
  this.content = props.content;
2051
2070
  this.tag_closing = props.tag_closing;
2071
+ this.then_keyword = props.then_keyword;
2052
2072
  this.statements = props.statements;
2053
2073
  }
2054
2074
  accept(visitor) {
@@ -2075,6 +2095,7 @@ class ERBWhenNode extends Node {
2075
2095
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2076
2096
  content: this.content ? this.content.toJSON() : null,
2077
2097
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2098
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2078
2099
  statements: this.statements.map(node => node.toJSON()),
2079
2100
  };
2080
2101
  }
@@ -2085,6 +2106,7 @@ class ERBWhenNode extends Node {
2085
2106
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2086
2107
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2087
2108
  output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
2109
+ output += `├── then_keyword: ${this.then_keyword ? "(location: " + this.then_keyword.treeInspect() + ")" : "∅"}\n`;
2088
2110
  output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
2089
2111
  return output;
2090
2112
  }
@@ -2703,6 +2725,7 @@ class ERBUnlessNode extends Node {
2703
2725
  tag_opening;
2704
2726
  content;
2705
2727
  tag_closing;
2728
+ then_keyword;
2706
2729
  statements;
2707
2730
  else_clause;
2708
2731
  end_node;
@@ -2717,6 +2740,7 @@ class ERBUnlessNode extends Node {
2717
2740
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2718
2741
  content: data.content ? Token.from(data.content) : null,
2719
2742
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2743
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2720
2744
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2721
2745
  else_clause: data.else_clause ? fromSerializedNode((data.else_clause)) : null,
2722
2746
  end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
@@ -2727,6 +2751,7 @@ class ERBUnlessNode extends Node {
2727
2751
  this.tag_opening = props.tag_opening;
2728
2752
  this.content = props.content;
2729
2753
  this.tag_closing = props.tag_closing;
2754
+ this.then_keyword = props.then_keyword;
2730
2755
  this.statements = props.statements;
2731
2756
  this.else_clause = props.else_clause;
2732
2757
  this.end_node = props.end_node;
@@ -2759,6 +2784,7 @@ class ERBUnlessNode extends Node {
2759
2784
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2760
2785
  content: this.content ? this.content.toJSON() : null,
2761
2786
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2787
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2762
2788
  statements: this.statements.map(node => node.toJSON()),
2763
2789
  else_clause: this.else_clause ? this.else_clause.toJSON() : null,
2764
2790
  end_node: this.end_node ? this.end_node.toJSON() : null,
@@ -2771,6 +2797,7 @@ class ERBUnlessNode extends Node {
2771
2797
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2772
2798
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2773
2799
  output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
2800
+ output += `├── then_keyword: ${this.then_keyword ? "(location: " + this.then_keyword.treeInspect() + ")" : "∅"}\n`;
2774
2801
  output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
2775
2802
  output += `├── else_clause: ${this.inspectNode(this.else_clause, "│ ")}`;
2776
2803
  output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
@@ -2837,6 +2864,7 @@ class ERBInNode extends Node {
2837
2864
  tag_opening;
2838
2865
  content;
2839
2866
  tag_closing;
2867
+ then_keyword;
2840
2868
  statements;
2841
2869
  static get type() {
2842
2870
  return "AST_ERB_IN_NODE";
@@ -2849,6 +2877,7 @@ class ERBInNode extends Node {
2849
2877
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2850
2878
  content: data.content ? Token.from(data.content) : null,
2851
2879
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2880
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2852
2881
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2853
2882
  });
2854
2883
  }
@@ -2857,6 +2886,7 @@ class ERBInNode extends Node {
2857
2886
  this.tag_opening = props.tag_opening;
2858
2887
  this.content = props.content;
2859
2888
  this.tag_closing = props.tag_closing;
2889
+ this.then_keyword = props.then_keyword;
2860
2890
  this.statements = props.statements;
2861
2891
  }
2862
2892
  accept(visitor) {
@@ -2883,6 +2913,7 @@ class ERBInNode extends Node {
2883
2913
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2884
2914
  content: this.content ? this.content.toJSON() : null,
2885
2915
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2916
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2886
2917
  statements: this.statements.map(node => node.toJSON()),
2887
2918
  };
2888
2919
  }
@@ -2893,6 +2924,7 @@ class ERBInNode extends Node {
2893
2924
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2894
2925
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2895
2926
  output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
2927
+ output += `├── then_keyword: ${this.then_keyword ? "(location: " + this.then_keyword.treeInspect() + ")" : "∅"}\n`;
2896
2928
  output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
2897
2929
  return output;
2898
2930
  }
@@ -3040,7 +3072,7 @@ class ParseResult extends Result {
3040
3072
  }
3041
3073
 
3042
3074
  // NOTE: This file is generated by the templates/template.rb script and should not
3043
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.6/templates/javascript/packages/core/src/node-type-guards.ts.erb
3075
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.8/templates/javascript/packages/core/src/node-type-guards.ts.erb
3044
3076
  /**
3045
3077
  * Type guard functions for AST nodes.
3046
3078
  * These functions provide type checking by combining both instanceof
@@ -3537,7 +3569,7 @@ function getNodesAfterPosition(nodes, position, inclusive = true) {
3537
3569
  }
3538
3570
 
3539
3571
  // NOTE: This file is generated by the templates/template.rb script and should not
3540
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.6/templates/javascript/packages/core/src/visitor.ts.erb
3572
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.8/templates/javascript/packages/core/src/visitor.ts.erb
3541
3573
  class Visitor {
3542
3574
  visit(node) {
3543
3575
  if (!node)
@@ -4528,6 +4560,11 @@ function isFrontmatter(node) {
4528
4560
  return content.startsWith("---") && /---\s*$/.test(content);
4529
4561
  }
4530
4562
 
4563
+ /**
4564
+ * ASCII whitespace pattern - use instead of \s to preserve Unicode whitespace
4565
+ * characters like NBSP (U+00A0) and full-width space (U+3000)
4566
+ */
4567
+ const ASCII_WHITESPACE = /[ \t\n\r]+/g;
4531
4568
  /**
4532
4569
  * Printer traverses the Herb AST using the Visitor pattern
4533
4570
  * and emits a formatted string with proper indentation, line breaks, and attribute wrapping.
@@ -4899,7 +4936,7 @@ class FormatPrinter extends Printer {
4899
4936
  return true;
4900
4937
  }
4901
4938
  wouldClassAttributeBeMultiline(content, indentLength) {
4902
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4939
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4903
4940
  const hasActualNewlines = /\r?\n/.test(content);
4904
4941
  if (hasActualNewlines && normalizedContent.length > 80) {
4905
4942
  const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
@@ -4937,12 +4974,12 @@ class FormatPrinter extends Printer {
4937
4974
  if (/\r?\n/.test(content)) {
4938
4975
  const name = attribute.name ? getCombinedAttributeName(attribute.name) : "";
4939
4976
  if (name === "class") {
4940
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4977
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4941
4978
  return normalizedContent.length > 80;
4942
4979
  }
4943
4980
  const lines = content.split(/\r?\n/);
4944
4981
  if (lines.length > 1) {
4945
- return lines.slice(1).some(line => /^\s+/.test(line));
4982
+ return lines.slice(1).some(line => /^[ \t\n\r]+/.test(line));
4946
4983
  }
4947
4984
  }
4948
4985
  }
@@ -4950,7 +4987,7 @@ class FormatPrinter extends Printer {
4950
4987
  });
4951
4988
  }
4952
4989
  formatClassAttribute(content, name, equals, open_quote, close_quote) {
4953
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4990
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4954
4991
  const hasActualNewlines = /\r?\n/.test(content);
4955
4992
  if (hasActualNewlines && normalizedContent.length > 80) {
4956
4993
  const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
@@ -4979,7 +5016,7 @@ class FormatPrinter extends Printer {
4979
5016
  }
4980
5017
  formatMultilineAttribute(content, name, open_quote, close_quote) {
4981
5018
  if (name === 'srcset' || name === 'sizes') {
4982
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
5019
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4983
5020
  return open_quote + normalizedContent + close_quote;
4984
5021
  }
4985
5022
  const lines = content.split('\n');
@@ -5169,7 +5206,7 @@ class FormatPrinter extends Printer {
5169
5206
  nodesToRender.forEach(child => {
5170
5207
  if (isNode(child, HTMLTextNode)) {
5171
5208
  if (hasTextFlow) {
5172
- const normalizedContent = child.content.replace(/\s+/g, ' ');
5209
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
5173
5210
  if (normalizedContent && normalizedContent !== ' ') {
5174
5211
  this.push(normalizedContent);
5175
5212
  }
@@ -5178,7 +5215,7 @@ class FormatPrinter extends Printer {
5178
5215
  }
5179
5216
  }
5180
5217
  else {
5181
- const normalizedContent = child.content.replace(/\s+/g, ' ');
5218
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
5182
5219
  if (shouldPreserveSpaces && normalizedContent) {
5183
5220
  this.push(normalizedContent);
5184
5221
  }
@@ -5203,8 +5240,8 @@ class FormatPrinter extends Printer {
5203
5240
  });
5204
5241
  const content = lines.join('');
5205
5242
  const inlineContent = shouldPreserveSpaces
5206
- ? (hasTextFlow ? content.replace(/\s+/g, ' ') : content)
5207
- : (hasTextFlow ? content.replace(/\s+/g, ' ').trim() : content.trim());
5243
+ ? (hasTextFlow ? content.replace(ASCII_WHITESPACE, ' ') : content)
5244
+ : (hasTextFlow ? content.replace(ASCII_WHITESPACE, ' ').trim() : content.trim());
5208
5245
  if (inlineContent) {
5209
5246
  this.pushToLastLine(inlineContent);
5210
5247
  }
@@ -5393,7 +5430,7 @@ class FormatPrinter extends Printer {
5393
5430
  }
5394
5431
  visitHTMLTextNode(node) {
5395
5432
  if (this.inlineMode) {
5396
- const normalizedContent = node.content.replace(/\s+/g, ' ').trim();
5433
+ const normalizedContent = node.content.replace(ASCII_WHITESPACE, ' ').trim();
5397
5434
  if (normalizedContent) {
5398
5435
  this.push(normalizedContent);
5399
5436
  }
@@ -5403,7 +5440,7 @@ class FormatPrinter extends Printer {
5403
5440
  if (!text)
5404
5441
  return;
5405
5442
  const wrapWidth = this.maxLineLength - this.indent.length;
5406
- const words = text.split(/\s+/);
5443
+ const words = text.split(/[ \t\n\r]+/);
5407
5444
  const lines = [];
5408
5445
  let line = "";
5409
5446
  for (const word of words) {
@@ -5912,7 +5949,7 @@ class FormatPrinter extends Printer {
5912
5949
  wrappedLines: []
5913
5950
  };
5914
5951
  }
5915
- const words = restText.split(/\s+/);
5952
+ const words = restText.split(/[ \t\n\r]+/);
5916
5953
  let toMerge = punctuation;
5917
5954
  let mergedWordCount = 0;
5918
5955
  for (const word of words) {
@@ -6062,7 +6099,7 @@ class FormatPrinter extends Printer {
6062
6099
  words.push({ word: unit.content, isHerbDisable: unit.isHerbDisable || false });
6063
6100
  }
6064
6101
  else {
6065
- const text = unit.content.replace(/\s+/g, ' ');
6102
+ const text = unit.content.replace(ASCII_WHITESPACE, ' ');
6066
6103
  const hasLeadingSpace = text.startsWith(' ');
6067
6104
  const hasTrailingSpace = text.endsWith(' ');
6068
6105
  const trimmedText = text.trim();
@@ -6108,7 +6145,7 @@ class FormatPrinter extends Printer {
6108
6145
  return false;
6109
6146
  const firstWord = words[0];
6110
6147
  const firstChar = firstWord[0];
6111
- if (/\s/.test(firstChar)) {
6148
+ if (' \t\n\r'.includes(firstChar)) {
6112
6149
  return false;
6113
6150
  }
6114
6151
  lastUnit.unit.content += firstWord;
@@ -6165,7 +6202,7 @@ class FormatPrinter extends Printer {
6165
6202
  if (hasWhitespaceBetween(children, lastProcessedIndex, currentIndex)) {
6166
6203
  return true;
6167
6204
  }
6168
- if (isNode(currentNode, HTMLTextNode) && /^\s/.test(currentNode.content)) {
6205
+ if (isNode(currentNode, HTMLTextNode) && /^[ \t\n\r]/.test(currentNode.content)) {
6169
6206
  return true;
6170
6207
  }
6171
6208
  return false;
@@ -6504,9 +6541,9 @@ class FormatPrinter extends Printer {
6504
6541
  const shouldPreserveSpaces = hasOnlyTextContent && tagName && isInlineElement(tagName);
6505
6542
  for (const child of children) {
6506
6543
  if (isNode(child, HTMLTextNode)) {
6507
- const normalizedContent = child.content.replace(/\s+/g, ' ');
6508
- const hasLeadingSpace = /^\s/.test(child.content);
6509
- const hasTrailingSpace = /\s$/.test(child.content);
6544
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
6545
+ const hasLeadingSpace = /^[ \t\n\r]/.test(child.content);
6546
+ const hasTrailingSpace = /[ \t\n\r]$/.test(child.content);
6510
6547
  const trimmedContent = normalizedContent.trim();
6511
6548
  if (trimmedContent) {
6512
6549
  if (hasLeadingSpace && (result || shouldPreserveSpaces) && !result.endsWith(' ')) {
@@ -6613,7 +6650,7 @@ class FormatPrinter extends Printer {
6613
6650
  content += this.reconstructERBNode(child, true);
6614
6651
  }
6615
6652
  }
6616
- return content.replace(/\s+/g, ' ').trim();
6653
+ return content.replace(ASCII_WHITESPACE, ' ').trim();
6617
6654
  }
6618
6655
  }
6619
6656