@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.esm.js CHANGED
@@ -253,7 +253,7 @@ class Token {
253
253
  }
254
254
 
255
255
  // NOTE: This file is generated by the templates/template.rb script and should not
256
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.7/templates/javascript/packages/core/src/errors.ts.erb
256
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.9/templates/javascript/packages/core/src/errors.ts.erb
257
257
  class HerbError {
258
258
  type;
259
259
  message;
@@ -818,7 +818,7 @@ function convertToUTF8(string) {
818
818
  }
819
819
 
820
820
  // NOTE: This file is generated by the templates/template.rb script and should not
821
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.7/templates/javascript/packages/core/src/nodes.ts.erb
821
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.9/templates/javascript/packages/core/src/nodes.ts.erb
822
822
  class Node {
823
823
  type;
824
824
  location;
@@ -1892,6 +1892,7 @@ class ERBIfNode extends Node {
1892
1892
  tag_opening;
1893
1893
  content;
1894
1894
  tag_closing;
1895
+ then_keyword;
1895
1896
  statements;
1896
1897
  subsequent;
1897
1898
  end_node;
@@ -1906,6 +1907,7 @@ class ERBIfNode extends Node {
1906
1907
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
1907
1908
  content: data.content ? Token.from(data.content) : null,
1908
1909
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
1910
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
1909
1911
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
1910
1912
  subsequent: data.subsequent ? fromSerializedNode((data.subsequent)) : null,
1911
1913
  end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
@@ -1916,6 +1918,7 @@ class ERBIfNode extends Node {
1916
1918
  this.tag_opening = props.tag_opening;
1917
1919
  this.content = props.content;
1918
1920
  this.tag_closing = props.tag_closing;
1921
+ this.then_keyword = props.then_keyword;
1919
1922
  this.statements = props.statements;
1920
1923
  this.subsequent = props.subsequent;
1921
1924
  this.end_node = props.end_node;
@@ -1948,6 +1951,7 @@ class ERBIfNode extends Node {
1948
1951
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
1949
1952
  content: this.content ? this.content.toJSON() : null,
1950
1953
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
1954
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
1951
1955
  statements: this.statements.map(node => node.toJSON()),
1952
1956
  subsequent: this.subsequent ? this.subsequent.toJSON() : null,
1953
1957
  end_node: this.end_node ? this.end_node.toJSON() : null,
@@ -1960,6 +1964,7 @@ class ERBIfNode extends Node {
1960
1964
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
1961
1965
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
1962
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`;
1963
1968
  output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
1964
1969
  output += `├── subsequent: ${this.inspectNode(this.subsequent, "│ ")}`;
1965
1970
  output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
@@ -2041,6 +2046,7 @@ class ERBWhenNode extends Node {
2041
2046
  tag_opening;
2042
2047
  content;
2043
2048
  tag_closing;
2049
+ then_keyword;
2044
2050
  statements;
2045
2051
  static get type() {
2046
2052
  return "AST_ERB_WHEN_NODE";
@@ -2053,6 +2059,7 @@ class ERBWhenNode extends Node {
2053
2059
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2054
2060
  content: data.content ? Token.from(data.content) : null,
2055
2061
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2062
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2056
2063
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2057
2064
  });
2058
2065
  }
@@ -2061,6 +2068,7 @@ class ERBWhenNode extends Node {
2061
2068
  this.tag_opening = props.tag_opening;
2062
2069
  this.content = props.content;
2063
2070
  this.tag_closing = props.tag_closing;
2071
+ this.then_keyword = props.then_keyword;
2064
2072
  this.statements = props.statements;
2065
2073
  }
2066
2074
  accept(visitor) {
@@ -2087,6 +2095,7 @@ class ERBWhenNode extends Node {
2087
2095
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2088
2096
  content: this.content ? this.content.toJSON() : null,
2089
2097
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2098
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2090
2099
  statements: this.statements.map(node => node.toJSON()),
2091
2100
  };
2092
2101
  }
@@ -2097,6 +2106,7 @@ class ERBWhenNode extends Node {
2097
2106
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2098
2107
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2099
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`;
2100
2110
  output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
2101
2111
  return output;
2102
2112
  }
@@ -2715,6 +2725,7 @@ class ERBUnlessNode extends Node {
2715
2725
  tag_opening;
2716
2726
  content;
2717
2727
  tag_closing;
2728
+ then_keyword;
2718
2729
  statements;
2719
2730
  else_clause;
2720
2731
  end_node;
@@ -2729,6 +2740,7 @@ class ERBUnlessNode extends Node {
2729
2740
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2730
2741
  content: data.content ? Token.from(data.content) : null,
2731
2742
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2743
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2732
2744
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2733
2745
  else_clause: data.else_clause ? fromSerializedNode((data.else_clause)) : null,
2734
2746
  end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
@@ -2739,6 +2751,7 @@ class ERBUnlessNode extends Node {
2739
2751
  this.tag_opening = props.tag_opening;
2740
2752
  this.content = props.content;
2741
2753
  this.tag_closing = props.tag_closing;
2754
+ this.then_keyword = props.then_keyword;
2742
2755
  this.statements = props.statements;
2743
2756
  this.else_clause = props.else_clause;
2744
2757
  this.end_node = props.end_node;
@@ -2771,6 +2784,7 @@ class ERBUnlessNode extends Node {
2771
2784
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2772
2785
  content: this.content ? this.content.toJSON() : null,
2773
2786
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2787
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2774
2788
  statements: this.statements.map(node => node.toJSON()),
2775
2789
  else_clause: this.else_clause ? this.else_clause.toJSON() : null,
2776
2790
  end_node: this.end_node ? this.end_node.toJSON() : null,
@@ -2783,6 +2797,7 @@ class ERBUnlessNode extends Node {
2783
2797
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2784
2798
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2785
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`;
2786
2801
  output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
2787
2802
  output += `├── else_clause: ${this.inspectNode(this.else_clause, "│ ")}`;
2788
2803
  output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
@@ -2849,6 +2864,7 @@ class ERBInNode extends Node {
2849
2864
  tag_opening;
2850
2865
  content;
2851
2866
  tag_closing;
2867
+ then_keyword;
2852
2868
  statements;
2853
2869
  static get type() {
2854
2870
  return "AST_ERB_IN_NODE";
@@ -2861,6 +2877,7 @@ class ERBInNode extends Node {
2861
2877
  tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
2862
2878
  content: data.content ? Token.from(data.content) : null,
2863
2879
  tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
2880
+ then_keyword: data.then_keyword ? Location.from(data.then_keyword) : null,
2864
2881
  statements: (data.statements || []).map(node => fromSerializedNode(node)),
2865
2882
  });
2866
2883
  }
@@ -2869,6 +2886,7 @@ class ERBInNode extends Node {
2869
2886
  this.tag_opening = props.tag_opening;
2870
2887
  this.content = props.content;
2871
2888
  this.tag_closing = props.tag_closing;
2889
+ this.then_keyword = props.then_keyword;
2872
2890
  this.statements = props.statements;
2873
2891
  }
2874
2892
  accept(visitor) {
@@ -2895,6 +2913,7 @@ class ERBInNode extends Node {
2895
2913
  tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
2896
2914
  content: this.content ? this.content.toJSON() : null,
2897
2915
  tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
2916
+ then_keyword: this.then_keyword ? this.then_keyword.toJSON() : null,
2898
2917
  statements: this.statements.map(node => node.toJSON()),
2899
2918
  };
2900
2919
  }
@@ -2905,6 +2924,7 @@ class ERBInNode extends Node {
2905
2924
  output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
2906
2925
  output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
2907
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`;
2908
2928
  output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
2909
2929
  return output;
2910
2930
  }
@@ -3052,7 +3072,7 @@ class ParseResult extends Result {
3052
3072
  }
3053
3073
 
3054
3074
  // NOTE: This file is generated by the templates/template.rb script and should not
3055
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.7/templates/javascript/packages/core/src/node-type-guards.ts.erb
3075
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.9/templates/javascript/packages/core/src/node-type-guards.ts.erb
3056
3076
  /**
3057
3077
  * Type guard functions for AST nodes.
3058
3078
  * These functions provide type checking by combining both instanceof
@@ -3549,7 +3569,7 @@ function getNodesAfterPosition(nodes, position, inclusive = true) {
3549
3569
  }
3550
3570
 
3551
3571
  // NOTE: This file is generated by the templates/template.rb script and should not
3552
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.7/templates/javascript/packages/core/src/visitor.ts.erb
3572
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.9/templates/javascript/packages/core/src/visitor.ts.erb
3553
3573
  class Visitor {
3554
3574
  visit(node) {
3555
3575
  if (!node)
@@ -4540,6 +4560,11 @@ function isFrontmatter(node) {
4540
4560
  return content.startsWith("---") && /---\s*$/.test(content);
4541
4561
  }
4542
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;
4543
4568
  /**
4544
4569
  * Printer traverses the Herb AST using the Visitor pattern
4545
4570
  * and emits a formatted string with proper indentation, line breaks, and attribute wrapping.
@@ -4911,7 +4936,7 @@ class FormatPrinter extends Printer {
4911
4936
  return true;
4912
4937
  }
4913
4938
  wouldClassAttributeBeMultiline(content, indentLength) {
4914
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4939
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4915
4940
  const hasActualNewlines = /\r?\n/.test(content);
4916
4941
  if (hasActualNewlines && normalizedContent.length > 80) {
4917
4942
  const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
@@ -4949,12 +4974,12 @@ class FormatPrinter extends Printer {
4949
4974
  if (/\r?\n/.test(content)) {
4950
4975
  const name = attribute.name ? getCombinedAttributeName(attribute.name) : "";
4951
4976
  if (name === "class") {
4952
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4977
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4953
4978
  return normalizedContent.length > 80;
4954
4979
  }
4955
4980
  const lines = content.split(/\r?\n/);
4956
4981
  if (lines.length > 1) {
4957
- return lines.slice(1).some(line => /^\s+/.test(line));
4982
+ return lines.slice(1).some(line => /^[ \t\n\r]+/.test(line));
4958
4983
  }
4959
4984
  }
4960
4985
  }
@@ -4962,7 +4987,7 @@ class FormatPrinter extends Printer {
4962
4987
  });
4963
4988
  }
4964
4989
  formatClassAttribute(content, name, equals, open_quote, close_quote) {
4965
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
4990
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4966
4991
  const hasActualNewlines = /\r?\n/.test(content);
4967
4992
  if (hasActualNewlines && normalizedContent.length > 80) {
4968
4993
  const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
@@ -4991,7 +5016,7 @@ class FormatPrinter extends Printer {
4991
5016
  }
4992
5017
  formatMultilineAttribute(content, name, open_quote, close_quote) {
4993
5018
  if (name === 'srcset' || name === 'sizes') {
4994
- const normalizedContent = content.replace(/\s+/g, ' ').trim();
5019
+ const normalizedContent = content.replace(ASCII_WHITESPACE, ' ').trim();
4995
5020
  return open_quote + normalizedContent + close_quote;
4996
5021
  }
4997
5022
  const lines = content.split('\n');
@@ -5181,7 +5206,7 @@ class FormatPrinter extends Printer {
5181
5206
  nodesToRender.forEach(child => {
5182
5207
  if (isNode(child, HTMLTextNode)) {
5183
5208
  if (hasTextFlow) {
5184
- const normalizedContent = child.content.replace(/\s+/g, ' ');
5209
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
5185
5210
  if (normalizedContent && normalizedContent !== ' ') {
5186
5211
  this.push(normalizedContent);
5187
5212
  }
@@ -5190,7 +5215,7 @@ class FormatPrinter extends Printer {
5190
5215
  }
5191
5216
  }
5192
5217
  else {
5193
- const normalizedContent = child.content.replace(/\s+/g, ' ');
5218
+ const normalizedContent = child.content.replace(ASCII_WHITESPACE, ' ');
5194
5219
  if (shouldPreserveSpaces && normalizedContent) {
5195
5220
  this.push(normalizedContent);
5196
5221
  }
@@ -5215,8 +5240,8 @@ class FormatPrinter extends Printer {
5215
5240
  });
5216
5241
  const content = lines.join('');
5217
5242
  const inlineContent = shouldPreserveSpaces
5218
- ? (hasTextFlow ? content.replace(/\s+/g, ' ') : content)
5219
- : (hasTextFlow ? content.replace(/\s+/g, ' ').trim() : content.trim());
5243
+ ? (hasTextFlow ? content.replace(ASCII_WHITESPACE, ' ') : content)
5244
+ : (hasTextFlow ? content.replace(ASCII_WHITESPACE, ' ').trim() : content.trim());
5220
5245
  if (inlineContent) {
5221
5246
  this.pushToLastLine(inlineContent);
5222
5247
  }
@@ -5405,7 +5430,7 @@ class FormatPrinter extends Printer {
5405
5430
  }
5406
5431
  visitHTMLTextNode(node) {
5407
5432
  if (this.inlineMode) {
5408
- const normalizedContent = node.content.replace(/\s+/g, ' ').trim();
5433
+ const normalizedContent = node.content.replace(ASCII_WHITESPACE, ' ').trim();
5409
5434
  if (normalizedContent) {
5410
5435
  this.push(normalizedContent);
5411
5436
  }
@@ -5415,7 +5440,7 @@ class FormatPrinter extends Printer {
5415
5440
  if (!text)
5416
5441
  return;
5417
5442
  const wrapWidth = this.maxLineLength - this.indent.length;
5418
- const words = text.split(/\s+/);
5443
+ const words = text.split(/[ \t\n\r]+/);
5419
5444
  const lines = [];
5420
5445
  let line = "";
5421
5446
  for (const word of words) {
@@ -5924,7 +5949,7 @@ class FormatPrinter extends Printer {
5924
5949
  wrappedLines: []
5925
5950
  };
5926
5951
  }
5927
- const words = restText.split(/\s+/);
5952
+ const words = restText.split(/[ \t\n\r]+/);
5928
5953
  let toMerge = punctuation;
5929
5954
  let mergedWordCount = 0;
5930
5955
  for (const word of words) {
@@ -6074,7 +6099,7 @@ class FormatPrinter extends Printer {
6074
6099
  words.push({ word: unit.content, isHerbDisable: unit.isHerbDisable || false });
6075
6100
  }
6076
6101
  else {
6077
- const text = unit.content.replace(/\s+/g, ' ');
6102
+ const text = unit.content.replace(ASCII_WHITESPACE, ' ');
6078
6103
  const hasLeadingSpace = text.startsWith(' ');
6079
6104
  const hasTrailingSpace = text.endsWith(' ');
6080
6105
  const trimmedText = text.trim();
@@ -6120,7 +6145,7 @@ class FormatPrinter extends Printer {
6120
6145
  return false;
6121
6146
  const firstWord = words[0];
6122
6147
  const firstChar = firstWord[0];
6123
- if (/\s/.test(firstChar)) {
6148
+ if (' \t\n\r'.includes(firstChar)) {
6124
6149
  return false;
6125
6150
  }
6126
6151
  lastUnit.unit.content += firstWord;
@@ -6177,7 +6202,7 @@ class FormatPrinter extends Printer {
6177
6202
  if (hasWhitespaceBetween(children, lastProcessedIndex, currentIndex)) {
6178
6203
  return true;
6179
6204
  }
6180
- if (isNode(currentNode, HTMLTextNode) && /^\s/.test(currentNode.content)) {
6205
+ if (isNode(currentNode, HTMLTextNode) && /^[ \t\n\r]/.test(currentNode.content)) {
6181
6206
  return true;
6182
6207
  }
6183
6208
  return false;
@@ -6516,9 +6541,9 @@ class FormatPrinter extends Printer {
6516
6541
  const shouldPreserveSpaces = hasOnlyTextContent && tagName && isInlineElement(tagName);
6517
6542
  for (const child of children) {
6518
6543
  if (isNode(child, HTMLTextNode)) {
6519
- const normalizedContent = child.content.replace(/\s+/g, ' ');
6520
- const hasLeadingSpace = /^\s/.test(child.content);
6521
- 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);
6522
6547
  const trimmedContent = normalizedContent.trim();
6523
6548
  if (trimmedContent) {
6524
6549
  if (hasLeadingSpace && (result || shouldPreserveSpaces) && !result.endsWith(' ')) {
@@ -6625,7 +6650,7 @@ class FormatPrinter extends Printer {
6625
6650
  content += this.reconstructERBNode(child, true);
6626
6651
  }
6627
6652
  }
6628
- return content.replace(/\s+/g, ' ').trim();
6653
+ return content.replace(ASCII_WHITESPACE, ' ').trim();
6629
6654
  }
6630
6655
  }
6631
6656