@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/herb-format.js +213 -23
- package/dist/herb-format.js.map +1 -1
- package/dist/index.cjs +198 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +198 -11
- package/dist/index.esm.js.map +1 -1
- package/dist/types/printer.d.ts +14 -0
- package/package.json +2 -2
- package/src/cli.ts +11 -6
- package/src/printer.ts +240 -6
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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
|
/**
|