@herb-tools/formatter 0.8.7 → 0.8.9

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
@@ -255,7 +255,7 @@ class Token {
255
255
  }
256
256
 
257
257
  // NOTE: This file is generated by the templates/template.rb script and should not
258
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.7/templates/javascript/packages/core/src/errors.ts.erb
258
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.9/templates/javascript/packages/core/src/errors.ts.erb
259
259
  class HerbError {
260
260
  type;
261
261
  message;
@@ -820,7 +820,7 @@ function convertToUTF8(string) {
820
820
  }
821
821
 
822
822
  // NOTE: This file is generated by the templates/template.rb script and should not
823
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.7/templates/javascript/packages/core/src/nodes.ts.erb
823
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.9/templates/javascript/packages/core/src/nodes.ts.erb
824
824
  class Node {
825
825
  type;
826
826
  location;
@@ -1894,6 +1894,7 @@ class ERBIfNode extends Node {
1894
1894
  tag_opening;
1895
1895
  content;
1896
1896
  tag_closing;
1897
+ then_keyword;
1897
1898
  statements;
1898
1899
  subsequent;
1899
1900
  end_node;
@@ -1908,6 +1909,7 @@ class ERBIfNode extends Node {
1908
1909
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
1909
1910
  content: data.content ? Token.from(data.content) : null,
1910
1911
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
1912
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
1911
1913
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
1912
1914
  subsequent: data.subsequent ? fromSerializedNode((data.subsequent)) : null,
1913
1915
  end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
@@ -1918,6 +1920,7 @@ class ERBIfNode extends Node {
1918
1920
  this.tag_opening = props.tag_opening;
1919
1921
  this.content = props.content;
1920
1922
  this.tag_closing = props.tag_closing;
1923
+ this.then_keyword = props.then_keyword;
1921
1924
  this.statements = props.statements;
1922
1925
  this.subsequent = props.subsequent;
1923
1926
  this.end_node = props.end_node;
@@ -1950,6 +1953,7 @@ class ERBIfNode extends Node {
1950
1953
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
1951
1954
  content: this.content ? this.content.toJSON() : null,
1952
1955
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
1956
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
1953
1957
  statements: this.statements.map(node => node.toJSON()),
1954
1958
  subsequent: this.subsequent ? this.subsequent.toJSON() : null,
1955
1959
  end_node: this.end_node ? this.end_node.toJSON() : null,
@@ -1962,6 +1966,7 @@ class ERBIfNode extends Node {
1962
1966
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
1963
1967
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
1964
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`;
1965
1970
  output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
1966
1971
  output += `├── subsequent: ${this.inspectNode(this.subsequent, "│ ")}`;
1967
1972
  output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
@@ -2043,6 +2048,7 @@ class ERBWhenNode extends Node {
2043
2048
  tag_opening;
2044
2049
  content;
2045
2050
  tag_closing;
2051
+ then_keyword;
2046
2052
  statements;
2047
2053
  static get type() {
2048
2054
  return "AST_ERB_WHEN_NODE";
@@ -2055,6 +2061,7 @@ class ERBWhenNode extends Node {
2055
2061
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2056
2062
  content: data.content ? Token.from(data.content) : null,
2057
2063
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2064
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2058
2065
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2059
2066
  });
2060
2067
  }
@@ -2063,6 +2070,7 @@ class ERBWhenNode extends Node {
2063
2070
  this.tag_opening = props.tag_opening;
2064
2071
  this.content = props.content;
2065
2072
  this.tag_closing = props.tag_closing;
2073
+ this.then_keyword = props.then_keyword;
2066
2074
  this.statements = props.statements;
2067
2075
  }
2068
2076
  accept(visitor) {
@@ -2089,6 +2097,7 @@ class ERBWhenNode extends Node {
2089
2097
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2090
2098
  content: this.content ? this.content.toJSON() : null,
2091
2099
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2100
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2092
2101
  statements: this.statements.map(node => node.toJSON()),
2093
2102
  };
2094
2103
  }
@@ -2099,6 +2108,7 @@ class ERBWhenNode extends Node {
2099
2108
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2100
2109
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2101
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`;
2102
2112
  output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
2103
2113
  return output;
2104
2114
  }
@@ -2717,6 +2727,7 @@ class ERBUnlessNode extends Node {
2717
2727
  tag_opening;
2718
2728
  content;
2719
2729
  tag_closing;
2730
+ then_keyword;
2720
2731
  statements;
2721
2732
  else_clause;
2722
2733
  end_node;
@@ -2731,6 +2742,7 @@ class ERBUnlessNode extends Node {
2731
2742
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2732
2743
  content: data.content ? Token.from(data.content) : null,
2733
2744
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2745
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2734
2746
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2735
2747
  else_clause: data.else_clause ? fromSerializedNode((data.else_clause)) : null,
2736
2748
  end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
@@ -2741,6 +2753,7 @@ class ERBUnlessNode extends Node {
2741
2753
  this.tag_opening = props.tag_opening;
2742
2754
  this.content = props.content;
2743
2755
  this.tag_closing = props.tag_closing;
2756
+ this.then_keyword = props.then_keyword;
2744
2757
  this.statements = props.statements;
2745
2758
  this.else_clause = props.else_clause;
2746
2759
  this.end_node = props.end_node;
@@ -2773,6 +2786,7 @@ class ERBUnlessNode extends Node {
2773
2786
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2774
2787
  content: this.content ? this.content.toJSON() : null,
2775
2788
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2789
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2776
2790
  statements: this.statements.map(node => node.toJSON()),
2777
2791
  else_clause: this.else_clause ? this.else_clause.toJSON() : null,
2778
2792
  end_node: this.end_node ? this.end_node.toJSON() : null,
@@ -2785,6 +2799,7 @@ class ERBUnlessNode extends Node {
2785
2799
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2786
2800
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2787
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`;
2788
2803
  output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
2789
2804
  output += `├── else_clause: ${this.inspectNode(this.else_clause, "│ ")}`;
2790
2805
  output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
@@ -2851,6 +2866,7 @@ class ERBInNode extends Node {
2851
2866
  tag_opening;
2852
2867
  content;
2853
2868
  tag_closing;
2869
+ then_keyword;
2854
2870
  statements;
2855
2871
  static get type() {
2856
2872
  return "AST_ERB_IN_NODE";
@@ -2863,6 +2879,7 @@ class ERBInNode extends Node {
2863
2879
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2864
2880
  content: data.content ? Token.from(data.content) : null,
2865
2881
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2882
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2866
2883
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2867
2884
  });
2868
2885
  }
@@ -2871,6 +2888,7 @@ class ERBInNode extends Node {
2871
2888
  this.tag_opening = props.tag_opening;
2872
2889
  this.content = props.content;
2873
2890
  this.tag_closing = props.tag_closing;
2891
+ this.then_keyword = props.then_keyword;
2874
2892
  this.statements = props.statements;
2875
2893
  }
2876
2894
  accept(visitor) {
@@ -2897,6 +2915,7 @@ class ERBInNode extends Node {
2897
2915
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2898
2916
  content: this.content ? this.content.toJSON() : null,
2899
2917
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2918
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2900
2919
  statements: this.statements.map(node => node.toJSON()),
2901
2920
  };
2902
2921
  }
@@ -2907,6 +2926,7 @@ class ERBInNode extends Node {
2907
2926
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2908
2927
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2909
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`;
2910
2930
  output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
2911
2931
  return output;
2912
2932
  }
@@ -3054,7 +3074,7 @@ class ParseResult extends Result {
3054
3074
  }
3055
3075
 
3056
3076
  // NOTE: This file is generated by the templates/template.rb script and should not
3057
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.7/templates/javascript/packages/core/src/node-type-guards.ts.erb
3077
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.9/templates/javascript/packages/core/src/node-type-guards.ts.erb
3058
3078
  /**
3059
3079
  * Type guard functions for AST nodes.
3060
3080
  * These functions provide type checking by combining both instanceof
@@ -3551,7 +3571,7 @@ function getNodesAfterPosition(nodes, position, inclusive = true) {
3551
3571
  }
3552
3572
 
3553
3573
  // NOTE: This file is generated by the templates/template.rb script and should not
3554
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.7/templates/javascript/packages/core/src/visitor.ts.erb
3574
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.9/templates/javascript/packages/core/src/visitor.ts.erb
3555
3575
  class Visitor {
3556
3576
  visit(node) {
3557
3577
  if (!node)
@@ -4542,6 +4562,11 @@ function isFrontmatter(node) {
4542
4562
  return content.startsWith("---") && /---\s*$/.test(content);
4543
4563
  }
4544
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;
4545
4570
  /**
4546
4571
  * Printer traverses the Herb AST using the Visitor pattern
4547
4572
  * and emits a formatted string with proper indentation, line breaks, and attribute wrapping.
@@ -4913,7 +4938,7 @@ class FormatPrinter extends Printer {
4913
4938
  return true;
4914
4939
  }
4915
4940
  wouldClassAttributeBeMultiline(content, indentLength) {
4916
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4941
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4917
4942
  const hasActualNewlines = /\r?\n/.test(content);
4918
4943
  if (hasActualNewlines && normalizedContent.length > 80) {
4919
4944
  const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
@@ -4951,12 +4976,12 @@ class FormatPrinter extends Printer {
4951
4976
  if (/\r?\n/.test(content)) {
4952
4977
  const name = attribute.name ? getCombinedAttributeName(attribute.name) : "";
4953
4978
  if (name === "class") {
4954
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4979
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4955
4980
  return normalizedContent.length > 80;
4956
4981
  }
4957
4982
  const lines = content.split(/\r?\n/);
4958
4983
  if (lines.length > 1) {
4959
- return lines.slice(1).some(line => /^\s+/.test(line));
4984
+ return lines.slice(1).some(line => /^[ \t\n\r]+/.test(line));
4960
4985
  }
4961
4986
  }
4962
4987
  }
@@ -4964,7 +4989,7 @@ class FormatPrinter extends Printer {
4964
4989
  });
4965
4990
  }
4966
4991
  formatClassAttribute(content, name, equals, open_quote, close_quote) {
4967
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4992
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4968
4993
  const hasActualNewlines = /\r?\n/.test(content);
4969
4994
  if (hasActualNewlines && normalizedContent.length > 80) {
4970
4995
  const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
@@ -4993,7 +5018,7 @@ class FormatPrinter extends Printer {
4993
5018
  }
4994
5019
  formatMultilineAttribute(content, name, open_quote, close_quote) {
4995
5020
  if (name === 'srcset' || name === 'sizes') {
4996
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
5021
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4997
5022
  return open_quote + normalizedContent + close_quote;
4998
5023
  }
4999
5024
  const lines = content.split('\n');
@@ -5183,7 +5208,7 @@ class FormatPrinter extends Printer {
5183
5208
  nodesToRender.forEach(child => {
5184
5209
  if (isNode(child, HTMLTextNode)) {
5185
5210
  if (hasTextFlow) {
5186
- const normalizedContent = child.content.replace(/\s+/g, ' ');
5211
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
5187
5212
  if (normalizedContent && normalizedContent !== ' ') {
5188
5213
  this.push(normalizedContent);
5189
5214
  }
@@ -5192,7 +5217,7 @@ class FormatPrinter extends Printer {
5192
5217
  }
5193
5218
  }
5194
5219
  else {
5195
- const normalizedContent = child.content.replace(/\s+/g, ' ');
5220
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
5196
5221
  if (shouldPreserveSpaces && normalizedContent) {
5197
5222
  this.push(normalizedContent);
5198
5223
  }
@@ -5217,8 +5242,8 @@ class FormatPrinter extends Printer {
5217
5242
  });
5218
5243
  const content = lines.join('');
5219
5244
  const inlineContent = shouldPreserveSpaces
5220
- ? (hasTextFlow ? content.replace(/\s+/g, ' ') : content)
5221
- : (hasTextFlow ? content.replace(/\s+/g, ' ').trim() : content.trim());
5245
+ ? (hasTextFlow ? content.replace(ASCII_WHITESPACE, ' ') : content)
5246
+ : (hasTextFlow ? content.replace(ASCII_WHITESPACE, ' ').trim() : content.trim());
5222
5247
  if (inlineContent) {
5223
5248
  this.pushToLastLine(inlineContent);
5224
5249
  }
@@ -5407,7 +5432,7 @@ class FormatPrinter extends Printer {
5407
5432
  }
5408
5433
  visitHTMLTextNode(node) {
5409
5434
  if (this.inlineMode) {
5410
- const normalizedContent = node.content.replace(/\s+/g, ' ').trim();
5435
+ const normalizedContent = node.content.replace(ASCII_WHITESPACE, ' ').trim();
5411
5436
  if (normalizedContent) {
5412
5437
  this.push(normalizedContent);
5413
5438
  }
@@ -5417,7 +5442,7 @@ class FormatPrinter extends Printer {
5417
5442
  if (!text)
5418
5443
  return;
5419
5444
  const wrapWidth = this.maxLineLength - this.indent.length;
5420
- const words = text.split(/\s+/);
5445
+ const words = text.split(/[ \t\n\r]+/);
5421
5446
  const lines = [];
5422
5447
  let line = "";
5423
5448
  for (const word of words) {
@@ -5926,7 +5951,7 @@ class FormatPrinter extends Printer {
5926
5951
  wrappedLines: []
5927
5952
  };
5928
5953
  }
5929
- const words = restText.split(/\s+/);
5954
+ const words = restText.split(/[ \t\n\r]+/);
5930
5955
  let toMerge = punctuation;
5931
5956
  let mergedWordCount = 0;
5932
5957
  for (const word of words) {
@@ -6076,7 +6101,7 @@ class FormatPrinter extends Printer {
6076
6101
  words.push({ word: unit.content, isHerbDisable: unit.isHerbDisable || false });
6077
6102
  }
6078
6103
  else {
6079
- const text = unit.content.replace(/\s+/g, ' ');
6104
+ const text = unit.content.replace(ASCII_WHITESPACE, ' ');
6080
6105
  const hasLeadingSpace = text.startsWith(' ');
6081
6106
  const hasTrailingSpace = text.endsWith(' ');
6082
6107
  const trimmedText = text.trim();
@@ -6122,7 +6147,7 @@ class FormatPrinter extends Printer {
6122
6147
  return false;
6123
6148
  const firstWord = words[0];
6124
6149
  const firstChar = firstWord[0];
6125
- if (/\s/.test(firstChar)) {
6150
+ if (' \t\n\r'.includes(firstChar)) {
6126
6151
  return false;
6127
6152
  }
6128
6153
  lastUnit.unit.content += firstWord;
@@ -6179,7 +6204,7 @@ class FormatPrinter extends Printer {
6179
6204
  if (hasWhitespaceBetween(children, lastProcessedIndex, currentIndex)) {
6180
6205
  return true;
6181
6206
  }
6182
- if (isNode(currentNode, HTMLTextNode) && /^\s/.test(currentNode.content)) {
6207
+ if (isNode(currentNode, HTMLTextNode) && /^[ \t\n\r]/.test(currentNode.content)) {
6183
6208
  return true;
6184
6209
  }
6185
6210
  return false;
@@ -6518,9 +6543,9 @@ class FormatPrinter extends Printer {
6518
6543
  const shouldPreserveSpaces = hasOnlyTextContent && tagName && isInlineElement(tagName);
6519
6544
  for (const child of children) {
6520
6545
  if (isNode(child, HTMLTextNode)) {
6521
- const normalizedContent = child.content.replace(/\s+/g, ' ');
6522
- const hasLeadingSpace = /^\s/.test(child.content);
6523
- 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);
6524
6549
  const trimmedContent = normalizedContent.trim();
6525
6550
  if (trimmedContent) {
6526
6551
  if (hasLeadingSpace && (result || shouldPreserveSpaces) && !result.endsWith(' ')) {
@@ -6627,7 +6652,7 @@ class FormatPrinter extends Printer {
6627
6652
  content += this.reconstructERBNode(child, true);
6628
6653
  }
6629
6654
  }
6630
- return content.replace(/\s+/g, ' ').trim();
6655
+ return content.replace(ASCII_WHITESPACE, ' ').trim();
6631
6656
  }
6632
6657
  }
6633
6658