@herb-tools/formatter 0.4.3 → 0.6.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 +3379 -2212
- package/dist/herb-format.js.map +1 -1
- package/dist/index.cjs +2069 -901
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +2069 -902
- package/dist/index.esm.js.map +1 -1
- package/dist/types/format-printer.d.ts +243 -0
- package/dist/types/index.d.ts +2 -1
- package/package.json +5 -2
- package/src/cli.ts +57 -29
- package/src/format-printer.ts +1822 -0
- package/src/formatter.ts +2 -2
- package/src/index.ts +2 -3
- package/dist/types/printer.d.ts +0 -127
- package/src/printer.ts +0 -1644
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.6.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.6.0/templates/javascript/packages/core/src/nodes.ts.erb
|
|
583
583
|
class Node {
|
|
584
584
|
type;
|
|
585
585
|
location;
|
|
@@ -684,7 +684,6 @@ class DocumentNode extends Node {
|
|
|
684
684
|
output += `@ DocumentNode ${this.location.treeInspectWithLabel()}\n`;
|
|
685
685
|
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
686
686
|
output += `└── children: ${this.inspectArray(this.children, " ")}`;
|
|
687
|
-
// output += "\n";
|
|
688
687
|
return output;
|
|
689
688
|
}
|
|
690
689
|
}
|
|
@@ -728,7 +727,6 @@ class LiteralNode extends Node {
|
|
|
728
727
|
output += `@ LiteralNode ${this.location.treeInspectWithLabel()}\n`;
|
|
729
728
|
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
730
729
|
output += `└── content: ${this.content ? JSON.stringify(this.content) : "∅"}\n`;
|
|
731
|
-
// output += "\n";
|
|
732
730
|
return output;
|
|
733
731
|
}
|
|
734
732
|
}
|
|
@@ -795,13 +793,13 @@ class HTMLOpenTagNode extends Node {
|
|
|
795
793
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
796
794
|
output += `├── children: ${this.inspectArray(this.children, "│ ")}`;
|
|
797
795
|
output += `└── is_void: ${typeof this.is_void === 'boolean' ? String(this.is_void) : "∅"}\n`;
|
|
798
|
-
// output += "\n";
|
|
799
796
|
return output;
|
|
800
797
|
}
|
|
801
798
|
}
|
|
802
799
|
class HTMLCloseTagNode extends Node {
|
|
803
800
|
tag_opening;
|
|
804
801
|
tag_name;
|
|
802
|
+
children;
|
|
805
803
|
tag_closing;
|
|
806
804
|
static from(data) {
|
|
807
805
|
return new HTMLCloseTagNode({
|
|
@@ -810,6 +808,7 @@ class HTMLCloseTagNode extends Node {
|
|
|
810
808
|
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
811
809
|
tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
|
|
812
810
|
tag_name: data.tag_name ? Token.from(data.tag_name) : null,
|
|
811
|
+
children: (data.children || []).map(node => fromSerializedNode(node)),
|
|
813
812
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
814
813
|
});
|
|
815
814
|
}
|
|
@@ -817,13 +816,16 @@ class HTMLCloseTagNode extends Node {
|
|
|
817
816
|
super(props.type, props.location, props.errors);
|
|
818
817
|
this.tag_opening = props.tag_opening;
|
|
819
818
|
this.tag_name = props.tag_name;
|
|
819
|
+
this.children = props.children;
|
|
820
820
|
this.tag_closing = props.tag_closing;
|
|
821
821
|
}
|
|
822
822
|
accept(visitor) {
|
|
823
823
|
visitor.visitHTMLCloseTagNode(this);
|
|
824
824
|
}
|
|
825
825
|
childNodes() {
|
|
826
|
-
return [
|
|
826
|
+
return [
|
|
827
|
+
...this.children,
|
|
828
|
+
];
|
|
827
829
|
}
|
|
828
830
|
compactChildNodes() {
|
|
829
831
|
return this.childNodes().filter(node => node !== null && node !== undefined);
|
|
@@ -831,6 +833,7 @@ class HTMLCloseTagNode extends Node {
|
|
|
831
833
|
recursiveErrors() {
|
|
832
834
|
return [
|
|
833
835
|
...this.errors,
|
|
836
|
+
...this.children.map(node => node.recursiveErrors()),
|
|
834
837
|
].flat();
|
|
835
838
|
}
|
|
836
839
|
toJSON() {
|
|
@@ -839,6 +842,7 @@ class HTMLCloseTagNode extends Node {
|
|
|
839
842
|
type: "AST_HTML_CLOSE_TAG_NODE",
|
|
840
843
|
tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
|
|
841
844
|
tag_name: this.tag_name ? this.tag_name.toJSON() : null,
|
|
845
|
+
children: this.children.map(node => node.toJSON()),
|
|
842
846
|
tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
|
|
843
847
|
};
|
|
844
848
|
}
|
|
@@ -848,75 +852,8 @@ class HTMLCloseTagNode extends Node {
|
|
|
848
852
|
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
849
853
|
output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
|
|
850
854
|
output += `├── tag_name: ${this.tag_name ? this.tag_name.treeInspect() : "∅"}\n`;
|
|
855
|
+
output += `├── children: ${this.inspectArray(this.children, "│ ")}`;
|
|
851
856
|
output += `└── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
852
|
-
// output += "\n";
|
|
853
|
-
return output;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
class HTMLSelfCloseTagNode extends Node {
|
|
857
|
-
tag_opening;
|
|
858
|
-
tag_name;
|
|
859
|
-
attributes;
|
|
860
|
-
tag_closing;
|
|
861
|
-
is_void;
|
|
862
|
-
static from(data) {
|
|
863
|
-
return new HTMLSelfCloseTagNode({
|
|
864
|
-
type: data.type,
|
|
865
|
-
location: Location.from(data.location),
|
|
866
|
-
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
867
|
-
tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
|
|
868
|
-
tag_name: data.tag_name ? Token.from(data.tag_name) : null,
|
|
869
|
-
attributes: (data.attributes || []).map(node => fromSerializedNode(node)),
|
|
870
|
-
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
871
|
-
is_void: data.is_void,
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
constructor(props) {
|
|
875
|
-
super(props.type, props.location, props.errors);
|
|
876
|
-
this.tag_opening = props.tag_opening;
|
|
877
|
-
this.tag_name = props.tag_name;
|
|
878
|
-
this.attributes = props.attributes;
|
|
879
|
-
this.tag_closing = props.tag_closing;
|
|
880
|
-
this.is_void = props.is_void;
|
|
881
|
-
}
|
|
882
|
-
accept(visitor) {
|
|
883
|
-
visitor.visitHTMLSelfCloseTagNode(this);
|
|
884
|
-
}
|
|
885
|
-
childNodes() {
|
|
886
|
-
return [
|
|
887
|
-
...this.attributes,
|
|
888
|
-
];
|
|
889
|
-
}
|
|
890
|
-
compactChildNodes() {
|
|
891
|
-
return this.childNodes().filter(node => node !== null && node !== undefined);
|
|
892
|
-
}
|
|
893
|
-
recursiveErrors() {
|
|
894
|
-
return [
|
|
895
|
-
...this.errors,
|
|
896
|
-
...this.attributes.map(node => node.recursiveErrors()),
|
|
897
|
-
].flat();
|
|
898
|
-
}
|
|
899
|
-
toJSON() {
|
|
900
|
-
return {
|
|
901
|
-
...super.toJSON(),
|
|
902
|
-
type: "AST_HTML_SELF_CLOSE_TAG_NODE",
|
|
903
|
-
tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
|
|
904
|
-
tag_name: this.tag_name ? this.tag_name.toJSON() : null,
|
|
905
|
-
attributes: this.attributes.map(node => node.toJSON()),
|
|
906
|
-
tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
|
|
907
|
-
is_void: this.is_void,
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
treeInspect() {
|
|
911
|
-
let output = "";
|
|
912
|
-
output += `@ HTMLSelfCloseTagNode ${this.location.treeInspectWithLabel()}\n`;
|
|
913
|
-
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
914
|
-
output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
|
|
915
|
-
output += `├── tag_name: ${this.tag_name ? this.tag_name.treeInspect() : "∅"}\n`;
|
|
916
|
-
output += `├── attributes: ${this.inspectArray(this.attributes, "│ ")}`;
|
|
917
|
-
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
918
|
-
output += `└── is_void: ${typeof this.is_void === 'boolean' ? String(this.is_void) : "∅"}\n`;
|
|
919
|
-
// output += "\n";
|
|
920
857
|
return output;
|
|
921
858
|
}
|
|
922
859
|
}
|
|
@@ -931,10 +868,10 @@ class HTMLElementNode extends Node {
|
|
|
931
868
|
type: data.type,
|
|
932
869
|
location: Location.from(data.location),
|
|
933
870
|
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
934
|
-
open_tag: data.open_tag ? fromSerializedNode(data.open_tag) : null,
|
|
871
|
+
open_tag: data.open_tag ? fromSerializedNode((data.open_tag)) : null,
|
|
935
872
|
tag_name: data.tag_name ? Token.from(data.tag_name) : null,
|
|
936
873
|
body: (data.body || []).map(node => fromSerializedNode(node)),
|
|
937
|
-
close_tag: data.close_tag ? fromSerializedNode(data.close_tag) : null,
|
|
874
|
+
close_tag: data.close_tag ? fromSerializedNode((data.close_tag)) : null,
|
|
938
875
|
is_void: data.is_void,
|
|
939
876
|
});
|
|
940
877
|
}
|
|
@@ -987,7 +924,6 @@ class HTMLElementNode extends Node {
|
|
|
987
924
|
output += `├── body: ${this.inspectArray(this.body, "│ ")}`;
|
|
988
925
|
output += `├── close_tag: ${this.inspectNode(this.close_tag, "│ ")}`;
|
|
989
926
|
output += `└── is_void: ${typeof this.is_void === 'boolean' ? String(this.is_void) : "∅"}\n`;
|
|
990
|
-
// output += "\n";
|
|
991
927
|
return output;
|
|
992
928
|
}
|
|
993
929
|
}
|
|
@@ -1049,29 +985,30 @@ class HTMLAttributeValueNode extends Node {
|
|
|
1049
985
|
output += `├── children: ${this.inspectArray(this.children, "│ ")}`;
|
|
1050
986
|
output += `├── close_quote: ${this.close_quote ? this.close_quote.treeInspect() : "∅"}\n`;
|
|
1051
987
|
output += `└── quoted: ${typeof this.quoted === 'boolean' ? String(this.quoted) : "∅"}\n`;
|
|
1052
|
-
// output += "\n";
|
|
1053
988
|
return output;
|
|
1054
989
|
}
|
|
1055
990
|
}
|
|
1056
991
|
class HTMLAttributeNameNode extends Node {
|
|
1057
|
-
|
|
992
|
+
children;
|
|
1058
993
|
static from(data) {
|
|
1059
994
|
return new HTMLAttributeNameNode({
|
|
1060
995
|
type: data.type,
|
|
1061
996
|
location: Location.from(data.location),
|
|
1062
997
|
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
1063
|
-
|
|
998
|
+
children: (data.children || []).map(node => fromSerializedNode(node)),
|
|
1064
999
|
});
|
|
1065
1000
|
}
|
|
1066
1001
|
constructor(props) {
|
|
1067
1002
|
super(props.type, props.location, props.errors);
|
|
1068
|
-
this.
|
|
1003
|
+
this.children = props.children;
|
|
1069
1004
|
}
|
|
1070
1005
|
accept(visitor) {
|
|
1071
1006
|
visitor.visitHTMLAttributeNameNode(this);
|
|
1072
1007
|
}
|
|
1073
1008
|
childNodes() {
|
|
1074
|
-
return [
|
|
1009
|
+
return [
|
|
1010
|
+
...this.children,
|
|
1011
|
+
];
|
|
1075
1012
|
}
|
|
1076
1013
|
compactChildNodes() {
|
|
1077
1014
|
return this.childNodes().filter(node => node !== null && node !== undefined);
|
|
@@ -1079,21 +1016,21 @@ class HTMLAttributeNameNode extends Node {
|
|
|
1079
1016
|
recursiveErrors() {
|
|
1080
1017
|
return [
|
|
1081
1018
|
...this.errors,
|
|
1019
|
+
...this.children.map(node => node.recursiveErrors()),
|
|
1082
1020
|
].flat();
|
|
1083
1021
|
}
|
|
1084
1022
|
toJSON() {
|
|
1085
1023
|
return {
|
|
1086
1024
|
...super.toJSON(),
|
|
1087
1025
|
type: "AST_HTML_ATTRIBUTE_NAME_NODE",
|
|
1088
|
-
|
|
1026
|
+
children: this.children.map(node => node.toJSON()),
|
|
1089
1027
|
};
|
|
1090
1028
|
}
|
|
1091
1029
|
treeInspect() {
|
|
1092
1030
|
let output = "";
|
|
1093
1031
|
output += `@ HTMLAttributeNameNode ${this.location.treeInspectWithLabel()}\n`;
|
|
1094
1032
|
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
1095
|
-
output += `└──
|
|
1096
|
-
// output += "\n";
|
|
1033
|
+
output += `└── children: ${this.inspectArray(this.children, " ")}`;
|
|
1097
1034
|
return output;
|
|
1098
1035
|
}
|
|
1099
1036
|
}
|
|
@@ -1106,9 +1043,9 @@ class HTMLAttributeNode extends Node {
|
|
|
1106
1043
|
type: data.type,
|
|
1107
1044
|
location: Location.from(data.location),
|
|
1108
1045
|
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
1109
|
-
name: data.name ? fromSerializedNode(data.name) : null,
|
|
1046
|
+
name: data.name ? fromSerializedNode((data.name)) : null,
|
|
1110
1047
|
equals: data.equals ? Token.from(data.equals) : null,
|
|
1111
|
-
value: data.value ? fromSerializedNode(data.value) : null,
|
|
1048
|
+
value: data.value ? fromSerializedNode((data.value)) : null,
|
|
1112
1049
|
});
|
|
1113
1050
|
}
|
|
1114
1051
|
constructor(props) {
|
|
@@ -1152,7 +1089,6 @@ class HTMLAttributeNode extends Node {
|
|
|
1152
1089
|
output += `├── name: ${this.inspectNode(this.name, "│ ")}`;
|
|
1153
1090
|
output += `├── equals: ${this.equals ? this.equals.treeInspect() : "∅"}\n`;
|
|
1154
1091
|
output += `└── value: ${this.inspectNode(this.value, " ")}`;
|
|
1155
|
-
// output += "\n";
|
|
1156
1092
|
return output;
|
|
1157
1093
|
}
|
|
1158
1094
|
}
|
|
@@ -1196,7 +1132,6 @@ class HTMLTextNode extends Node {
|
|
|
1196
1132
|
output += `@ HTMLTextNode ${this.location.treeInspectWithLabel()}\n`;
|
|
1197
1133
|
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
1198
1134
|
output += `└── content: ${this.content ? JSON.stringify(this.content) : "∅"}\n`;
|
|
1199
|
-
// output += "\n";
|
|
1200
1135
|
return output;
|
|
1201
1136
|
}
|
|
1202
1137
|
}
|
|
@@ -1253,7 +1188,6 @@ class HTMLCommentNode extends Node {
|
|
|
1253
1188
|
output += `├── comment_start: ${this.comment_start ? this.comment_start.treeInspect() : "∅"}\n`;
|
|
1254
1189
|
output += `├── children: ${this.inspectArray(this.children, "│ ")}`;
|
|
1255
1190
|
output += `└── comment_end: ${this.comment_end ? this.comment_end.treeInspect() : "∅"}\n`;
|
|
1256
|
-
// output += "\n";
|
|
1257
1191
|
return output;
|
|
1258
1192
|
}
|
|
1259
1193
|
}
|
|
@@ -1310,7 +1244,118 @@ class HTMLDoctypeNode extends Node {
|
|
|
1310
1244
|
output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
|
|
1311
1245
|
output += `├── children: ${this.inspectArray(this.children, "│ ")}`;
|
|
1312
1246
|
output += `└── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
1313
|
-
|
|
1247
|
+
return output;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
class XMLDeclarationNode extends Node {
|
|
1251
|
+
tag_opening;
|
|
1252
|
+
children;
|
|
1253
|
+
tag_closing;
|
|
1254
|
+
static from(data) {
|
|
1255
|
+
return new XMLDeclarationNode({
|
|
1256
|
+
type: data.type,
|
|
1257
|
+
location: Location.from(data.location),
|
|
1258
|
+
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
1259
|
+
tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
|
|
1260
|
+
children: (data.children || []).map(node => fromSerializedNode(node)),
|
|
1261
|
+
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
constructor(props) {
|
|
1265
|
+
super(props.type, props.location, props.errors);
|
|
1266
|
+
this.tag_opening = props.tag_opening;
|
|
1267
|
+
this.children = props.children;
|
|
1268
|
+
this.tag_closing = props.tag_closing;
|
|
1269
|
+
}
|
|
1270
|
+
accept(visitor) {
|
|
1271
|
+
visitor.visitXMLDeclarationNode(this);
|
|
1272
|
+
}
|
|
1273
|
+
childNodes() {
|
|
1274
|
+
return [
|
|
1275
|
+
...this.children,
|
|
1276
|
+
];
|
|
1277
|
+
}
|
|
1278
|
+
compactChildNodes() {
|
|
1279
|
+
return this.childNodes().filter(node => node !== null && node !== undefined);
|
|
1280
|
+
}
|
|
1281
|
+
recursiveErrors() {
|
|
1282
|
+
return [
|
|
1283
|
+
...this.errors,
|
|
1284
|
+
...this.children.map(node => node.recursiveErrors()),
|
|
1285
|
+
].flat();
|
|
1286
|
+
}
|
|
1287
|
+
toJSON() {
|
|
1288
|
+
return {
|
|
1289
|
+
...super.toJSON(),
|
|
1290
|
+
type: "AST_XML_DECLARATION_NODE",
|
|
1291
|
+
tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
|
|
1292
|
+
children: this.children.map(node => node.toJSON()),
|
|
1293
|
+
tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
treeInspect() {
|
|
1297
|
+
let output = "";
|
|
1298
|
+
output += `@ XMLDeclarationNode ${this.location.treeInspectWithLabel()}\n`;
|
|
1299
|
+
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
1300
|
+
output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
|
|
1301
|
+
output += `├── children: ${this.inspectArray(this.children, "│ ")}`;
|
|
1302
|
+
output += `└── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
1303
|
+
return output;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
class CDATANode extends Node {
|
|
1307
|
+
tag_opening;
|
|
1308
|
+
children;
|
|
1309
|
+
tag_closing;
|
|
1310
|
+
static from(data) {
|
|
1311
|
+
return new CDATANode({
|
|
1312
|
+
type: data.type,
|
|
1313
|
+
location: Location.from(data.location),
|
|
1314
|
+
errors: (data.errors || []).map(error => HerbError.from(error)),
|
|
1315
|
+
tag_opening: data.tag_opening ? Token.from(data.tag_opening) : null,
|
|
1316
|
+
children: (data.children || []).map(node => fromSerializedNode(node)),
|
|
1317
|
+
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
constructor(props) {
|
|
1321
|
+
super(props.type, props.location, props.errors);
|
|
1322
|
+
this.tag_opening = props.tag_opening;
|
|
1323
|
+
this.children = props.children;
|
|
1324
|
+
this.tag_closing = props.tag_closing;
|
|
1325
|
+
}
|
|
1326
|
+
accept(visitor) {
|
|
1327
|
+
visitor.visitCDATANode(this);
|
|
1328
|
+
}
|
|
1329
|
+
childNodes() {
|
|
1330
|
+
return [
|
|
1331
|
+
...this.children,
|
|
1332
|
+
];
|
|
1333
|
+
}
|
|
1334
|
+
compactChildNodes() {
|
|
1335
|
+
return this.childNodes().filter(node => node !== null && node !== undefined);
|
|
1336
|
+
}
|
|
1337
|
+
recursiveErrors() {
|
|
1338
|
+
return [
|
|
1339
|
+
...this.errors,
|
|
1340
|
+
...this.children.map(node => node.recursiveErrors()),
|
|
1341
|
+
].flat();
|
|
1342
|
+
}
|
|
1343
|
+
toJSON() {
|
|
1344
|
+
return {
|
|
1345
|
+
...super.toJSON(),
|
|
1346
|
+
type: "AST_CDATA_NODE",
|
|
1347
|
+
tag_opening: this.tag_opening ? this.tag_opening.toJSON() : null,
|
|
1348
|
+
children: this.children.map(node => node.toJSON()),
|
|
1349
|
+
tag_closing: this.tag_closing ? this.tag_closing.toJSON() : null,
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
treeInspect() {
|
|
1353
|
+
let output = "";
|
|
1354
|
+
output += `@ CDATANode ${this.location.treeInspectWithLabel()}\n`;
|
|
1355
|
+
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
1356
|
+
output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
|
|
1357
|
+
output += `├── children: ${this.inspectArray(this.children, "│ ")}`;
|
|
1358
|
+
output += `└── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
1314
1359
|
return output;
|
|
1315
1360
|
}
|
|
1316
1361
|
}
|
|
@@ -1354,7 +1399,6 @@ class WhitespaceNode extends Node {
|
|
|
1354
1399
|
output += `@ WhitespaceNode ${this.location.treeInspectWithLabel()}\n`;
|
|
1355
1400
|
output += `├── errors: ${this.inspectArray(this.errors, "│ ")}`;
|
|
1356
1401
|
output += `└── value: ${this.value ? this.value.treeInspect() : "∅"}\n`;
|
|
1357
|
-
// output += "\n";
|
|
1358
1402
|
return output;
|
|
1359
1403
|
}
|
|
1360
1404
|
}
|
|
@@ -1423,7 +1467,6 @@ class ERBContentNode extends Node {
|
|
|
1423
1467
|
// no-op for analyzed_ruby
|
|
1424
1468
|
output += `├── parsed: ${typeof this.parsed === 'boolean' ? String(this.parsed) : "∅"}\n`;
|
|
1425
1469
|
output += `└── valid: ${typeof this.valid === 'boolean' ? String(this.valid) : "∅"}\n`;
|
|
1426
|
-
// output += "\n";
|
|
1427
1470
|
return output;
|
|
1428
1471
|
}
|
|
1429
1472
|
}
|
|
@@ -1477,7 +1520,6 @@ class ERBEndNode extends Node {
|
|
|
1477
1520
|
output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
|
|
1478
1521
|
output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
|
|
1479
1522
|
output += `└── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
1480
|
-
// output += "\n";
|
|
1481
1523
|
return output;
|
|
1482
1524
|
}
|
|
1483
1525
|
}
|
|
@@ -1539,7 +1581,6 @@ class ERBElseNode extends Node {
|
|
|
1539
1581
|
output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
|
|
1540
1582
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
1541
1583
|
output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
|
|
1542
|
-
// output += "\n";
|
|
1543
1584
|
return output;
|
|
1544
1585
|
}
|
|
1545
1586
|
}
|
|
@@ -1559,8 +1600,8 @@ class ERBIfNode extends Node {
|
|
|
1559
1600
|
content: data.content ? Token.from(data.content) : null,
|
|
1560
1601
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
1561
1602
|
statements: (data.statements || []).map(node => fromSerializedNode(node)),
|
|
1562
|
-
subsequent: data.subsequent ? fromSerializedNode(data.subsequent) : null,
|
|
1563
|
-
end_node: data.end_node ? fromSerializedNode(data.end_node) : null,
|
|
1603
|
+
subsequent: data.subsequent ? fromSerializedNode((data.subsequent)) : null,
|
|
1604
|
+
end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
|
|
1564
1605
|
});
|
|
1565
1606
|
}
|
|
1566
1607
|
constructor(props) {
|
|
@@ -1615,7 +1656,6 @@ class ERBIfNode extends Node {
|
|
|
1615
1656
|
output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
|
|
1616
1657
|
output += `├── subsequent: ${this.inspectNode(this.subsequent, "│ ")}`;
|
|
1617
1658
|
output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
|
|
1618
|
-
// output += "\n";
|
|
1619
1659
|
return output;
|
|
1620
1660
|
}
|
|
1621
1661
|
}
|
|
@@ -1634,7 +1674,7 @@ class ERBBlockNode extends Node {
|
|
|
1634
1674
|
content: data.content ? Token.from(data.content) : null,
|
|
1635
1675
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
1636
1676
|
body: (data.body || []).map(node => fromSerializedNode(node)),
|
|
1637
|
-
end_node: data.end_node ? fromSerializedNode(data.end_node) : null,
|
|
1677
|
+
end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
|
|
1638
1678
|
});
|
|
1639
1679
|
}
|
|
1640
1680
|
constructor(props) {
|
|
@@ -1684,7 +1724,6 @@ class ERBBlockNode extends Node {
|
|
|
1684
1724
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
1685
1725
|
output += `├── body: ${this.inspectArray(this.body, "│ ")}`;
|
|
1686
1726
|
output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
|
|
1687
|
-
// output += "\n";
|
|
1688
1727
|
return output;
|
|
1689
1728
|
}
|
|
1690
1729
|
}
|
|
@@ -1746,7 +1785,6 @@ class ERBWhenNode extends Node {
|
|
|
1746
1785
|
output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
|
|
1747
1786
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
1748
1787
|
output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
|
|
1749
|
-
// output += "\n";
|
|
1750
1788
|
return output;
|
|
1751
1789
|
}
|
|
1752
1790
|
}
|
|
@@ -1768,8 +1806,8 @@ class ERBCaseNode extends Node {
|
|
|
1768
1806
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
1769
1807
|
children: (data.children || []).map(node => fromSerializedNode(node)),
|
|
1770
1808
|
conditions: (data.conditions || []).map(node => fromSerializedNode(node)),
|
|
1771
|
-
else_clause: data.else_clause ? fromSerializedNode(data.else_clause) : null,
|
|
1772
|
-
end_node: data.end_node ? fromSerializedNode(data.end_node) : null,
|
|
1809
|
+
else_clause: data.else_clause ? fromSerializedNode((data.else_clause)) : null,
|
|
1810
|
+
end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
|
|
1773
1811
|
});
|
|
1774
1812
|
}
|
|
1775
1813
|
constructor(props) {
|
|
@@ -1829,7 +1867,6 @@ class ERBCaseNode extends Node {
|
|
|
1829
1867
|
output += `├── conditions: ${this.inspectArray(this.conditions, "│ ")}`;
|
|
1830
1868
|
output += `├── else_clause: ${this.inspectNode(this.else_clause, "│ ")}`;
|
|
1831
1869
|
output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
|
|
1832
|
-
// output += "\n";
|
|
1833
1870
|
return output;
|
|
1834
1871
|
}
|
|
1835
1872
|
}
|
|
@@ -1851,8 +1888,8 @@ class ERBCaseMatchNode extends Node {
|
|
|
1851
1888
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
1852
1889
|
children: (data.children || []).map(node => fromSerializedNode(node)),
|
|
1853
1890
|
conditions: (data.conditions || []).map(node => fromSerializedNode(node)),
|
|
1854
|
-
else_clause: data.else_clause ? fromSerializedNode(data.else_clause) : null,
|
|
1855
|
-
end_node: data.end_node ? fromSerializedNode(data.end_node) : null,
|
|
1891
|
+
else_clause: data.else_clause ? fromSerializedNode((data.else_clause)) : null,
|
|
1892
|
+
end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
|
|
1856
1893
|
});
|
|
1857
1894
|
}
|
|
1858
1895
|
constructor(props) {
|
|
@@ -1912,7 +1949,6 @@ class ERBCaseMatchNode extends Node {
|
|
|
1912
1949
|
output += `├── conditions: ${this.inspectArray(this.conditions, "│ ")}`;
|
|
1913
1950
|
output += `├── else_clause: ${this.inspectNode(this.else_clause, "│ ")}`;
|
|
1914
1951
|
output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
|
|
1915
|
-
// output += "\n";
|
|
1916
1952
|
return output;
|
|
1917
1953
|
}
|
|
1918
1954
|
}
|
|
@@ -1931,7 +1967,7 @@ class ERBWhileNode extends Node {
|
|
|
1931
1967
|
content: data.content ? Token.from(data.content) : null,
|
|
1932
1968
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
1933
1969
|
statements: (data.statements || []).map(node => fromSerializedNode(node)),
|
|
1934
|
-
end_node: data.end_node ? fromSerializedNode(data.end_node) : null,
|
|
1970
|
+
end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
|
|
1935
1971
|
});
|
|
1936
1972
|
}
|
|
1937
1973
|
constructor(props) {
|
|
@@ -1981,7 +2017,6 @@ class ERBWhileNode extends Node {
|
|
|
1981
2017
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
1982
2018
|
output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
|
|
1983
2019
|
output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
|
|
1984
|
-
// output += "\n";
|
|
1985
2020
|
return output;
|
|
1986
2021
|
}
|
|
1987
2022
|
}
|
|
@@ -2000,7 +2035,7 @@ class ERBUntilNode extends Node {
|
|
|
2000
2035
|
content: data.content ? Token.from(data.content) : null,
|
|
2001
2036
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
2002
2037
|
statements: (data.statements || []).map(node => fromSerializedNode(node)),
|
|
2003
|
-
end_node: data.end_node ? fromSerializedNode(data.end_node) : null,
|
|
2038
|
+
end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
|
|
2004
2039
|
});
|
|
2005
2040
|
}
|
|
2006
2041
|
constructor(props) {
|
|
@@ -2050,7 +2085,6 @@ class ERBUntilNode extends Node {
|
|
|
2050
2085
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
2051
2086
|
output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
|
|
2052
2087
|
output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
|
|
2053
|
-
// output += "\n";
|
|
2054
2088
|
return output;
|
|
2055
2089
|
}
|
|
2056
2090
|
}
|
|
@@ -2069,7 +2103,7 @@ class ERBForNode extends Node {
|
|
|
2069
2103
|
content: data.content ? Token.from(data.content) : null,
|
|
2070
2104
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
2071
2105
|
statements: (data.statements || []).map(node => fromSerializedNode(node)),
|
|
2072
|
-
end_node: data.end_node ? fromSerializedNode(data.end_node) : null,
|
|
2106
|
+
end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
|
|
2073
2107
|
});
|
|
2074
2108
|
}
|
|
2075
2109
|
constructor(props) {
|
|
@@ -2119,7 +2153,6 @@ class ERBForNode extends Node {
|
|
|
2119
2153
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
2120
2154
|
output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
|
|
2121
2155
|
output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
|
|
2122
|
-
// output += "\n";
|
|
2123
2156
|
return output;
|
|
2124
2157
|
}
|
|
2125
2158
|
}
|
|
@@ -2138,7 +2171,7 @@ class ERBRescueNode extends Node {
|
|
|
2138
2171
|
content: data.content ? Token.from(data.content) : null,
|
|
2139
2172
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
2140
2173
|
statements: (data.statements || []).map(node => fromSerializedNode(node)),
|
|
2141
|
-
subsequent: data.subsequent ? fromSerializedNode(data.subsequent) : null,
|
|
2174
|
+
subsequent: data.subsequent ? fromSerializedNode((data.subsequent)) : null,
|
|
2142
2175
|
});
|
|
2143
2176
|
}
|
|
2144
2177
|
constructor(props) {
|
|
@@ -2188,7 +2221,6 @@ class ERBRescueNode extends Node {
|
|
|
2188
2221
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
2189
2222
|
output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
|
|
2190
2223
|
output += `└── subsequent: ${this.inspectNode(this.subsequent, " ")}`;
|
|
2191
|
-
// output += "\n";
|
|
2192
2224
|
return output;
|
|
2193
2225
|
}
|
|
2194
2226
|
}
|
|
@@ -2250,7 +2282,6 @@ class ERBEnsureNode extends Node {
|
|
|
2250
2282
|
output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
|
|
2251
2283
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
2252
2284
|
output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
|
|
2253
|
-
// output += "\n";
|
|
2254
2285
|
return output;
|
|
2255
2286
|
}
|
|
2256
2287
|
}
|
|
@@ -2272,10 +2303,10 @@ class ERBBeginNode extends Node {
|
|
|
2272
2303
|
content: data.content ? Token.from(data.content) : null,
|
|
2273
2304
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
2274
2305
|
statements: (data.statements || []).map(node => fromSerializedNode(node)),
|
|
2275
|
-
rescue_clause: data.rescue_clause ? fromSerializedNode(data.rescue_clause) : null,
|
|
2276
|
-
else_clause: data.else_clause ? fromSerializedNode(data.else_clause) : null,
|
|
2277
|
-
ensure_clause: data.ensure_clause ? fromSerializedNode(data.ensure_clause) : null,
|
|
2278
|
-
end_node: data.end_node ? fromSerializedNode(data.end_node) : null,
|
|
2306
|
+
rescue_clause: data.rescue_clause ? fromSerializedNode((data.rescue_clause)) : null,
|
|
2307
|
+
else_clause: data.else_clause ? fromSerializedNode((data.else_clause)) : null,
|
|
2308
|
+
ensure_clause: data.ensure_clause ? fromSerializedNode((data.ensure_clause)) : null,
|
|
2309
|
+
end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
|
|
2279
2310
|
});
|
|
2280
2311
|
}
|
|
2281
2312
|
constructor(props) {
|
|
@@ -2340,7 +2371,6 @@ class ERBBeginNode extends Node {
|
|
|
2340
2371
|
output += `├── else_clause: ${this.inspectNode(this.else_clause, "│ ")}`;
|
|
2341
2372
|
output += `├── ensure_clause: ${this.inspectNode(this.ensure_clause, "│ ")}`;
|
|
2342
2373
|
output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
|
|
2343
|
-
// output += "\n";
|
|
2344
2374
|
return output;
|
|
2345
2375
|
}
|
|
2346
2376
|
}
|
|
@@ -2360,8 +2390,8 @@ class ERBUnlessNode extends Node {
|
|
|
2360
2390
|
content: data.content ? Token.from(data.content) : null,
|
|
2361
2391
|
tag_closing: data.tag_closing ? Token.from(data.tag_closing) : null,
|
|
2362
2392
|
statements: (data.statements || []).map(node => fromSerializedNode(node)),
|
|
2363
|
-
else_clause: data.else_clause ? fromSerializedNode(data.else_clause) : null,
|
|
2364
|
-
end_node: data.end_node ? fromSerializedNode(data.end_node) : null,
|
|
2393
|
+
else_clause: data.else_clause ? fromSerializedNode((data.else_clause)) : null,
|
|
2394
|
+
end_node: data.end_node ? fromSerializedNode((data.end_node)) : null,
|
|
2365
2395
|
});
|
|
2366
2396
|
}
|
|
2367
2397
|
constructor(props) {
|
|
@@ -2416,7 +2446,6 @@ class ERBUnlessNode extends Node {
|
|
|
2416
2446
|
output += `├── statements: ${this.inspectArray(this.statements, "│ ")}`;
|
|
2417
2447
|
output += `├── else_clause: ${this.inspectNode(this.else_clause, "│ ")}`;
|
|
2418
2448
|
output += `└── end_node: ${this.inspectNode(this.end_node, " ")}`;
|
|
2419
|
-
// output += "\n";
|
|
2420
2449
|
return output;
|
|
2421
2450
|
}
|
|
2422
2451
|
}
|
|
@@ -2470,7 +2499,6 @@ class ERBYieldNode extends Node {
|
|
|
2470
2499
|
output += `├── tag_opening: ${this.tag_opening ? this.tag_opening.treeInspect() : "∅"}\n`;
|
|
2471
2500
|
output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
|
|
2472
2501
|
output += `└── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
2473
|
-
// output += "\n";
|
|
2474
2502
|
return output;
|
|
2475
2503
|
}
|
|
2476
2504
|
}
|
|
@@ -2532,7 +2560,6 @@ class ERBInNode extends Node {
|
|
|
2532
2560
|
output += `├── content: ${this.content ? this.content.treeInspect() : "∅"}\n`;
|
|
2533
2561
|
output += `├── tag_closing: ${this.tag_closing ? this.tag_closing.treeInspect() : "∅"}\n`;
|
|
2534
2562
|
output += `└── statements: ${this.inspectArray(this.statements, " ")}`;
|
|
2535
|
-
// output += "\n";
|
|
2536
2563
|
return output;
|
|
2537
2564
|
}
|
|
2538
2565
|
}
|
|
@@ -2542,7 +2569,6 @@ function fromSerializedNode(node) {
|
|
|
2542
2569
|
case "AST_LITERAL_NODE": return LiteralNode.from(node);
|
|
2543
2570
|
case "AST_HTML_OPEN_TAG_NODE": return HTMLOpenTagNode.from(node);
|
|
2544
2571
|
case "AST_HTML_CLOSE_TAG_NODE": return HTMLCloseTagNode.from(node);
|
|
2545
|
-
case "AST_HTML_SELF_CLOSE_TAG_NODE": return HTMLSelfCloseTagNode.from(node);
|
|
2546
2572
|
case "AST_HTML_ELEMENT_NODE": return HTMLElementNode.from(node);
|
|
2547
2573
|
case "AST_HTML_ATTRIBUTE_VALUE_NODE": return HTMLAttributeValueNode.from(node);
|
|
2548
2574
|
case "AST_HTML_ATTRIBUTE_NAME_NODE": return HTMLAttributeNameNode.from(node);
|
|
@@ -2550,6 +2576,8 @@ function fromSerializedNode(node) {
|
|
|
2550
2576
|
case "AST_HTML_TEXT_NODE": return HTMLTextNode.from(node);
|
|
2551
2577
|
case "AST_HTML_COMMENT_NODE": return HTMLCommentNode.from(node);
|
|
2552
2578
|
case "AST_HTML_DOCTYPE_NODE": return HTMLDoctypeNode.from(node);
|
|
2579
|
+
case "AST_XML_DECLARATION_NODE": return XMLDeclarationNode.from(node);
|
|
2580
|
+
case "AST_CDATA_NODE": return CDATANode.from(node);
|
|
2553
2581
|
case "AST_WHITESPACE_NODE": return WhitespaceNode.from(node);
|
|
2554
2582
|
case "AST_ERB_CONTENT_NODE": return ERBContentNode.from(node);
|
|
2555
2583
|
case "AST_ERB_END_NODE": return ERBEndNode.from(node);
|
|
@@ -2573,63 +2601,613 @@ function fromSerializedNode(node) {
|
|
|
2573
2601
|
}
|
|
2574
2602
|
}
|
|
2575
2603
|
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
visitAll(nodes) {
|
|
2585
|
-
nodes.forEach(node => node?.accept(this));
|
|
2586
|
-
}
|
|
2587
|
-
visitChildNodes(node) {
|
|
2588
|
-
node.compactChildNodes().forEach(node => node.accept(this));
|
|
2589
|
-
}
|
|
2590
|
-
visitDocumentNode(node) {
|
|
2591
|
-
this.visitChildNodes(node);
|
|
2592
|
-
}
|
|
2593
|
-
visitLiteralNode(node) {
|
|
2594
|
-
this.visitChildNodes(node);
|
|
2604
|
+
class Result {
|
|
2605
|
+
source;
|
|
2606
|
+
warnings;
|
|
2607
|
+
errors;
|
|
2608
|
+
constructor(source, warnings = [], errors = []) {
|
|
2609
|
+
this.source = source;
|
|
2610
|
+
this.warnings = warnings || [];
|
|
2611
|
+
this.errors = errors || [];
|
|
2595
2612
|
}
|
|
2596
|
-
|
|
2597
|
-
|
|
2613
|
+
/**
|
|
2614
|
+
* Determines if the parsing was successful.
|
|
2615
|
+
* @returns `true` if there are no errors, otherwise `false`.
|
|
2616
|
+
*/
|
|
2617
|
+
get successful() {
|
|
2618
|
+
return this.errors.length === 0;
|
|
2598
2619
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2620
|
+
/**
|
|
2621
|
+
* Determines if the parsing failed.
|
|
2622
|
+
* @returns `true` if there are errors, otherwise `false`.
|
|
2623
|
+
*/
|
|
2624
|
+
get failed() {
|
|
2625
|
+
return this.errors.length > 0;
|
|
2601
2626
|
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
class HerbWarning {
|
|
2630
|
+
message;
|
|
2631
|
+
location;
|
|
2632
|
+
static from(warning) {
|
|
2633
|
+
return new HerbWarning(warning.message, Location.from(warning.location));
|
|
2604
2634
|
}
|
|
2605
|
-
|
|
2606
|
-
this.
|
|
2635
|
+
constructor(message, location) {
|
|
2636
|
+
this.message = message;
|
|
2637
|
+
this.location = location;
|
|
2607
2638
|
}
|
|
2608
|
-
|
|
2609
|
-
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
/**
|
|
2642
|
+
* Represents the result of a parsing operation, extending the base `Result` class.
|
|
2643
|
+
* It contains the parsed document node, source code, warnings, and errors.
|
|
2644
|
+
*/
|
|
2645
|
+
class ParseResult extends Result {
|
|
2646
|
+
/** The document node generated from the source code. */
|
|
2647
|
+
value;
|
|
2648
|
+
/**
|
|
2649
|
+
* Creates a `ParseResult` instance from a serialized result.
|
|
2650
|
+
* @param result - The serialized parse result containing the value and source.
|
|
2651
|
+
* @returns A new `ParseResult` instance.
|
|
2652
|
+
*/
|
|
2653
|
+
static from(result) {
|
|
2654
|
+
return new ParseResult(DocumentNode.from(result.value), result.source, result.warnings.map((warning) => HerbWarning.from(warning)), result.errors.map((error) => HerbError.from(error)));
|
|
2610
2655
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2656
|
+
/**
|
|
2657
|
+
* Constructs a new `ParseResult`.
|
|
2658
|
+
* @param value - The document node.
|
|
2659
|
+
* @param source - The source code that was parsed.
|
|
2660
|
+
* @param warnings - An array of warnings encountered during parsing.
|
|
2661
|
+
* @param errors - An array of errors encountered during parsing.
|
|
2662
|
+
*/
|
|
2663
|
+
constructor(value, source, warnings = [], errors = []) {
|
|
2664
|
+
super(source, warnings, errors);
|
|
2665
|
+
this.value = value;
|
|
2613
2666
|
}
|
|
2614
|
-
|
|
2615
|
-
|
|
2667
|
+
/**
|
|
2668
|
+
* Determines if the parsing failed.
|
|
2669
|
+
* @returns `true` if there are errors, otherwise `false`.
|
|
2670
|
+
*/
|
|
2671
|
+
get failed() {
|
|
2672
|
+
// Consider errors on this result and recursively in the document tree
|
|
2673
|
+
return this.recursiveErrors().length > 0;
|
|
2616
2674
|
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2675
|
+
/**
|
|
2676
|
+
* Determines if the parsing was successful.
|
|
2677
|
+
* @returns `true` if there are no errors, otherwise `false`.
|
|
2678
|
+
*/
|
|
2679
|
+
get successful() {
|
|
2680
|
+
return !this.failed;
|
|
2619
2681
|
}
|
|
2620
|
-
|
|
2621
|
-
|
|
2682
|
+
/**
|
|
2683
|
+
* Returns a pretty-printed JSON string of the errors.
|
|
2684
|
+
* @returns A string representation of the errors.
|
|
2685
|
+
*/
|
|
2686
|
+
prettyErrors() {
|
|
2687
|
+
return JSON.stringify([...this.errors, ...this.value.errors], null, 2);
|
|
2622
2688
|
}
|
|
2623
|
-
|
|
2624
|
-
this.
|
|
2689
|
+
recursiveErrors() {
|
|
2690
|
+
return [...this.errors, ...this.value.recursiveErrors()];
|
|
2625
2691
|
}
|
|
2626
|
-
|
|
2627
|
-
|
|
2692
|
+
/**
|
|
2693
|
+
* Returns a pretty-printed string of the parse result.
|
|
2694
|
+
* @returns A string representation of the parse result.
|
|
2695
|
+
*/
|
|
2696
|
+
inspect() {
|
|
2697
|
+
return this.value.inspect();
|
|
2628
2698
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
2699
|
+
/**
|
|
2700
|
+
* Accepts a visitor to traverse the document node.
|
|
2701
|
+
* @param visitor - The visitor instance.
|
|
2702
|
+
*/
|
|
2703
|
+
visit(visitor) {
|
|
2704
|
+
visitor.visit(this.value);
|
|
2631
2705
|
}
|
|
2632
|
-
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
2709
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.6.0/templates/javascript/packages/core/src/node-type-guards.ts.erb
|
|
2710
|
+
/**
|
|
2711
|
+
* Type guard functions for AST nodes.
|
|
2712
|
+
* These functions provide type checking by combining both instanceof
|
|
2713
|
+
* checks and type string comparisons for maximum reliability across different
|
|
2714
|
+
* runtime scenarios (e.g., serialized/deserialized nodes).
|
|
2715
|
+
*/
|
|
2716
|
+
/**
|
|
2717
|
+
* Checks if a node is a DocumentNode
|
|
2718
|
+
*/
|
|
2719
|
+
function isDocumentNode(node) {
|
|
2720
|
+
return node instanceof DocumentNode || node.type === "AST_DOCUMENT_NODE";
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Checks if a node is a LiteralNode
|
|
2724
|
+
*/
|
|
2725
|
+
function isLiteralNode(node) {
|
|
2726
|
+
return node instanceof LiteralNode || node.type === "AST_LITERAL_NODE";
|
|
2727
|
+
}
|
|
2728
|
+
/**
|
|
2729
|
+
* Checks if a node is a HTMLOpenTagNode
|
|
2730
|
+
*/
|
|
2731
|
+
function isHTMLOpenTagNode(node) {
|
|
2732
|
+
return node instanceof HTMLOpenTagNode || node.type === "AST_HTML_OPEN_TAG_NODE";
|
|
2733
|
+
}
|
|
2734
|
+
/**
|
|
2735
|
+
* Checks if a node is a HTMLCloseTagNode
|
|
2736
|
+
*/
|
|
2737
|
+
function isHTMLCloseTagNode(node) {
|
|
2738
|
+
return node instanceof HTMLCloseTagNode || node.type === "AST_HTML_CLOSE_TAG_NODE";
|
|
2739
|
+
}
|
|
2740
|
+
/**
|
|
2741
|
+
* Checks if a node is a HTMLElementNode
|
|
2742
|
+
*/
|
|
2743
|
+
function isHTMLElementNode(node) {
|
|
2744
|
+
return node instanceof HTMLElementNode || node.type === "AST_HTML_ELEMENT_NODE";
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* Checks if a node is a HTMLAttributeValueNode
|
|
2748
|
+
*/
|
|
2749
|
+
function isHTMLAttributeValueNode(node) {
|
|
2750
|
+
return node instanceof HTMLAttributeValueNode || node.type === "AST_HTML_ATTRIBUTE_VALUE_NODE";
|
|
2751
|
+
}
|
|
2752
|
+
/**
|
|
2753
|
+
* Checks if a node is a HTMLAttributeNameNode
|
|
2754
|
+
*/
|
|
2755
|
+
function isHTMLAttributeNameNode(node) {
|
|
2756
|
+
return node instanceof HTMLAttributeNameNode || node.type === "AST_HTML_ATTRIBUTE_NAME_NODE";
|
|
2757
|
+
}
|
|
2758
|
+
/**
|
|
2759
|
+
* Checks if a node is a HTMLAttributeNode
|
|
2760
|
+
*/
|
|
2761
|
+
function isHTMLAttributeNode(node) {
|
|
2762
|
+
return node instanceof HTMLAttributeNode || node.type === "AST_HTML_ATTRIBUTE_NODE";
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* Checks if a node is a HTMLTextNode
|
|
2766
|
+
*/
|
|
2767
|
+
function isHTMLTextNode(node) {
|
|
2768
|
+
return node instanceof HTMLTextNode || node.type === "AST_HTML_TEXT_NODE";
|
|
2769
|
+
}
|
|
2770
|
+
/**
|
|
2771
|
+
* Checks if a node is a HTMLCommentNode
|
|
2772
|
+
*/
|
|
2773
|
+
function isHTMLCommentNode(node) {
|
|
2774
|
+
return node instanceof HTMLCommentNode || node.type === "AST_HTML_COMMENT_NODE";
|
|
2775
|
+
}
|
|
2776
|
+
/**
|
|
2777
|
+
* Checks if a node is a HTMLDoctypeNode
|
|
2778
|
+
*/
|
|
2779
|
+
function isHTMLDoctypeNode(node) {
|
|
2780
|
+
return node instanceof HTMLDoctypeNode || node.type === "AST_HTML_DOCTYPE_NODE";
|
|
2781
|
+
}
|
|
2782
|
+
/**
|
|
2783
|
+
* Checks if a node is a XMLDeclarationNode
|
|
2784
|
+
*/
|
|
2785
|
+
function isXMLDeclarationNode(node) {
|
|
2786
|
+
return node instanceof XMLDeclarationNode || node.type === "AST_XML_DECLARATION_NODE";
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Checks if a node is a CDATANode
|
|
2790
|
+
*/
|
|
2791
|
+
function isCDATANode(node) {
|
|
2792
|
+
return node instanceof CDATANode || node.type === "AST_CDATA_NODE";
|
|
2793
|
+
}
|
|
2794
|
+
/**
|
|
2795
|
+
* Checks if a node is a WhitespaceNode
|
|
2796
|
+
*/
|
|
2797
|
+
function isWhitespaceNode(node) {
|
|
2798
|
+
return node instanceof WhitespaceNode || node.type === "AST_WHITESPACE_NODE";
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* Checks if a node is a ERBContentNode
|
|
2802
|
+
*/
|
|
2803
|
+
function isERBContentNode(node) {
|
|
2804
|
+
return node instanceof ERBContentNode || node.type === "AST_ERB_CONTENT_NODE";
|
|
2805
|
+
}
|
|
2806
|
+
/**
|
|
2807
|
+
* Checks if a node is a ERBEndNode
|
|
2808
|
+
*/
|
|
2809
|
+
function isERBEndNode(node) {
|
|
2810
|
+
return node instanceof ERBEndNode || node.type === "AST_ERB_END_NODE";
|
|
2811
|
+
}
|
|
2812
|
+
/**
|
|
2813
|
+
* Checks if a node is a ERBElseNode
|
|
2814
|
+
*/
|
|
2815
|
+
function isERBElseNode(node) {
|
|
2816
|
+
return node instanceof ERBElseNode || node.type === "AST_ERB_ELSE_NODE";
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* Checks if a node is a ERBIfNode
|
|
2820
|
+
*/
|
|
2821
|
+
function isERBIfNode(node) {
|
|
2822
|
+
return node instanceof ERBIfNode || node.type === "AST_ERB_IF_NODE";
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Checks if a node is a ERBBlockNode
|
|
2826
|
+
*/
|
|
2827
|
+
function isERBBlockNode(node) {
|
|
2828
|
+
return node instanceof ERBBlockNode || node.type === "AST_ERB_BLOCK_NODE";
|
|
2829
|
+
}
|
|
2830
|
+
/**
|
|
2831
|
+
* Checks if a node is a ERBWhenNode
|
|
2832
|
+
*/
|
|
2833
|
+
function isERBWhenNode(node) {
|
|
2834
|
+
return node instanceof ERBWhenNode || node.type === "AST_ERB_WHEN_NODE";
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Checks if a node is a ERBCaseNode
|
|
2838
|
+
*/
|
|
2839
|
+
function isERBCaseNode(node) {
|
|
2840
|
+
return node instanceof ERBCaseNode || node.type === "AST_ERB_CASE_NODE";
|
|
2841
|
+
}
|
|
2842
|
+
/**
|
|
2843
|
+
* Checks if a node is a ERBCaseMatchNode
|
|
2844
|
+
*/
|
|
2845
|
+
function isERBCaseMatchNode(node) {
|
|
2846
|
+
return node instanceof ERBCaseMatchNode || node.type === "AST_ERB_CASE_MATCH_NODE";
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Checks if a node is a ERBWhileNode
|
|
2850
|
+
*/
|
|
2851
|
+
function isERBWhileNode(node) {
|
|
2852
|
+
return node instanceof ERBWhileNode || node.type === "AST_ERB_WHILE_NODE";
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Checks if a node is a ERBUntilNode
|
|
2856
|
+
*/
|
|
2857
|
+
function isERBUntilNode(node) {
|
|
2858
|
+
return node instanceof ERBUntilNode || node.type === "AST_ERB_UNTIL_NODE";
|
|
2859
|
+
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Checks if a node is a ERBForNode
|
|
2862
|
+
*/
|
|
2863
|
+
function isERBForNode(node) {
|
|
2864
|
+
return node instanceof ERBForNode || node.type === "AST_ERB_FOR_NODE";
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* Checks if a node is a ERBRescueNode
|
|
2868
|
+
*/
|
|
2869
|
+
function isERBRescueNode(node) {
|
|
2870
|
+
return node instanceof ERBRescueNode || node.type === "AST_ERB_RESCUE_NODE";
|
|
2871
|
+
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Checks if a node is a ERBEnsureNode
|
|
2874
|
+
*/
|
|
2875
|
+
function isERBEnsureNode(node) {
|
|
2876
|
+
return node instanceof ERBEnsureNode || node.type === "AST_ERB_ENSURE_NODE";
|
|
2877
|
+
}
|
|
2878
|
+
/**
|
|
2879
|
+
* Checks if a node is a ERBBeginNode
|
|
2880
|
+
*/
|
|
2881
|
+
function isERBBeginNode(node) {
|
|
2882
|
+
return node instanceof ERBBeginNode || node.type === "AST_ERB_BEGIN_NODE";
|
|
2883
|
+
}
|
|
2884
|
+
/**
|
|
2885
|
+
* Checks if a node is a ERBUnlessNode
|
|
2886
|
+
*/
|
|
2887
|
+
function isERBUnlessNode(node) {
|
|
2888
|
+
return node instanceof ERBUnlessNode || node.type === "AST_ERB_UNLESS_NODE";
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Checks if a node is a ERBYieldNode
|
|
2892
|
+
*/
|
|
2893
|
+
function isERBYieldNode(node) {
|
|
2894
|
+
return node instanceof ERBYieldNode || node.type === "AST_ERB_YIELD_NODE";
|
|
2895
|
+
}
|
|
2896
|
+
/**
|
|
2897
|
+
* Checks if a node is a ERBInNode
|
|
2898
|
+
*/
|
|
2899
|
+
function isERBInNode(node) {
|
|
2900
|
+
return node instanceof ERBInNode || node.type === "AST_ERB_IN_NODE";
|
|
2901
|
+
}
|
|
2902
|
+
/**
|
|
2903
|
+
* Checks if a node is any ERB node type
|
|
2904
|
+
*/
|
|
2905
|
+
function isERBNode(node) {
|
|
2906
|
+
return isERBContentNode(node) ||
|
|
2907
|
+
isERBEndNode(node) ||
|
|
2908
|
+
isERBElseNode(node) ||
|
|
2909
|
+
isERBIfNode(node) ||
|
|
2910
|
+
isERBBlockNode(node) ||
|
|
2911
|
+
isERBWhenNode(node) ||
|
|
2912
|
+
isERBCaseNode(node) ||
|
|
2913
|
+
isERBCaseMatchNode(node) ||
|
|
2914
|
+
isERBWhileNode(node) ||
|
|
2915
|
+
isERBUntilNode(node) ||
|
|
2916
|
+
isERBForNode(node) ||
|
|
2917
|
+
isERBRescueNode(node) ||
|
|
2918
|
+
isERBEnsureNode(node) ||
|
|
2919
|
+
isERBBeginNode(node) ||
|
|
2920
|
+
isERBUnlessNode(node) ||
|
|
2921
|
+
isERBYieldNode(node) ||
|
|
2922
|
+
isERBInNode(node);
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2925
|
+
* Map of node classes to their corresponding type guard functions
|
|
2926
|
+
*
|
|
2927
|
+
* @example
|
|
2928
|
+
* const guard = NODE_TYPE_GUARDS[HTMLTextNode]
|
|
2929
|
+
*
|
|
2930
|
+
* if (guard(node)) {
|
|
2931
|
+
* // node is HTMLTextNode
|
|
2932
|
+
* }
|
|
2933
|
+
*/
|
|
2934
|
+
const NODE_TYPE_GUARDS = new Map([
|
|
2935
|
+
[DocumentNode, isDocumentNode],
|
|
2936
|
+
[LiteralNode, isLiteralNode],
|
|
2937
|
+
[HTMLOpenTagNode, isHTMLOpenTagNode],
|
|
2938
|
+
[HTMLCloseTagNode, isHTMLCloseTagNode],
|
|
2939
|
+
[HTMLElementNode, isHTMLElementNode],
|
|
2940
|
+
[HTMLAttributeValueNode, isHTMLAttributeValueNode],
|
|
2941
|
+
[HTMLAttributeNameNode, isHTMLAttributeNameNode],
|
|
2942
|
+
[HTMLAttributeNode, isHTMLAttributeNode],
|
|
2943
|
+
[HTMLTextNode, isHTMLTextNode],
|
|
2944
|
+
[HTMLCommentNode, isHTMLCommentNode],
|
|
2945
|
+
[HTMLDoctypeNode, isHTMLDoctypeNode],
|
|
2946
|
+
[XMLDeclarationNode, isXMLDeclarationNode],
|
|
2947
|
+
[CDATANode, isCDATANode],
|
|
2948
|
+
[WhitespaceNode, isWhitespaceNode],
|
|
2949
|
+
[ERBContentNode, isERBContentNode],
|
|
2950
|
+
[ERBEndNode, isERBEndNode],
|
|
2951
|
+
[ERBElseNode, isERBElseNode],
|
|
2952
|
+
[ERBIfNode, isERBIfNode],
|
|
2953
|
+
[ERBBlockNode, isERBBlockNode],
|
|
2954
|
+
[ERBWhenNode, isERBWhenNode],
|
|
2955
|
+
[ERBCaseNode, isERBCaseNode],
|
|
2956
|
+
[ERBCaseMatchNode, isERBCaseMatchNode],
|
|
2957
|
+
[ERBWhileNode, isERBWhileNode],
|
|
2958
|
+
[ERBUntilNode, isERBUntilNode],
|
|
2959
|
+
[ERBForNode, isERBForNode],
|
|
2960
|
+
[ERBRescueNode, isERBRescueNode],
|
|
2961
|
+
[ERBEnsureNode, isERBEnsureNode],
|
|
2962
|
+
[ERBBeginNode, isERBBeginNode],
|
|
2963
|
+
[ERBUnlessNode, isERBUnlessNode],
|
|
2964
|
+
[ERBYieldNode, isERBYieldNode],
|
|
2965
|
+
[ERBInNode, isERBInNode],
|
|
2966
|
+
]);
|
|
2967
|
+
/**
|
|
2968
|
+
* Map of AST node type strings to their corresponding type guard functions
|
|
2969
|
+
*
|
|
2970
|
+
* @example
|
|
2971
|
+
* const guard = AST_TYPE_GUARDS["AST_HTML_TEXT_NODE"]
|
|
2972
|
+
*
|
|
2973
|
+
* if (guard(node)) {
|
|
2974
|
+
* // node is HTMLTextNode
|
|
2975
|
+
* }
|
|
2976
|
+
*/
|
|
2977
|
+
const AST_TYPE_GUARDS = new Map([
|
|
2978
|
+
["AST_DOCUMENT_NODE", isDocumentNode],
|
|
2979
|
+
["AST_LITERAL_NODE", isLiteralNode],
|
|
2980
|
+
["AST_HTML_OPEN_TAG_NODE", isHTMLOpenTagNode],
|
|
2981
|
+
["AST_HTML_CLOSE_TAG_NODE", isHTMLCloseTagNode],
|
|
2982
|
+
["AST_HTML_ELEMENT_NODE", isHTMLElementNode],
|
|
2983
|
+
["AST_HTML_ATTRIBUTE_VALUE_NODE", isHTMLAttributeValueNode],
|
|
2984
|
+
["AST_HTML_ATTRIBUTE_NAME_NODE", isHTMLAttributeNameNode],
|
|
2985
|
+
["AST_HTML_ATTRIBUTE_NODE", isHTMLAttributeNode],
|
|
2986
|
+
["AST_HTML_TEXT_NODE", isHTMLTextNode],
|
|
2987
|
+
["AST_HTML_COMMENT_NODE", isHTMLCommentNode],
|
|
2988
|
+
["AST_HTML_DOCTYPE_NODE", isHTMLDoctypeNode],
|
|
2989
|
+
["AST_XML_DECLARATION_NODE", isXMLDeclarationNode],
|
|
2990
|
+
["AST_CDATA_NODE", isCDATANode],
|
|
2991
|
+
["AST_WHITESPACE_NODE", isWhitespaceNode],
|
|
2992
|
+
["AST_ERB_CONTENT_NODE", isERBContentNode],
|
|
2993
|
+
["AST_ERB_END_NODE", isERBEndNode],
|
|
2994
|
+
["AST_ERB_ELSE_NODE", isERBElseNode],
|
|
2995
|
+
["AST_ERB_IF_NODE", isERBIfNode],
|
|
2996
|
+
["AST_ERB_BLOCK_NODE", isERBBlockNode],
|
|
2997
|
+
["AST_ERB_WHEN_NODE", isERBWhenNode],
|
|
2998
|
+
["AST_ERB_CASE_NODE", isERBCaseNode],
|
|
2999
|
+
["AST_ERB_CASE_MATCH_NODE", isERBCaseMatchNode],
|
|
3000
|
+
["AST_ERB_WHILE_NODE", isERBWhileNode],
|
|
3001
|
+
["AST_ERB_UNTIL_NODE", isERBUntilNode],
|
|
3002
|
+
["AST_ERB_FOR_NODE", isERBForNode],
|
|
3003
|
+
["AST_ERB_RESCUE_NODE", isERBRescueNode],
|
|
3004
|
+
["AST_ERB_ENSURE_NODE", isERBEnsureNode],
|
|
3005
|
+
["AST_ERB_BEGIN_NODE", isERBBeginNode],
|
|
3006
|
+
["AST_ERB_UNLESS_NODE", isERBUnlessNode],
|
|
3007
|
+
["AST_ERB_YIELD_NODE", isERBYieldNode],
|
|
3008
|
+
["AST_ERB_IN_NODE", isERBInNode],
|
|
3009
|
+
]);
|
|
3010
|
+
/**
|
|
3011
|
+
* Checks if a node matches any of the provided type identifiers with proper type narrowing
|
|
3012
|
+
* Supports AST type strings, node classes, or type guard functions
|
|
3013
|
+
*
|
|
3014
|
+
* @example
|
|
3015
|
+
* if (isAnyOf(node, "AST_HTML_TEXT_NODE", "AST_LITERAL_NODE")) {
|
|
3016
|
+
* // node is narrowed to HTMLTextNode | LiteralNode
|
|
3017
|
+
* }
|
|
3018
|
+
*
|
|
3019
|
+
* @example
|
|
3020
|
+
* if (isAnyOf(node, HTMLTextNode, LiteralNode)) {
|
|
3021
|
+
* // node is narrowed to HTMLTextNode | LiteralNode
|
|
3022
|
+
* }
|
|
3023
|
+
*/
|
|
3024
|
+
function isAnyOf(node, ...types) {
|
|
3025
|
+
return types.some(type => {
|
|
3026
|
+
if (typeof type === 'string') {
|
|
3027
|
+
return isNode(node, type);
|
|
3028
|
+
}
|
|
3029
|
+
else if (typeof type === 'function' && type.prototype && type.prototype.constructor === type && NODE_TYPE_GUARDS.has(type)) {
|
|
3030
|
+
return isNode(node, type);
|
|
3031
|
+
}
|
|
3032
|
+
else if (typeof type === 'function') {
|
|
3033
|
+
return type(node);
|
|
3034
|
+
}
|
|
3035
|
+
else {
|
|
3036
|
+
return false;
|
|
3037
|
+
}
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
/**
|
|
3041
|
+
* Checks if a node does NOT match any of the provided type identifiers
|
|
3042
|
+
* Supports AST type strings, node classes, or type guard functions
|
|
3043
|
+
* This is the logical inverse of isAnyOf
|
|
3044
|
+
*
|
|
3045
|
+
* @example
|
|
3046
|
+
* if (isNoneOf(node, "AST_HTML_TEXT_NODE", "AST_LITERAL_NODE")) {
|
|
3047
|
+
* // node is neither HTMLTextNode nor LiteralNode
|
|
3048
|
+
* }
|
|
3049
|
+
*
|
|
3050
|
+
* @example
|
|
3051
|
+
* if (isNoneOf(node, HTMLTextNode, LiteralNode)) {
|
|
3052
|
+
* // node is neither HTMLTextNode nor LiteralNode
|
|
3053
|
+
* }
|
|
3054
|
+
*
|
|
3055
|
+
* @example
|
|
3056
|
+
* if (isNoneOf(node, isHTMLTextNode, isLiteralNode)) {
|
|
3057
|
+
* // node is neither HTMLTextNode nor LiteralNode
|
|
3058
|
+
* }
|
|
3059
|
+
*/
|
|
3060
|
+
function isNoneOf(node, ...types) {
|
|
3061
|
+
return !isAnyOf(node, ...types);
|
|
3062
|
+
}
|
|
3063
|
+
function filterNodes(nodes, ...types) {
|
|
3064
|
+
if (!nodes)
|
|
3065
|
+
return [];
|
|
3066
|
+
return nodes.filter(node => isAnyOf(node, ...types));
|
|
3067
|
+
}
|
|
3068
|
+
function isNode(node, type) {
|
|
3069
|
+
if (typeof type === 'string') {
|
|
3070
|
+
const guard = AST_TYPE_GUARDS.get(type);
|
|
3071
|
+
return guard ? guard(node) : false;
|
|
3072
|
+
}
|
|
3073
|
+
else if (typeof type === 'function') {
|
|
3074
|
+
const guard = NODE_TYPE_GUARDS.get(type);
|
|
3075
|
+
return guard ? guard(node) : false;
|
|
3076
|
+
}
|
|
3077
|
+
else {
|
|
3078
|
+
return false;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
function isToken(object) {
|
|
3082
|
+
return (object instanceof Token) || (object?.constructor?.name === "Token" && "value" in object) || object.type?.startsWith('TOKEN_');
|
|
3083
|
+
}
|
|
3084
|
+
function isParseResult(object) {
|
|
3085
|
+
return (object instanceof ParseResult) || (object?.constructor?.name === "ParseResult" && "value" in object);
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
/**
|
|
3089
|
+
* Checks if a node is an ERB output node (generates content: <%= %> or <%== %>)
|
|
3090
|
+
*/
|
|
3091
|
+
function isERBOutputNode(node) {
|
|
3092
|
+
return isNode(node, ERBContentNode) && ["<%=", "<%=="].includes(node.tag_opening?.value);
|
|
3093
|
+
}
|
|
3094
|
+
/**
|
|
3095
|
+
* Checks if a node is a non-output ERB node (control flow: <% %>)
|
|
3096
|
+
*/
|
|
3097
|
+
function isERBControlFlowNode(node) {
|
|
3098
|
+
return isAnyOf(node, ERBIfNode, ERBUnlessNode, ERBBlockNode, ERBCaseNode, ERBCaseMatchNode, ERBWhileNode, ERBForNode, ERBBeginNode);
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Checks if an array of nodes contains any ERB output nodes (dynamic content)
|
|
3102
|
+
*/
|
|
3103
|
+
function hasERBOutput(nodes) {
|
|
3104
|
+
return nodes.some(isERBOutputNode);
|
|
3105
|
+
}
|
|
3106
|
+
/**
|
|
3107
|
+
* Extracts a combined string from nodes, including ERB content
|
|
3108
|
+
* For ERB nodes, includes the full tag syntax (e.g., "<%= foo %>")
|
|
3109
|
+
* This is useful for debugging or displaying the full attribute name
|
|
3110
|
+
*/
|
|
3111
|
+
function getCombinedStringFromNodes(nodes) {
|
|
3112
|
+
return nodes.map(node => {
|
|
3113
|
+
if (isLiteralNode(node)) {
|
|
3114
|
+
return node.content;
|
|
3115
|
+
}
|
|
3116
|
+
else if (isERBContentNode(node)) {
|
|
3117
|
+
const opening = node.tag_opening?.value || "";
|
|
3118
|
+
const content = node.content?.value || "";
|
|
3119
|
+
const closing = node.tag_closing?.value || "";
|
|
3120
|
+
return `${opening}${content}${closing}`;
|
|
3121
|
+
}
|
|
3122
|
+
else {
|
|
3123
|
+
// For other node types, return a placeholder or empty string
|
|
3124
|
+
return `[${node.type}]`;
|
|
3125
|
+
}
|
|
3126
|
+
}).join("");
|
|
3127
|
+
}
|
|
3128
|
+
/**
|
|
3129
|
+
* Gets the combined string representation of an HTML attribute name node
|
|
3130
|
+
* This includes both static and dynamic content, useful for debugging
|
|
3131
|
+
*/
|
|
3132
|
+
function getCombinedAttributeName(attributeNameNode) {
|
|
3133
|
+
if (!attributeNameNode.children) {
|
|
3134
|
+
return "";
|
|
3135
|
+
}
|
|
3136
|
+
return getCombinedStringFromNodes(attributeNameNode.children);
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Gets the tag name of an HTML element node
|
|
3140
|
+
*/
|
|
3141
|
+
function getTagName(node) {
|
|
3142
|
+
return node.tag_name?.value ?? "";
|
|
3143
|
+
}
|
|
3144
|
+
/**
|
|
3145
|
+
* Check if a node is a comment (HTML comment or ERB comment)
|
|
3146
|
+
*/
|
|
3147
|
+
function isCommentNode(node) {
|
|
3148
|
+
return isNode(node, HTMLCommentNode) || (isERBNode(node) && !isERBControlFlowNode(node));
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
3152
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.6.0/templates/javascript/packages/core/src/visitor.ts.erb
|
|
3153
|
+
class Visitor {
|
|
3154
|
+
visit(node) {
|
|
3155
|
+
if (!node)
|
|
3156
|
+
return;
|
|
3157
|
+
node.accept(this);
|
|
3158
|
+
}
|
|
3159
|
+
visitAll(nodes) {
|
|
3160
|
+
nodes.forEach(node => node?.accept(this));
|
|
3161
|
+
}
|
|
3162
|
+
visitChildNodes(node) {
|
|
3163
|
+
node.compactChildNodes().forEach(node => node.accept(this));
|
|
3164
|
+
}
|
|
3165
|
+
visitDocumentNode(node) {
|
|
3166
|
+
this.visitChildNodes(node);
|
|
3167
|
+
}
|
|
3168
|
+
visitLiteralNode(node) {
|
|
3169
|
+
this.visitChildNodes(node);
|
|
3170
|
+
}
|
|
3171
|
+
visitHTMLOpenTagNode(node) {
|
|
3172
|
+
this.visitChildNodes(node);
|
|
3173
|
+
}
|
|
3174
|
+
visitHTMLCloseTagNode(node) {
|
|
3175
|
+
this.visitChildNodes(node);
|
|
3176
|
+
}
|
|
3177
|
+
visitHTMLElementNode(node) {
|
|
3178
|
+
this.visitChildNodes(node);
|
|
3179
|
+
}
|
|
3180
|
+
visitHTMLAttributeValueNode(node) {
|
|
3181
|
+
this.visitChildNodes(node);
|
|
3182
|
+
}
|
|
3183
|
+
visitHTMLAttributeNameNode(node) {
|
|
3184
|
+
this.visitChildNodes(node);
|
|
3185
|
+
}
|
|
3186
|
+
visitHTMLAttributeNode(node) {
|
|
3187
|
+
this.visitChildNodes(node);
|
|
3188
|
+
}
|
|
3189
|
+
visitHTMLTextNode(node) {
|
|
3190
|
+
this.visitChildNodes(node);
|
|
3191
|
+
}
|
|
3192
|
+
visitHTMLCommentNode(node) {
|
|
3193
|
+
this.visitChildNodes(node);
|
|
3194
|
+
}
|
|
3195
|
+
visitHTMLDoctypeNode(node) {
|
|
3196
|
+
this.visitChildNodes(node);
|
|
3197
|
+
}
|
|
3198
|
+
visitXMLDeclarationNode(node) {
|
|
3199
|
+
this.visitChildNodes(node);
|
|
3200
|
+
}
|
|
3201
|
+
visitCDATANode(node) {
|
|
3202
|
+
this.visitChildNodes(node);
|
|
3203
|
+
}
|
|
3204
|
+
visitWhitespaceNode(node) {
|
|
3205
|
+
this.visitChildNodes(node);
|
|
3206
|
+
}
|
|
3207
|
+
visitERBContentNode(node) {
|
|
3208
|
+
this.visitChildNodes(node);
|
|
3209
|
+
}
|
|
3210
|
+
visitERBEndNode(node) {
|
|
2633
3211
|
this.visitChildNodes(node);
|
|
2634
3212
|
}
|
|
2635
3213
|
visitERBElseNode(node) {
|
|
@@ -2674,51 +3252,573 @@ class Visitor {
|
|
|
2674
3252
|
visitERBYieldNode(node) {
|
|
2675
3253
|
this.visitChildNodes(node);
|
|
2676
3254
|
}
|
|
2677
|
-
visitERBInNode(node) {
|
|
2678
|
-
this.visitChildNodes(node);
|
|
3255
|
+
visitERBInNode(node) {
|
|
3256
|
+
this.visitChildNodes(node);
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
class PrintContext {
|
|
3261
|
+
output = "";
|
|
3262
|
+
indentLevel = 0;
|
|
3263
|
+
currentColumn = 0;
|
|
3264
|
+
preserveStack = [];
|
|
3265
|
+
/**
|
|
3266
|
+
* Write text to the output
|
|
3267
|
+
*/
|
|
3268
|
+
write(text) {
|
|
3269
|
+
this.output += text;
|
|
3270
|
+
this.currentColumn += text.length;
|
|
3271
|
+
}
|
|
3272
|
+
/**
|
|
3273
|
+
* Write text and update column tracking for newlines
|
|
3274
|
+
*/
|
|
3275
|
+
writeWithColumnTracking(text) {
|
|
3276
|
+
this.output += text;
|
|
3277
|
+
const lines = text.split('\n');
|
|
3278
|
+
if (lines.length > 1) {
|
|
3279
|
+
this.currentColumn = lines[lines.length - 1].length;
|
|
3280
|
+
}
|
|
3281
|
+
else {
|
|
3282
|
+
this.currentColumn += text.length;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
/**
|
|
3286
|
+
* Increase indentation level
|
|
3287
|
+
*/
|
|
3288
|
+
indent() {
|
|
3289
|
+
this.indentLevel++;
|
|
3290
|
+
}
|
|
3291
|
+
/**
|
|
3292
|
+
* Decrease indentation level
|
|
3293
|
+
*/
|
|
3294
|
+
dedent() {
|
|
3295
|
+
if (this.indentLevel > 0) {
|
|
3296
|
+
this.indentLevel--;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
/**
|
|
3300
|
+
* Enter a tag that may preserve whitespace
|
|
3301
|
+
*/
|
|
3302
|
+
enterTag(tagName) {
|
|
3303
|
+
this.preserveStack.push(tagName.toLowerCase());
|
|
3304
|
+
}
|
|
3305
|
+
/**
|
|
3306
|
+
* Exit the current tag
|
|
3307
|
+
*/
|
|
3308
|
+
exitTag() {
|
|
3309
|
+
this.preserveStack.pop();
|
|
3310
|
+
}
|
|
3311
|
+
/**
|
|
3312
|
+
* Check if we're at the start of a line
|
|
3313
|
+
*/
|
|
3314
|
+
isAtStartOfLine() {
|
|
3315
|
+
return this.currentColumn === 0;
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Get current indentation level
|
|
3319
|
+
*/
|
|
3320
|
+
getCurrentIndentLevel() {
|
|
3321
|
+
return this.indentLevel;
|
|
3322
|
+
}
|
|
3323
|
+
/**
|
|
3324
|
+
* Get current column position
|
|
3325
|
+
*/
|
|
3326
|
+
getCurrentColumn() {
|
|
3327
|
+
return this.currentColumn;
|
|
3328
|
+
}
|
|
3329
|
+
/**
|
|
3330
|
+
* Get the current tag stack (for debugging)
|
|
3331
|
+
*/
|
|
3332
|
+
getTagStack() {
|
|
3333
|
+
return [...this.preserveStack];
|
|
3334
|
+
}
|
|
3335
|
+
/**
|
|
3336
|
+
* Get the complete output string
|
|
3337
|
+
*/
|
|
3338
|
+
getOutput() {
|
|
3339
|
+
return this.output;
|
|
3340
|
+
}
|
|
3341
|
+
/**
|
|
3342
|
+
* Reset the context for reuse
|
|
3343
|
+
*/
|
|
3344
|
+
reset() {
|
|
3345
|
+
this.output = "";
|
|
3346
|
+
this.indentLevel = 0;
|
|
3347
|
+
this.currentColumn = 0;
|
|
3348
|
+
this.preserveStack = [];
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
/**
|
|
3353
|
+
* Default print options used when none are provided
|
|
3354
|
+
*/
|
|
3355
|
+
const DEFAULT_PRINT_OPTIONS = {
|
|
3356
|
+
ignoreErrors: false
|
|
3357
|
+
};
|
|
3358
|
+
class Printer extends Visitor {
|
|
3359
|
+
context = new PrintContext();
|
|
3360
|
+
/**
|
|
3361
|
+
* Static method to print a node without creating an instance
|
|
3362
|
+
*
|
|
3363
|
+
* @param input - The AST Node, Token, or ParseResult to print
|
|
3364
|
+
* @param options - Print options to control behavior
|
|
3365
|
+
* @returns The printed string representation of the input
|
|
3366
|
+
* @throws {Error} When node has parse errors and ignoreErrors is false
|
|
3367
|
+
*/
|
|
3368
|
+
static print(input, options = DEFAULT_PRINT_OPTIONS) {
|
|
3369
|
+
const printer = new this();
|
|
3370
|
+
return printer.print(input, options);
|
|
3371
|
+
}
|
|
3372
|
+
/**
|
|
3373
|
+
* Print a node, token, or parse result to a string
|
|
3374
|
+
*
|
|
3375
|
+
* @param input - The AST Node, Token, or ParseResult to print
|
|
3376
|
+
* @param options - Print options to control behavior
|
|
3377
|
+
* @returns The printed string representation of the input
|
|
3378
|
+
* @throws {Error} When node has parse errors and ignoreErrors is false
|
|
3379
|
+
*/
|
|
3380
|
+
print(input, options = DEFAULT_PRINT_OPTIONS) {
|
|
3381
|
+
if (isToken(input)) {
|
|
3382
|
+
return input.value;
|
|
3383
|
+
}
|
|
3384
|
+
const node = isParseResult(input) ? input.value : input;
|
|
3385
|
+
if (options.ignoreErrors === false && node.recursiveErrors().length > 0) {
|
|
3386
|
+
throw new Error(`Cannot print the node (${node.type}) since it or any of its children has parse errors. Either pass in a valid Node or call \`print()\` using \`print(node, { ignoreErrors: true })\``);
|
|
3387
|
+
}
|
|
3388
|
+
this.context.reset();
|
|
3389
|
+
this.visit(node);
|
|
3390
|
+
return this.context.getOutput();
|
|
3391
|
+
}
|
|
3392
|
+
visitDocumentNode(node) {
|
|
3393
|
+
this.visitChildNodes(node);
|
|
3394
|
+
}
|
|
3395
|
+
visitLiteralNode(node) {
|
|
3396
|
+
this.context.write(node.content);
|
|
3397
|
+
}
|
|
3398
|
+
visitHTMLTextNode(node) {
|
|
3399
|
+
this.write(node.content);
|
|
3400
|
+
}
|
|
3401
|
+
visitWhitespaceNode(node) {
|
|
3402
|
+
if (node.value) {
|
|
3403
|
+
this.write(node.value.value);
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
visitHTMLOpenTagNode(node) {
|
|
3407
|
+
if (node.tag_opening) {
|
|
3408
|
+
this.context.write(node.tag_opening.value);
|
|
3409
|
+
}
|
|
3410
|
+
if (node.tag_name) {
|
|
3411
|
+
this.context.write(node.tag_name.value);
|
|
3412
|
+
}
|
|
3413
|
+
this.visitChildNodes(node);
|
|
3414
|
+
if (node.tag_closing) {
|
|
3415
|
+
this.context.write(node.tag_closing.value);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
visitHTMLCloseTagNode(node) {
|
|
3419
|
+
if (node.tag_opening) {
|
|
3420
|
+
this.context.write(node.tag_opening.value);
|
|
3421
|
+
}
|
|
3422
|
+
if (node.tag_name) {
|
|
3423
|
+
this.context.write(node.tag_name.value);
|
|
3424
|
+
}
|
|
3425
|
+
if (node.tag_closing) {
|
|
3426
|
+
this.context.write(node.tag_closing.value);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
visitHTMLElementNode(node) {
|
|
3430
|
+
const tagName = node.tag_name?.value;
|
|
3431
|
+
if (tagName) {
|
|
3432
|
+
this.context.enterTag(tagName);
|
|
3433
|
+
}
|
|
3434
|
+
if (node.open_tag) {
|
|
3435
|
+
this.visit(node.open_tag);
|
|
3436
|
+
}
|
|
3437
|
+
if (node.body) {
|
|
3438
|
+
node.body.forEach(child => this.visit(child));
|
|
3439
|
+
}
|
|
3440
|
+
if (node.close_tag) {
|
|
3441
|
+
this.visit(node.close_tag);
|
|
3442
|
+
}
|
|
3443
|
+
if (tagName) {
|
|
3444
|
+
this.context.exitTag();
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
visitHTMLAttributeNode(node) {
|
|
3448
|
+
if (node.name) {
|
|
3449
|
+
this.visit(node.name);
|
|
3450
|
+
}
|
|
3451
|
+
if (node.equals) {
|
|
3452
|
+
this.context.write(node.equals.value);
|
|
3453
|
+
}
|
|
3454
|
+
if (node.equals && node.value) {
|
|
3455
|
+
this.visit(node.value);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
visitHTMLAttributeNameNode(node) {
|
|
3459
|
+
this.visitChildNodes(node);
|
|
3460
|
+
}
|
|
3461
|
+
visitHTMLAttributeValueNode(node) {
|
|
3462
|
+
if (node.quoted && node.open_quote) {
|
|
3463
|
+
this.context.write(node.open_quote.value);
|
|
3464
|
+
}
|
|
3465
|
+
this.visitChildNodes(node);
|
|
3466
|
+
if (node.quoted && node.close_quote) {
|
|
3467
|
+
this.context.write(node.close_quote.value);
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
visitHTMLCommentNode(node) {
|
|
3471
|
+
if (node.comment_start) {
|
|
3472
|
+
this.context.write(node.comment_start.value);
|
|
3473
|
+
}
|
|
3474
|
+
this.visitChildNodes(node);
|
|
3475
|
+
if (node.comment_end) {
|
|
3476
|
+
this.context.write(node.comment_end.value);
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
visitHTMLDoctypeNode(node) {
|
|
3480
|
+
if (node.tag_opening) {
|
|
3481
|
+
this.context.write(node.tag_opening.value);
|
|
3482
|
+
}
|
|
3483
|
+
this.visitChildNodes(node);
|
|
3484
|
+
if (node.tag_closing) {
|
|
3485
|
+
this.context.write(node.tag_closing.value);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
visitXMLDeclarationNode(node) {
|
|
3489
|
+
if (node.tag_opening) {
|
|
3490
|
+
this.context.write(node.tag_opening.value);
|
|
3491
|
+
}
|
|
3492
|
+
this.visitChildNodes(node);
|
|
3493
|
+
if (node.tag_closing) {
|
|
3494
|
+
this.context.write(node.tag_closing.value);
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
visitCDATANode(node) {
|
|
3498
|
+
if (node.tag_opening) {
|
|
3499
|
+
this.context.write(node.tag_opening.value);
|
|
3500
|
+
}
|
|
3501
|
+
this.visitChildNodes(node);
|
|
3502
|
+
if (node.tag_closing) {
|
|
3503
|
+
this.context.write(node.tag_closing.value);
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
visitERBContentNode(node) {
|
|
3507
|
+
this.printERBNode(node);
|
|
3508
|
+
}
|
|
3509
|
+
visitERBIfNode(node) {
|
|
3510
|
+
this.printERBNode(node);
|
|
3511
|
+
if (node.statements) {
|
|
3512
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3513
|
+
}
|
|
3514
|
+
if (node.subsequent) {
|
|
3515
|
+
this.visit(node.subsequent);
|
|
3516
|
+
}
|
|
3517
|
+
if (node.end_node) {
|
|
3518
|
+
this.visit(node.end_node);
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
visitERBElseNode(node) {
|
|
3522
|
+
this.printERBNode(node);
|
|
3523
|
+
if (node.statements) {
|
|
3524
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
visitERBEndNode(node) {
|
|
3528
|
+
this.printERBNode(node);
|
|
3529
|
+
}
|
|
3530
|
+
visitERBBlockNode(node) {
|
|
3531
|
+
this.printERBNode(node);
|
|
3532
|
+
if (node.body) {
|
|
3533
|
+
node.body.forEach(child => this.visit(child));
|
|
3534
|
+
}
|
|
3535
|
+
if (node.end_node) {
|
|
3536
|
+
this.visit(node.end_node);
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
visitERBCaseNode(node) {
|
|
3540
|
+
this.printERBNode(node);
|
|
3541
|
+
if (node.children) {
|
|
3542
|
+
node.children.forEach(child => this.visit(child));
|
|
3543
|
+
}
|
|
3544
|
+
if (node.conditions) {
|
|
3545
|
+
node.conditions.forEach(condition => this.visit(condition));
|
|
3546
|
+
}
|
|
3547
|
+
if (node.else_clause) {
|
|
3548
|
+
this.visit(node.else_clause);
|
|
3549
|
+
}
|
|
3550
|
+
if (node.end_node) {
|
|
3551
|
+
this.visit(node.end_node);
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
visitERBWhenNode(node) {
|
|
3555
|
+
this.printERBNode(node);
|
|
3556
|
+
if (node.statements) {
|
|
3557
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
visitERBWhileNode(node) {
|
|
3561
|
+
this.printERBNode(node);
|
|
3562
|
+
if (node.statements) {
|
|
3563
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3564
|
+
}
|
|
3565
|
+
if (node.end_node) {
|
|
3566
|
+
this.visit(node.end_node);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
visitERBUntilNode(node) {
|
|
3570
|
+
this.printERBNode(node);
|
|
3571
|
+
if (node.statements) {
|
|
3572
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3573
|
+
}
|
|
3574
|
+
if (node.end_node) {
|
|
3575
|
+
this.visit(node.end_node);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
visitERBForNode(node) {
|
|
3579
|
+
this.printERBNode(node);
|
|
3580
|
+
if (node.statements) {
|
|
3581
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3582
|
+
}
|
|
3583
|
+
if (node.end_node) {
|
|
3584
|
+
this.visit(node.end_node);
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
visitERBBeginNode(node) {
|
|
3588
|
+
this.printERBNode(node);
|
|
3589
|
+
if (node.statements) {
|
|
3590
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3591
|
+
}
|
|
3592
|
+
if (node.rescue_clause) {
|
|
3593
|
+
this.visit(node.rescue_clause);
|
|
3594
|
+
}
|
|
3595
|
+
if (node.else_clause) {
|
|
3596
|
+
this.visit(node.else_clause);
|
|
3597
|
+
}
|
|
3598
|
+
if (node.ensure_clause) {
|
|
3599
|
+
this.visit(node.ensure_clause);
|
|
3600
|
+
}
|
|
3601
|
+
if (node.end_node) {
|
|
3602
|
+
this.visit(node.end_node);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
visitERBRescueNode(node) {
|
|
3606
|
+
this.printERBNode(node);
|
|
3607
|
+
if (node.statements) {
|
|
3608
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3609
|
+
}
|
|
3610
|
+
if (node.subsequent) {
|
|
3611
|
+
this.visit(node.subsequent);
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
visitERBEnsureNode(node) {
|
|
3615
|
+
this.printERBNode(node);
|
|
3616
|
+
if (node.statements) {
|
|
3617
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
visitERBUnlessNode(node) {
|
|
3621
|
+
this.printERBNode(node);
|
|
3622
|
+
if (node.statements) {
|
|
3623
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3624
|
+
}
|
|
3625
|
+
if (node.else_clause) {
|
|
3626
|
+
this.visit(node.else_clause);
|
|
3627
|
+
}
|
|
3628
|
+
if (node.end_node) {
|
|
3629
|
+
this.visit(node.end_node);
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
visitERBYieldNode(node) {
|
|
3633
|
+
this.printERBNode(node);
|
|
3634
|
+
}
|
|
3635
|
+
visitERBInNode(node) {
|
|
3636
|
+
this.printERBNode(node);
|
|
3637
|
+
if (node.statements) {
|
|
3638
|
+
node.statements.forEach(statement => this.visit(statement));
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
visitERBCaseMatchNode(node) {
|
|
3642
|
+
this.printERBNode(node);
|
|
3643
|
+
if (node.children) {
|
|
3644
|
+
node.children.forEach(child => this.visit(child));
|
|
3645
|
+
}
|
|
3646
|
+
if (node.conditions) {
|
|
3647
|
+
node.conditions.forEach(condition => this.visit(condition));
|
|
3648
|
+
}
|
|
3649
|
+
if (node.else_clause) {
|
|
3650
|
+
this.visit(node.else_clause);
|
|
3651
|
+
}
|
|
3652
|
+
if (node.end_node) {
|
|
3653
|
+
this.visit(node.end_node);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
/**
|
|
3657
|
+
* Print ERB node tags and content
|
|
3658
|
+
*/
|
|
3659
|
+
printERBNode(node) {
|
|
3660
|
+
if (node.tag_opening) {
|
|
3661
|
+
this.context.write(node.tag_opening.value);
|
|
3662
|
+
}
|
|
3663
|
+
if (node.content) {
|
|
3664
|
+
this.context.write(node.content.value);
|
|
3665
|
+
}
|
|
3666
|
+
if (node.tag_closing) {
|
|
3667
|
+
this.context.write(node.tag_closing.value);
|
|
3668
|
+
}
|
|
2679
3669
|
}
|
|
3670
|
+
write(content) {
|
|
3671
|
+
this.context.write(content);
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
/**
|
|
3676
|
+
* IdentityPrinter - Provides lossless reconstruction of the original source
|
|
3677
|
+
*
|
|
3678
|
+
* This printer aims to reconstruct the original input as faithfully as possible,
|
|
3679
|
+
* preserving all whitespace, formatting, and structure. It's useful for:
|
|
3680
|
+
* - Testing parser accuracy (input should equal output)
|
|
3681
|
+
* - Baseline printing before applying transformations
|
|
3682
|
+
* - Verifying AST round-trip fidelity
|
|
3683
|
+
*/
|
|
3684
|
+
class IdentityPrinter extends Printer {
|
|
2680
3685
|
}
|
|
2681
3686
|
|
|
3687
|
+
// TODO: we can probably expand this list with more tags/attributes
|
|
3688
|
+
const FORMATTABLE_ATTRIBUTES = {
|
|
3689
|
+
'*': ['class'],
|
|
3690
|
+
'img': ['srcset', 'sizes']
|
|
3691
|
+
};
|
|
2682
3692
|
/**
|
|
2683
3693
|
* Printer traverses the Herb AST using the Visitor pattern
|
|
2684
3694
|
* and emits a formatted string with proper indentation, line breaks, and attribute wrapping.
|
|
2685
3695
|
*/
|
|
2686
|
-
class
|
|
3696
|
+
class FormatPrinter extends Printer {
|
|
3697
|
+
/**
|
|
3698
|
+
* @deprecated integrate indentWidth into this.options and update FormatOptions to extend from @herb-tools/printer options
|
|
3699
|
+
*/
|
|
2687
3700
|
indentWidth;
|
|
3701
|
+
/**
|
|
3702
|
+
* @deprecated integrate maxLineLength into this.options and update FormatOptions to extend from @herb-tools/printer options
|
|
3703
|
+
*/
|
|
2688
3704
|
maxLineLength;
|
|
2689
|
-
|
|
3705
|
+
/**
|
|
3706
|
+
* @deprecated refactor to use @herb-tools/printer infrastructre (or rework printer use push and this.lines)
|
|
3707
|
+
*/
|
|
2690
3708
|
lines = [];
|
|
2691
3709
|
indentLevel = 0;
|
|
2692
3710
|
inlineMode = false;
|
|
2693
|
-
|
|
3711
|
+
currentAttributeName = null;
|
|
3712
|
+
elementStack = [];
|
|
3713
|
+
elementFormattingAnalysis = new Map();
|
|
3714
|
+
source;
|
|
3715
|
+
// TODO: extract
|
|
2694
3716
|
static INLINE_ELEMENTS = new Set([
|
|
2695
3717
|
'a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'cite', 'code',
|
|
2696
3718
|
'dfn', 'em', 'i', 'img', 'kbd', 'label', 'map', 'object', 'q',
|
|
2697
3719
|
'samp', 'small', 'span', 'strong', 'sub', 'sup',
|
|
2698
3720
|
'tt', 'var', 'del', 'ins', 'mark', 's', 'u', 'time', 'wbr'
|
|
2699
3721
|
]);
|
|
3722
|
+
static SPACEABLE_CONTAINERS = new Set([
|
|
3723
|
+
'div', 'section', 'article', 'main', 'header', 'footer', 'aside',
|
|
3724
|
+
'figure', 'details', 'summary', 'dialog', 'fieldset'
|
|
3725
|
+
]);
|
|
3726
|
+
static TIGHT_GROUP_PARENTS = new Set([
|
|
3727
|
+
'ul', 'ol', 'nav', 'select', 'datalist', 'optgroup', 'tr', 'thead', 'tbody', 'tfoot'
|
|
3728
|
+
]);
|
|
3729
|
+
static TIGHT_GROUP_CHILDREN = new Set([
|
|
3730
|
+
'li', 'option', 'td', 'th', 'dt', 'dd'
|
|
3731
|
+
]);
|
|
3732
|
+
static SPACING_THRESHOLD = 3;
|
|
2700
3733
|
constructor(source, options) {
|
|
2701
3734
|
super();
|
|
2702
3735
|
this.source = source;
|
|
2703
3736
|
this.indentWidth = options.indentWidth;
|
|
2704
3737
|
this.maxLineLength = options.maxLineLength;
|
|
2705
3738
|
}
|
|
2706
|
-
print(
|
|
2707
|
-
if (
|
|
2708
|
-
return
|
|
2709
|
-
|
|
2710
|
-
|
|
3739
|
+
print(input) {
|
|
3740
|
+
if (isToken(input))
|
|
3741
|
+
return input.value;
|
|
3742
|
+
const node = isParseResult(input) ? input.value : input;
|
|
3743
|
+
// TODO: refactor to use @herb-tools/printer infrastructre (or rework printer use push and this.lines)
|
|
2711
3744
|
this.lines = [];
|
|
2712
|
-
this.indentLevel =
|
|
2713
|
-
this.
|
|
2714
|
-
|
|
2715
|
-
|
|
3745
|
+
this.indentLevel = 0;
|
|
3746
|
+
this.visit(node);
|
|
3747
|
+
return this.lines.join("\n");
|
|
3748
|
+
}
|
|
3749
|
+
/**
|
|
3750
|
+
* Get the current element (top of stack)
|
|
3751
|
+
*/
|
|
3752
|
+
get currentElement() {
|
|
3753
|
+
return this.elementStack.length > 0 ? this.elementStack[this.elementStack.length - 1] : null;
|
|
3754
|
+
}
|
|
3755
|
+
/**
|
|
3756
|
+
* Get the current tag name from the current element context
|
|
3757
|
+
*/
|
|
3758
|
+
get currentTagName() {
|
|
3759
|
+
return this.currentElement?.open_tag?.tag_name?.value ?? "";
|
|
3760
|
+
}
|
|
3761
|
+
/**
|
|
3762
|
+
* Append text to the last line instead of creating a new line
|
|
3763
|
+
*/
|
|
3764
|
+
pushToLastLine(text) {
|
|
3765
|
+
if (this.lines.length > 0) {
|
|
3766
|
+
this.lines[this.lines.length - 1] += text;
|
|
2716
3767
|
}
|
|
2717
3768
|
else {
|
|
2718
|
-
this.
|
|
3769
|
+
this.lines.push(text);
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
/**
|
|
3773
|
+
* Capture output from a callback into a separate lines array
|
|
3774
|
+
* Useful for testing what output would be generated without affecting the main output
|
|
3775
|
+
*/
|
|
3776
|
+
capture(callback) {
|
|
3777
|
+
const previousLines = this.lines;
|
|
3778
|
+
const previousInlineMode = this.inlineMode;
|
|
3779
|
+
this.lines = [];
|
|
3780
|
+
try {
|
|
3781
|
+
callback();
|
|
3782
|
+
return this.lines;
|
|
3783
|
+
}
|
|
3784
|
+
finally {
|
|
3785
|
+
this.lines = previousLines;
|
|
3786
|
+
this.inlineMode = previousInlineMode;
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
/**
|
|
3790
|
+
* Capture all nodes that would be visited during a callback
|
|
3791
|
+
* Returns a flat list of all nodes without generating any output
|
|
3792
|
+
*/
|
|
3793
|
+
captureNodes(callback) {
|
|
3794
|
+
const capturedNodes = [];
|
|
3795
|
+
const previousLines = this.lines;
|
|
3796
|
+
const previousInlineMode = this.inlineMode;
|
|
3797
|
+
const originalPush = this.push.bind(this);
|
|
3798
|
+
const originalPushToLastLine = this.pushToLastLine.bind(this);
|
|
3799
|
+
const originalVisit = this.visit.bind(this);
|
|
3800
|
+
this.lines = [];
|
|
3801
|
+
this.push = () => { };
|
|
3802
|
+
this.pushToLastLine = () => { };
|
|
3803
|
+
this.visit = (node) => {
|
|
3804
|
+
capturedNodes.push(node);
|
|
3805
|
+
originalVisit(node);
|
|
3806
|
+
};
|
|
3807
|
+
try {
|
|
3808
|
+
callback();
|
|
3809
|
+
return capturedNodes;
|
|
3810
|
+
}
|
|
3811
|
+
finally {
|
|
3812
|
+
this.lines = previousLines;
|
|
3813
|
+
this.inlineMode = previousInlineMode;
|
|
3814
|
+
this.push = originalPush;
|
|
3815
|
+
this.pushToLastLine = originalPushToLastLine;
|
|
3816
|
+
this.visit = originalVisit;
|
|
2719
3817
|
}
|
|
2720
|
-
return this.lines.join("\n");
|
|
2721
3818
|
}
|
|
3819
|
+
/**
|
|
3820
|
+
* @deprecated refactor to use @herb-tools/printer infrastructre (or rework printer use push and this.lines)
|
|
3821
|
+
*/
|
|
2722
3822
|
push(line) {
|
|
2723
3823
|
this.lines.push(line);
|
|
2724
3824
|
}
|
|
@@ -2728,7 +3828,7 @@ class Printer extends Visitor {
|
|
|
2728
3828
|
this.indentLevel--;
|
|
2729
3829
|
return result;
|
|
2730
3830
|
}
|
|
2731
|
-
indent() {
|
|
3831
|
+
get indent() {
|
|
2732
3832
|
return " ".repeat(this.indentLevel * this.indentWidth);
|
|
2733
3833
|
}
|
|
2734
3834
|
/**
|
|
@@ -2738,45 +3838,128 @@ class Printer extends Visitor {
|
|
|
2738
3838
|
formatERBContent(content) {
|
|
2739
3839
|
return content.trim() ? ` ${content.trim()} ` : "";
|
|
2740
3840
|
}
|
|
2741
|
-
/**
|
|
2742
|
-
* Check if a node is an ERB control flow node (if, unless, block, case, while, for)
|
|
2743
|
-
*/
|
|
2744
|
-
isERBControlFlow(node) {
|
|
2745
|
-
return node instanceof ERBIfNode || node.type === 'AST_ERB_IF_NODE' ||
|
|
2746
|
-
node instanceof ERBUnlessNode || node.type === 'AST_ERB_UNLESS_NODE' ||
|
|
2747
|
-
node instanceof ERBBlockNode || node.type === 'AST_ERB_BLOCK_NODE' ||
|
|
2748
|
-
node instanceof ERBCaseNode || node.type === 'AST_ERB_CASE_NODE' ||
|
|
2749
|
-
node instanceof ERBCaseMatchNode || node.type === 'AST_ERB_CASE_MATCH_NODE' ||
|
|
2750
|
-
node instanceof ERBWhileNode || node.type === 'AST_ERB_WHILE_NODE' ||
|
|
2751
|
-
node instanceof ERBForNode || node.type === 'AST_ERB_FOR_NODE';
|
|
2752
|
-
}
|
|
2753
3841
|
/**
|
|
2754
3842
|
* Count total attributes including those inside ERB conditionals
|
|
2755
3843
|
*/
|
|
2756
3844
|
getTotalAttributeCount(attributes, inlineNodes = []) {
|
|
2757
3845
|
let totalAttributeCount = attributes.length;
|
|
2758
3846
|
inlineNodes.forEach(node => {
|
|
2759
|
-
if (
|
|
2760
|
-
const
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
}
|
|
3847
|
+
if (isERBControlFlowNode(node)) {
|
|
3848
|
+
const capturedNodes = this.captureNodes(() => this.visit(node));
|
|
3849
|
+
const attributeNodes = filterNodes(capturedNodes, HTMLAttributeNode);
|
|
3850
|
+
totalAttributeCount += attributeNodes.length;
|
|
2764
3851
|
}
|
|
2765
3852
|
});
|
|
2766
3853
|
return totalAttributeCount;
|
|
2767
3854
|
}
|
|
2768
3855
|
/**
|
|
2769
|
-
* Extract
|
|
3856
|
+
* Extract inline nodes (non-attribute, non-whitespace) from a list of nodes
|
|
3857
|
+
*/
|
|
3858
|
+
extractInlineNodes(nodes) {
|
|
3859
|
+
return nodes.filter(child => isNoneOf(child, HTMLAttributeNode, WhitespaceNode));
|
|
3860
|
+
}
|
|
3861
|
+
/**
|
|
3862
|
+
* Determine if spacing should be added between sibling elements
|
|
3863
|
+
*
|
|
3864
|
+
* This implements the "rule of three" intelligent spacing system:
|
|
3865
|
+
* - Adds spacing between 3 or more meaningful siblings
|
|
3866
|
+
* - Respects semantic groupings (e.g., ul/li, nav/a stay tight)
|
|
3867
|
+
* - Groups comments with following elements
|
|
3868
|
+
* - Preserves user-added spacing
|
|
3869
|
+
*
|
|
3870
|
+
* @param parentElement - The parent element containing the siblings
|
|
3871
|
+
* @param siblings - Array of all sibling nodes
|
|
3872
|
+
* @param currentIndex - Index of the current node being evaluated
|
|
3873
|
+
* @param hasExistingSpacing - Whether user-added spacing already exists
|
|
3874
|
+
* @returns true if spacing should be added before the current element
|
|
2770
3875
|
*/
|
|
2771
|
-
|
|
2772
|
-
|
|
3876
|
+
shouldAddSpacingBetweenSiblings(parentElement, siblings, currentIndex, hasExistingSpacing) {
|
|
3877
|
+
if (hasExistingSpacing) {
|
|
3878
|
+
return true;
|
|
3879
|
+
}
|
|
3880
|
+
const hasMixedContent = siblings.some(child => isNode(child, HTMLTextNode) && child.content.trim() !== "");
|
|
3881
|
+
if (hasMixedContent) {
|
|
3882
|
+
return false;
|
|
3883
|
+
}
|
|
3884
|
+
const meaningfulSiblings = siblings.filter(child => this.isNonWhitespaceNode(child));
|
|
3885
|
+
if (meaningfulSiblings.length < FormatPrinter.SPACING_THRESHOLD) {
|
|
3886
|
+
return false;
|
|
3887
|
+
}
|
|
3888
|
+
const parentTagName = parentElement ? getTagName(parentElement) : null;
|
|
3889
|
+
if (parentTagName && FormatPrinter.TIGHT_GROUP_PARENTS.has(parentTagName)) {
|
|
3890
|
+
return false;
|
|
3891
|
+
}
|
|
3892
|
+
const isSpaceableContainer = !parentTagName || (parentTagName && FormatPrinter.SPACEABLE_CONTAINERS.has(parentTagName));
|
|
3893
|
+
if (!isSpaceableContainer && meaningfulSiblings.length < 5) {
|
|
3894
|
+
return false;
|
|
3895
|
+
}
|
|
3896
|
+
const currentNode = siblings[currentIndex];
|
|
3897
|
+
const previousMeaningfulIndex = this.findPreviousMeaningfulSibling(siblings, currentIndex);
|
|
3898
|
+
const isCurrentComment = isCommentNode(currentNode);
|
|
3899
|
+
if (previousMeaningfulIndex !== -1) {
|
|
3900
|
+
const previousNode = siblings[previousMeaningfulIndex];
|
|
3901
|
+
const isPreviousComment = isCommentNode(previousNode);
|
|
3902
|
+
if (isPreviousComment && !isCurrentComment && (isNode(currentNode, HTMLElementNode) || isERBNode(currentNode))) {
|
|
3903
|
+
return false;
|
|
3904
|
+
}
|
|
3905
|
+
if (isPreviousComment && isCurrentComment) {
|
|
3906
|
+
return false;
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
if (isNode(currentNode, HTMLElementNode)) {
|
|
3910
|
+
const currentTagName = getTagName(currentNode);
|
|
3911
|
+
if (FormatPrinter.INLINE_ELEMENTS.has(currentTagName)) {
|
|
3912
|
+
return false;
|
|
3913
|
+
}
|
|
3914
|
+
if (FormatPrinter.TIGHT_GROUP_CHILDREN.has(currentTagName)) {
|
|
3915
|
+
return false;
|
|
3916
|
+
}
|
|
3917
|
+
if (currentTagName === 'a' && parentTagName === 'nav') {
|
|
3918
|
+
return false;
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
const isBlockElement = this.isBlockLevelNode(currentNode);
|
|
3922
|
+
const isERBBlock = isERBNode(currentNode) && isERBControlFlowNode(currentNode);
|
|
3923
|
+
const isComment = isCommentNode(currentNode);
|
|
3924
|
+
return isBlockElement || isERBBlock || isComment;
|
|
2773
3925
|
}
|
|
2774
3926
|
/**
|
|
2775
|
-
*
|
|
3927
|
+
* Token list attributes that contain space-separated values and benefit from
|
|
3928
|
+
* spacing around ERB content for readability
|
|
2776
3929
|
*/
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
3930
|
+
static TOKEN_LIST_ATTRIBUTES = new Set([
|
|
3931
|
+
'class', 'data-controller', 'data-action'
|
|
3932
|
+
]);
|
|
3933
|
+
/**
|
|
3934
|
+
* Check if we're currently processing a token list attribute that needs spacing
|
|
3935
|
+
*/
|
|
3936
|
+
isInTokenListAttribute() {
|
|
3937
|
+
return this.currentAttributeName !== null &&
|
|
3938
|
+
FormatPrinter.TOKEN_LIST_ATTRIBUTES.has(this.currentAttributeName);
|
|
3939
|
+
}
|
|
3940
|
+
/**
|
|
3941
|
+
* Find the previous meaningful (non-whitespace) sibling
|
|
3942
|
+
*/
|
|
3943
|
+
findPreviousMeaningfulSibling(siblings, currentIndex) {
|
|
3944
|
+
for (let i = currentIndex - 1; i >= 0; i--) {
|
|
3945
|
+
if (this.isNonWhitespaceNode(siblings[i])) {
|
|
3946
|
+
return i;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
return -1;
|
|
3950
|
+
}
|
|
3951
|
+
/**
|
|
3952
|
+
* Check if a node represents a block-level element
|
|
3953
|
+
*/
|
|
3954
|
+
isBlockLevelNode(node) {
|
|
3955
|
+
if (!isNode(node, HTMLElementNode)) {
|
|
3956
|
+
return false;
|
|
3957
|
+
}
|
|
3958
|
+
const tagName = getTagName(node);
|
|
3959
|
+
if (FormatPrinter.INLINE_ELEMENTS.has(tagName)) {
|
|
3960
|
+
return false;
|
|
3961
|
+
}
|
|
3962
|
+
return true;
|
|
2780
3963
|
}
|
|
2781
3964
|
/**
|
|
2782
3965
|
* Render attributes as a space-separated string
|
|
@@ -2784,63 +3967,198 @@ class Printer extends Visitor {
|
|
|
2784
3967
|
renderAttributesString(attributes) {
|
|
2785
3968
|
if (attributes.length === 0)
|
|
2786
3969
|
return "";
|
|
2787
|
-
return ` ${attributes.map(
|
|
3970
|
+
return ` ${attributes.map(attribute => this.renderAttribute(attribute)).join(" ")}`;
|
|
2788
3971
|
}
|
|
2789
3972
|
/**
|
|
2790
3973
|
* Determine if a tag should be rendered inline based on attribute count and other factors
|
|
2791
3974
|
*/
|
|
2792
|
-
shouldRenderInline(totalAttributeCount, inlineLength, indentLength, maxLineLength = this.maxLineLength, hasComplexERB = false,
|
|
2793
|
-
if (hasComplexERB)
|
|
3975
|
+
shouldRenderInline(totalAttributeCount, inlineLength, indentLength, maxLineLength = this.maxLineLength, hasComplexERB = false, hasMultilineAttributes = false, attributes = []) {
|
|
3976
|
+
if (hasComplexERB || hasMultilineAttributes)
|
|
2794
3977
|
return false;
|
|
2795
|
-
// Special case: no attributes at all, always inline if it fits
|
|
2796
3978
|
if (totalAttributeCount === 0) {
|
|
2797
3979
|
return inlineLength + indentLength <= maxLineLength;
|
|
2798
3980
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
3981
|
+
if (totalAttributeCount === 1 && attributes.length === 1) {
|
|
3982
|
+
const attribute = attributes[0];
|
|
3983
|
+
const attributeName = this.getAttributeName(attribute);
|
|
3984
|
+
if (attributeName === 'class') {
|
|
3985
|
+
const attributeValue = this.getAttributeValue(attribute);
|
|
3986
|
+
const wouldBeMultiline = this.wouldClassAttributeBeMultiline(attributeValue, indentLength);
|
|
3987
|
+
if (!wouldBeMultiline) {
|
|
3988
|
+
return true;
|
|
3989
|
+
}
|
|
3990
|
+
else {
|
|
3991
|
+
return false;
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
if (totalAttributeCount > 3 || inlineLength + indentLength > maxLineLength) {
|
|
3996
|
+
return false;
|
|
3997
|
+
}
|
|
3998
|
+
return true;
|
|
3999
|
+
}
|
|
4000
|
+
getAttributeName(attribute) {
|
|
4001
|
+
return attribute.name ? getCombinedAttributeName(attribute.name) : "";
|
|
4002
|
+
}
|
|
4003
|
+
wouldClassAttributeBeMultiline(content, indentLength) {
|
|
4004
|
+
const normalizedContent = content.replace(/\s+/g, ' ').trim();
|
|
4005
|
+
const hasActualNewlines = /\r?\n/.test(content);
|
|
4006
|
+
if (hasActualNewlines && normalizedContent.length > 80) {
|
|
4007
|
+
const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
|
|
4008
|
+
if (lines.length > 1) {
|
|
4009
|
+
return true;
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
const attributeLine = `class="${normalizedContent}"`;
|
|
4013
|
+
const currentIndent = indentLength;
|
|
4014
|
+
if (currentIndent + attributeLine.length > this.maxLineLength && normalizedContent.length > 60) {
|
|
4015
|
+
if (/<%[^%]*%>/.test(normalizedContent)) {
|
|
4016
|
+
return false;
|
|
4017
|
+
}
|
|
4018
|
+
const classes = normalizedContent.split(' ');
|
|
4019
|
+
const lines = this.breakTokensIntoLines(classes, currentIndent);
|
|
4020
|
+
return lines.length > 1;
|
|
4021
|
+
}
|
|
4022
|
+
return false;
|
|
4023
|
+
}
|
|
4024
|
+
getAttributeValue(attribute) {
|
|
4025
|
+
if (attribute.value && isNode(attribute.value, HTMLAttributeValueNode)) {
|
|
4026
|
+
const content = attribute.value.children.map(child => {
|
|
4027
|
+
if (isNode(child, HTMLTextNode)) {
|
|
4028
|
+
return child.content;
|
|
4029
|
+
}
|
|
4030
|
+
return IdentityPrinter.print(child);
|
|
4031
|
+
}).join('');
|
|
4032
|
+
return content;
|
|
4033
|
+
}
|
|
4034
|
+
return '';
|
|
4035
|
+
}
|
|
4036
|
+
hasMultilineAttributes(attributes) {
|
|
4037
|
+
return attributes.some(attribute => {
|
|
4038
|
+
if (attribute.value && isNode(attribute.value, HTMLAttributeValueNode)) {
|
|
4039
|
+
const content = getCombinedStringFromNodes(attribute.value.children);
|
|
4040
|
+
if (/\r?\n/.test(content)) {
|
|
4041
|
+
const name = attribute.name ? getCombinedAttributeName(attribute.name) : "";
|
|
4042
|
+
if (name === "class") {
|
|
4043
|
+
const normalizedContent = content.replace(/\s+/g, ' ').trim();
|
|
4044
|
+
return normalizedContent.length > 80;
|
|
4045
|
+
}
|
|
4046
|
+
const lines = content.split(/\r?\n/);
|
|
4047
|
+
if (lines.length > 1) {
|
|
4048
|
+
return lines.slice(1).some(line => /^\s+/.test(line));
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
return false;
|
|
4053
|
+
});
|
|
4054
|
+
}
|
|
4055
|
+
formatClassAttribute(content, name, equals, open_quote, close_quote) {
|
|
4056
|
+
const normalizedContent = content.replace(/\s+/g, ' ').trim();
|
|
4057
|
+
const hasActualNewlines = /\r?\n/.test(content);
|
|
4058
|
+
if (hasActualNewlines && normalizedContent.length > 80) {
|
|
4059
|
+
const lines = content.split(/\r?\n/).map(line => line.trim()).filter(line => line);
|
|
4060
|
+
if (lines.length > 1) {
|
|
4061
|
+
return open_quote + this.formatMultilineAttributeValue(lines) + close_quote;
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
const currentIndent = this.indentLevel * this.indentWidth;
|
|
4065
|
+
const attributeLine = `${name}${equals}${open_quote}${normalizedContent}${close_quote}`;
|
|
4066
|
+
if (currentIndent + attributeLine.length > this.maxLineLength && normalizedContent.length > 60) {
|
|
4067
|
+
if (/<%[^%]*%>/.test(normalizedContent)) {
|
|
4068
|
+
return open_quote + normalizedContent + close_quote;
|
|
4069
|
+
}
|
|
4070
|
+
const classes = normalizedContent.split(' ');
|
|
4071
|
+
const lines = this.breakTokensIntoLines(classes, currentIndent);
|
|
4072
|
+
if (lines.length > 1) {
|
|
4073
|
+
return open_quote + this.formatMultilineAttributeValue(lines) + close_quote;
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
return open_quote + normalizedContent + close_quote;
|
|
4077
|
+
}
|
|
4078
|
+
isFormattableAttribute(attributeName, tagName) {
|
|
4079
|
+
const globalFormattable = FORMATTABLE_ATTRIBUTES['*'] || [];
|
|
4080
|
+
const tagSpecificFormattable = FORMATTABLE_ATTRIBUTES[tagName.toLowerCase()] || [];
|
|
4081
|
+
return globalFormattable.includes(attributeName) || tagSpecificFormattable.includes(attributeName);
|
|
4082
|
+
}
|
|
4083
|
+
formatMultilineAttribute(content, name, open_quote, close_quote) {
|
|
4084
|
+
if (name === 'srcset' || name === 'sizes') {
|
|
4085
|
+
const normalizedContent = content.replace(/\s+/g, ' ').trim();
|
|
4086
|
+
return open_quote + normalizedContent + close_quote;
|
|
4087
|
+
}
|
|
4088
|
+
const lines = content.split('\n');
|
|
4089
|
+
if (lines.length <= 1) {
|
|
4090
|
+
return open_quote + content + close_quote;
|
|
4091
|
+
}
|
|
4092
|
+
const formattedContent = this.formatMultilineAttributeValue(lines);
|
|
4093
|
+
return open_quote + formattedContent + close_quote;
|
|
4094
|
+
}
|
|
4095
|
+
formatMultilineAttributeValue(lines) {
|
|
4096
|
+
const indent = " ".repeat((this.indentLevel + 1) * this.indentWidth);
|
|
4097
|
+
const closeIndent = " ".repeat(this.indentLevel * this.indentWidth);
|
|
4098
|
+
return "\n" + lines.map(line => indent + line).join("\n") + "\n" + closeIndent;
|
|
4099
|
+
}
|
|
4100
|
+
breakTokensIntoLines(tokens, currentIndent, separator = ' ') {
|
|
4101
|
+
const lines = [];
|
|
4102
|
+
let currentLine = '';
|
|
4103
|
+
for (const token of tokens) {
|
|
4104
|
+
const testLine = currentLine ? currentLine + separator + token : token;
|
|
4105
|
+
if (testLine.length > (this.maxLineLength - currentIndent - 6)) {
|
|
4106
|
+
if (currentLine) {
|
|
4107
|
+
lines.push(currentLine);
|
|
4108
|
+
currentLine = token;
|
|
4109
|
+
}
|
|
4110
|
+
else {
|
|
4111
|
+
lines.push(token);
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
else {
|
|
4115
|
+
currentLine = testLine;
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
if (currentLine)
|
|
4119
|
+
lines.push(currentLine);
|
|
4120
|
+
return lines;
|
|
2803
4121
|
}
|
|
2804
4122
|
/**
|
|
2805
4123
|
* Render multiline attributes for a tag
|
|
2806
4124
|
*/
|
|
2807
|
-
renderMultilineAttributes(tagName,
|
|
2808
|
-
|
|
2809
|
-
this.push(indent + `<${tagName}`);
|
|
4125
|
+
renderMultilineAttributes(tagName, allChildren = [], isSelfClosing = false) {
|
|
4126
|
+
this.push(this.indent + `<${tagName}`);
|
|
2810
4127
|
this.withIndent(() => {
|
|
2811
|
-
// Render children in order, handling both attributes and ERB nodes
|
|
2812
4128
|
allChildren.forEach(child => {
|
|
2813
|
-
if (child
|
|
2814
|
-
this.push(this.indent
|
|
4129
|
+
if (isNode(child, HTMLAttributeNode)) {
|
|
4130
|
+
this.push(this.indent + this.renderAttribute(child));
|
|
2815
4131
|
}
|
|
2816
|
-
else if (!(child
|
|
4132
|
+
else if (!isNode(child, WhitespaceNode)) {
|
|
2817
4133
|
this.visit(child);
|
|
2818
4134
|
}
|
|
2819
4135
|
});
|
|
2820
4136
|
});
|
|
2821
4137
|
if (isSelfClosing) {
|
|
2822
|
-
this.push(indent + "/>");
|
|
2823
|
-
}
|
|
2824
|
-
else if (isVoid) {
|
|
2825
|
-
this.push(indent + ">");
|
|
2826
|
-
}
|
|
2827
|
-
else if (!hasBodyContent) {
|
|
2828
|
-
this.push(indent + `></${tagName}>`);
|
|
4138
|
+
this.push(this.indent + "/>");
|
|
2829
4139
|
}
|
|
2830
4140
|
else {
|
|
2831
|
-
this.push(indent + ">");
|
|
4141
|
+
this.push(this.indent + ">");
|
|
2832
4142
|
}
|
|
2833
4143
|
}
|
|
2834
4144
|
/**
|
|
2835
|
-
*
|
|
4145
|
+
* Reconstruct the text representation of an ERB node
|
|
4146
|
+
* @param withFormatting - if true, format the content; if false, preserve original
|
|
2836
4147
|
*/
|
|
2837
|
-
|
|
2838
|
-
const indent = this.inlineMode ? "" : this.indent();
|
|
4148
|
+
reconstructERBNode(node, withFormatting = true) {
|
|
2839
4149
|
const open = node.tag_opening?.value ?? "";
|
|
2840
4150
|
const close = node.tag_closing?.value ?? "";
|
|
2841
4151
|
const content = node.content?.value ?? "";
|
|
2842
|
-
const inner = this.formatERBContent(content);
|
|
2843
|
-
|
|
4152
|
+
const inner = withFormatting ? this.formatERBContent(content) : content;
|
|
4153
|
+
return open + inner + close;
|
|
4154
|
+
}
|
|
4155
|
+
/**
|
|
4156
|
+
* Print an ERB tag (<% %> or <%= %>) with single spaces around inner content.
|
|
4157
|
+
*/
|
|
4158
|
+
printERBNode(node) {
|
|
4159
|
+
const indent = this.inlineMode ? "" : this.indent;
|
|
4160
|
+
const erbText = this.reconstructERBNode(node, true);
|
|
4161
|
+
this.push(indent + erbText);
|
|
2844
4162
|
}
|
|
2845
4163
|
// --- Visitor methods ---
|
|
2846
4164
|
visitDocumentNode(node) {
|
|
@@ -2848,14 +4166,13 @@ class Printer extends Visitor {
|
|
|
2848
4166
|
let hasHandledSpacing = false;
|
|
2849
4167
|
for (let i = 0; i < node.children.length; i++) {
|
|
2850
4168
|
const child = node.children[i];
|
|
2851
|
-
if (child
|
|
2852
|
-
const
|
|
2853
|
-
const isWhitespaceOnly = textNode.content.trim() === "";
|
|
4169
|
+
if (isNode(child, HTMLTextNode)) {
|
|
4170
|
+
const isWhitespaceOnly = child.content.trim() === "";
|
|
2854
4171
|
if (isWhitespaceOnly) {
|
|
2855
|
-
const
|
|
4172
|
+
const hasPreviousNonWhitespace = i > 0 && this.isNonWhitespaceNode(node.children[i - 1]);
|
|
2856
4173
|
const hasNextNonWhitespace = i < node.children.length - 1 && this.isNonWhitespaceNode(node.children[i + 1]);
|
|
2857
|
-
const hasMultipleNewlines =
|
|
2858
|
-
if (
|
|
4174
|
+
const hasMultipleNewlines = child.content.includes('\n\n');
|
|
4175
|
+
if (hasPreviousNonWhitespace && hasNextNonWhitespace && hasMultipleNewlines) {
|
|
2859
4176
|
this.push("");
|
|
2860
4177
|
hasHandledSpacing = true;
|
|
2861
4178
|
}
|
|
@@ -2873,326 +4190,145 @@ class Printer extends Visitor {
|
|
|
2873
4190
|
}
|
|
2874
4191
|
}
|
|
2875
4192
|
visitHTMLElementNode(node) {
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
const hasTextFlow = this.isInTextFlowContext(null, node.body);
|
|
2882
|
-
const children = node.body.filter(child => {
|
|
2883
|
-
if (child instanceof WhitespaceNode || child.type === 'AST_WHITESPACE_NODE') {
|
|
2884
|
-
return false;
|
|
2885
|
-
}
|
|
2886
|
-
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
2887
|
-
const content = child.content;
|
|
2888
|
-
if (hasTextFlow && content === " ") {
|
|
2889
|
-
return true;
|
|
2890
|
-
}
|
|
2891
|
-
return content.trim() !== "";
|
|
2892
|
-
}
|
|
2893
|
-
return true;
|
|
2894
|
-
});
|
|
2895
|
-
const isInlineElement = this.isInlineElement(tagName);
|
|
2896
|
-
const hasClosing = open.tag_closing?.value === ">" || open.tag_closing?.value === "/>";
|
|
2897
|
-
const isSelfClosing = open.tag_closing?.value === "/>";
|
|
2898
|
-
if (!hasClosing) {
|
|
2899
|
-
this.push(indent + `<${tagName}`);
|
|
2900
|
-
return;
|
|
4193
|
+
this.elementStack.push(node);
|
|
4194
|
+
this.elementFormattingAnalysis.set(node, this.analyzeElementFormatting(node));
|
|
4195
|
+
this.visit(node.open_tag);
|
|
4196
|
+
if (node.body.length > 0) {
|
|
4197
|
+
this.visitHTMLElementBody(node.body, node);
|
|
2901
4198
|
}
|
|
2902
|
-
if (
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
4199
|
+
if (node.close_tag) {
|
|
4200
|
+
this.visit(node.close_tag);
|
|
4201
|
+
}
|
|
4202
|
+
this.elementStack.pop();
|
|
4203
|
+
}
|
|
4204
|
+
visitHTMLElementBody(body, element) {
|
|
4205
|
+
const analysis = this.elementFormattingAnalysis.get(element);
|
|
4206
|
+
const hasTextFlow = this.isInTextFlowContext(null, body);
|
|
4207
|
+
const children = this.filterSignificantChildren(body, hasTextFlow);
|
|
4208
|
+
if (analysis?.elementContentInline) {
|
|
4209
|
+
if (children.length === 0)
|
|
2913
4210
|
return;
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
const
|
|
2922
|
-
if (
|
|
2923
|
-
this.push(
|
|
2924
|
-
return;
|
|
2925
|
-
}
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
}
|
|
2929
|
-
else {
|
|
2930
|
-
const inlineResult = this.tryRenderInline(children, tagName, 0, false, hasTextFlow);
|
|
2931
|
-
if (inlineResult && (indent.length + inlineResult.length) <= this.maxLineLength) {
|
|
2932
|
-
this.push(indent + inlineResult);
|
|
2933
|
-
return;
|
|
2934
|
-
}
|
|
2935
|
-
if (hasTextFlow) {
|
|
2936
|
-
const hasAnyNewlines = children.some(child => {
|
|
2937
|
-
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') {
|
|
2938
|
-
return child.content.includes('\n');
|
|
4211
|
+
const oldInlineMode = this.inlineMode;
|
|
4212
|
+
const nodesToRender = hasTextFlow ? body : children;
|
|
4213
|
+
this.inlineMode = true;
|
|
4214
|
+
const lines = this.capture(() => {
|
|
4215
|
+
nodesToRender.forEach(child => {
|
|
4216
|
+
if (isNode(child, HTMLTextNode)) {
|
|
4217
|
+
if (hasTextFlow) {
|
|
4218
|
+
const normalizedContent = child.content.replace(/\s+/g, ' ');
|
|
4219
|
+
if (normalizedContent && normalizedContent !== ' ') {
|
|
4220
|
+
this.push(normalizedContent);
|
|
2939
4221
|
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
if (!hasAnyNewlines) {
|
|
2943
|
-
const fullInlineResult = this.tryRenderInlineFull(node, tagName, attributes, children);
|
|
2944
|
-
if (fullInlineResult) {
|
|
2945
|
-
const totalLength = indent.length + fullInlineResult.length;
|
|
2946
|
-
const maxNesting = this.getMaxNestingDepth(children, 0);
|
|
2947
|
-
const maxInlineLength = maxNesting <= 1 ? this.maxLineLength : 60;
|
|
2948
|
-
if (totalLength <= maxInlineLength) {
|
|
2949
|
-
this.push(indent + fullInlineResult);
|
|
2950
|
-
return;
|
|
2951
|
-
}
|
|
4222
|
+
else if (normalizedContent === ' ') {
|
|
4223
|
+
this.push(' ');
|
|
2952
4224
|
}
|
|
2953
4225
|
}
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
const fullInlineResult = this.tryRenderInlineFull(node, tagName, [], children);
|
|
2959
|
-
if (fullInlineResult) {
|
|
2960
|
-
const totalLength = indent.length + fullInlineResult.length;
|
|
2961
|
-
const maxNesting = this.getMaxNestingDepth(children, 0);
|
|
2962
|
-
const maxInlineLength = maxNesting <= 1 ? this.maxLineLength : 60;
|
|
2963
|
-
if (totalLength <= maxInlineLength) {
|
|
2964
|
-
this.push(indent + fullInlineResult);
|
|
2965
|
-
return;
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
}
|
|
2969
|
-
this.push(indent + `<${tagName}>`);
|
|
2970
|
-
this.withIndent(() => {
|
|
2971
|
-
if (hasTextFlow) {
|
|
2972
|
-
this.visitTextFlowChildren(children);
|
|
2973
|
-
}
|
|
2974
|
-
else {
|
|
2975
|
-
children.forEach(child => this.visit(child));
|
|
2976
|
-
}
|
|
2977
|
-
});
|
|
2978
|
-
if (!node.is_void && !isSelfClosing) {
|
|
2979
|
-
this.push(indent + `</${tagName}>`);
|
|
2980
|
-
}
|
|
2981
|
-
return;
|
|
2982
|
-
}
|
|
2983
|
-
if (attributes.length === 0 && inlineNodes.length > 0) {
|
|
2984
|
-
const inline = this.renderInlineOpen(tagName, [], isSelfClosing, inlineNodes, open.children);
|
|
2985
|
-
if (children.length === 0) {
|
|
2986
|
-
if (isSelfClosing || node.is_void) {
|
|
2987
|
-
this.push(indent + inline);
|
|
2988
|
-
}
|
|
2989
|
-
else {
|
|
2990
|
-
this.push(indent + inline + `</${tagName}>`);
|
|
2991
|
-
}
|
|
2992
|
-
return;
|
|
2993
|
-
}
|
|
2994
|
-
this.push(indent + inline);
|
|
2995
|
-
this.withIndent(() => {
|
|
2996
|
-
children.forEach(child => this.visit(child));
|
|
2997
|
-
});
|
|
2998
|
-
if (!node.is_void && !isSelfClosing) {
|
|
2999
|
-
this.push(indent + `</${tagName}>`);
|
|
3000
|
-
}
|
|
3001
|
-
return;
|
|
3002
|
-
}
|
|
3003
|
-
const hasERBControlFlow = inlineNodes.some(node => this.isERBControlFlow(node)) ||
|
|
3004
|
-
open.children.some(node => this.isERBControlFlow(node));
|
|
3005
|
-
const hasComplexERB = hasERBControlFlow && inlineNodes.some(node => {
|
|
3006
|
-
if (node instanceof ERBIfNode || node.type === 'AST_ERB_IF_NODE') {
|
|
3007
|
-
const erbNode = node;
|
|
3008
|
-
if (erbNode.statements.length > 0 && erbNode.location) {
|
|
3009
|
-
const startLine = erbNode.location.start.line;
|
|
3010
|
-
const endLine = erbNode.location.end.line;
|
|
3011
|
-
return startLine !== endLine;
|
|
3012
|
-
}
|
|
3013
|
-
return false;
|
|
3014
|
-
}
|
|
3015
|
-
return false;
|
|
3016
|
-
});
|
|
3017
|
-
const inline = hasComplexERB ? "" : this.renderInlineOpen(tagName, attributes, isSelfClosing, inlineNodes, open.children);
|
|
3018
|
-
const nestingDepth = this.getMaxNestingDepth(children, 0);
|
|
3019
|
-
const totalAttributeCount = this.getTotalAttributeCount(attributes, inlineNodes);
|
|
3020
|
-
const shouldKeepInline = this.shouldRenderInline(totalAttributeCount, inline.length, indent.length, this.maxLineLength, hasComplexERB, nestingDepth, inlineNodes.length);
|
|
3021
|
-
if (shouldKeepInline) {
|
|
3022
|
-
if (children.length === 0) {
|
|
3023
|
-
if (isSelfClosing) {
|
|
3024
|
-
this.push(indent + inline);
|
|
3025
|
-
}
|
|
3026
|
-
else if (node.is_void) {
|
|
3027
|
-
this.push(indent + inline);
|
|
3028
|
-
}
|
|
3029
|
-
else {
|
|
3030
|
-
let result = `<${tagName}`;
|
|
3031
|
-
result += this.renderAttributesString(attributes);
|
|
3032
|
-
if (inlineNodes.length > 0) {
|
|
3033
|
-
const currentIndentLevel = this.indentLevel;
|
|
3034
|
-
this.indentLevel = 0;
|
|
3035
|
-
const tempLines = this.lines;
|
|
3036
|
-
this.lines = [];
|
|
3037
|
-
inlineNodes.forEach(node => {
|
|
3038
|
-
const wasInlineMode = this.inlineMode;
|
|
3039
|
-
if (!this.isERBControlFlow(node)) {
|
|
3040
|
-
this.inlineMode = true;
|
|
4226
|
+
else {
|
|
4227
|
+
const normalizedContent = child.content.replace(/\s+/g, ' ').trim();
|
|
4228
|
+
if (normalizedContent) {
|
|
4229
|
+
this.push(normalizedContent);
|
|
3041
4230
|
}
|
|
3042
|
-
|
|
3043
|
-
this.inlineMode = wasInlineMode;
|
|
3044
|
-
});
|
|
3045
|
-
const inlineContent = this.lines.join("");
|
|
3046
|
-
this.lines = tempLines;
|
|
3047
|
-
this.indentLevel = currentIndentLevel;
|
|
3048
|
-
result += inlineContent;
|
|
4231
|
+
}
|
|
3049
4232
|
}
|
|
3050
|
-
|
|
3051
|
-
this.push(indent + result);
|
|
3052
|
-
}
|
|
3053
|
-
return;
|
|
3054
|
-
}
|
|
3055
|
-
if (isInlineElement && children.length > 0 && !hasERBControlFlow) {
|
|
3056
|
-
const fullInlineResult = this.tryRenderInlineFull(node, tagName, attributes, children);
|
|
3057
|
-
if (fullInlineResult) {
|
|
3058
|
-
const totalLength = indent.length + fullInlineResult.length;
|
|
3059
|
-
if (totalLength <= this.maxLineLength || totalLength <= 120) {
|
|
3060
|
-
this.push(indent + fullInlineResult);
|
|
4233
|
+
else if (isNode(child, WhitespaceNode)) {
|
|
3061
4234
|
return;
|
|
3062
4235
|
}
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
if (!isInlineElement && children.length > 0 && !hasERBControlFlow) {
|
|
3066
|
-
this.push(indent + inline);
|
|
3067
|
-
this.withIndent(() => {
|
|
3068
|
-
if (hasTextFlow) {
|
|
3069
|
-
this.visitTextFlowChildren(children);
|
|
3070
|
-
}
|
|
3071
4236
|
else {
|
|
3072
|
-
|
|
4237
|
+
this.visit(child);
|
|
3073
4238
|
}
|
|
3074
4239
|
});
|
|
3075
|
-
if (!node.is_void && !isSelfClosing) {
|
|
3076
|
-
this.push(indent + `</${tagName}>`);
|
|
3077
|
-
}
|
|
3078
|
-
return;
|
|
3079
|
-
}
|
|
3080
|
-
if (isSelfClosing) {
|
|
3081
|
-
this.push(indent + inline.replace(' />', '>'));
|
|
3082
|
-
}
|
|
3083
|
-
else {
|
|
3084
|
-
this.push(indent + inline);
|
|
3085
|
-
}
|
|
3086
|
-
this.withIndent(() => {
|
|
3087
|
-
if (hasTextFlow) {
|
|
3088
|
-
this.visitTextFlowChildren(children);
|
|
3089
|
-
}
|
|
3090
|
-
else {
|
|
3091
|
-
children.forEach(child => this.visit(child));
|
|
3092
|
-
}
|
|
3093
4240
|
});
|
|
3094
|
-
|
|
3095
|
-
|
|
4241
|
+
const content = lines.join('');
|
|
4242
|
+
const inlineContent = hasTextFlow ? content.replace(/\s+/g, ' ').trim() : content.trim();
|
|
4243
|
+
if (inlineContent) {
|
|
4244
|
+
this.pushToLastLine(inlineContent);
|
|
3096
4245
|
}
|
|
4246
|
+
this.inlineMode = oldInlineMode;
|
|
3097
4247
|
return;
|
|
3098
4248
|
}
|
|
3099
|
-
if (
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
});
|
|
3105
|
-
this.push(indent + `</${tagName}>`);
|
|
4249
|
+
if (children.length === 0)
|
|
4250
|
+
return;
|
|
4251
|
+
this.withIndent(() => {
|
|
4252
|
+
if (hasTextFlow) {
|
|
4253
|
+
this.visitTextFlowChildren(children);
|
|
3106
4254
|
}
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
this.push(indent + this.renderInlineOpen(tagName, attributes, isSelfClosing, inlineNodes, open.children));
|
|
3110
|
-
if (!isSelfClosing && !node.is_void && children.length > 0) {
|
|
3111
|
-
this.withIndent(() => {
|
|
3112
|
-
children.forEach(child => this.visit(child));
|
|
3113
|
-
});
|
|
3114
|
-
this.push(indent + `</${tagName}>`);
|
|
4255
|
+
else {
|
|
4256
|
+
this.visitElementChildren(body, element);
|
|
3115
4257
|
}
|
|
3116
|
-
}
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
4258
|
+
});
|
|
4259
|
+
}
|
|
4260
|
+
/**
|
|
4261
|
+
* Visit element children with intelligent spacing logic
|
|
4262
|
+
*/
|
|
4263
|
+
visitElementChildren(body, parentElement) {
|
|
4264
|
+
let lastWasMeaningful = false;
|
|
4265
|
+
let hasHandledSpacing = false;
|
|
4266
|
+
for (let i = 0; i < body.length; i++) {
|
|
4267
|
+
const child = body[i];
|
|
4268
|
+
if (isNode(child, HTMLTextNode)) {
|
|
4269
|
+
const isWhitespaceOnly = child.content.trim() === "";
|
|
4270
|
+
if (isWhitespaceOnly) {
|
|
4271
|
+
const hasPreviousNonWhitespace = i > 0 && this.isNonWhitespaceNode(body[i - 1]);
|
|
4272
|
+
const hasNextNonWhitespace = i < body.length - 1 && this.isNonWhitespaceNode(body[i + 1]);
|
|
4273
|
+
const hasMultipleNewlines = child.content.includes('\n\n');
|
|
4274
|
+
if (hasPreviousNonWhitespace && hasNextNonWhitespace && hasMultipleNewlines) {
|
|
4275
|
+
this.push("");
|
|
4276
|
+
hasHandledSpacing = true;
|
|
3125
4277
|
}
|
|
4278
|
+
continue;
|
|
3126
4279
|
}
|
|
3127
4280
|
}
|
|
3128
|
-
if (
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
else if (node.is_void) {
|
|
3135
|
-
result += ">";
|
|
3136
|
-
}
|
|
3137
|
-
else {
|
|
3138
|
-
result += `></${tagName}>`;
|
|
4281
|
+
if (this.isNonWhitespaceNode(child) && lastWasMeaningful && !hasHandledSpacing) {
|
|
4282
|
+
const element = body[i - 1];
|
|
4283
|
+
const hasExistingSpacing = i > 0 && isNode(element, HTMLTextNode) && element.content.trim() === "" && (element.content.includes('\n\n') || element.content.split('\n').length > 2);
|
|
4284
|
+
const shouldAddSpacing = this.shouldAddSpacingBetweenSiblings(parentElement, body, i, hasExistingSpacing);
|
|
4285
|
+
if (shouldAddSpacing) {
|
|
4286
|
+
this.push("");
|
|
3139
4287
|
}
|
|
3140
|
-
this.push(indent + result);
|
|
3141
|
-
return;
|
|
3142
4288
|
}
|
|
3143
|
-
this.
|
|
3144
|
-
if (
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
this.visitTextFlowChildren(children);
|
|
3148
|
-
}
|
|
3149
|
-
else {
|
|
3150
|
-
children.forEach(child => this.visit(child));
|
|
3151
|
-
}
|
|
3152
|
-
});
|
|
3153
|
-
this.push(indent + `</${tagName}>`);
|
|
4289
|
+
this.visit(child);
|
|
4290
|
+
if (this.isNonWhitespaceNode(child)) {
|
|
4291
|
+
lastWasMeaningful = true;
|
|
4292
|
+
hasHandledSpacing = false;
|
|
3154
4293
|
}
|
|
3155
4294
|
}
|
|
3156
4295
|
}
|
|
3157
4296
|
visitHTMLOpenTagNode(node) {
|
|
3158
|
-
const
|
|
3159
|
-
const indent = this.indent();
|
|
3160
|
-
const attributes = this.extractAttributes(node.children);
|
|
4297
|
+
const attributes = filterNodes(node.children, HTMLAttributeNode);
|
|
3161
4298
|
const inlineNodes = this.extractInlineNodes(node.children);
|
|
3162
|
-
const
|
|
3163
|
-
if (
|
|
3164
|
-
this.
|
|
3165
|
-
|
|
4299
|
+
const isSelfClosing = node.tag_closing?.value === "/>";
|
|
4300
|
+
if (this.currentElement && this.elementFormattingAnalysis.has(this.currentElement)) {
|
|
4301
|
+
const analysis = this.elementFormattingAnalysis.get(this.currentElement);
|
|
4302
|
+
if (analysis.openTagInline) {
|
|
4303
|
+
const inline = this.renderInlineOpen(getTagName(node), attributes, isSelfClosing, inlineNodes, node.children);
|
|
4304
|
+
this.push(this.inlineMode ? inline : this.indent + inline);
|
|
4305
|
+
return;
|
|
4306
|
+
}
|
|
4307
|
+
else {
|
|
4308
|
+
this.renderMultilineAttributes(getTagName(node), node.children, isSelfClosing);
|
|
4309
|
+
return;
|
|
4310
|
+
}
|
|
3166
4311
|
}
|
|
3167
|
-
const inline = this.renderInlineOpen(
|
|
4312
|
+
const inline = this.renderInlineOpen(getTagName(node), attributes, isSelfClosing, inlineNodes, node.children);
|
|
3168
4313
|
const totalAttributeCount = this.getTotalAttributeCount(attributes, inlineNodes);
|
|
3169
|
-
const shouldKeepInline = this.shouldRenderInline(totalAttributeCount, inline.length, indent.length, this.maxLineLength, false,
|
|
4314
|
+
const shouldKeepInline = this.shouldRenderInline(totalAttributeCount, inline.length, this.indent.length, this.maxLineLength, false, this.hasMultilineAttributes(attributes), attributes);
|
|
3170
4315
|
if (shouldKeepInline) {
|
|
3171
|
-
this.push(indent + inline);
|
|
3172
|
-
return;
|
|
4316
|
+
this.push(this.inlineMode ? inline : this.indent + inline);
|
|
3173
4317
|
}
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
visitHTMLSelfCloseTagNode(node) {
|
|
3177
|
-
const tagName = node.tag_name?.value ?? "";
|
|
3178
|
-
const indent = this.indent();
|
|
3179
|
-
const attributes = this.extractAttributes(node.attributes);
|
|
3180
|
-
const inlineNodes = this.extractInlineNodes(node.attributes);
|
|
3181
|
-
const inline = this.renderInlineOpen(tagName, attributes, true, inlineNodes, node.attributes);
|
|
3182
|
-
const totalAttributeCount = this.getTotalAttributeCount(attributes, inlineNodes);
|
|
3183
|
-
const shouldKeepInline = this.shouldRenderInline(totalAttributeCount, inline.length, indent.length, this.maxLineLength, false, 0, inlineNodes.length);
|
|
3184
|
-
if (shouldKeepInline) {
|
|
3185
|
-
this.push(indent + inline);
|
|
3186
|
-
return;
|
|
4318
|
+
else {
|
|
4319
|
+
this.renderMultilineAttributes(getTagName(node), node.children, isSelfClosing);
|
|
3187
4320
|
}
|
|
3188
|
-
this.renderMultilineAttributes(tagName, attributes, inlineNodes, node.attributes, true, false, false);
|
|
3189
4321
|
}
|
|
3190
4322
|
visitHTMLCloseTagNode(node) {
|
|
3191
|
-
const
|
|
3192
|
-
const
|
|
3193
|
-
const
|
|
3194
|
-
|
|
3195
|
-
|
|
4323
|
+
const closingTag = IdentityPrinter.print(node);
|
|
4324
|
+
const analysis = this.currentElement && this.elementFormattingAnalysis.get(this.currentElement);
|
|
4325
|
+
const closeTagInline = analysis?.closeTagInline;
|
|
4326
|
+
if (this.currentElement && closeTagInline) {
|
|
4327
|
+
this.pushToLastLine(closingTag);
|
|
4328
|
+
}
|
|
4329
|
+
else {
|
|
4330
|
+
this.push(this.indent + closingTag);
|
|
4331
|
+
}
|
|
3196
4332
|
}
|
|
3197
4333
|
visitHTMLTextNode(node) {
|
|
3198
4334
|
if (this.inlineMode) {
|
|
@@ -3202,17 +4338,16 @@ class Printer extends Visitor {
|
|
|
3202
4338
|
}
|
|
3203
4339
|
return;
|
|
3204
4340
|
}
|
|
3205
|
-
const indent = this.indent();
|
|
3206
4341
|
let text = node.content.trim();
|
|
3207
4342
|
if (!text)
|
|
3208
4343
|
return;
|
|
3209
|
-
const wrapWidth = this.maxLineLength - indent.length;
|
|
4344
|
+
const wrapWidth = this.maxLineLength - this.indent.length;
|
|
3210
4345
|
const words = text.split(/\s+/);
|
|
3211
4346
|
const lines = [];
|
|
3212
4347
|
let line = "";
|
|
3213
4348
|
for (const word of words) {
|
|
3214
4349
|
if ((line + (line ? " " : "") + word).length > wrapWidth && line) {
|
|
3215
|
-
lines.push(indent + line);
|
|
4350
|
+
lines.push(this.indent + line);
|
|
3216
4351
|
line = word;
|
|
3217
4352
|
}
|
|
3218
4353
|
else {
|
|
@@ -3220,68 +4355,82 @@ class Printer extends Visitor {
|
|
|
3220
4355
|
}
|
|
3221
4356
|
}
|
|
3222
4357
|
if (line)
|
|
3223
|
-
lines.push(indent + line);
|
|
4358
|
+
lines.push(this.indent + line);
|
|
3224
4359
|
lines.forEach(line => this.push(line));
|
|
3225
4360
|
}
|
|
3226
4361
|
visitHTMLAttributeNode(node) {
|
|
3227
|
-
|
|
3228
|
-
this.push(indent + this.renderAttribute(node));
|
|
4362
|
+
this.push(this.indent + this.renderAttribute(node));
|
|
3229
4363
|
}
|
|
3230
4364
|
visitHTMLAttributeNameNode(node) {
|
|
3231
|
-
|
|
3232
|
-
const name = node.name?.value ?? "";
|
|
3233
|
-
this.push(indent + name);
|
|
4365
|
+
this.push(this.indent + getCombinedAttributeName(node));
|
|
3234
4366
|
}
|
|
3235
4367
|
visitHTMLAttributeValueNode(node) {
|
|
3236
|
-
|
|
3237
|
-
const open_quote = node.open_quote?.value ?? "";
|
|
3238
|
-
const close_quote = node.close_quote?.value ?? "";
|
|
3239
|
-
const attribute_value = node.children.map(child => {
|
|
3240
|
-
if (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE' ||
|
|
3241
|
-
child instanceof LiteralNode || child.type === 'AST_LITERAL_NODE') {
|
|
3242
|
-
return child.content;
|
|
3243
|
-
}
|
|
3244
|
-
else if (child instanceof ERBContentNode || child.type === 'AST_ERB_CONTENT_NODE') {
|
|
3245
|
-
const erbChild = child;
|
|
3246
|
-
return (erbChild.tag_opening.value + erbChild.content.value + erbChild.tag_closing.value);
|
|
3247
|
-
}
|
|
3248
|
-
return "";
|
|
3249
|
-
}).join("");
|
|
3250
|
-
this.push(indent + open_quote + attribute_value + close_quote);
|
|
4368
|
+
this.push(this.indent + IdentityPrinter.print(node));
|
|
3251
4369
|
}
|
|
4370
|
+
// TODO: rework
|
|
3252
4371
|
visitHTMLCommentNode(node) {
|
|
3253
|
-
const indent = this.indent();
|
|
3254
4372
|
const open = node.comment_start?.value ?? "";
|
|
3255
4373
|
const close = node.comment_end?.value ?? "";
|
|
3256
4374
|
let inner;
|
|
3257
|
-
if (node.
|
|
3258
|
-
// TODO: use .value
|
|
3259
|
-
const [_, startIndex] = node.comment_start.range.toArray();
|
|
3260
|
-
const [endIndex] = node.comment_end.range.toArray();
|
|
3261
|
-
const rawInner = this.source.slice(startIndex, endIndex);
|
|
3262
|
-
inner = ` ${rawInner.trim()} `;
|
|
3263
|
-
}
|
|
3264
|
-
else {
|
|
4375
|
+
if (node.children && node.children.length > 0) {
|
|
3265
4376
|
inner = node.children.map(child => {
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
4377
|
+
if (isNode(child, HTMLTextNode) || isNode(child, LiteralNode)) {
|
|
4378
|
+
return child.content;
|
|
4379
|
+
}
|
|
4380
|
+
else if (isERBNode(child) || isNode(child, ERBContentNode)) {
|
|
4381
|
+
return this.reconstructERBNode(child, false);
|
|
4382
|
+
}
|
|
4383
|
+
else {
|
|
4384
|
+
return "";
|
|
4385
|
+
}
|
|
3269
4386
|
}).join("");
|
|
4387
|
+
const hasNewlines = inner.includes('\n');
|
|
4388
|
+
if (hasNewlines) {
|
|
4389
|
+
const lines = inner.split('\n');
|
|
4390
|
+
const childIndent = " ".repeat(this.indentWidth);
|
|
4391
|
+
const firstLineHasContent = lines[0].trim() !== '';
|
|
4392
|
+
if (firstLineHasContent && lines.length > 1) {
|
|
4393
|
+
const contentLines = lines.map(line => line.trim()).filter(line => line !== '');
|
|
4394
|
+
inner = '\n' + contentLines.map(line => childIndent + line).join('\n') + '\n';
|
|
4395
|
+
}
|
|
4396
|
+
else {
|
|
4397
|
+
const contentLines = lines.filter((line, index) => {
|
|
4398
|
+
return line.trim() !== '' && !(index === 0 || index === lines.length - 1);
|
|
4399
|
+
});
|
|
4400
|
+
const minIndent = contentLines.length > 0 ? Math.min(...contentLines.map(line => line.length - line.trimStart().length)) : 0;
|
|
4401
|
+
const processedLines = lines.map((line, index) => {
|
|
4402
|
+
const trimmedLine = line.trim();
|
|
4403
|
+
if ((index === 0 || index === lines.length - 1) && trimmedLine === '') {
|
|
4404
|
+
return line;
|
|
4405
|
+
}
|
|
4406
|
+
if (trimmedLine !== '') {
|
|
4407
|
+
const currentIndent = line.length - line.trimStart().length;
|
|
4408
|
+
const relativeIndent = Math.max(0, currentIndent - minIndent);
|
|
4409
|
+
return childIndent + " ".repeat(relativeIndent) + trimmedLine;
|
|
4410
|
+
}
|
|
4411
|
+
return line;
|
|
4412
|
+
});
|
|
4413
|
+
inner = processedLines.join('\n');
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
else {
|
|
4417
|
+
inner = ` ${inner.trim()} `;
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
else {
|
|
4421
|
+
inner = "";
|
|
3270
4422
|
}
|
|
3271
|
-
this.push(indent + open + inner + close);
|
|
4423
|
+
this.push(this.indent + open + inner + close);
|
|
3272
4424
|
}
|
|
3273
4425
|
visitERBCommentNode(node) {
|
|
3274
|
-
const indent = this.indent();
|
|
3275
4426
|
const open = node.tag_opening?.value ?? "";
|
|
3276
4427
|
const close = node.tag_closing?.value ?? "";
|
|
3277
4428
|
let inner;
|
|
3278
|
-
if (node.
|
|
3279
|
-
const
|
|
3280
|
-
const [closingStart] = node.tag_closing.range.toArray();
|
|
3281
|
-
const rawInner = this.source.slice(openingEnd, closingStart);
|
|
4429
|
+
if (node.content && node.content.value) {
|
|
4430
|
+
const rawInner = node.content.value;
|
|
3282
4431
|
const lines = rawInner.split("\n");
|
|
3283
4432
|
if (lines.length > 2) {
|
|
3284
|
-
const childIndent = indent + " ".repeat(this.indentWidth);
|
|
4433
|
+
const childIndent = this.indent + " ".repeat(this.indentWidth);
|
|
3285
4434
|
const innerLines = lines.slice(1, -1).map(line => childIndent + line.trim());
|
|
3286
4435
|
inner = "\n" + innerLines.join("\n") + "\n";
|
|
3287
4436
|
}
|
|
@@ -3290,33 +4439,18 @@ class Printer extends Visitor {
|
|
|
3290
4439
|
}
|
|
3291
4440
|
}
|
|
3292
4441
|
else {
|
|
3293
|
-
inner =
|
|
3294
|
-
.map((child) => {
|
|
3295
|
-
const prevLines = this.lines.length;
|
|
3296
|
-
this.visit(child);
|
|
3297
|
-
return this.lines.slice(prevLines).join("");
|
|
3298
|
-
})
|
|
3299
|
-
.join("");
|
|
4442
|
+
inner = "";
|
|
3300
4443
|
}
|
|
3301
|
-
this.push(indent + open + inner + close);
|
|
4444
|
+
this.push(this.indent + open + inner + close);
|
|
3302
4445
|
}
|
|
3303
4446
|
visitHTMLDoctypeNode(node) {
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
innerDoctype = this.source.slice(openingEnd, closingStart);
|
|
3312
|
-
}
|
|
3313
|
-
else {
|
|
3314
|
-
innerDoctype = node.children
|
|
3315
|
-
.map(child => child instanceof HTMLTextNode ? child.content : (() => { const prevLines = this.lines.length; this.visit(child); return this.lines.slice(prevLines).join(""); })())
|
|
3316
|
-
.join("");
|
|
3317
|
-
}
|
|
3318
|
-
const close = node.tag_closing?.value ?? "";
|
|
3319
|
-
this.push(indent + open + innerDoctype + close);
|
|
4447
|
+
this.push(this.indent + IdentityPrinter.print(node));
|
|
4448
|
+
}
|
|
4449
|
+
visitXMLDeclarationNode(node) {
|
|
4450
|
+
this.push(this.indent + IdentityPrinter.print(node));
|
|
4451
|
+
}
|
|
4452
|
+
visitCDATANode(node) {
|
|
4453
|
+
this.push(this.indent + IdentityPrinter.print(node));
|
|
3320
4454
|
}
|
|
3321
4455
|
visitERBContentNode(node) {
|
|
3322
4456
|
// TODO: this feels hacky
|
|
@@ -3335,109 +4469,81 @@ class Printer extends Visitor {
|
|
|
3335
4469
|
}
|
|
3336
4470
|
visitERBInNode(node) {
|
|
3337
4471
|
this.printERBNode(node);
|
|
3338
|
-
this.withIndent(() =>
|
|
3339
|
-
node.statements.forEach(stmt => this.visit(stmt));
|
|
3340
|
-
});
|
|
4472
|
+
this.withIndent(() => this.visitAll(node.statements));
|
|
3341
4473
|
}
|
|
3342
4474
|
visitERBCaseMatchNode(node) {
|
|
3343
4475
|
this.printERBNode(node);
|
|
3344
|
-
node.conditions
|
|
4476
|
+
this.visitAll(node.conditions);
|
|
3345
4477
|
if (node.else_clause)
|
|
3346
4478
|
this.visit(node.else_clause);
|
|
3347
4479
|
if (node.end_node)
|
|
3348
4480
|
this.visit(node.end_node);
|
|
3349
4481
|
}
|
|
3350
4482
|
visitERBBlockNode(node) {
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
const close = node.tag_closing?.value ?? "";
|
|
3355
|
-
this.push(indent + open + content + close);
|
|
3356
|
-
this.withIndent(() => {
|
|
3357
|
-
node.body.forEach(child => this.visit(child));
|
|
3358
|
-
});
|
|
3359
|
-
if (node.end_node) {
|
|
4483
|
+
this.printERBNode(node);
|
|
4484
|
+
this.withIndent(() => this.visitElementChildren(node.body, null));
|
|
4485
|
+
if (node.end_node)
|
|
3360
4486
|
this.visit(node.end_node);
|
|
3361
|
-
}
|
|
3362
4487
|
}
|
|
3363
4488
|
visitERBIfNode(node) {
|
|
3364
4489
|
if (this.inlineMode) {
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
this.lines.push(open + inner + close);
|
|
3370
|
-
node.statements.forEach((child, _index) => {
|
|
3371
|
-
this.lines.push(" ");
|
|
3372
|
-
if (child instanceof HTMLAttributeNode || child.type === 'AST_HTML_ATTRIBUTE_NODE') {
|
|
4490
|
+
this.printERBNode(node);
|
|
4491
|
+
node.statements.forEach(child => {
|
|
4492
|
+
if (isNode(child, HTMLAttributeNode)) {
|
|
4493
|
+
this.lines.push(" ");
|
|
3373
4494
|
this.lines.push(this.renderAttribute(child));
|
|
3374
4495
|
}
|
|
3375
4496
|
else {
|
|
4497
|
+
const shouldAddSpaces = this.isInTokenListAttribute();
|
|
4498
|
+
if (shouldAddSpaces) {
|
|
4499
|
+
this.lines.push(" ");
|
|
4500
|
+
}
|
|
3376
4501
|
this.visit(child);
|
|
4502
|
+
if (shouldAddSpaces) {
|
|
4503
|
+
this.lines.push(" ");
|
|
4504
|
+
}
|
|
3377
4505
|
}
|
|
3378
4506
|
});
|
|
3379
|
-
|
|
4507
|
+
const hasHTMLAttributes = node.statements.some(child => isNode(child, HTMLAttributeNode));
|
|
4508
|
+
const isTokenList = this.isInTokenListAttribute();
|
|
4509
|
+
if ((hasHTMLAttributes || isTokenList) && node.end_node) {
|
|
3380
4510
|
this.lines.push(" ");
|
|
3381
4511
|
}
|
|
3382
|
-
if (node.subsequent)
|
|
3383
|
-
this.visit(node.
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
const endNode = node.end_node;
|
|
3387
|
-
const endOpen = endNode.tag_opening?.value ?? "";
|
|
3388
|
-
const endContent = endNode.content?.value ?? "";
|
|
3389
|
-
const endClose = endNode.tag_closing?.value ?? "";
|
|
3390
|
-
const endInner = this.formatERBContent(endContent);
|
|
3391
|
-
this.lines.push(endOpen + endInner + endClose);
|
|
3392
|
-
}
|
|
4512
|
+
if (node.subsequent)
|
|
4513
|
+
this.visit(node.end_node);
|
|
4514
|
+
if (node.end_node)
|
|
4515
|
+
this.visit(node.end_node);
|
|
3393
4516
|
}
|
|
3394
4517
|
else {
|
|
3395
4518
|
this.printERBNode(node);
|
|
3396
4519
|
this.withIndent(() => {
|
|
3397
4520
|
node.statements.forEach(child => this.visit(child));
|
|
3398
4521
|
});
|
|
3399
|
-
if (node.subsequent)
|
|
4522
|
+
if (node.subsequent)
|
|
3400
4523
|
this.visit(node.subsequent);
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
this.printERBNode(node.end_node);
|
|
3404
|
-
}
|
|
4524
|
+
if (node.end_node)
|
|
4525
|
+
this.visit(node.end_node);
|
|
3405
4526
|
}
|
|
3406
4527
|
}
|
|
3407
4528
|
visitERBElseNode(node) {
|
|
3408
4529
|
this.printERBNode(node);
|
|
3409
|
-
this.withIndent(() =>
|
|
3410
|
-
node.statements.forEach(child => this.visit(child));
|
|
3411
|
-
});
|
|
4530
|
+
this.withIndent(() => node.statements.forEach(statement => this.visit(statement)));
|
|
3412
4531
|
}
|
|
3413
4532
|
visitERBWhenNode(node) {
|
|
3414
4533
|
this.printERBNode(node);
|
|
3415
|
-
this.withIndent(() =>
|
|
3416
|
-
node.statements.forEach(stmt => this.visit(stmt));
|
|
3417
|
-
});
|
|
4534
|
+
this.withIndent(() => this.visitAll(node.statements));
|
|
3418
4535
|
}
|
|
3419
4536
|
visitERBCaseNode(node) {
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
const content = node.content?.value ?? "";
|
|
3423
|
-
const close = node.tag_closing?.value ?? "";
|
|
3424
|
-
this.push(indent + open + content + close);
|
|
3425
|
-
node.conditions.forEach(condition => this.visit(condition));
|
|
4537
|
+
this.printERBNode(node);
|
|
4538
|
+
this.visitAll(node.conditions);
|
|
3426
4539
|
if (node.else_clause)
|
|
3427
4540
|
this.visit(node.else_clause);
|
|
3428
|
-
if (node.end_node)
|
|
4541
|
+
if (node.end_node)
|
|
3429
4542
|
this.visit(node.end_node);
|
|
3430
|
-
}
|
|
3431
4543
|
}
|
|
3432
4544
|
visitERBBeginNode(node) {
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
const content = node.content?.value ?? "";
|
|
3436
|
-
const close = node.tag_closing?.value ?? "";
|
|
3437
|
-
this.push(indent + open + content + close);
|
|
3438
|
-
this.withIndent(() => {
|
|
3439
|
-
node.statements.forEach(statement => this.visit(statement));
|
|
3440
|
-
});
|
|
4545
|
+
this.printERBNode(node);
|
|
4546
|
+
this.withIndent(() => this.visitAll(node.statements));
|
|
3441
4547
|
if (node.rescue_clause)
|
|
3442
4548
|
this.visit(node.rescue_clause);
|
|
3443
4549
|
if (node.else_clause)
|
|
@@ -3448,61 +4554,147 @@ class Printer extends Visitor {
|
|
|
3448
4554
|
this.visit(node.end_node);
|
|
3449
4555
|
}
|
|
3450
4556
|
visitERBWhileNode(node) {
|
|
3451
|
-
this.
|
|
4557
|
+
this.printERBNode(node);
|
|
4558
|
+
this.withIndent(() => this.visitAll(node.statements));
|
|
4559
|
+
if (node.end_node)
|
|
4560
|
+
this.visit(node.end_node);
|
|
3452
4561
|
}
|
|
3453
4562
|
visitERBUntilNode(node) {
|
|
3454
|
-
this.
|
|
4563
|
+
this.printERBNode(node);
|
|
4564
|
+
this.withIndent(() => this.visitAll(node.statements));
|
|
4565
|
+
if (node.end_node)
|
|
4566
|
+
this.visit(node.end_node);
|
|
3455
4567
|
}
|
|
3456
4568
|
visitERBForNode(node) {
|
|
3457
|
-
this.
|
|
4569
|
+
this.printERBNode(node);
|
|
4570
|
+
this.withIndent(() => this.visitAll(node.statements));
|
|
4571
|
+
if (node.end_node)
|
|
4572
|
+
this.visit(node.end_node);
|
|
3458
4573
|
}
|
|
3459
4574
|
visitERBRescueNode(node) {
|
|
3460
|
-
this.
|
|
4575
|
+
this.printERBNode(node);
|
|
4576
|
+
this.withIndent(() => this.visitAll(node.statements));
|
|
3461
4577
|
}
|
|
3462
4578
|
visitERBEnsureNode(node) {
|
|
3463
|
-
this.
|
|
4579
|
+
this.printERBNode(node);
|
|
4580
|
+
this.withIndent(() => this.visitAll(node.statements));
|
|
3464
4581
|
}
|
|
3465
4582
|
visitERBUnlessNode(node) {
|
|
3466
|
-
this.
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
const indent = this.indent();
|
|
3471
|
-
const open = node.tag_opening?.value ?? "";
|
|
3472
|
-
const content = node.content?.value ?? "";
|
|
3473
|
-
const close = node.tag_closing?.value ?? "";
|
|
3474
|
-
this.push(indent + open + content + close);
|
|
3475
|
-
this.withIndent(() => {
|
|
3476
|
-
const statements = node.statements ?? node.body ?? node.children ?? [];
|
|
3477
|
-
statements.forEach(statement => this.visit(statement));
|
|
3478
|
-
});
|
|
4583
|
+
this.printERBNode(node);
|
|
4584
|
+
this.withIndent(() => this.visitAll(node.statements));
|
|
4585
|
+
if (node.else_clause)
|
|
4586
|
+
this.visit(node.else_clause);
|
|
3479
4587
|
if (node.end_node)
|
|
3480
4588
|
this.visit(node.end_node);
|
|
3481
4589
|
}
|
|
4590
|
+
// --- Element Formatting Analysis Helpers ---
|
|
4591
|
+
/**
|
|
4592
|
+
* Analyzes an HTMLElementNode and returns formatting decisions for all parts
|
|
4593
|
+
*/
|
|
4594
|
+
analyzeElementFormatting(node) {
|
|
4595
|
+
const openTagInline = this.shouldRenderOpenTagInline(node);
|
|
4596
|
+
const elementContentInline = this.shouldRenderElementContentInline(node);
|
|
4597
|
+
const closeTagInline = this.shouldRenderCloseTagInline(node, elementContentInline);
|
|
4598
|
+
return {
|
|
4599
|
+
openTagInline,
|
|
4600
|
+
elementContentInline,
|
|
4601
|
+
closeTagInline
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
/**
|
|
4605
|
+
* Determines if the open tag should be rendered inline
|
|
4606
|
+
*/
|
|
4607
|
+
shouldRenderOpenTagInline(node) {
|
|
4608
|
+
const children = node.open_tag?.children || [];
|
|
4609
|
+
const attributes = filterNodes(children, HTMLAttributeNode);
|
|
4610
|
+
const inlineNodes = this.extractInlineNodes(children);
|
|
4611
|
+
const hasERBControlFlow = inlineNodes.some(node => isERBControlFlowNode(node)) || children.some(node => isERBControlFlowNode(node));
|
|
4612
|
+
const hasComplexERB = hasERBControlFlow && this.hasComplexERBControlFlow(inlineNodes);
|
|
4613
|
+
if (hasComplexERB)
|
|
4614
|
+
return false;
|
|
4615
|
+
const totalAttributeCount = this.getTotalAttributeCount(attributes, inlineNodes);
|
|
4616
|
+
const hasMultilineAttrs = this.hasMultilineAttributes(attributes);
|
|
4617
|
+
if (hasMultilineAttrs)
|
|
4618
|
+
return false;
|
|
4619
|
+
const inline = this.renderInlineOpen(getTagName(node), attributes, node.open_tag?.tag_closing?.value === "/>", inlineNodes, children);
|
|
4620
|
+
return this.shouldRenderInline(totalAttributeCount, inline.length, this.indent.length, this.maxLineLength, hasComplexERB, hasMultilineAttrs, attributes);
|
|
4621
|
+
}
|
|
4622
|
+
/**
|
|
4623
|
+
* Determines if the element content should be rendered inline
|
|
4624
|
+
*/
|
|
4625
|
+
shouldRenderElementContentInline(node) {
|
|
4626
|
+
const tagName = getTagName(node);
|
|
4627
|
+
const children = this.filterSignificantChildren(node.body, this.isInTextFlowContext(null, node.body));
|
|
4628
|
+
const isInlineElement = this.isInlineElement(tagName);
|
|
4629
|
+
const openTagInline = this.shouldRenderOpenTagInline(node);
|
|
4630
|
+
if (!openTagInline)
|
|
4631
|
+
return false;
|
|
4632
|
+
if (children.length === 0)
|
|
4633
|
+
return true;
|
|
4634
|
+
if (isInlineElement) {
|
|
4635
|
+
const fullInlineResult = this.tryRenderInlineFull(node, tagName, filterNodes(node.open_tag?.children, HTMLAttributeNode), children);
|
|
4636
|
+
if (fullInlineResult) {
|
|
4637
|
+
const totalLength = this.indent.length + fullInlineResult.length;
|
|
4638
|
+
return totalLength <= this.maxLineLength || totalLength <= 120;
|
|
4639
|
+
}
|
|
4640
|
+
return false;
|
|
4641
|
+
}
|
|
4642
|
+
const allNestedAreInline = this.areAllNestedElementsInline(children);
|
|
4643
|
+
const hasMultilineText = this.hasMultilineTextContent(children);
|
|
4644
|
+
const hasMixedContent = this.hasMixedTextAndInlineContent(children);
|
|
4645
|
+
if (allNestedAreInline && (!hasMultilineText || hasMixedContent)) {
|
|
4646
|
+
const fullInlineResult = this.tryRenderInlineFull(node, tagName, filterNodes(node.open_tag?.children, HTMLAttributeNode), children);
|
|
4647
|
+
if (fullInlineResult) {
|
|
4648
|
+
const totalLength = this.indent.length + fullInlineResult.length;
|
|
4649
|
+
if (totalLength <= this.maxLineLength) {
|
|
4650
|
+
return true;
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
const inlineResult = this.tryRenderInline(children, tagName);
|
|
4655
|
+
if (inlineResult) {
|
|
4656
|
+
const openTagResult = this.renderInlineOpen(tagName, filterNodes(node.open_tag?.children, HTMLAttributeNode), false, [], node.open_tag?.children || []);
|
|
4657
|
+
const childrenContent = this.renderChildrenInline(children);
|
|
4658
|
+
const fullLine = openTagResult + childrenContent + `</${tagName}>`;
|
|
4659
|
+
if ((this.indent.length + fullLine.length) <= this.maxLineLength) {
|
|
4660
|
+
return true;
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
return false;
|
|
4664
|
+
}
|
|
4665
|
+
/**
|
|
4666
|
+
* Determines if the close tag should be rendered inline (usually follows content decision)
|
|
4667
|
+
*/
|
|
4668
|
+
shouldRenderCloseTagInline(node, elementContentInline) {
|
|
4669
|
+
const isSelfClosing = node.open_tag?.tag_closing?.value === "/>";
|
|
4670
|
+
if (isSelfClosing || node.is_void)
|
|
4671
|
+
return true;
|
|
4672
|
+
const children = this.filterSignificantChildren(node.body, this.isInTextFlowContext(null, node.body));
|
|
4673
|
+
if (children.length === 0)
|
|
4674
|
+
return true;
|
|
4675
|
+
return elementContentInline;
|
|
4676
|
+
}
|
|
3482
4677
|
// --- Utility methods ---
|
|
3483
4678
|
isNonWhitespaceNode(node) {
|
|
3484
|
-
if (node
|
|
3485
|
-
return node.content.trim() !== "";
|
|
3486
|
-
}
|
|
3487
|
-
if (node instanceof WhitespaceNode || node.type === 'AST_WHITESPACE_NODE') {
|
|
4679
|
+
if (isNode(node, WhitespaceNode))
|
|
3488
4680
|
return false;
|
|
3489
|
-
|
|
4681
|
+
if (isNode(node, HTMLTextNode))
|
|
4682
|
+
return node.content.trim() !== "";
|
|
3490
4683
|
return true;
|
|
3491
4684
|
}
|
|
3492
4685
|
/**
|
|
3493
4686
|
* Check if an element should be treated as inline based on its tag name
|
|
3494
4687
|
*/
|
|
3495
4688
|
isInlineElement(tagName) {
|
|
3496
|
-
return
|
|
4689
|
+
return FormatPrinter.INLINE_ELEMENTS.has(tagName.toLowerCase());
|
|
3497
4690
|
}
|
|
3498
4691
|
/**
|
|
3499
4692
|
* Check if we're in a text flow context (parent contains mixed text and inline elements)
|
|
3500
4693
|
*/
|
|
3501
4694
|
visitTextFlowChildren(children) {
|
|
3502
|
-
const indent = this.indent();
|
|
3503
4695
|
let currentLineContent = "";
|
|
3504
4696
|
for (const child of children) {
|
|
3505
|
-
if (child
|
|
4697
|
+
if (isNode(child, HTMLTextNode)) {
|
|
3506
4698
|
const content = child.content;
|
|
3507
4699
|
let processedContent = content.replace(/\s+/g, ' ').trim();
|
|
3508
4700
|
if (processedContent) {
|
|
@@ -3515,29 +4707,26 @@ class Printer extends Visitor {
|
|
|
3515
4707
|
if (hasTrailingSpace && !currentLineContent.endsWith(' ')) {
|
|
3516
4708
|
currentLineContent += ' ';
|
|
3517
4709
|
}
|
|
3518
|
-
if ((indent.length + currentLineContent.length) > Math.max(this.maxLineLength, 120)) {
|
|
3519
|
-
this.
|
|
4710
|
+
if ((this.indent.length + currentLineContent.length) > Math.max(this.maxLineLength, 120)) {
|
|
4711
|
+
children.forEach(child => this.visit(child));
|
|
3520
4712
|
return;
|
|
3521
4713
|
}
|
|
3522
4714
|
}
|
|
3523
4715
|
}
|
|
3524
|
-
else if (child
|
|
3525
|
-
const
|
|
3526
|
-
const openTag = element.open_tag;
|
|
3527
|
-
const childTagName = openTag?.tag_name?.value || '';
|
|
4716
|
+
else if (isNode(child, HTMLElementNode)) {
|
|
4717
|
+
const childTagName = getTagName(child);
|
|
3528
4718
|
if (this.isInlineElement(childTagName)) {
|
|
3529
|
-
const childInline = this.tryRenderInlineFull(
|
|
3530
|
-
!((c instanceof HTMLTextNode || c.type === 'AST_HTML_TEXT_NODE') && c?.content.trim() === "")));
|
|
4719
|
+
const childInline = this.tryRenderInlineFull(child, childTagName, filterNodes(child.open_tag?.children, HTMLAttributeNode), this.filterEmptyNodes(child.body));
|
|
3531
4720
|
if (childInline) {
|
|
3532
4721
|
currentLineContent += childInline;
|
|
3533
|
-
if ((indent.length + currentLineContent.length) > this.maxLineLength) {
|
|
3534
|
-
this.
|
|
4722
|
+
if ((this.indent.length + currentLineContent.length) > this.maxLineLength) {
|
|
4723
|
+
children.forEach(child => this.visit(child));
|
|
3535
4724
|
return;
|
|
3536
4725
|
}
|
|
3537
4726
|
}
|
|
3538
4727
|
else {
|
|
3539
4728
|
if (currentLineContent.trim()) {
|
|
3540
|
-
this.push(indent + currentLineContent.trim());
|
|
4729
|
+
this.push(this.indent + currentLineContent.trim());
|
|
3541
4730
|
currentLineContent = "";
|
|
3542
4731
|
}
|
|
3543
4732
|
this.visit(child);
|
|
@@ -3545,23 +4734,26 @@ class Printer extends Visitor {
|
|
|
3545
4734
|
}
|
|
3546
4735
|
else {
|
|
3547
4736
|
if (currentLineContent.trim()) {
|
|
3548
|
-
this.push(indent + currentLineContent.trim());
|
|
4737
|
+
this.push(this.indent + currentLineContent.trim());
|
|
3549
4738
|
currentLineContent = "";
|
|
3550
4739
|
}
|
|
3551
4740
|
this.visit(child);
|
|
3552
4741
|
}
|
|
3553
4742
|
}
|
|
3554
|
-
else if (child
|
|
4743
|
+
else if (isNode(child, ERBContentNode)) {
|
|
3555
4744
|
const oldLines = this.lines;
|
|
3556
4745
|
const oldInlineMode = this.inlineMode;
|
|
4746
|
+
// TODO: use this.capture
|
|
3557
4747
|
try {
|
|
3558
4748
|
this.lines = [];
|
|
3559
4749
|
this.inlineMode = true;
|
|
3560
4750
|
this.visit(child);
|
|
3561
4751
|
const erbContent = this.lines.join("");
|
|
3562
4752
|
currentLineContent += erbContent;
|
|
3563
|
-
if ((indent.length + currentLineContent.length) > Math.max(this.maxLineLength, 120)) {
|
|
3564
|
-
this.
|
|
4753
|
+
if ((this.indent.length + currentLineContent.length) > Math.max(this.maxLineLength, 120)) {
|
|
4754
|
+
this.lines = oldLines;
|
|
4755
|
+
this.inlineMode = oldInlineMode;
|
|
4756
|
+
children.forEach(child => this.visit(child));
|
|
3565
4757
|
return;
|
|
3566
4758
|
}
|
|
3567
4759
|
}
|
|
@@ -3572,53 +4764,38 @@ class Printer extends Visitor {
|
|
|
3572
4764
|
}
|
|
3573
4765
|
else {
|
|
3574
4766
|
if (currentLineContent.trim()) {
|
|
3575
|
-
this.push(indent + currentLineContent.trim());
|
|
4767
|
+
this.push(this.indent + currentLineContent.trim());
|
|
3576
4768
|
currentLineContent = "";
|
|
3577
4769
|
}
|
|
3578
4770
|
this.visit(child);
|
|
3579
4771
|
}
|
|
3580
4772
|
}
|
|
3581
4773
|
if (currentLineContent.trim()) {
|
|
3582
|
-
const finalLine = indent + currentLineContent.trim();
|
|
4774
|
+
const finalLine = this.indent + currentLineContent.trim();
|
|
3583
4775
|
if (finalLine.length > Math.max(this.maxLineLength, 120)) {
|
|
3584
|
-
this.
|
|
4776
|
+
this.visitAll(children);
|
|
3585
4777
|
return;
|
|
3586
4778
|
}
|
|
3587
4779
|
this.push(finalLine);
|
|
3588
4780
|
}
|
|
3589
4781
|
}
|
|
3590
|
-
|
|
3591
|
-
children.
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
const hasTextContent = children.some(child => (child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE') &&
|
|
3595
|
-
child.content.trim() !== "");
|
|
3596
|
-
if (!hasTextContent) {
|
|
4782
|
+
isInTextFlowContext(_parent, children) {
|
|
4783
|
+
const hasTextContent = children.some(child => isNode(child, HTMLTextNode) && child.content.trim() !== "");
|
|
4784
|
+
const nonTextChildren = children.filter(child => !isNode(child, HTMLTextNode));
|
|
4785
|
+
if (!hasTextContent)
|
|
3597
4786
|
return false;
|
|
3598
|
-
|
|
3599
|
-
const nonTextChildren = children.filter(child => !(child instanceof HTMLTextNode || child.type === 'AST_HTML_TEXT_NODE'));
|
|
3600
|
-
if (nonTextChildren.length === 0) {
|
|
4787
|
+
if (nonTextChildren.length === 0)
|
|
3601
4788
|
return false;
|
|
3602
|
-
}
|
|
3603
4789
|
const allInline = nonTextChildren.every(child => {
|
|
3604
|
-
if (child
|
|
4790
|
+
if (isNode(child, ERBContentNode))
|
|
3605
4791
|
return true;
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
const element = child;
|
|
3609
|
-
const openTag = element.open_tag;
|
|
3610
|
-
const tagName = openTag?.tag_name?.value || '';
|
|
3611
|
-
return this.isInlineElement(tagName);
|
|
4792
|
+
if (isNode(child, HTMLElementNode)) {
|
|
4793
|
+
return this.isInlineElement(getTagName(child));
|
|
3612
4794
|
}
|
|
3613
4795
|
return false;
|
|
3614
4796
|
});
|
|
3615
|
-
if (!allInline)
|
|
4797
|
+
if (!allInline)
|
|
3616
4798
|
return false;
|
|
3617
|
-
}
|
|
3618
|
-
const maxNestingDepth = this.getMaxNestingDepth(children, 0);
|
|
3619
|
-
if (maxNestingDepth > 2) {
|
|
3620
|
-
return false;
|
|
3621
|
-
}
|
|
3622
4799
|
return true;
|
|
3623
4800
|
}
|
|
3624
4801
|
renderInlineOpen(name, attributes, selfClose, inlineNodes = [], allChildren = []) {
|
|
@@ -3626,73 +4803,68 @@ class Printer extends Visitor {
|
|
|
3626
4803
|
if (inlineNodes.length > 0) {
|
|
3627
4804
|
let result = `<${name}`;
|
|
3628
4805
|
if (allChildren.length > 0) {
|
|
3629
|
-
const
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
this.inlineMode = wasInlineMode;
|
|
3643
|
-
}
|
|
4806
|
+
const lines = this.capture(() => {
|
|
4807
|
+
allChildren.forEach(child => {
|
|
4808
|
+
if (isNode(child, HTMLAttributeNode)) {
|
|
4809
|
+
this.lines.push(" " + this.renderAttribute(child));
|
|
4810
|
+
}
|
|
4811
|
+
else if (!(isNode(child, WhitespaceNode))) {
|
|
4812
|
+
const wasInlineMode = this.inlineMode;
|
|
4813
|
+
this.inlineMode = true;
|
|
4814
|
+
this.lines.push(" ");
|
|
4815
|
+
this.visit(child);
|
|
4816
|
+
this.inlineMode = wasInlineMode;
|
|
4817
|
+
}
|
|
4818
|
+
});
|
|
3644
4819
|
});
|
|
3645
|
-
|
|
3646
|
-
this.lines = tempLines;
|
|
3647
|
-
this.indentLevel = currentIndentLevel;
|
|
3648
|
-
result += inlineContent;
|
|
4820
|
+
result += lines.join("");
|
|
3649
4821
|
}
|
|
3650
4822
|
else {
|
|
3651
4823
|
if (parts.length > 0) {
|
|
3652
4824
|
result += ` ${parts.join(" ")}`;
|
|
3653
4825
|
}
|
|
3654
|
-
const
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
this.inlineMode =
|
|
3662
|
-
}
|
|
3663
|
-
this.visit(node);
|
|
3664
|
-
this.inlineMode = wasInlineMode;
|
|
4826
|
+
const lines = this.capture(() => {
|
|
4827
|
+
inlineNodes.forEach(node => {
|
|
4828
|
+
const wasInlineMode = this.inlineMode;
|
|
4829
|
+
if (!isERBControlFlowNode(node)) {
|
|
4830
|
+
this.inlineMode = true;
|
|
4831
|
+
}
|
|
4832
|
+
this.visit(node);
|
|
4833
|
+
this.inlineMode = wasInlineMode;
|
|
4834
|
+
});
|
|
3665
4835
|
});
|
|
3666
|
-
|
|
3667
|
-
this.lines = tempLines;
|
|
3668
|
-
this.indentLevel = currentIndentLevel;
|
|
3669
|
-
result += inlineContent;
|
|
4836
|
+
result += lines.join("");
|
|
3670
4837
|
}
|
|
3671
4838
|
result += selfClose ? " />" : ">";
|
|
3672
4839
|
return result;
|
|
3673
4840
|
}
|
|
3674
|
-
return `<${name}${parts.length ? " " + parts.join(" ") : ""}${selfClose ? "
|
|
4841
|
+
return `<${name}${parts.length ? " " + parts.join(" ") : ""}${selfClose ? " />" : ">"}`;
|
|
3675
4842
|
}
|
|
3676
4843
|
renderAttribute(attribute) {
|
|
3677
|
-
const name = attribute.name.name
|
|
4844
|
+
const name = attribute.name ? getCombinedAttributeName(attribute.name) : "";
|
|
3678
4845
|
const equals = attribute.equals?.value ?? "";
|
|
4846
|
+
this.currentAttributeName = name;
|
|
3679
4847
|
let value = "";
|
|
3680
|
-
if (attribute.value && (attribute.value
|
|
4848
|
+
if (attribute.value && isNode(attribute.value, HTMLAttributeValueNode)) {
|
|
3681
4849
|
const attributeValue = attribute.value;
|
|
3682
4850
|
let open_quote = attributeValue.open_quote?.value ?? "";
|
|
3683
4851
|
let close_quote = attributeValue.close_quote?.value ?? "";
|
|
3684
4852
|
let htmlTextContent = "";
|
|
3685
4853
|
const content = attributeValue.children.map((child) => {
|
|
3686
|
-
if (child
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
4854
|
+
if (isNode(child, HTMLTextNode) || isNode(child, LiteralNode)) {
|
|
4855
|
+
htmlTextContent += child.content;
|
|
4856
|
+
return child.content;
|
|
4857
|
+
}
|
|
4858
|
+
else if (isNode(child, ERBContentNode)) {
|
|
4859
|
+
return IdentityPrinter.print(child);
|
|
3690
4860
|
}
|
|
3691
|
-
else
|
|
3692
|
-
const
|
|
3693
|
-
|
|
4861
|
+
else {
|
|
4862
|
+
const printed = IdentityPrinter.print(child);
|
|
4863
|
+
if (this.currentAttributeName && FormatPrinter.TOKEN_LIST_ATTRIBUTES.has(this.currentAttributeName)) {
|
|
4864
|
+
return printed.replace(/%>([^<\s])/g, '%> $1').replace(/([^>\s])<%/g, '$1 <%');
|
|
4865
|
+
}
|
|
4866
|
+
return printed;
|
|
3694
4867
|
}
|
|
3695
|
-
return "";
|
|
3696
4868
|
}).join("");
|
|
3697
4869
|
if (open_quote === "" && close_quote === "") {
|
|
3698
4870
|
open_quote = '"';
|
|
@@ -3702,21 +4874,31 @@ class Printer extends Visitor {
|
|
|
3702
4874
|
open_quote = '"';
|
|
3703
4875
|
close_quote = '"';
|
|
3704
4876
|
}
|
|
3705
|
-
|
|
4877
|
+
if (this.isFormattableAttribute(name, this.currentTagName)) {
|
|
4878
|
+
if (name === 'class') {
|
|
4879
|
+
value = this.formatClassAttribute(content, name, equals, open_quote, close_quote);
|
|
4880
|
+
}
|
|
4881
|
+
else {
|
|
4882
|
+
value = this.formatMultilineAttribute(content, name, open_quote, close_quote);
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
else {
|
|
4886
|
+
value = open_quote + content + close_quote;
|
|
4887
|
+
}
|
|
3706
4888
|
}
|
|
4889
|
+
this.currentAttributeName = null;
|
|
3707
4890
|
return name + equals + value;
|
|
3708
4891
|
}
|
|
3709
4892
|
/**
|
|
3710
4893
|
* Try to render a complete element inline including opening tag, children, and closing tag
|
|
3711
4894
|
*/
|
|
3712
|
-
tryRenderInlineFull(
|
|
4895
|
+
tryRenderInlineFull(_node, tagName, attributes, children) {
|
|
3713
4896
|
let result = `<${tagName}`;
|
|
3714
4897
|
result += this.renderAttributesString(attributes);
|
|
3715
4898
|
result += ">";
|
|
3716
4899
|
const childrenContent = this.tryRenderChildrenInline(children);
|
|
3717
|
-
if (!childrenContent)
|
|
4900
|
+
if (!childrenContent)
|
|
3718
4901
|
return null;
|
|
3719
|
-
}
|
|
3720
4902
|
result += childrenContent;
|
|
3721
4903
|
result += `</${tagName}>`;
|
|
3722
4904
|
return result;
|
|
@@ -3727,11 +4909,10 @@ class Printer extends Visitor {
|
|
|
3727
4909
|
tryRenderChildrenInline(children) {
|
|
3728
4910
|
let result = "";
|
|
3729
4911
|
for (const child of children) {
|
|
3730
|
-
if (child
|
|
3731
|
-
const
|
|
3732
|
-
const
|
|
3733
|
-
const
|
|
3734
|
-
const hasTrailingSpace = /\s$/.test(content);
|
|
4912
|
+
if (isNode(child, HTMLTextNode)) {
|
|
4913
|
+
const normalizedContent = child.content.replace(/\s+/g, ' ');
|
|
4914
|
+
const hasLeadingSpace = /^\s/.test(child.content);
|
|
4915
|
+
const hasTrailingSpace = /\s$/.test(child.content);
|
|
3735
4916
|
const trimmedContent = normalizedContent.trim();
|
|
3736
4917
|
if (trimmedContent) {
|
|
3737
4918
|
let finalContent = trimmedContent;
|
|
@@ -3749,36 +4930,19 @@ class Printer extends Visitor {
|
|
|
3749
4930
|
}
|
|
3750
4931
|
}
|
|
3751
4932
|
}
|
|
3752
|
-
else if (child
|
|
3753
|
-
const
|
|
3754
|
-
|
|
3755
|
-
const childTagName = openTag?.tag_name?.value || '';
|
|
3756
|
-
if (!this.isInlineElement(childTagName)) {
|
|
4933
|
+
else if (isNode(child, HTMLElementNode)) {
|
|
4934
|
+
const tagName = getTagName(child);
|
|
4935
|
+
if (!this.isInlineElement(tagName)) {
|
|
3757
4936
|
return null;
|
|
3758
4937
|
}
|
|
3759
|
-
const childInline = this.tryRenderInlineFull(
|
|
3760
|
-
!((c instanceof HTMLTextNode || c.type === 'AST_HTML_TEXT_NODE') && c?.content.trim() === "")));
|
|
4938
|
+
const childInline = this.tryRenderInlineFull(child, tagName, filterNodes(child.open_tag?.children, HTMLAttributeNode), this.filterEmptyNodes(child.body));
|
|
3761
4939
|
if (!childInline) {
|
|
3762
4940
|
return null;
|
|
3763
4941
|
}
|
|
3764
4942
|
result += childInline;
|
|
3765
4943
|
}
|
|
3766
4944
|
else {
|
|
3767
|
-
|
|
3768
|
-
const oldInlineMode = this.inlineMode;
|
|
3769
|
-
const oldIndentLevel = this.indentLevel;
|
|
3770
|
-
try {
|
|
3771
|
-
this.lines = [];
|
|
3772
|
-
this.inlineMode = true;
|
|
3773
|
-
this.indentLevel = 0;
|
|
3774
|
-
this.visit(child);
|
|
3775
|
-
result += this.lines.join("");
|
|
3776
|
-
}
|
|
3777
|
-
finally {
|
|
3778
|
-
this.lines = oldLines;
|
|
3779
|
-
this.inlineMode = oldInlineMode;
|
|
3780
|
-
this.indentLevel = oldIndentLevel;
|
|
3781
|
-
}
|
|
4945
|
+
result += this.capture(() => this.visit(child)).join("");
|
|
3782
4946
|
}
|
|
3783
4947
|
}
|
|
3784
4948
|
return result.trim();
|
|
@@ -3787,142 +4951,145 @@ class Printer extends Visitor {
|
|
|
3787
4951
|
* Try to render children inline if they are simple enough.
|
|
3788
4952
|
* Returns the inline string if possible, null otherwise.
|
|
3789
4953
|
*/
|
|
3790
|
-
tryRenderInline(children, tagName
|
|
3791
|
-
if (!forceInline && children.length > 10) {
|
|
3792
|
-
return null;
|
|
3793
|
-
}
|
|
3794
|
-
const maxNestingDepth = this.getMaxNestingDepth(children, 0);
|
|
3795
|
-
let maxAllowedDepth = forceInline ? 5 : (tagName && ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div'].includes(tagName) ? 1 : 2);
|
|
3796
|
-
if (hasTextFlow && maxNestingDepth >= 2) {
|
|
3797
|
-
const roughContentLength = this.estimateContentLength(children);
|
|
3798
|
-
if (roughContentLength > 47) {
|
|
3799
|
-
maxAllowedDepth = 1;
|
|
3800
|
-
}
|
|
3801
|
-
}
|
|
3802
|
-
if (!forceInline && maxNestingDepth > maxAllowedDepth) {
|
|
3803
|
-
this.isInComplexNesting = true;
|
|
3804
|
-
return null;
|
|
3805
|
-
}
|
|
4954
|
+
tryRenderInline(children, tagName) {
|
|
3806
4955
|
for (const child of children) {
|
|
3807
|
-
if (child
|
|
3808
|
-
|
|
3809
|
-
if (textContent.includes('\n')) {
|
|
4956
|
+
if (isNode(child, HTMLTextNode)) {
|
|
4957
|
+
if (child.content.includes('\n')) {
|
|
3810
4958
|
return null;
|
|
3811
4959
|
}
|
|
3812
4960
|
}
|
|
3813
|
-
else if (child
|
|
3814
|
-
const
|
|
3815
|
-
const openTag = element.open_tag;
|
|
3816
|
-
const elementTagName = openTag?.tag_name?.value || '';
|
|
3817
|
-
const isInlineElement = this.isInlineElement(elementTagName);
|
|
4961
|
+
else if (isNode(child, HTMLElementNode)) {
|
|
4962
|
+
const isInlineElement = this.isInlineElement(getTagName(child));
|
|
3818
4963
|
if (!isInlineElement) {
|
|
3819
4964
|
return null;
|
|
3820
4965
|
}
|
|
3821
4966
|
}
|
|
3822
|
-
else if (child
|
|
4967
|
+
else if (isNode(child, ERBContentNode)) ;
|
|
3823
4968
|
else {
|
|
3824
4969
|
return null;
|
|
3825
4970
|
}
|
|
3826
4971
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
4972
|
+
let content = "";
|
|
4973
|
+
this.capture(() => {
|
|
4974
|
+
content = this.renderChildrenInline(children);
|
|
4975
|
+
});
|
|
4976
|
+
return `<${tagName}>${content}</${tagName}>`;
|
|
4977
|
+
}
|
|
4978
|
+
/**
|
|
4979
|
+
* Check if children contain mixed text and inline elements (like "text<em>inline</em>text")
|
|
4980
|
+
* or mixed ERB output and text (like "<%= value %> text")
|
|
4981
|
+
* This indicates content that should be formatted inline even with structural newlines
|
|
4982
|
+
*/
|
|
4983
|
+
hasMixedTextAndInlineContent(children) {
|
|
4984
|
+
let hasText = false;
|
|
4985
|
+
let hasInlineElements = false;
|
|
4986
|
+
for (const child of children) {
|
|
4987
|
+
if (isNode(child, HTMLTextNode)) {
|
|
4988
|
+
if (child.content.trim() !== "") {
|
|
4989
|
+
hasText = true;
|
|
3845
4990
|
}
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
const close = erbNode.tag_closing?.value ?? "";
|
|
3851
|
-
content += `${open}${this.formatERBContent(erbContent)}${close}`;
|
|
4991
|
+
}
|
|
4992
|
+
else if (isNode(child, HTMLElementNode)) {
|
|
4993
|
+
if (this.isInlineElement(getTagName(child))) {
|
|
4994
|
+
hasInlineElements = true;
|
|
3852
4995
|
}
|
|
3853
4996
|
}
|
|
3854
|
-
content = content.replace(/\s+/g, ' ').trim();
|
|
3855
|
-
return `<${tagName}>${content}</${tagName}>`;
|
|
3856
|
-
}
|
|
3857
|
-
finally {
|
|
3858
|
-
this.lines = oldLines;
|
|
3859
|
-
this.inlineMode = oldInlineMode;
|
|
3860
4997
|
}
|
|
4998
|
+
return (hasText && hasInlineElements) || (hasERBOutput(children) && hasText);
|
|
3861
4999
|
}
|
|
3862
5000
|
/**
|
|
3863
|
-
*
|
|
5001
|
+
* Check if children contain any text content with newlines
|
|
3864
5002
|
*/
|
|
3865
|
-
|
|
3866
|
-
let length = 0;
|
|
5003
|
+
hasMultilineTextContent(children) {
|
|
3867
5004
|
for (const child of children) {
|
|
3868
|
-
if (child
|
|
3869
|
-
|
|
3870
|
-
}
|
|
3871
|
-
else if (child instanceof HTMLElementNode || child.type === 'AST_HTML_ELEMENT_NODE') {
|
|
3872
|
-
const element = child;
|
|
3873
|
-
const openTag = element.open_tag;
|
|
3874
|
-
const tagName = openTag?.tag_name?.value || '';
|
|
3875
|
-
length += tagName.length + 5; // Rough estimate for tag overhead
|
|
3876
|
-
length += this.estimateContentLength(element.body);
|
|
5005
|
+
if (isNode(child, HTMLTextNode)) {
|
|
5006
|
+
return child.content.includes('\n');
|
|
3877
5007
|
}
|
|
3878
|
-
|
|
3879
|
-
|
|
5008
|
+
if (isNode(child, HTMLElementNode)) {
|
|
5009
|
+
const nestedChildren = this.filterEmptyNodes(child.body);
|
|
5010
|
+
if (this.hasMultilineTextContent(nestedChildren)) {
|
|
5011
|
+
return true;
|
|
5012
|
+
}
|
|
3880
5013
|
}
|
|
3881
5014
|
}
|
|
3882
|
-
return
|
|
5015
|
+
return false;
|
|
3883
5016
|
}
|
|
3884
5017
|
/**
|
|
3885
|
-
*
|
|
5018
|
+
* Check if all nested elements in the children are inline elements
|
|
3886
5019
|
*/
|
|
3887
|
-
|
|
3888
|
-
let maxDepth = currentDepth;
|
|
5020
|
+
areAllNestedElementsInline(children) {
|
|
3889
5021
|
for (const child of children) {
|
|
3890
|
-
if (child
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
const
|
|
3895
|
-
|
|
5022
|
+
if (isNode(child, HTMLElementNode)) {
|
|
5023
|
+
if (!this.isInlineElement(getTagName(child))) {
|
|
5024
|
+
return false;
|
|
5025
|
+
}
|
|
5026
|
+
const nestedChildren = this.filterEmptyNodes(child.body);
|
|
5027
|
+
if (!this.areAllNestedElementsInline(nestedChildren)) {
|
|
5028
|
+
return false;
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
else if (isAnyOf(child, HTMLDoctypeNode, HTMLCommentNode, isERBControlFlowNode)) {
|
|
5032
|
+
return false;
|
|
3896
5033
|
}
|
|
3897
5034
|
}
|
|
3898
|
-
return
|
|
5035
|
+
return true;
|
|
5036
|
+
}
|
|
5037
|
+
/**
|
|
5038
|
+
* Check if element has complex ERB control flow
|
|
5039
|
+
*/
|
|
5040
|
+
hasComplexERBControlFlow(inlineNodes) {
|
|
5041
|
+
return inlineNodes.some(node => {
|
|
5042
|
+
if (isNode(node, ERBIfNode)) {
|
|
5043
|
+
if (node.statements.length > 0 && node.location) {
|
|
5044
|
+
const startLine = node.location.start.line;
|
|
5045
|
+
const endLine = node.location.end.line;
|
|
5046
|
+
return startLine !== endLine;
|
|
5047
|
+
}
|
|
5048
|
+
return false;
|
|
5049
|
+
}
|
|
5050
|
+
return false;
|
|
5051
|
+
});
|
|
5052
|
+
}
|
|
5053
|
+
/**
|
|
5054
|
+
* Filter children to remove insignificant whitespace
|
|
5055
|
+
*/
|
|
5056
|
+
filterSignificantChildren(body, hasTextFlow) {
|
|
5057
|
+
return body.filter(child => {
|
|
5058
|
+
if (isNode(child, WhitespaceNode))
|
|
5059
|
+
return false;
|
|
5060
|
+
if (isNode(child, HTMLTextNode)) {
|
|
5061
|
+
if (hasTextFlow && child.content === " ")
|
|
5062
|
+
return true;
|
|
5063
|
+
return child.content.trim() !== "";
|
|
5064
|
+
}
|
|
5065
|
+
return true;
|
|
5066
|
+
});
|
|
3899
5067
|
}
|
|
3900
5068
|
/**
|
|
3901
|
-
*
|
|
5069
|
+
* Filter out empty text nodes and whitespace nodes
|
|
3902
5070
|
*/
|
|
5071
|
+
filterEmptyNodes(nodes) {
|
|
5072
|
+
return nodes.filter(child => !isNode(child, WhitespaceNode) && !(isNode(child, HTMLTextNode) && child.content.trim() === ""));
|
|
5073
|
+
}
|
|
3903
5074
|
renderElementInline(element) {
|
|
3904
|
-
const children = element.body
|
|
3905
|
-
|
|
5075
|
+
const children = this.filterEmptyNodes(element.body);
|
|
5076
|
+
return this.renderChildrenInline(children);
|
|
5077
|
+
}
|
|
5078
|
+
renderChildrenInline(children) {
|
|
3906
5079
|
let content = '';
|
|
3907
5080
|
for (const child of children) {
|
|
3908
|
-
if (child
|
|
5081
|
+
if (isNode(child, HTMLTextNode)) {
|
|
3909
5082
|
content += child.content;
|
|
3910
5083
|
}
|
|
3911
|
-
else if (child
|
|
3912
|
-
const
|
|
3913
|
-
const
|
|
3914
|
-
const childTagName = openTag?.tag_name?.value || '';
|
|
3915
|
-
const attributes = this.extractAttributes(openTag.children);
|
|
5084
|
+
else if (isNode(child, HTMLElementNode)) {
|
|
5085
|
+
const tagName = getTagName(child);
|
|
5086
|
+
const attributes = filterNodes(child.open_tag?.children, HTMLAttributeNode);
|
|
3916
5087
|
const attributesString = this.renderAttributesString(attributes);
|
|
3917
|
-
const childContent = this.renderElementInline(
|
|
3918
|
-
content += `<${
|
|
5088
|
+
const childContent = this.renderElementInline(child);
|
|
5089
|
+
content += `<${tagName}${attributesString}>${childContent}</${tagName}>`;
|
|
3919
5090
|
}
|
|
3920
|
-
else if (child
|
|
3921
|
-
|
|
3922
|
-
const open = erbNode.tag_opening?.value ?? "";
|
|
3923
|
-
const erbContent = erbNode.content?.value ?? "";
|
|
3924
|
-
const close = erbNode.tag_closing?.value ?? "";
|
|
3925
|
-
content += `${open}${this.formatERBContent(erbContent)}${close}`;
|
|
5091
|
+
else if (isNode(child, ERBContentNode)) {
|
|
5092
|
+
content += this.reconstructERBNode(child, true);
|
|
3926
5093
|
}
|
|
3927
5094
|
}
|
|
3928
5095
|
return content.replace(/\s+/g, ' ').trim();
|
|
@@ -3967,7 +5134,7 @@ class Formatter {
|
|
|
3967
5134
|
if (result.failed)
|
|
3968
5135
|
return source;
|
|
3969
5136
|
const resolvedOptions = resolveFormatOptions({ ...this.options, ...options });
|
|
3970
|
-
return new
|
|
5137
|
+
return new FormatPrinter(source, resolvedOptions).print(result.value);
|
|
3971
5138
|
}
|
|
3972
5139
|
parse(source) {
|
|
3973
5140
|
this.herb.ensureBackend();
|
|
@@ -3975,5 +5142,5 @@ class Formatter {
|
|
|
3975
5142
|
}
|
|
3976
5143
|
}
|
|
3977
5144
|
|
|
3978
|
-
export { Formatter, defaultFormatOptions, resolveFormatOptions };
|
|
5145
|
+
export { FormatPrinter, Formatter, defaultFormatOptions, resolveFormatOptions };
|
|
3979
5146
|
//# sourceMappingURL=index.esm.js.map
|