@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.cjs CHANGED
@@ -66,6 +66,18 @@ function createDedent(options) {
66
66
  if (escapeSpecialCharacters) {
67
67
  result = result.replace(/\\n/g, "\n");
68
68
  }
69
+
70
+ // Workaround for Bun issue with Unicode characters
71
+ // https://github.com/oven-sh/bun/issues/8745
72
+ if (typeof Bun !== "undefined") {
73
+ result = result.replace(
74
+ // Matches e.g. \\u{1f60a} or \\u5F1F
75
+ /\\u(?:\{([\da-fA-F]{1,6})\}|([\da-fA-F]{4}))/g, (_, braced, unbraced) => {
76
+ var _ref;
77
+ const hex = (_ref = braced !== null && braced !== void 0 ? braced : unbraced) !== null && _ref !== void 0 ? _ref : "";
78
+ return String.fromCodePoint(parseInt(hex, 16));
79
+ });
80
+ }
69
81
  return result;
70
82
  }
71
83
  }
@@ -243,7 +255,7 @@ class Token {
243
255
  }
244
256
 
245
257
  // NOTE: This file is generated by the templates/template.rb script and should not
246
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.6/templates/javascript/packages/core/src/errors.ts.erb
258
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.8/templates/javascript/packages/core/src/errors.ts.erb
247
259
  class HerbError {
248
260
  type;
249
261
  message;
@@ -808,7 +820,7 @@ function convertToUTF8(string) {
808
820
  }
809
821
 
810
822
  // NOTE: This file is generated by the templates/template.rb script and should not
811
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.6/templates/javascript/packages/core/src/nodes.ts.erb
823
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.8/templates/javascript/packages/core/src/nodes.ts.erb
812
824
  class Node {
813
825
  type;
814
826
  location;
@@ -1882,6 +1894,7 @@ class ERBIfNode extends Node {
1882
1894
  tag_opening;
1883
1895
  content;
1884
1896
  tag_closing;
1897
+ then_keyword;
1885
1898
  statements;
1886
1899
  subsequent;
1887
1900
  end_node;
@@ -1896,6 +1909,7 @@ class ERBIfNode extends Node {
1896
1909
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
1897
1910
  content: data.content ? Token.from(data.content) : null,
1898
1911
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
1912
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
1899
1913
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
1900
1914
  subsequent: data.subsequent ? fromSerializedNode((data.subsequent)) : null,
1901
1915
  end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
@@ -1906,6 +1920,7 @@ class ERBIfNode extends Node {
1906
1920
  this.tag_opening = props.tag_opening;
1907
1921
  this.content = props.content;
1908
1922
  this.tag_closing = props.tag_closing;
1923
+ this.then_keyword = props.then_keyword;
1909
1924
  this.statements = props.statements;
1910
1925
  this.subsequent = props.subsequent;
1911
1926
  this.end_node = props.end_node;
@@ -1938,6 +1953,7 @@ class ERBIfNode extends Node {
1938
1953
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
1939
1954
  content: this.content ? this.content.toJSON() : null,
1940
1955
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
1956
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
1941
1957
  statements: this.statements.map(node => node.toJSON()),
1942
1958
  subsequent: this.subsequent ? this.subsequent.toJSON() : null,
1943
1959
  end_node: this.end_node ? this.end_node.toJSON() : null,
@@ -1950,6 +1966,7 @@ class ERBIfNode extends Node {
1950
1966
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
1951
1967
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
1952
1968
  output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
1969
+ output += `├── then_keyword: ${this.then_keyword ? "(location: " + this.then_keyword.treeInspect() + ")" : "∅"}\n`;
1953
1970
  output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
1954
1971
  output += `├── subsequent: ${this.inspectNode(this.subsequent, "│ ")}`;
1955
1972
  output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
@@ -2031,6 +2048,7 @@ class ERBWhenNode extends Node {
2031
2048
  tag_opening;
2032
2049
  content;
2033
2050
  tag_closing;
2051
+ then_keyword;
2034
2052
  statements;
2035
2053
  static get type() {
2036
2054
  return "AST_ERB_WHEN_NODE";
@@ -2043,6 +2061,7 @@ class ERBWhenNode extends Node {
2043
2061
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2044
2062
  content: data.content ? Token.from(data.content) : null,
2045
2063
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2064
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2046
2065
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2047
2066
  });
2048
2067
  }
@@ -2051,6 +2070,7 @@ class ERBWhenNode extends Node {
2051
2070
  this.tag_opening = props.tag_opening;
2052
2071
  this.content = props.content;
2053
2072
  this.tag_closing = props.tag_closing;
2073
+ this.then_keyword = props.then_keyword;
2054
2074
  this.statements = props.statements;
2055
2075
  }
2056
2076
  accept(visitor) {
@@ -2077,6 +2097,7 @@ class ERBWhenNode extends Node {
2077
2097
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2078
2098
  content: this.content ? this.content.toJSON() : null,
2079
2099
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2100
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2080
2101
  statements: this.statements.map(node => node.toJSON()),
2081
2102
  };
2082
2103
  }
@@ -2087,6 +2108,7 @@ class ERBWhenNode extends Node {
2087
2108
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2088
2109
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2089
2110
  output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
2111
+ output += `├── then_keyword: ${this.then_keyword ? "(location: " + this.then_keyword.treeInspect() + ")" : "∅"}\n`;
2090
2112
  output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
2091
2113
  return output;
2092
2114
  }
@@ -2705,6 +2727,7 @@ class ERBUnlessNode extends Node {
2705
2727
  tag_opening;
2706
2728
  content;
2707
2729
  tag_closing;
2730
+ then_keyword;
2708
2731
  statements;
2709
2732
  else_clause;
2710
2733
  end_node;
@@ -2719,6 +2742,7 @@ class ERBUnlessNode extends Node {
2719
2742
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2720
2743
  content: data.content ? Token.from(data.content) : null,
2721
2744
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2745
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2722
2746
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2723
2747
  else_clause: data.else_clause ? fromSerializedNode((data.else_clause)) : null,
2724
2748
  end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
@@ -2729,6 +2753,7 @@ class ERBUnlessNode extends Node {
2729
2753
  this.tag_opening = props.tag_opening;
2730
2754
  this.content = props.content;
2731
2755
  this.tag_closing = props.tag_closing;
2756
+ this.then_keyword = props.then_keyword;
2732
2757
  this.statements = props.statements;
2733
2758
  this.else_clause = props.else_clause;
2734
2759
  this.end_node = props.end_node;
@@ -2761,6 +2786,7 @@ class ERBUnlessNode extends Node {
2761
2786
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2762
2787
  content: this.content ? this.content.toJSON() : null,
2763
2788
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2789
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2764
2790
  statements: this.statements.map(node => node.toJSON()),
2765
2791
  else_clause: this.else_clause ? this.else_clause.toJSON() : null,
2766
2792
  end_node: this.end_node ? this.end_node.toJSON() : null,
@@ -2773,6 +2799,7 @@ class ERBUnlessNode extends Node {
2773
2799
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2774
2800
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2775
2801
  output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
2802
+ output += `├── then_keyword: ${this.then_keyword ? "(location: " + this.then_keyword.treeInspect() + ")" : "∅"}\n`;
2776
2803
  output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
2777
2804
  output += `├── else_clause: ${this.inspectNode(this.else_clause, "│ ")}`;
2778
2805
  output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
@@ -2839,6 +2866,7 @@ class ERBInNode extends Node {
2839
2866
  tag_opening;
2840
2867
  content;
2841
2868
  tag_closing;
2869
+ then_keyword;
2842
2870
  statements;
2843
2871
  static get type() {
2844
2872
  return "AST_ERB_IN_NODE";
@@ -2851,6 +2879,7 @@ class ERBInNode extends Node {
2851
2879
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2852
2880
  content: data.content ? Token.from(data.content) : null,
2853
2881
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2882
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2854
2883
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2855
2884
  });
2856
2885
  }
@@ -2859,6 +2888,7 @@ class ERBInNode extends Node {
2859
2888
  this.tag_opening = props.tag_opening;
2860
2889
  this.content = props.content;
2861
2890
  this.tag_closing = props.tag_closing;
2891
+ this.then_keyword = props.then_keyword;
2862
2892
  this.statements = props.statements;
2863
2893
  }
2864
2894
  accept(visitor) {
@@ -2885,6 +2915,7 @@ class ERBInNode extends Node {
2885
2915
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2886
2916
  content: this.content ? this.content.toJSON() : null,
2887
2917
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2918
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2888
2919
  statements: this.statements.map(node => node.toJSON()),
2889
2920
  };
2890
2921
  }
@@ -2895,6 +2926,7 @@ class ERBInNode extends Node {
2895
2926
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2896
2927
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2897
2928
  output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
2929
+ output += `├── then_keyword: ${this.then_keyword ? "(location: " + this.then_keyword.treeInspect() + ")" : "∅"}\n`;
2898
2930
  output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
2899
2931
  return output;
2900
2932
  }
@@ -3042,7 +3074,7 @@ class ParseResult extends Result {
3042
3074
  }
3043
3075
 
3044
3076
  // NOTE: This file is generated by the templates/template.rb script and should not
3045
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.6/templates/javascript/packages/core/src/node-type-guards.ts.erb
3077
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.8/templates/javascript/packages/core/src/node-type-guards.ts.erb
3046
3078
  /**
3047
3079
  * Type guard functions for AST nodes.
3048
3080
  * These functions provide type checking by combining both instanceof
@@ -3539,7 +3571,7 @@ function getNodesAfterPosition(nodes, position, inclusive = true) {
3539
3571
  }
3540
3572
 
3541
3573
  // NOTE: This file is generated by the templates/template.rb script and should not
3542
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.6/templates/javascript/packages/core/src/visitor.ts.erb
3574
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.8/templates/javascript/packages/core/src/visitor.ts.erb
3543
3575
  class Visitor {
3544
3576
  visit(node) {
3545
3577
  if (!node)
@@ -4530,6 +4562,11 @@ function isFrontmatter(node) {
4530
4562
  return content.startsWith("---") && /---\s*$/.test(content);
4531
4563
  }
4532
4564
 
4565
+ /**
4566
+ * ASCII whitespace pattern - use instead of \s to preserve Unicode whitespace
4567
+ * characters like NBSP (U+00A0) and full-width space (U+3000)
4568
+ */
4569
+ const ASCII_WHITESPACE = /[ \t\n\r]+/g;
4533
4570
  /**
4534
4571
  * Printer traverses the Herb AST using the Visitor pattern
4535
4572
  * and emits a formatted string with proper indentation, line breaks, and attribute wrapping.
@@ -4901,7 +4938,7 @@ class FormatPrinter extends Printer {
4901
4938
  return true;
4902
4939
  }
4903
4940
  wouldClassAttributeBeMultiline(content, indentLength) {
4904
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4941
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4905
4942
  const hasActualNewlines = /\r?\n/.test(content);
4906
4943
  if (hasActualNewlines && normalizedContent.length > 80) {
4907
4944
  const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
@@ -4939,12 +4976,12 @@ class FormatPrinter extends Printer {
4939
4976
  if (/\r?\n/.test(content)) {
4940
4977
  const name = attribute.name ? getCombinedAttributeName(attribute.name) : "";
4941
4978
  if (name === "class") {
4942
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4979
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4943
4980
  return normalizedContent.length > 80;
4944
4981
  }
4945
4982
  const lines = content.split(/\r?\n/);
4946
4983
  if (lines.length > 1) {
4947
- return lines.slice(1).some(line => /^\s+/.test(line));
4984
+ return lines.slice(1).some(line => /^[ \t\n\r]+/.test(line));
4948
4985
  }
4949
4986
  }
4950
4987
  }
@@ -4952,7 +4989,7 @@ class FormatPrinter extends Printer {
4952
4989
  });
4953
4990
  }
4954
4991
  formatClassAttribute(content, name, equals, open_quote, close_quote) {
4955
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4992
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4956
4993
  const hasActualNewlines = /\r?\n/.test(content);
4957
4994
  if (hasActualNewlines && normalizedContent.length > 80) {
4958
4995
  const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
@@ -4981,7 +5018,7 @@ class FormatPrinter extends Printer {
4981
5018
  }
4982
5019
  formatMultilineAttribute(content, name, open_quote, close_quote) {
4983
5020
  if (name === 'srcset' || name === 'sizes') {
4984
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
5021
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4985
5022
  return open_quote + normalizedContent + close_quote;
4986
5023
  }
4987
5024
  const lines = content.split('\n');
@@ -5171,7 +5208,7 @@ class FormatPrinter extends Printer {
5171
5208
  nodesToRender.forEach(child => {
5172
5209
  if (isNode(child, HTMLTextNode)) {
5173
5210
  if (hasTextFlow) {
5174
- const normalizedContent = child.content.replace(/\s+/g, ' ');
5211
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
5175
5212
  if (normalizedContent && normalizedContent !== ' ') {
5176
5213
  this.push(normalizedContent);
5177
5214
  }
@@ -5180,7 +5217,7 @@ class FormatPrinter extends Printer {
5180
5217
  }
5181
5218
  }
5182
5219
  else {
5183
- const normalizedContent = child.content.replace(/\s+/g, ' ');
5220
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
5184
5221
  if (shouldPreserveSpaces && normalizedContent) {
5185
5222
  this.push(normalizedContent);
5186
5223
  }
@@ -5205,8 +5242,8 @@ class FormatPrinter extends Printer {
5205
5242
  });
5206
5243
  const content = lines.join('');
5207
5244
  const inlineContent = shouldPreserveSpaces
5208
- ? (hasTextFlow ? content.replace(/\s+/g, ' ') : content)
5209
- : (hasTextFlow ? content.replace(/\s+/g, ' ').trim() : content.trim());
5245
+ ? (hasTextFlow ? content.replace(ASCII_WHITESPACE, ' ') : content)
5246
+ : (hasTextFlow ? content.replace(ASCII_WHITESPACE, ' ').trim() : content.trim());
5210
5247
  if (inlineContent) {
5211
5248
  this.pushToLastLine(inlineContent);
5212
5249
  }
@@ -5395,7 +5432,7 @@ class FormatPrinter extends Printer {
5395
5432
  }
5396
5433
  visitHTMLTextNode(node) {
5397
5434
  if (this.inlineMode) {
5398
- const normalizedContent = node.content.replace(/\s+/g, ' ').trim();
5435
+ const normalizedContent = node.content.replace(ASCII_WHITESPACE, ' ').trim();
5399
5436
  if (normalizedContent) {
5400
5437
  this.push(normalizedContent);
5401
5438
  }
@@ -5405,7 +5442,7 @@ class FormatPrinter extends Printer {
5405
5442
  if (!text)
5406
5443
  return;
5407
5444
  const wrapWidth = this.maxLineLength - this.indent.length;
5408
- const words = text.split(/\s+/);
5445
+ const words = text.split(/[ \t\n\r]+/);
5409
5446
  const lines = [];
5410
5447
  let line = "";
5411
5448
  for (const word of words) {
@@ -5914,7 +5951,7 @@ class FormatPrinter extends Printer {
5914
5951
  wrappedLines: []
5915
5952
  };
5916
5953
  }
5917
- const words = restText.split(/\s+/);
5954
+ const words = restText.split(/[ \t\n\r]+/);
5918
5955
  let toMerge = punctuation;
5919
5956
  let mergedWordCount = 0;
5920
5957
  for (const word of words) {
@@ -6064,7 +6101,7 @@ class FormatPrinter extends Printer {
6064
6101
  words.push({ word: unit.content, isHerbDisable: unit.isHerbDisable || false });
6065
6102
  }
6066
6103
  else {
6067
- const text = unit.content.replace(/\s+/g, ' ');
6104
+ const text = unit.content.replace(ASCII_WHITESPACE, ' ');
6068
6105
  const hasLeadingSpace = text.startsWith(' ');
6069
6106
  const hasTrailingSpace = text.endsWith(' ');
6070
6107
  const trimmedText = text.trim();
@@ -6110,7 +6147,7 @@ class FormatPrinter extends Printer {
6110
6147
  return false;
6111
6148
  const firstWord = words[0];
6112
6149
  const firstChar = firstWord[0];
6113
- if (/\s/.test(firstChar)) {
6150
+ if (' \t\n\r'.includes(firstChar)) {
6114
6151
  return false;
6115
6152
  }
6116
6153
  lastUnit.unit.content += firstWord;
@@ -6167,7 +6204,7 @@ class FormatPrinter extends Printer {
6167
6204
  if (hasWhitespaceBetween(children, lastProcessedIndex, currentIndex)) {
6168
6205
  return true;
6169
6206
  }
6170
- if (isNode(currentNode, HTMLTextNode) && /^\s/.test(currentNode.content)) {
6207
+ if (isNode(currentNode, HTMLTextNode) && /^[ \t\n\r]/.test(currentNode.content)) {
6171
6208
  return true;
6172
6209
  }
6173
6210
  return false;
@@ -6506,9 +6543,9 @@ class FormatPrinter extends Printer {
6506
6543
  const shouldPreserveSpaces = hasOnlyTextContent && tagName && isInlineElement(tagName);
6507
6544
  for (const child of children) {
6508
6545
  if (isNode(child, HTMLTextNode)) {
6509
- const normalizedContent = child.content.replace(/\s+/g, ' ');
6510
- const hasLeadingSpace = /^\s/.test(child.content);
6511
- const hasTrailingSpace = /\s$/.test(child.content);
6546
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
6547
+ const hasLeadingSpace = /^[ \t\n\r]/.test(child.content);
6548
+ const hasTrailingSpace = /[ \t\n\r]$/.test(child.content);
6512
6549
  const trimmedContent = normalizedContent.trim();
6513
6550
  if (trimmedContent) {
6514
6551
  if (hasLeadingSpace && (result || shouldPreserveSpaces) && !result.endsWith(' ')) {
@@ -6615,7 +6652,7 @@ class FormatPrinter extends Printer {
6615
6652
  content += this.reconstructERBNode(child, true);
6616
6653
  }
6617
6654
  }
6618
- return content.replace(/\s+/g, ' ').trim();
6655
+ return content.replace(ASCII_WHITESPACE, ' ').trim();
6619
6656
  }
6620
6657
  }
6621
6658