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