@herb-tools/formatter 0.4.2 → 0.5.0
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/README.md +113 -15
- package/dist/herb-format.js +784 -179
- package/dist/herb-format.js.map +1 -1
- package/dist/index.cjs +776 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +776 -171
- package/dist/index.esm.js.map +1 -1
- package/dist/types/printer.d.ts +64 -0
- package/package.json +3 -2
- package/src/cli.ts +2 -2
- package/src/printer.ts +1006 -189
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-0.5.0/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-0.5.0/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-0.5.0/templates/javascript/packages/core/src/visitor.ts.erb
|
|
2580
2580
|
class Visitor {
|
|
2581
2581
|
visit(node) {
|
|
2582
2582
|
if (!node)
|
|
@@ -2681,6 +2681,11 @@ class Visitor {
|
|
|
2681
2681
|
}
|
|
2682
2682
|
}
|
|
2683
2683
|
|
|
2684
|
+
// TODO: we can probably expand this list with more tags/attributes
|
|
2685
|
+
const FORMATTABLE_ATTRIBUTES = {
|
|
2686
|
+
'*': ['class'],
|
|
2687
|
+
'img': ['srcset', 'sizes']
|
|
2688
|
+
};
|
|
2684
2689
|
/**
|
|
2685
2690
|
* Printer traverses the Herb AST using the Visitor pattern
|
|
2686
2691
|
* and emits a formatted string with proper indentation, line breaks, and attribute wrapping.
|
|
@@ -2693,6 +2698,13 @@ class Printer extends Visitor {
|
|
|
2693
2698
|
indentLevel = 0;
|
|
2694
2699
|
inlineMode = false;
|
|
2695
2700
|
isInComplexNesting = false;
|
|
2701
|
+
currentTagName = "";
|
|
2702
|
+
static INLINE_ELEMENTS = new Set([
|
|
2703
|
+
'a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'cite', 'code',
|
|
2704
|
+
'dfn', 'em', 'i', 'img', 'kbd', 'label', 'map', 'object', 'q',
|
|
2705
|
+
'samp', 'small', 'span', 'strong', 'sub', 'sup',
|
|
2706
|
+
'tt', 'var', 'del', 'ins', 'mark', 's', 'u', 'time', 'wbr'
|
|
2707
|
+
]);
|
|
2696
2708
|
constructor(source, options) {
|
|
2697
2709
|
super();
|
|
2698
2710
|
this.source = source;
|
|
@@ -2713,7 +2725,7 @@ class Printer extends Visitor {
|
|
|
2713
2725
|
else {
|
|
2714
2726
|
this.visit(node);
|
|
2715
2727
|
}
|
|
2716
|
-
return this.lines.
|
|
2728
|
+
return this.lines.join("\n");
|
|
2717
2729
|
}
|
|
2718
2730
|
push(line) {
|
|
2719
2731
|
this.lines.push(line);
|
|
@@ -2728,38 +2740,259 @@ class Printer extends Visitor {
|
|
|
2728
2740
|
return " ".repeat(this.indentLevel * this.indentWidth);
|
|
2729
2741
|
}
|
|
2730
2742
|
/**
|
|
2731
|
-
*
|
|
2743
|
+
* Format ERB content with proper spacing around the inner content.
|
|
2744
|
+
* Returns empty string if content is empty, otherwise wraps content with single spaces.
|
|
2732
2745
|
*/
|
|
2733
|
-
|
|
2746
|
+
formatERBContent(content) {
|
|
2747
|
+
return content.trim() ? ` ${content.trim()} ` : "";
|
|
2748
|
+
}
|
|
2749
|
+
/**
|
|
2750
|
+
* Check if a node is an ERB control flow node (if, unless, block, case, while, for)
|
|
2751
|
+
*/
|
|
2752
|
+
isERBControlFlow(node) {
|
|
2753
|
+
return node instanceof ERBIfNode || node.type === 'AST_ERB_IF_NODE' ||
|
|
2754
|
+
node instanceof ERBUnlessNode || node.type === 'AST_ERB_UNLESS_NODE' ||
|
|
2755
|
+
node instanceof ERBBlockNode || node.type === 'AST_ERB_BLOCK_NODE' ||
|
|
2756
|
+
node instanceof ERBCaseNode || node.type === 'AST_ERB_CASE_NODE' ||
|
|
2757
|
+
node instanceof ERBCaseMatchNode || node.type === 'AST_ERB_CASE_MATCH_NODE' ||
|
|
2758
|
+
node instanceof ERBWhileNode || node.type === 'AST_ERB_WHILE_NODE' ||
|
|
2759
|
+
node instanceof ERBForNode || node.type === 'AST_ERB_FOR_NODE';
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Count total attributes including those inside ERB conditionals
|
|
2763
|
+
*/
|
|
2764
|
+
getTotalAttributeCount(attributes, inlineNodes = []) {
|
|
2765
|
+
let totalAttributeCount = attributes.length;
|
|
2766
|
+
inlineNodes.forEach(node => {
|
|
2767
|
+
if (this.isERBControlFlow(node)) {
|
|
2768
|
+
const erbNode = node;
|
|
2769
|
+
if (erbNode.statements) {
|
|
2770
|
+
totalAttributeCount += erbNode.statements.length;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
});
|
|
2774
|
+
return totalAttributeCount;
|
|
2775
|
+
}
|
|
2776
|
+
/**
|
|
2777
|
+
* Extract HTML attributes from a list of nodes
|
|
2778
|
+
*/
|
|
2779
|
+
extractAttributes(nodes) {
|
|
2780
|
+
return nodes.filter((child) => child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE');
|
|
2781
|
+
}
|
|
2782
|
+
/**
|
|
2783
|
+
* Extract inline nodes (non-attribute, non-whitespace) from a list of nodes
|
|
2784
|
+
*/
|
|
2785
|
+
extractInlineNodes(nodes) {
|
|
2786
|
+
return nodes.filter(child => !(child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE') &&
|
|
2787
|
+
!(child instanceof WhitespaceNode || child.type === 'AST_WHITESPACE_NODE'));
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Render attributes as a space-separated string
|
|
2791
|
+
*/
|
|
2792
|
+
renderAttributesString(attributes) {
|
|
2793
|
+
if (attributes.length === 0)
|
|
2794
|
+
return "";
|
|
2795
|
+
return ` ${attributes.map(attr => this.renderAttribute(attr)).join(" ")}`;
|
|
2796
|
+
}
|
|
2797
|
+
/**
|
|
2798
|
+
* Determine if a tag should be rendered inline based on attribute count and other factors
|
|
2799
|
+
*/
|
|
2800
|
+
shouldRenderInline(totalAttributeCount, inlineLength, indentLength, maxLineLength = this.maxLineLength, hasComplexERB = false, _nestingDepth = 0, _inlineNodesLength = 0, hasMultilineAttributes = false) {
|
|
2801
|
+
if (hasComplexERB || hasMultilineAttributes)
|
|
2802
|
+
return false;
|
|
2803
|
+
if (totalAttributeCount === 0) {
|
|
2804
|
+
return inlineLength + indentLength <= maxLineLength;
|
|
2805
|
+
}
|
|
2806
|
+
if (totalAttributeCount > 3 || inlineLength + indentLength > maxLineLength) {
|
|
2807
|
+
return false;
|
|
2808
|
+
}
|
|
2809
|
+
return true;
|
|
2810
|
+
}
|
|
2811
|
+
hasMultilineAttributes(attributes) {
|
|
2812
|
+
return attributes.some(attribute => {
|
|
2813
|
+
if (attribute.value && (attribute.value instanceof HTMLAttributeValueNode || attribute.value?.type === 'AST_HTML_ATTRIBUTE_VALUE_NODE')) {
|
|
2814
|
+
const attributeValue = attribute.value;
|
|
2815
|
+
const content = attributeValue.children.map((child) => {
|
|
2816
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE' || child instanceof LiteralNode || child.type === 'AST_LITERAL_NODE') {
|
|
2817
|
+
return child.content;
|
|
2818
|
+
}
|
|
2819
|
+
else if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') {
|
|
2820
|
+
const erbAttribute = child;
|
|
2821
|
+
return erbAttribute.tag_opening.value + erbAttribute.content.value + erbAttribute.tag_closing.value;
|
|
2822
|
+
}
|
|
2823
|
+
return "";
|
|
2824
|
+
}).join("");
|
|
2825
|
+
if (/\r?\n/.test(content)) {
|
|
2826
|
+
const name = attribute.name.name.value ?? "";
|
|
2827
|
+
if (name === 'class') {
|
|
2828
|
+
const normalizedContent = content.replace(/\s+/g, ' ').trim();
|
|
2829
|
+
return normalizedContent.length > 80;
|
|
2830
|
+
}
|
|
2831
|
+
const lines = content.split(/\r?\n/);
|
|
2832
|
+
if (lines.length > 1) {
|
|
2833
|
+
return lines.slice(1).some(line => /^\s+/.test(line));
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
return false;
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
formatClassAttribute(content, name, equals, open_quote, close_quote) {
|
|
2841
|
+
const normalizedContent = content.replace(/\s+/g, ' ').trim();
|
|
2842
|
+
const hasActualNewlines = /\r?\n/.test(content);
|
|
2843
|
+
if (hasActualNewlines && normalizedContent.length > 80) {
|
|
2844
|
+
const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
|
|
2845
|
+
if (lines.length > 1) {
|
|
2846
|
+
return open_quote + this.formatMultilineAttributeValue(lines) + close_quote;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
const currentIndent = this.indentLevel * this.indentWidth;
|
|
2850
|
+
const attributeLine = `${name}${equals}${open_quote}${normalizedContent}${close_quote}`;
|
|
2851
|
+
if (currentIndent + attributeLine.length > this.maxLineLength && normalizedContent.length > 60) {
|
|
2852
|
+
const classes = normalizedContent.split(' ');
|
|
2853
|
+
const lines = this.breakTokensIntoLines(classes, currentIndent);
|
|
2854
|
+
if (lines.length > 1) {
|
|
2855
|
+
return open_quote + this.formatMultilineAttributeValue(lines) + close_quote;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
return open_quote + normalizedContent + close_quote;
|
|
2859
|
+
}
|
|
2860
|
+
isFormattableAttribute(attributeName, tagName) {
|
|
2861
|
+
const globalFormattable = FORMATTABLE_ATTRIBUTES['*'] || [];
|
|
2862
|
+
const tagSpecificFormattable = FORMATTABLE_ATTRIBUTES[tagName.toLowerCase()] || [];
|
|
2863
|
+
return globalFormattable.includes(attributeName) || tagSpecificFormattable.includes(attributeName);
|
|
2864
|
+
}
|
|
2865
|
+
formatMultilineAttribute(content, name, equals, open_quote, close_quote) {
|
|
2866
|
+
if (name === 'srcset' || name === 'sizes') {
|
|
2867
|
+
const normalizedContent = content.replace(/\s+/g, ' ').trim();
|
|
2868
|
+
return open_quote + normalizedContent + close_quote;
|
|
2869
|
+
}
|
|
2870
|
+
const lines = content.split('\n');
|
|
2871
|
+
if (lines.length <= 1) {
|
|
2872
|
+
return open_quote + content + close_quote;
|
|
2873
|
+
}
|
|
2874
|
+
const formattedContent = this.formatMultilineAttributeValue(lines);
|
|
2875
|
+
return open_quote + formattedContent + close_quote;
|
|
2876
|
+
}
|
|
2877
|
+
formatMultilineAttributeValue(lines) {
|
|
2878
|
+
const indent = " ".repeat((this.indentLevel + 1) * this.indentWidth);
|
|
2879
|
+
const closeIndent = " ".repeat(this.indentLevel * this.indentWidth);
|
|
2880
|
+
return "\n" + lines.map(line => indent + line).join("\n") + "\n" + closeIndent;
|
|
2881
|
+
}
|
|
2882
|
+
breakTokensIntoLines(tokens, currentIndent, separator = ' ') {
|
|
2883
|
+
const lines = [];
|
|
2884
|
+
let currentLine = '';
|
|
2885
|
+
for (const token of tokens) {
|
|
2886
|
+
const testLine = currentLine ? currentLine + separator + token : token;
|
|
2887
|
+
if (testLine.length > (this.maxLineLength - currentIndent - 6)) {
|
|
2888
|
+
if (currentLine) {
|
|
2889
|
+
lines.push(currentLine);
|
|
2890
|
+
currentLine = token;
|
|
2891
|
+
}
|
|
2892
|
+
else {
|
|
2893
|
+
lines.push(token);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
else {
|
|
2897
|
+
currentLine = testLine;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
if (currentLine)
|
|
2901
|
+
lines.push(currentLine);
|
|
2902
|
+
return lines;
|
|
2903
|
+
}
|
|
2904
|
+
/**
|
|
2905
|
+
* Render multiline attributes for a tag
|
|
2906
|
+
*/
|
|
2907
|
+
renderMultilineAttributes(tagName, _attributes, _inlineNodes = [], allChildren = [], isSelfClosing = false, isVoid = false, hasBodyContent = false) {
|
|
2734
2908
|
const indent = this.indent();
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2909
|
+
this.push(indent + `<${tagName}`);
|
|
2910
|
+
this.withIndent(() => {
|
|
2911
|
+
allChildren.forEach(child => {
|
|
2912
|
+
if (child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE') {
|
|
2913
|
+
this.push(this.indent() + this.renderAttribute(child));
|
|
2914
|
+
}
|
|
2915
|
+
else if (!(child instanceof WhitespaceNode || child.type === 'AST_WHITESPACE_NODE')) {
|
|
2916
|
+
this.visit(child);
|
|
2917
|
+
}
|
|
2918
|
+
});
|
|
2919
|
+
});
|
|
2920
|
+
if (isSelfClosing) {
|
|
2921
|
+
this.push(indent + "/>");
|
|
2922
|
+
}
|
|
2923
|
+
else if (isVoid) {
|
|
2924
|
+
this.push(indent + ">");
|
|
2925
|
+
}
|
|
2926
|
+
else if (!hasBodyContent) {
|
|
2927
|
+
this.push(indent + `></${tagName}>`);
|
|
2743
2928
|
}
|
|
2744
2929
|
else {
|
|
2745
|
-
|
|
2746
|
-
inner = txt.trim() ? ` ${txt.trim()} ` : "";
|
|
2930
|
+
this.push(indent + ">");
|
|
2747
2931
|
}
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* Print an ERB tag (<% %> or <%= %>) with single spaces around inner content.
|
|
2935
|
+
*/
|
|
2936
|
+
printERBNode(node) {
|
|
2937
|
+
const indent = this.inlineMode ? "" : this.indent();
|
|
2938
|
+
const open = node.tag_opening?.value ?? "";
|
|
2939
|
+
const close = node.tag_closing?.value ?? "";
|
|
2940
|
+
const content = node.content?.value ?? "";
|
|
2941
|
+
const inner = this.formatERBContent(content);
|
|
2748
2942
|
this.push(indent + open + inner + close);
|
|
2749
2943
|
}
|
|
2750
2944
|
// --- Visitor methods ---
|
|
2751
2945
|
visitDocumentNode(node) {
|
|
2752
|
-
|
|
2946
|
+
let lastWasMeaningful = false;
|
|
2947
|
+
let hasHandledSpacing = false;
|
|
2948
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
2949
|
+
const child = node.children[i];
|
|
2950
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
2951
|
+
const textNode = child;
|
|
2952
|
+
const isWhitespaceOnly = textNode.content.trim() === "";
|
|
2953
|
+
if (isWhitespaceOnly) {
|
|
2954
|
+
const hasPrevNonWhitespace = i > 0 && this.isNonWhitespaceNode(node.children[i - 1]);
|
|
2955
|
+
const hasNextNonWhitespace = i < node.children.length - 1 && this.isNonWhitespaceNode(node.children[i + 1]);
|
|
2956
|
+
const hasMultipleNewlines = textNode.content.includes('\n\n');
|
|
2957
|
+
if (hasPrevNonWhitespace && hasNextNonWhitespace && hasMultipleNewlines) {
|
|
2958
|
+
this.push("");
|
|
2959
|
+
hasHandledSpacing = true;
|
|
2960
|
+
}
|
|
2961
|
+
continue;
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
if (this.isNonWhitespaceNode(child) && lastWasMeaningful && !hasHandledSpacing) {
|
|
2965
|
+
this.push("");
|
|
2966
|
+
}
|
|
2967
|
+
this.visit(child);
|
|
2968
|
+
if (this.isNonWhitespaceNode(child)) {
|
|
2969
|
+
lastWasMeaningful = true;
|
|
2970
|
+
hasHandledSpacing = false;
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2753
2973
|
}
|
|
2754
2974
|
visitHTMLElementNode(node) {
|
|
2755
2975
|
const open = node.open_tag;
|
|
2756
2976
|
const tagName = open.tag_name?.value ?? "";
|
|
2757
2977
|
const indent = this.indent();
|
|
2758
|
-
|
|
2759
|
-
const
|
|
2760
|
-
|
|
2761
|
-
const
|
|
2762
|
-
|
|
2978
|
+
this.currentTagName = tagName;
|
|
2979
|
+
const attributes = this.extractAttributes(open.children);
|
|
2980
|
+
const inlineNodes = this.extractInlineNodes(open.children);
|
|
2981
|
+
const hasTextFlow = this.isInTextFlowContext(null, node.body);
|
|
2982
|
+
const children = node.body.filter(child => {
|
|
2983
|
+
if (child instanceof WhitespaceNode || child.type === 'AST_WHITESPACE_NODE') {
|
|
2984
|
+
return false;
|
|
2985
|
+
}
|
|
2986
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
2987
|
+
const content = child.content;
|
|
2988
|
+
if (hasTextFlow && content === " ") {
|
|
2989
|
+
return true;
|
|
2990
|
+
}
|
|
2991
|
+
return content.trim() !== "";
|
|
2992
|
+
}
|
|
2993
|
+
return true;
|
|
2994
|
+
});
|
|
2995
|
+
const isInlineElement = this.isInlineElement(tagName);
|
|
2763
2996
|
const hasClosing = open.tag_closing?.value === ">" || open.tag_closing?.value === "/>";
|
|
2764
2997
|
const isSelfClosing = open.tag_closing?.value === "/>";
|
|
2765
2998
|
if (!hasClosing) {
|
|
@@ -2794,16 +3027,53 @@ class Printer extends Visitor {
|
|
|
2794
3027
|
}
|
|
2795
3028
|
}
|
|
2796
3029
|
else {
|
|
2797
|
-
const inlineResult = this.tryRenderInline(children, tagName);
|
|
3030
|
+
const inlineResult = this.tryRenderInline(children, tagName, 0, false, hasTextFlow);
|
|
2798
3031
|
if (inlineResult && (indent.length + inlineResult.length) <= this.maxLineLength) {
|
|
2799
3032
|
this.push(indent + inlineResult);
|
|
2800
3033
|
return;
|
|
2801
3034
|
}
|
|
3035
|
+
if (hasTextFlow) {
|
|
3036
|
+
const hasAnyNewlines = children.some(child => {
|
|
3037
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
3038
|
+
return child.content.includes('\n');
|
|
3039
|
+
}
|
|
3040
|
+
return false;
|
|
3041
|
+
});
|
|
3042
|
+
if (!hasAnyNewlines) {
|
|
3043
|
+
const fullInlineResult = this.tryRenderInlineFull(node, tagName, attributes, children);
|
|
3044
|
+
if (fullInlineResult) {
|
|
3045
|
+
const totalLength = indent.length + fullInlineResult.length;
|
|
3046
|
+
const maxNesting = this.getMaxNestingDepth(children, 0);
|
|
3047
|
+
const maxInlineLength = maxNesting <= 1 ? this.maxLineLength : 60;
|
|
3048
|
+
if (totalLength <= maxInlineLength) {
|
|
3049
|
+
this.push(indent + fullInlineResult);
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
if (hasTextFlow) {
|
|
3058
|
+
const fullInlineResult = this.tryRenderInlineFull(node, tagName, [], children);
|
|
3059
|
+
if (fullInlineResult) {
|
|
3060
|
+
const totalLength = indent.length + fullInlineResult.length;
|
|
3061
|
+
const maxNesting = this.getMaxNestingDepth(children, 0);
|
|
3062
|
+
const maxInlineLength = maxNesting <= 1 ? this.maxLineLength : 60;
|
|
3063
|
+
if (totalLength <= maxInlineLength) {
|
|
3064
|
+
this.push(indent + fullInlineResult);
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
2802
3067
|
}
|
|
2803
3068
|
}
|
|
2804
3069
|
this.push(indent + `<${tagName}>`);
|
|
2805
3070
|
this.withIndent(() => {
|
|
2806
|
-
|
|
3071
|
+
if (hasTextFlow) {
|
|
3072
|
+
this.visitTextFlowChildren(children);
|
|
3073
|
+
}
|
|
3074
|
+
else {
|
|
3075
|
+
children.forEach(child => this.visit(child));
|
|
3076
|
+
}
|
|
2807
3077
|
});
|
|
2808
3078
|
if (!node.is_void && !isSelfClosing) {
|
|
2809
3079
|
this.push(indent + `</${tagName}>`);
|
|
@@ -2830,20 +3100,24 @@ class Printer extends Visitor {
|
|
|
2830
3100
|
}
|
|
2831
3101
|
return;
|
|
2832
3102
|
}
|
|
2833
|
-
const
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
(
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
3103
|
+
const hasERBControlFlow = inlineNodes.some(node => this.isERBControlFlow(node)) ||
|
|
3104
|
+
open.children.some(node => this.isERBControlFlow(node));
|
|
3105
|
+
const hasComplexERB = hasERBControlFlow && inlineNodes.some(node => {
|
|
3106
|
+
if (node instanceof ERBIfNode || node.type === 'AST_ERB_IF_NODE') {
|
|
3107
|
+
const erbNode = node;
|
|
3108
|
+
if (erbNode.statements.length > 0 && erbNode.location) {
|
|
3109
|
+
const startLine = erbNode.location.start.line;
|
|
3110
|
+
const endLine = erbNode.location.end.line;
|
|
3111
|
+
return startLine !== endLine;
|
|
3112
|
+
}
|
|
3113
|
+
return false;
|
|
3114
|
+
}
|
|
3115
|
+
return false;
|
|
3116
|
+
});
|
|
3117
|
+
const inline = hasComplexERB ? "" : this.renderInlineOpen(tagName, attributes, isSelfClosing, inlineNodes, open.children);
|
|
3118
|
+
const nestingDepth = this.getMaxNestingDepth(children, 0);
|
|
3119
|
+
const totalAttributeCount = this.getTotalAttributeCount(attributes, inlineNodes);
|
|
3120
|
+
const shouldKeepInline = this.shouldRenderInline(totalAttributeCount, inline.length, indent.length, this.maxLineLength, hasComplexERB, nestingDepth, inlineNodes.length, this.hasMultilineAttributes(attributes));
|
|
2847
3121
|
if (shouldKeepInline) {
|
|
2848
3122
|
if (children.length === 0) {
|
|
2849
3123
|
if (isSelfClosing) {
|
|
@@ -2853,7 +3127,53 @@ class Printer extends Visitor {
|
|
|
2853
3127
|
this.push(indent + inline);
|
|
2854
3128
|
}
|
|
2855
3129
|
else {
|
|
2856
|
-
|
|
3130
|
+
let result = `<${tagName}`;
|
|
3131
|
+
result += this.renderAttributesString(attributes);
|
|
3132
|
+
if (inlineNodes.length > 0) {
|
|
3133
|
+
const currentIndentLevel = this.indentLevel;
|
|
3134
|
+
this.indentLevel = 0;
|
|
3135
|
+
const tempLines = this.lines;
|
|
3136
|
+
this.lines = [];
|
|
3137
|
+
inlineNodes.forEach(node => {
|
|
3138
|
+
const wasInlineMode = this.inlineMode;
|
|
3139
|
+
if (!this.isERBControlFlow(node)) {
|
|
3140
|
+
this.inlineMode = true;
|
|
3141
|
+
}
|
|
3142
|
+
this.visit(node);
|
|
3143
|
+
this.inlineMode = wasInlineMode;
|
|
3144
|
+
});
|
|
3145
|
+
const inlineContent = this.lines.join("");
|
|
3146
|
+
this.lines = tempLines;
|
|
3147
|
+
this.indentLevel = currentIndentLevel;
|
|
3148
|
+
result += inlineContent;
|
|
3149
|
+
}
|
|
3150
|
+
result += `></${tagName}>`;
|
|
3151
|
+
this.push(indent + result);
|
|
3152
|
+
}
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
if (isInlineElement && children.length > 0 && !hasERBControlFlow) {
|
|
3156
|
+
const fullInlineResult = this.tryRenderInlineFull(node, tagName, attributes, children);
|
|
3157
|
+
if (fullInlineResult) {
|
|
3158
|
+
const totalLength = indent.length + fullInlineResult.length;
|
|
3159
|
+
if (totalLength <= this.maxLineLength || totalLength <= 120) {
|
|
3160
|
+
this.push(indent + fullInlineResult);
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
if (!isInlineElement && children.length > 0 && !hasERBControlFlow) {
|
|
3166
|
+
this.push(indent + inline);
|
|
3167
|
+
this.withIndent(() => {
|
|
3168
|
+
if (hasTextFlow) {
|
|
3169
|
+
this.visitTextFlowChildren(children);
|
|
3170
|
+
}
|
|
3171
|
+
else {
|
|
3172
|
+
children.forEach(child => this.visit(child));
|
|
3173
|
+
}
|
|
3174
|
+
});
|
|
3175
|
+
if (!node.is_void && !isSelfClosing) {
|
|
3176
|
+
this.push(indent + `</${tagName}>`);
|
|
2857
3177
|
}
|
|
2858
3178
|
return;
|
|
2859
3179
|
}
|
|
@@ -2864,7 +3184,12 @@ class Printer extends Visitor {
|
|
|
2864
3184
|
this.push(indent + inline);
|
|
2865
3185
|
}
|
|
2866
3186
|
this.withIndent(() => {
|
|
2867
|
-
|
|
3187
|
+
if (hasTextFlow) {
|
|
3188
|
+
this.visitTextFlowChildren(children);
|
|
3189
|
+
}
|
|
3190
|
+
else {
|
|
3191
|
+
children.forEach(child => this.visit(child));
|
|
3192
|
+
}
|
|
2868
3193
|
});
|
|
2869
3194
|
if (!node.is_void && !isSelfClosing) {
|
|
2870
3195
|
this.push(indent + `</${tagName}>`);
|
|
@@ -2872,28 +3197,8 @@ class Printer extends Visitor {
|
|
|
2872
3197
|
return;
|
|
2873
3198
|
}
|
|
2874
3199
|
if (inlineNodes.length > 0 && hasERBControlFlow) {
|
|
2875
|
-
this.
|
|
2876
|
-
|
|
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 + ">");
|
|
3200
|
+
this.renderMultilineAttributes(tagName, attributes, inlineNodes, open.children, isSelfClosing, node.is_void, children.length > 0);
|
|
3201
|
+
if (!isSelfClosing && !node.is_void && children.length > 0) {
|
|
2897
3202
|
this.withIndent(() => {
|
|
2898
3203
|
children.forEach(child => this.visit(child));
|
|
2899
3204
|
});
|
|
@@ -2910,25 +3215,45 @@ class Printer extends Visitor {
|
|
|
2910
3215
|
}
|
|
2911
3216
|
}
|
|
2912
3217
|
else {
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
else if (node.is_void) {
|
|
2923
|
-
this.push(indent + ">");
|
|
3218
|
+
if (isInlineElement && children.length > 0) {
|
|
3219
|
+
const fullInlineResult = this.tryRenderInlineFull(node, tagName, attributes, children);
|
|
3220
|
+
if (fullInlineResult) {
|
|
3221
|
+
const totalLength = indent.length + fullInlineResult.length;
|
|
3222
|
+
if (totalLength <= this.maxLineLength || totalLength <= 120) {
|
|
3223
|
+
this.push(indent + fullInlineResult);
|
|
3224
|
+
return;
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
2924
3227
|
}
|
|
2925
|
-
|
|
2926
|
-
this.
|
|
3228
|
+
if (isInlineElement && children.length === 0) {
|
|
3229
|
+
const inline = this.renderInlineOpen(tagName, attributes, isSelfClosing, inlineNodes, open.children);
|
|
3230
|
+
const totalAttributeCount = this.getTotalAttributeCount(attributes, inlineNodes);
|
|
3231
|
+
const shouldKeepInline = this.shouldRenderInline(totalAttributeCount, inline.length, indent.length, this.maxLineLength, false, 0, inlineNodes.length, this.hasMultilineAttributes(attributes));
|
|
3232
|
+
if (shouldKeepInline) {
|
|
3233
|
+
let result = `<${tagName}`;
|
|
3234
|
+
result += this.renderAttributesString(attributes);
|
|
3235
|
+
if (isSelfClosing) {
|
|
3236
|
+
result += " />";
|
|
3237
|
+
}
|
|
3238
|
+
else if (node.is_void) {
|
|
3239
|
+
result += ">";
|
|
3240
|
+
}
|
|
3241
|
+
else {
|
|
3242
|
+
result += `></${tagName}>`;
|
|
3243
|
+
}
|
|
3244
|
+
this.push(indent + result);
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
2927
3247
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
3248
|
+
this.renderMultilineAttributes(tagName, attributes, inlineNodes, open.children, isSelfClosing, node.is_void, children.length > 0);
|
|
3249
|
+
if (!isSelfClosing && !node.is_void && children.length > 0) {
|
|
2930
3250
|
this.withIndent(() => {
|
|
2931
|
-
|
|
3251
|
+
if (hasTextFlow) {
|
|
3252
|
+
this.visitTextFlowChildren(children);
|
|
3253
|
+
}
|
|
3254
|
+
else {
|
|
3255
|
+
children.forEach(child => this.visit(child));
|
|
3256
|
+
}
|
|
2932
3257
|
});
|
|
2933
3258
|
this.push(indent + `</${tagName}>`);
|
|
2934
3259
|
}
|
|
@@ -2937,47 +3262,35 @@ class Printer extends Visitor {
|
|
|
2937
3262
|
visitHTMLOpenTagNode(node) {
|
|
2938
3263
|
const tagName = node.tag_name?.value ?? "";
|
|
2939
3264
|
const indent = this.indent();
|
|
2940
|
-
const attributes = node.children
|
|
3265
|
+
const attributes = this.extractAttributes(node.children);
|
|
3266
|
+
const inlineNodes = this.extractInlineNodes(node.children);
|
|
2941
3267
|
const hasClosing = node.tag_closing?.value === ">";
|
|
2942
3268
|
if (!hasClosing) {
|
|
2943
3269
|
this.push(indent + `<${tagName}`);
|
|
2944
3270
|
return;
|
|
2945
3271
|
}
|
|
2946
|
-
const inline = this.renderInlineOpen(tagName, attributes, node.is_void);
|
|
2947
|
-
|
|
3272
|
+
const inline = this.renderInlineOpen(tagName, attributes, node.is_void, inlineNodes, node.children);
|
|
3273
|
+
const totalAttributeCount = this.getTotalAttributeCount(attributes, inlineNodes);
|
|
3274
|
+
const shouldKeepInline = this.shouldRenderInline(totalAttributeCount, inline.length, indent.length, this.maxLineLength, false, 0, inlineNodes.length, this.hasMultilineAttributes(attributes));
|
|
3275
|
+
if (shouldKeepInline) {
|
|
2948
3276
|
this.push(indent + inline);
|
|
2949
3277
|
return;
|
|
2950
3278
|
}
|
|
2951
|
-
this.
|
|
2952
|
-
this.withIndent(() => {
|
|
2953
|
-
attributes.forEach(attribute => {
|
|
2954
|
-
this.push(this.indent() + this.renderAttribute(attribute));
|
|
2955
|
-
});
|
|
2956
|
-
});
|
|
2957
|
-
this.push(indent + (node.is_void ? "/>" : ">"));
|
|
3279
|
+
this.renderMultilineAttributes(tagName, attributes, inlineNodes, node.children, false, node.is_void, false);
|
|
2958
3280
|
}
|
|
2959
3281
|
visitHTMLSelfCloseTagNode(node) {
|
|
2960
3282
|
const tagName = node.tag_name?.value ?? "";
|
|
2961
3283
|
const indent = this.indent();
|
|
2962
|
-
const attributes = node.attributes
|
|
2963
|
-
const
|
|
2964
|
-
const
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
singleAttribute.value?.children.length === 0;
|
|
2968
|
-
const shouldKeepInline = attributes.length <= 3 &&
|
|
2969
|
-
inline.length + indent.length <= this.maxLineLength;
|
|
3284
|
+
const attributes = this.extractAttributes(node.attributes);
|
|
3285
|
+
const inlineNodes = this.extractInlineNodes(node.attributes);
|
|
3286
|
+
const inline = this.renderInlineOpen(tagName, attributes, true, inlineNodes, node.attributes);
|
|
3287
|
+
const totalAttributeCount = this.getTotalAttributeCount(attributes, inlineNodes);
|
|
3288
|
+
const shouldKeepInline = this.shouldRenderInline(totalAttributeCount, inline.length, indent.length, this.maxLineLength, false, 0, inlineNodes.length, this.hasMultilineAttributes(attributes));
|
|
2970
3289
|
if (shouldKeepInline) {
|
|
2971
3290
|
this.push(indent + inline);
|
|
2972
3291
|
return;
|
|
2973
3292
|
}
|
|
2974
|
-
this.
|
|
2975
|
-
this.withIndent(() => {
|
|
2976
|
-
attributes.forEach(attribute => {
|
|
2977
|
-
this.push(this.indent() + this.renderAttribute(attribute));
|
|
2978
|
-
});
|
|
2979
|
-
});
|
|
2980
|
-
this.push(indent + "/>");
|
|
3293
|
+
this.renderMultilineAttributes(tagName, attributes, inlineNodes, node.attributes, true, false, false);
|
|
2981
3294
|
}
|
|
2982
3295
|
visitHTMLCloseTagNode(node) {
|
|
2983
3296
|
const indent = this.indent();
|
|
@@ -2987,6 +3300,13 @@ class Printer extends Visitor {
|
|
|
2987
3300
|
this.push(indent + open + name + close);
|
|
2988
3301
|
}
|
|
2989
3302
|
visitHTMLTextNode(node) {
|
|
3303
|
+
if (this.inlineMode) {
|
|
3304
|
+
const normalizedContent = node.content.replace(/\s+/g, ' ').trim();
|
|
3305
|
+
if (normalizedContent) {
|
|
3306
|
+
this.push(normalizedContent);
|
|
3307
|
+
}
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
2990
3310
|
const indent = this.indent();
|
|
2991
3311
|
let text = node.content.trim();
|
|
2992
3312
|
if (!text)
|
|
@@ -3039,19 +3359,24 @@ class Printer extends Visitor {
|
|
|
3039
3359
|
const open = node.comment_start?.value ?? "";
|
|
3040
3360
|
const close = node.comment_end?.value ?? "";
|
|
3041
3361
|
let inner;
|
|
3042
|
-
if (node.
|
|
3043
|
-
// TODO: use .value
|
|
3044
|
-
const [_, startIndex] = node.comment_start.range.toArray();
|
|
3045
|
-
const [endIndex] = node.comment_end.range.toArray();
|
|
3046
|
-
const rawInner = this.source.slice(startIndex, endIndex);
|
|
3047
|
-
inner = ` ${rawInner.trim()} `;
|
|
3048
|
-
}
|
|
3049
|
-
else {
|
|
3362
|
+
if (node.children && node.children.length > 0) {
|
|
3050
3363
|
inner = node.children.map(child => {
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3364
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
3365
|
+
return child.content;
|
|
3366
|
+
}
|
|
3367
|
+
else if (child instanceof LiteralNode || child.type === 'AST_LITERAL_NODE') {
|
|
3368
|
+
return child.content;
|
|
3369
|
+
}
|
|
3370
|
+
else {
|
|
3371
|
+
const prevLines = this.lines.length;
|
|
3372
|
+
this.visit(child);
|
|
3373
|
+
return this.lines.slice(prevLines).join("");
|
|
3374
|
+
}
|
|
3054
3375
|
}).join("");
|
|
3376
|
+
inner = ` ${inner.trim()} `;
|
|
3377
|
+
}
|
|
3378
|
+
else {
|
|
3379
|
+
inner = "";
|
|
3055
3380
|
}
|
|
3056
3381
|
this.push(indent + open + inner + close);
|
|
3057
3382
|
}
|
|
@@ -3060,10 +3385,8 @@ class Printer extends Visitor {
|
|
|
3060
3385
|
const open = node.tag_opening?.value ?? "";
|
|
3061
3386
|
const close = node.tag_closing?.value ?? "";
|
|
3062
3387
|
let inner;
|
|
3063
|
-
if (node.
|
|
3064
|
-
const
|
|
3065
|
-
const [closingStart] = node.tag_closing.range.toArray();
|
|
3066
|
-
const rawInner = this.source.slice(openingEnd, closingStart);
|
|
3388
|
+
if (node.content && node.content.value) {
|
|
3389
|
+
const rawInner = node.content.value;
|
|
3067
3390
|
const lines = rawInner.split("\n");
|
|
3068
3391
|
if (lines.length > 2) {
|
|
3069
3392
|
const childIndent = indent + " ".repeat(this.indentWidth);
|
|
@@ -3074,32 +3397,41 @@ class Printer extends Visitor {
|
|
|
3074
3397
|
inner = ` ${rawInner.trim()} `;
|
|
3075
3398
|
}
|
|
3076
3399
|
}
|
|
3077
|
-
else {
|
|
3078
|
-
inner = node.children
|
|
3079
|
-
.map((child) => {
|
|
3400
|
+
else if (node.children) {
|
|
3401
|
+
inner = node.children.map((child) => {
|
|
3080
3402
|
const prevLines = this.lines.length;
|
|
3081
3403
|
this.visit(child);
|
|
3082
3404
|
return this.lines.slice(prevLines).join("");
|
|
3083
|
-
})
|
|
3084
|
-
|
|
3405
|
+
}).join("");
|
|
3406
|
+
}
|
|
3407
|
+
else {
|
|
3408
|
+
inner = "";
|
|
3085
3409
|
}
|
|
3086
3410
|
this.push(indent + open + inner + close);
|
|
3087
3411
|
}
|
|
3088
3412
|
visitHTMLDoctypeNode(node) {
|
|
3089
3413
|
const indent = this.indent();
|
|
3090
3414
|
const open = node.tag_opening?.value ?? "";
|
|
3091
|
-
let innerDoctype
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
.
|
|
3102
|
-
|
|
3415
|
+
let innerDoctype = node.children.map(child => {
|
|
3416
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
3417
|
+
return child.content;
|
|
3418
|
+
}
|
|
3419
|
+
else if (child instanceof LiteralNode || child.type === 'AST_LITERAL_NODE') {
|
|
3420
|
+
return child.content;
|
|
3421
|
+
}
|
|
3422
|
+
else if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') {
|
|
3423
|
+
const erbNode = child;
|
|
3424
|
+
const erbOpen = erbNode.tag_opening?.value ?? "";
|
|
3425
|
+
const erbContent = erbNode.content?.value ?? "";
|
|
3426
|
+
const erbClose = erbNode.tag_closing?.value ?? "";
|
|
3427
|
+
return erbOpen + (erbContent ? ` ${erbContent.trim()} ` : "") + erbClose;
|
|
3428
|
+
}
|
|
3429
|
+
else {
|
|
3430
|
+
const prevLines = this.lines.length;
|
|
3431
|
+
this.visit(child);
|
|
3432
|
+
return this.lines.slice(prevLines).join("");
|
|
3433
|
+
}
|
|
3434
|
+
}).join("");
|
|
3103
3435
|
const close = node.tag_closing?.value ?? "";
|
|
3104
3436
|
this.push(indent + open + innerDoctype + close);
|
|
3105
3437
|
}
|
|
@@ -3120,9 +3452,17 @@ class Printer extends Visitor {
|
|
|
3120
3452
|
}
|
|
3121
3453
|
visitERBInNode(node) {
|
|
3122
3454
|
this.printERBNode(node);
|
|
3455
|
+
this.withIndent(() => {
|
|
3456
|
+
node.statements.forEach(stmt => this.visit(stmt));
|
|
3457
|
+
});
|
|
3123
3458
|
}
|
|
3124
3459
|
visitERBCaseMatchNode(node) {
|
|
3125
3460
|
this.printERBNode(node);
|
|
3461
|
+
node.conditions.forEach(condition => this.visit(condition));
|
|
3462
|
+
if (node.else_clause)
|
|
3463
|
+
this.visit(node.else_clause);
|
|
3464
|
+
if (node.end_node)
|
|
3465
|
+
this.visit(node.end_node);
|
|
3126
3466
|
}
|
|
3127
3467
|
visitERBBlockNode(node) {
|
|
3128
3468
|
const indent = this.indent();
|
|
@@ -3142,22 +3482,18 @@ class Printer extends Visitor {
|
|
|
3142
3482
|
const open = node.tag_opening?.value ?? "";
|
|
3143
3483
|
const content = node.content?.value ?? "";
|
|
3144
3484
|
const close = node.tag_closing?.value ?? "";
|
|
3145
|
-
this.
|
|
3146
|
-
|
|
3485
|
+
const inner = this.formatERBContent(content);
|
|
3486
|
+
this.lines.push(open + inner + close);
|
|
3487
|
+
node.statements.forEach((child, _index) => {
|
|
3147
3488
|
this.lines.push(" ");
|
|
3148
|
-
}
|
|
3149
|
-
node.statements.forEach((child, index) => {
|
|
3150
3489
|
if (child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE') {
|
|
3151
3490
|
this.lines.push(this.renderAttribute(child));
|
|
3152
3491
|
}
|
|
3153
3492
|
else {
|
|
3154
3493
|
this.visit(child);
|
|
3155
3494
|
}
|
|
3156
|
-
if (index < node.statements.length - 1) {
|
|
3157
|
-
this.lines.push(" ");
|
|
3158
|
-
}
|
|
3159
3495
|
});
|
|
3160
|
-
if (node.statements.length > 0) {
|
|
3496
|
+
if (node.statements.length > 0 && node.end_node) {
|
|
3161
3497
|
this.lines.push(" ");
|
|
3162
3498
|
}
|
|
3163
3499
|
if (node.subsequent) {
|
|
@@ -3168,7 +3504,8 @@ class Printer extends Visitor {
|
|
|
3168
3504
|
const endOpen = endNode.tag_opening?.value ?? "";
|
|
3169
3505
|
const endContent = endNode.content?.value ?? "";
|
|
3170
3506
|
const endClose = endNode.tag_closing?.value ?? "";
|
|
3171
|
-
this.
|
|
3507
|
+
const endInner = this.formatERBContent(endContent);
|
|
3508
|
+
this.lines.push(endOpen + endInner + endClose);
|
|
3172
3509
|
}
|
|
3173
3510
|
}
|
|
3174
3511
|
else {
|
|
@@ -3197,7 +3534,6 @@ class Printer extends Visitor {
|
|
|
3197
3534
|
});
|
|
3198
3535
|
}
|
|
3199
3536
|
visitERBCaseNode(node) {
|
|
3200
|
-
this.indentLevel;
|
|
3201
3537
|
const indent = this.indent();
|
|
3202
3538
|
const open = node.tag_opening?.value ?? "";
|
|
3203
3539
|
const content = node.content?.value ?? "";
|
|
@@ -3261,6 +3597,149 @@ class Printer extends Visitor {
|
|
|
3261
3597
|
this.visit(node.end_node);
|
|
3262
3598
|
}
|
|
3263
3599
|
// --- Utility methods ---
|
|
3600
|
+
isNonWhitespaceNode(node) {
|
|
3601
|
+
if (node instanceof HTMLTextNode || node.type === 'AST_HTML_TEXT_NODE') {
|
|
3602
|
+
return node.content.trim() !== "";
|
|
3603
|
+
}
|
|
3604
|
+
if (node instanceof WhitespaceNode || node.type === 'AST_WHITESPACE_NODE') {
|
|
3605
|
+
return false;
|
|
3606
|
+
}
|
|
3607
|
+
return true;
|
|
3608
|
+
}
|
|
3609
|
+
/**
|
|
3610
|
+
* Check if an element should be treated as inline based on its tag name
|
|
3611
|
+
*/
|
|
3612
|
+
isInlineElement(tagName) {
|
|
3613
|
+
return Printer.INLINE_ELEMENTS.has(tagName.toLowerCase());
|
|
3614
|
+
}
|
|
3615
|
+
/**
|
|
3616
|
+
* Check if we're in a text flow context (parent contains mixed text and inline elements)
|
|
3617
|
+
*/
|
|
3618
|
+
visitTextFlowChildren(children) {
|
|
3619
|
+
const indent = this.indent();
|
|
3620
|
+
let currentLineContent = "";
|
|
3621
|
+
for (const child of children) {
|
|
3622
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
3623
|
+
const content = child.content;
|
|
3624
|
+
let processedContent = content.replace(/\s+/g, ' ').trim();
|
|
3625
|
+
if (processedContent) {
|
|
3626
|
+
const hasLeadingSpace = /^\s/.test(content);
|
|
3627
|
+
if (currentLineContent && hasLeadingSpace && !currentLineContent.endsWith(' ')) {
|
|
3628
|
+
currentLineContent += ' ';
|
|
3629
|
+
}
|
|
3630
|
+
currentLineContent += processedContent;
|
|
3631
|
+
const hasTrailingSpace = /\s$/.test(content);
|
|
3632
|
+
if (hasTrailingSpace && !currentLineContent.endsWith(' ')) {
|
|
3633
|
+
currentLineContent += ' ';
|
|
3634
|
+
}
|
|
3635
|
+
if ((indent.length + currentLineContent.length) > Math.max(this.maxLineLength, 120)) {
|
|
3636
|
+
this.visitTextFlowChildrenMultiline(children);
|
|
3637
|
+
return;
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
else if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
|
|
3642
|
+
const element = child;
|
|
3643
|
+
const openTag = element.open_tag;
|
|
3644
|
+
const childTagName = openTag?.tag_name?.value || '';
|
|
3645
|
+
if (this.isInlineElement(childTagName)) {
|
|
3646
|
+
const childInline = this.tryRenderInlineFull(element, childTagName, this.extractAttributes(openTag.children), element.body.filter(c => !(c instanceof WhitespaceNode || c.type === 'AST_WHITESPACE_NODE') &&
|
|
3647
|
+
!((c instanceof HTMLTextNode || c.type === 'AST_HTML_TEXT_NODE') && c?.content.trim() === "")));
|
|
3648
|
+
if (childInline) {
|
|
3649
|
+
currentLineContent += childInline;
|
|
3650
|
+
if ((indent.length + currentLineContent.length) > this.maxLineLength) {
|
|
3651
|
+
this.visitTextFlowChildrenMultiline(children);
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
else {
|
|
3656
|
+
if (currentLineContent.trim()) {
|
|
3657
|
+
this.push(indent + currentLineContent.trim());
|
|
3658
|
+
currentLineContent = "";
|
|
3659
|
+
}
|
|
3660
|
+
this.visit(child);
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
else {
|
|
3664
|
+
if (currentLineContent.trim()) {
|
|
3665
|
+
this.push(indent + currentLineContent.trim());
|
|
3666
|
+
currentLineContent = "";
|
|
3667
|
+
}
|
|
3668
|
+
this.visit(child);
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
else if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') {
|
|
3672
|
+
const oldLines = this.lines;
|
|
3673
|
+
const oldInlineMode = this.inlineMode;
|
|
3674
|
+
try {
|
|
3675
|
+
this.lines = [];
|
|
3676
|
+
this.inlineMode = true;
|
|
3677
|
+
this.visit(child);
|
|
3678
|
+
const erbContent = this.lines.join("");
|
|
3679
|
+
currentLineContent += erbContent;
|
|
3680
|
+
if ((indent.length + currentLineContent.length) > Math.max(this.maxLineLength, 120)) {
|
|
3681
|
+
this.lines = oldLines;
|
|
3682
|
+
this.inlineMode = oldInlineMode;
|
|
3683
|
+
this.visitTextFlowChildrenMultiline(children);
|
|
3684
|
+
return;
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
finally {
|
|
3688
|
+
this.lines = oldLines;
|
|
3689
|
+
this.inlineMode = oldInlineMode;
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
else {
|
|
3693
|
+
if (currentLineContent.trim()) {
|
|
3694
|
+
this.push(indent + currentLineContent.trim());
|
|
3695
|
+
currentLineContent = "";
|
|
3696
|
+
}
|
|
3697
|
+
this.visit(child);
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
if (currentLineContent.trim()) {
|
|
3701
|
+
const finalLine = indent + currentLineContent.trim();
|
|
3702
|
+
if (finalLine.length > Math.max(this.maxLineLength, 120)) {
|
|
3703
|
+
this.visitTextFlowChildrenMultiline(children);
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
this.push(finalLine);
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
visitTextFlowChildrenMultiline(children) {
|
|
3710
|
+
children.forEach(child => this.visit(child));
|
|
3711
|
+
}
|
|
3712
|
+
isInTextFlowContext(parent, children) {
|
|
3713
|
+
const hasTextContent = children.some(child => (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') &&
|
|
3714
|
+
child.content.trim() !== "");
|
|
3715
|
+
if (!hasTextContent) {
|
|
3716
|
+
return false;
|
|
3717
|
+
}
|
|
3718
|
+
const nonTextChildren = children.filter(child => !(child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE'));
|
|
3719
|
+
if (nonTextChildren.length === 0) {
|
|
3720
|
+
return false;
|
|
3721
|
+
}
|
|
3722
|
+
const allInline = nonTextChildren.every(child => {
|
|
3723
|
+
if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') {
|
|
3724
|
+
return true;
|
|
3725
|
+
}
|
|
3726
|
+
if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
|
|
3727
|
+
const element = child;
|
|
3728
|
+
const openTag = element.open_tag;
|
|
3729
|
+
const tagName = openTag?.tag_name?.value || '';
|
|
3730
|
+
return this.isInlineElement(tagName);
|
|
3731
|
+
}
|
|
3732
|
+
return false;
|
|
3733
|
+
});
|
|
3734
|
+
if (!allInline) {
|
|
3735
|
+
return false;
|
|
3736
|
+
}
|
|
3737
|
+
const maxNestingDepth = this.getMaxNestingDepth(children, 0);
|
|
3738
|
+
if (maxNestingDepth > 2) {
|
|
3739
|
+
return false;
|
|
3740
|
+
}
|
|
3741
|
+
return true;
|
|
3742
|
+
}
|
|
3264
3743
|
renderInlineOpen(name, attributes, selfClose, inlineNodes = [], allChildren = []) {
|
|
3265
3744
|
const parts = attributes.map(attribute => this.renderAttribute(attribute));
|
|
3266
3745
|
if (inlineNodes.length > 0) {
|
|
@@ -3297,7 +3776,9 @@ class Printer extends Visitor {
|
|
|
3297
3776
|
this.lines = [];
|
|
3298
3777
|
inlineNodes.forEach(node => {
|
|
3299
3778
|
const wasInlineMode = this.inlineMode;
|
|
3300
|
-
this.
|
|
3779
|
+
if (!this.isERBControlFlow(node)) {
|
|
3780
|
+
this.inlineMode = true;
|
|
3781
|
+
}
|
|
3301
3782
|
this.visit(node);
|
|
3302
3783
|
this.inlineMode = wasInlineMode;
|
|
3303
3784
|
});
|
|
@@ -3316,33 +3797,138 @@ class Printer extends Visitor {
|
|
|
3316
3797
|
const equals = attribute.equals?.value ?? "";
|
|
3317
3798
|
let value = "";
|
|
3318
3799
|
if (attribute.value && (attribute.value instanceof HTMLAttributeValueNode || attribute.value?.type === 'AST_HTML_ATTRIBUTE_VALUE_NODE')) {
|
|
3319
|
-
const
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3800
|
+
const attributeValue = attribute.value;
|
|
3801
|
+
let open_quote = attributeValue.open_quote?.value ?? "";
|
|
3802
|
+
let close_quote = attributeValue.close_quote?.value ?? "";
|
|
3803
|
+
let htmlTextContent = "";
|
|
3804
|
+
const content = attributeValue.children.map((child) => {
|
|
3805
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE' || child instanceof LiteralNode || child.type === 'AST_LITERAL_NODE') {
|
|
3806
|
+
const textContent = child.content;
|
|
3807
|
+
htmlTextContent += textContent;
|
|
3808
|
+
return textContent;
|
|
3325
3809
|
}
|
|
3326
|
-
else if (
|
|
3327
|
-
const
|
|
3328
|
-
return
|
|
3810
|
+
else if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') {
|
|
3811
|
+
const erbAttribute = child;
|
|
3812
|
+
return erbAttribute.tag_opening.value + erbAttribute.content.value + erbAttribute.tag_closing.value;
|
|
3329
3813
|
}
|
|
3330
3814
|
return "";
|
|
3331
3815
|
}).join("");
|
|
3332
|
-
|
|
3816
|
+
if (open_quote === "" && close_quote === "") {
|
|
3817
|
+
open_quote = '"';
|
|
3818
|
+
close_quote = '"';
|
|
3819
|
+
}
|
|
3820
|
+
else if (open_quote === "'" && close_quote === "'" && !htmlTextContent.includes('"')) {
|
|
3821
|
+
open_quote = '"';
|
|
3822
|
+
close_quote = '"';
|
|
3823
|
+
}
|
|
3824
|
+
if (this.isFormattableAttribute(name, this.currentTagName)) {
|
|
3825
|
+
if (name === 'class') {
|
|
3826
|
+
value = this.formatClassAttribute(content, name, equals, open_quote, close_quote);
|
|
3827
|
+
}
|
|
3828
|
+
else {
|
|
3829
|
+
value = this.formatMultilineAttribute(content, name, equals, open_quote, close_quote);
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
else {
|
|
3833
|
+
value = open_quote + content + close_quote;
|
|
3834
|
+
}
|
|
3333
3835
|
}
|
|
3334
3836
|
return name + equals + value;
|
|
3335
3837
|
}
|
|
3838
|
+
/**
|
|
3839
|
+
* Try to render a complete element inline including opening tag, children, and closing tag
|
|
3840
|
+
*/
|
|
3841
|
+
tryRenderInlineFull(_node, tagName, attributes, children) {
|
|
3842
|
+
let result = `<${tagName}`;
|
|
3843
|
+
result += this.renderAttributesString(attributes);
|
|
3844
|
+
result += ">";
|
|
3845
|
+
const childrenContent = this.tryRenderChildrenInline(children);
|
|
3846
|
+
if (!childrenContent) {
|
|
3847
|
+
return null;
|
|
3848
|
+
}
|
|
3849
|
+
result += childrenContent;
|
|
3850
|
+
result += `</${tagName}>`;
|
|
3851
|
+
return result;
|
|
3852
|
+
}
|
|
3853
|
+
/**
|
|
3854
|
+
* Try to render just the children inline (without tags)
|
|
3855
|
+
*/
|
|
3856
|
+
tryRenderChildrenInline(children) {
|
|
3857
|
+
let result = "";
|
|
3858
|
+
for (const child of children) {
|
|
3859
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
3860
|
+
const content = child.content;
|
|
3861
|
+
const normalizedContent = content.replace(/\s+/g, ' ');
|
|
3862
|
+
const hasLeadingSpace = /^\s/.test(content);
|
|
3863
|
+
const hasTrailingSpace = /\s$/.test(content);
|
|
3864
|
+
const trimmedContent = normalizedContent.trim();
|
|
3865
|
+
if (trimmedContent) {
|
|
3866
|
+
let finalContent = trimmedContent;
|
|
3867
|
+
if (hasLeadingSpace && result && !result.endsWith(' ')) {
|
|
3868
|
+
finalContent = ' ' + finalContent;
|
|
3869
|
+
}
|
|
3870
|
+
if (hasTrailingSpace) {
|
|
3871
|
+
finalContent = finalContent + ' ';
|
|
3872
|
+
}
|
|
3873
|
+
result += finalContent;
|
|
3874
|
+
}
|
|
3875
|
+
else if (hasLeadingSpace || hasTrailingSpace) {
|
|
3876
|
+
if (result && !result.endsWith(' ')) {
|
|
3877
|
+
result += ' ';
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
else if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
|
|
3882
|
+
const element = child;
|
|
3883
|
+
const openTag = element.open_tag;
|
|
3884
|
+
const childTagName = openTag?.tag_name?.value || '';
|
|
3885
|
+
if (!this.isInlineElement(childTagName)) {
|
|
3886
|
+
return null;
|
|
3887
|
+
}
|
|
3888
|
+
const childInline = this.tryRenderInlineFull(element, childTagName, this.extractAttributes(openTag.children), element.body.filter(c => !(c instanceof WhitespaceNode || c.type === 'AST_WHITESPACE_NODE') &&
|
|
3889
|
+
!((c instanceof HTMLTextNode || c.type === 'AST_HTML_TEXT_NODE') && c?.content.trim() === "")));
|
|
3890
|
+
if (!childInline) {
|
|
3891
|
+
return null;
|
|
3892
|
+
}
|
|
3893
|
+
result += childInline;
|
|
3894
|
+
}
|
|
3895
|
+
else {
|
|
3896
|
+
const oldLines = this.lines;
|
|
3897
|
+
const oldInlineMode = this.inlineMode;
|
|
3898
|
+
const oldIndentLevel = this.indentLevel;
|
|
3899
|
+
try {
|
|
3900
|
+
this.lines = [];
|
|
3901
|
+
this.inlineMode = true;
|
|
3902
|
+
this.indentLevel = 0;
|
|
3903
|
+
this.visit(child);
|
|
3904
|
+
result += this.lines.join("");
|
|
3905
|
+
}
|
|
3906
|
+
finally {
|
|
3907
|
+
this.lines = oldLines;
|
|
3908
|
+
this.inlineMode = oldInlineMode;
|
|
3909
|
+
this.indentLevel = oldIndentLevel;
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
return result.trim();
|
|
3914
|
+
}
|
|
3336
3915
|
/**
|
|
3337
3916
|
* Try to render children inline if they are simple enough.
|
|
3338
3917
|
* Returns the inline string if possible, null otherwise.
|
|
3339
3918
|
*/
|
|
3340
|
-
tryRenderInline(children, tagName, depth = 0) {
|
|
3341
|
-
if (children.length > 10) {
|
|
3919
|
+
tryRenderInline(children, tagName, depth = 0, forceInline = false, hasTextFlow = false) {
|
|
3920
|
+
if (!forceInline && children.length > 10) {
|
|
3342
3921
|
return null;
|
|
3343
3922
|
}
|
|
3344
3923
|
const maxNestingDepth = this.getMaxNestingDepth(children, 0);
|
|
3345
|
-
|
|
3924
|
+
let maxAllowedDepth = forceInline ? 5 : (tagName && ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div'].includes(tagName) ? 1 : 2);
|
|
3925
|
+
if (hasTextFlow && maxNestingDepth >= 2) {
|
|
3926
|
+
const roughContentLength = this.estimateContentLength(children);
|
|
3927
|
+
if (roughContentLength > 47) {
|
|
3928
|
+
maxAllowedDepth = 1;
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
if (!forceInline && maxNestingDepth > maxAllowedDepth) {
|
|
3346
3932
|
this.isInComplexNesting = true;
|
|
3347
3933
|
return null;
|
|
3348
3934
|
}
|
|
@@ -3356,8 +3942,9 @@ class Printer extends Visitor {
|
|
|
3356
3942
|
else if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
|
|
3357
3943
|
const element = child;
|
|
3358
3944
|
const openTag = element.open_tag;
|
|
3359
|
-
const
|
|
3360
|
-
|
|
3945
|
+
const elementTagName = openTag?.tag_name?.value || '';
|
|
3946
|
+
const isInlineElement = this.isInlineElement(elementTagName);
|
|
3947
|
+
if (!isInlineElement) {
|
|
3361
3948
|
return null;
|
|
3362
3949
|
}
|
|
3363
3950
|
}
|
|
@@ -3380,10 +3967,8 @@ class Printer extends Visitor {
|
|
|
3380
3967
|
const element = child;
|
|
3381
3968
|
const openTag = element.open_tag;
|
|
3382
3969
|
const childTagName = openTag?.tag_name?.value || '';
|
|
3383
|
-
const attributes = openTag.children
|
|
3384
|
-
const attributesString = attributes
|
|
3385
|
-
? ' ' + attributes.map(attr => this.renderAttribute(attr)).join(' ')
|
|
3386
|
-
: '';
|
|
3970
|
+
const attributes = this.extractAttributes(openTag.children);
|
|
3971
|
+
const attributesString = this.renderAttributesString(attributes);
|
|
3387
3972
|
const elementContent = this.renderElementInline(element);
|
|
3388
3973
|
content += `<${childTagName}${attributesString}>${elementContent}</${childTagName}>`;
|
|
3389
3974
|
}
|
|
@@ -3392,7 +3977,7 @@ class Printer extends Visitor {
|
|
|
3392
3977
|
const open = erbNode.tag_opening?.value ?? "";
|
|
3393
3978
|
const erbContent = erbNode.content?.value ?? "";
|
|
3394
3979
|
const close = erbNode.tag_closing?.value ?? "";
|
|
3395
|
-
content += `${open}
|
|
3980
|
+
content += `${open}${this.formatERBContent(erbContent)}${close}`;
|
|
3396
3981
|
}
|
|
3397
3982
|
}
|
|
3398
3983
|
content = content.replace(/\s+/g, ' ').trim();
|
|
@@ -3403,6 +3988,28 @@ class Printer extends Visitor {
|
|
|
3403
3988
|
this.inlineMode = oldInlineMode;
|
|
3404
3989
|
}
|
|
3405
3990
|
}
|
|
3991
|
+
/**
|
|
3992
|
+
* Estimate the total content length of children nodes for decision making.
|
|
3993
|
+
*/
|
|
3994
|
+
estimateContentLength(children) {
|
|
3995
|
+
let length = 0;
|
|
3996
|
+
for (const child of children) {
|
|
3997
|
+
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
3998
|
+
length += child.content.length;
|
|
3999
|
+
}
|
|
4000
|
+
else if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
|
|
4001
|
+
const element = child;
|
|
4002
|
+
const openTag = element.open_tag;
|
|
4003
|
+
const tagName = openTag?.tag_name?.value || '';
|
|
4004
|
+
length += tagName.length + 5; // Rough estimate for tag overhead
|
|
4005
|
+
length += this.estimateContentLength(element.body);
|
|
4006
|
+
}
|
|
4007
|
+
else if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') {
|
|
4008
|
+
length += child.content?.value.length || 0;
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
return length;
|
|
4012
|
+
}
|
|
3406
4013
|
/**
|
|
3407
4014
|
* Calculate the maximum nesting depth in a subtree of nodes.
|
|
3408
4015
|
*/
|
|
@@ -3434,10 +4041,8 @@ class Printer extends Visitor {
|
|
|
3434
4041
|
const childElement = child;
|
|
3435
4042
|
const openTag = childElement.open_tag;
|
|
3436
4043
|
const childTagName = openTag?.tag_name?.value || '';
|
|
3437
|
-
const attributes = openTag.children
|
|
3438
|
-
const attributesString = attributes
|
|
3439
|
-
? ' ' + attributes.map(attr => this.renderAttribute(attr)).join(' ')
|
|
3440
|
-
: '';
|
|
4044
|
+
const attributes = this.extractAttributes(openTag.children);
|
|
4045
|
+
const attributesString = this.renderAttributesString(attributes);
|
|
3441
4046
|
const childContent = this.renderElementInline(childElement);
|
|
3442
4047
|
content += `<${childTagName}${attributesString}>${childContent}</${childTagName}>`;
|
|
3443
4048
|
}
|
|
@@ -3446,7 +4051,7 @@ class Printer extends Visitor {
|
|
|
3446
4051
|
const open = erbNode.tag_opening?.value ?? "";
|
|
3447
4052
|
const erbContent = erbNode.content?.value ?? "";
|
|
3448
4053
|
const close = erbNode.tag_closing?.value ?? "";
|
|
3449
|
-
content += `${open}
|
|
4054
|
+
content += `${open}${this.formatERBContent(erbContent)}${close}`;
|
|
3450
4055
|
}
|
|
3451
4056
|
}
|
|
3452
4057
|
return content.replace(/\s+/g, ' ').trim();
|