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