@herb-tools/formatter 0.4.1 → 0.4.2

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
@@ -131,7 +131,7 @@ class Token {
131
131
  }
132
132
 
133
133
  // NOTE: This file is generated by the templates/template.rb script and should not
134
- // be modified manually. See /Users/marcoroth/Development/herb-release-4/templates/javascript/packages/core/src/errors.ts.erb
134
+ // be modified manually. See /Users/marcoroth/Development/herb-release-6/templates/javascript/packages/core/src/errors.ts.erb
135
135
  class HerbError {
136
136
  type;
137
137
  message;
@@ -581,7 +581,7 @@ function convertToUTF8(string) {
581
581
  }
582
582
 
583
583
  // NOTE: This file is generated by the templates/template.rb script and should not
584
- // be modified manually. See /Users/marcoroth/Development/herb-release-4/templates/javascript/packages/core/src/nodes.ts.erb
584
+ // be modified manually. See /Users/marcoroth/Development/herb-release-6/templates/javascript/packages/core/src/nodes.ts.erb
585
585
  class Node {
586
586
  type;
587
587
  location;
@@ -2576,7 +2576,7 @@ function fromSerializedNode(node) {
2576
2576
  }
2577
2577
 
2578
2578
  // NOTE: This file is generated by the templates/template.rb script and should not
2579
- // be modified manually. See /Users/marcoroth/Development/herb-release-4/templates/javascript/packages/core/src/visitor.ts.erb
2579
+ // be modified manually. See /Users/marcoroth/Development/herb-release-6/templates/javascript/packages/core/src/visitor.ts.erb
2580
2580
  class Visitor {
2581
2581
  visit(node) {
2582
2582
  if (!node)
@@ -2692,6 +2692,7 @@ class Printer extends Visitor {
2692
2692
  lines = [];
2693
2693
  indentLevel = 0;
2694
2694
  inlineMode = false;
2695
+ isInComplexNesting = false;
2695
2696
  constructor(source, options) {
2696
2697
  super();
2697
2698
  this.source = source;
@@ -2705,6 +2706,7 @@ class Printer extends Visitor {
2705
2706
  const node = object;
2706
2707
  this.lines = [];
2707
2708
  this.indentLevel = indentLevel;
2709
+ this.isInComplexNesting = false; // Reset for each top-level element
2708
2710
  if (typeof node.accept === 'function') {
2709
2711
  node.accept(this);
2710
2712
  }
@@ -2777,6 +2779,28 @@ class Printer extends Visitor {
2777
2779
  }
2778
2780
  return;
2779
2781
  }
2782
+ if (children.length >= 1) {
2783
+ if (this.isInComplexNesting) {
2784
+ if (children.length === 1) {
2785
+ const child = children[0];
2786
+ if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
2787
+ const textContent = child.content.trim();
2788
+ const singleLine = `<${tagName}>${textContent}</${tagName}>`;
2789
+ if (!textContent.includes('\n') && (indent.length + singleLine.length) <= this.maxLineLength) {
2790
+ this.push(indent + singleLine);
2791
+ return;
2792
+ }
2793
+ }
2794
+ }
2795
+ }
2796
+ else {
2797
+ const inlineResult = this.tryRenderInline(children, tagName);
2798
+ if (inlineResult && (indent.length + inlineResult.length) <= this.maxLineLength) {
2799
+ this.push(indent + inlineResult);
2800
+ return;
2801
+ }
2802
+ }
2803
+ }
2780
2804
  this.push(indent + `<${tagName}>`);
2781
2805
  this.withIndent(() => {
2782
2806
  children.forEach(child => this.visit(child));
@@ -2808,13 +2832,18 @@ class Printer extends Visitor {
2808
2832
  }
2809
2833
  const inline = this.renderInlineOpen(tagName, attributes, isSelfClosing, inlineNodes, open.children);
2810
2834
  const singleAttribute = attributes[0];
2811
- const hasEmptyValue = singleAttribute &&
2835
+ singleAttribute &&
2812
2836
  (singleAttribute.value instanceof HTMLAttributeValueNode || singleAttribute.value?.type === 'AST_HTML_ATTRIBUTE_VALUE_NODE') &&
2813
2837
  singleAttribute.value?.children.length === 0;
2838
+ const hasERBControlFlow = inlineNodes.some(node => node instanceof ERBIfNode || node.type === 'AST_ERB_IF_NODE' ||
2839
+ node instanceof ERBUnlessNode || node.type === 'AST_ERB_UNLESS_NODE' ||
2840
+ node instanceof ERBBlockNode || node.type === 'AST_ERB_BLOCK_NODE' ||
2841
+ node instanceof ERBCaseNode || node.type === 'AST_ERB_CASE_NODE' ||
2842
+ node instanceof ERBWhileNode || node.type === 'AST_ERB_WHILE_NODE' ||
2843
+ node instanceof ERBForNode || node.type === 'AST_ERB_FOR_NODE');
2814
2844
  const shouldKeepInline = (attributes.length <= 3 &&
2815
- !hasEmptyValue &&
2816
2845
  inline.length + indent.length <= this.maxLineLength) ||
2817
- inlineNodes.length > 0;
2846
+ (inlineNodes.length > 0 && !hasERBControlFlow);
2818
2847
  if (shouldKeepInline) {
2819
2848
  if (children.length === 0) {
2820
2849
  if (isSelfClosing) {
@@ -2842,7 +2871,36 @@ class Printer extends Visitor {
2842
2871
  }
2843
2872
  return;
2844
2873
  }
2845
- if (inlineNodes.length > 0) {
2874
+ if (inlineNodes.length > 0 && hasERBControlFlow) {
2875
+ this.push(indent + `<${tagName}`);
2876
+ this.withIndent(() => {
2877
+ open.children.forEach(child => {
2878
+ if (child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE') {
2879
+ this.push(this.indent() + this.renderAttribute(child));
2880
+ }
2881
+ else if (!(child instanceof WhitespaceNode || child.type === 'AST_WHITESPACE_NODE')) {
2882
+ this.visit(child);
2883
+ }
2884
+ });
2885
+ });
2886
+ if (isSelfClosing) {
2887
+ this.push(indent + "/>");
2888
+ }
2889
+ else if (node.is_void) {
2890
+ this.push(indent + ">");
2891
+ }
2892
+ else if (children.length === 0) {
2893
+ this.push(indent + ">" + `</${tagName}>`);
2894
+ }
2895
+ else {
2896
+ this.push(indent + ">");
2897
+ this.withIndent(() => {
2898
+ children.forEach(child => this.visit(child));
2899
+ });
2900
+ this.push(indent + `</${tagName}>`);
2901
+ }
2902
+ }
2903
+ else if (inlineNodes.length > 0) {
2846
2904
  this.push(indent + this.renderInlineOpen(tagName, attributes, isSelfClosing, inlineNodes, open.children));
2847
2905
  if (!isSelfClosing && !node.is_void && children.length > 0) {
2848
2906
  this.withIndent(() => {
@@ -2904,11 +2962,10 @@ class Printer extends Visitor {
2904
2962
  const attributes = node.attributes.filter((attribute) => attribute instanceof HTMLAttributeNode || attribute.type === 'AST_HTML_ATTRIBUTE_NODE');
2905
2963
  const inline = this.renderInlineOpen(tagName, attributes, true);
2906
2964
  const singleAttribute = attributes[0];
2907
- const hasEmptyValue = singleAttribute &&
2965
+ singleAttribute &&
2908
2966
  (singleAttribute.value instanceof HTMLAttributeValueNode || singleAttribute.value?.type === 'AST_HTML_ATTRIBUTE_VALUE_NODE') &&
2909
2967
  singleAttribute.value?.children.length === 0;
2910
2968
  const shouldKeepInline = attributes.length <= 3 &&
2911
- !hasEmptyValue &&
2912
2969
  inline.length + indent.length <= this.maxLineLength;
2913
2970
  if (shouldKeepInline) {
2914
2971
  this.push(indent + inline);
@@ -3086,14 +3143,26 @@ class Printer extends Visitor {
3086
3143
  const content = node.content?.value ?? "";
3087
3144
  const close = node.tag_closing?.value ?? "";
3088
3145
  this.lines.push(open + content + close);
3089
- node.statements.forEach(child => {
3146
+ if (node.statements.length > 0) {
3147
+ this.lines.push(" ");
3148
+ }
3149
+ node.statements.forEach((child, index) => {
3090
3150
  if (child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE') {
3091
- this.lines.push(" " + this.renderAttribute(child) + " ");
3151
+ this.lines.push(this.renderAttribute(child));
3092
3152
  }
3093
3153
  else {
3094
3154
  this.visit(child);
3095
3155
  }
3156
+ if (index < node.statements.length - 1) {
3157
+ this.lines.push(" ");
3158
+ }
3096
3159
  });
3160
+ if (node.statements.length > 0) {
3161
+ this.lines.push(" ");
3162
+ }
3163
+ if (node.subsequent) {
3164
+ this.visit(node.subsequent);
3165
+ }
3097
3166
  if (node.end_node) {
3098
3167
  const endNode = node.end_node;
3099
3168
  const endOpen = endNode.tag_opening?.value ?? "";
@@ -3264,6 +3333,124 @@ class Printer extends Visitor {
3264
3333
  }
3265
3334
  return name + equals + value;
3266
3335
  }
3336
+ /**
3337
+ * Try to render children inline if they are simple enough.
3338
+ * Returns the inline string if possible, null otherwise.
3339
+ */
3340
+ tryRenderInline(children, tagName, depth = 0) {
3341
+ if (children.length > 10) {
3342
+ return null;
3343
+ }
3344
+ const maxNestingDepth = this.getMaxNestingDepth(children, 0);
3345
+ if (maxNestingDepth > 1) {
3346
+ this.isInComplexNesting = true;
3347
+ return null;
3348
+ }
3349
+ for (const child of children) {
3350
+ if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
3351
+ const textContent = child.content;
3352
+ if (textContent.includes('\n')) {
3353
+ return null;
3354
+ }
3355
+ }
3356
+ else if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
3357
+ const element = child;
3358
+ const openTag = element.open_tag;
3359
+ const attributes = openTag.children.filter((child) => child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE');
3360
+ if (attributes.length > 0) {
3361
+ return null;
3362
+ }
3363
+ }
3364
+ else if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') ;
3365
+ else {
3366
+ return null;
3367
+ }
3368
+ }
3369
+ const oldLines = this.lines;
3370
+ const oldInlineMode = this.inlineMode;
3371
+ try {
3372
+ this.lines = [];
3373
+ this.inlineMode = true;
3374
+ let content = '';
3375
+ for (const child of children) {
3376
+ if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
3377
+ content += child.content;
3378
+ }
3379
+ else if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
3380
+ const element = child;
3381
+ const openTag = element.open_tag;
3382
+ const childTagName = openTag?.tag_name?.value || '';
3383
+ const attributes = openTag.children.filter((child) => child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE');
3384
+ const attributesString = attributes.length > 0
3385
+ ? ' ' + attributes.map(attr => this.renderAttribute(attr)).join(' ')
3386
+ : '';
3387
+ const elementContent = this.renderElementInline(element);
3388
+ content += `<${childTagName}${attributesString}>${elementContent}</${childTagName}>`;
3389
+ }
3390
+ else if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') {
3391
+ const erbNode = child;
3392
+ const open = erbNode.tag_opening?.value ?? "";
3393
+ const erbContent = erbNode.content?.value ?? "";
3394
+ const close = erbNode.tag_closing?.value ?? "";
3395
+ content += `${open} ${erbContent.trim()} ${close}`;
3396
+ }
3397
+ }
3398
+ content = content.replace(/\s+/g, ' ').trim();
3399
+ return `<${tagName}>${content}</${tagName}>`;
3400
+ }
3401
+ finally {
3402
+ this.lines = oldLines;
3403
+ this.inlineMode = oldInlineMode;
3404
+ }
3405
+ }
3406
+ /**
3407
+ * Calculate the maximum nesting depth in a subtree of nodes.
3408
+ */
3409
+ getMaxNestingDepth(children, currentDepth) {
3410
+ let maxDepth = currentDepth;
3411
+ for (const child of children) {
3412
+ if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
3413
+ const element = child;
3414
+ const elementChildren = element.body.filter(child => !(child instanceof WhitespaceNode || child.type === 'AST_WHITESPACE_NODE') &&
3415
+ !((child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') && child?.content.trim() === ""));
3416
+ const childDepth = this.getMaxNestingDepth(elementChildren, currentDepth + 1);
3417
+ maxDepth = Math.max(maxDepth, childDepth);
3418
+ }
3419
+ }
3420
+ return maxDepth;
3421
+ }
3422
+ /**
3423
+ * Render an HTML element's content inline (without the wrapping tags).
3424
+ */
3425
+ renderElementInline(element) {
3426
+ const children = element.body.filter(child => !(child instanceof WhitespaceNode || child.type === 'AST_WHITESPACE_NODE') &&
3427
+ !((child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') && child?.content.trim() === ""));
3428
+ let content = '';
3429
+ for (const child of children) {
3430
+ if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
3431
+ content += child.content;
3432
+ }
3433
+ else if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
3434
+ const childElement = child;
3435
+ const openTag = childElement.open_tag;
3436
+ const childTagName = openTag?.tag_name?.value || '';
3437
+ const attributes = openTag.children.filter((child) => child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE');
3438
+ const attributesString = attributes.length > 0
3439
+ ? ' ' + attributes.map(attr => this.renderAttribute(attr)).join(' ')
3440
+ : '';
3441
+ const childContent = this.renderElementInline(childElement);
3442
+ content += `<${childTagName}${attributesString}>${childContent}</${childTagName}>`;
3443
+ }
3444
+ else if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') {
3445
+ const erbNode = child;
3446
+ const open = erbNode.tag_opening?.value ?? "";
3447
+ const erbContent = erbNode.content?.value ?? "";
3448
+ const close = erbNode.tag_closing?.value ?? "";
3449
+ content += `${open} ${erbContent.trim()} ${close}`;
3450
+ }
3451
+ }
3452
+ return content.replace(/\s+/g, ' ').trim();
3453
+ }
3267
3454
  }
3268
3455
 
3269
3456
  /**