@@ -81,7 +81,7 @@ pub struct Context<'a> {
81
81
}
82
82
83
83
#[ derive( Default ) ]
84
- pub struct ExportedClass {
84
+ struct ExportedClass {
85
85
comments : String ,
86
86
contents : String ,
87
87
/// The TypeScript for the class's methods.
@@ -95,9 +95,29 @@ pub struct ExportedClass {
95
95
is_inspectable : bool ,
96
96
/// All readable properties of the class
97
97
readable_properties : Vec < String > ,
98
- /// Map from field name to type as a string, docs plus whether it has a setter,
99
- /// whether it's optional and whether it's static.
100
- typescript_fields : HashMap < String , ( String , String , bool , bool , bool ) > ,
98
+ /// Map from field to information about those fields
99
+ typescript_fields : HashMap < FieldLocation , FieldInfo > ,
100
+ }
101
+
102
+ #[ derive( Debug , PartialEq , Eq , PartialOrd , Ord , Hash ) ]
103
+ struct FieldLocation {
104
+ name : String ,
105
+ is_static : bool ,
106
+ }
107
+ #[ derive( Debug ) ]
108
+ struct FieldInfo {
109
+ name : String ,
110
+ is_static : bool ,
111
+ order : usize ,
112
+ getter : Option < FieldAccessor > ,
113
+ setter : Option < FieldAccessor > ,
114
+ }
115
+ /// A getter or setter for a field.
116
+ #[ derive( Debug ) ]
117
+ struct FieldAccessor {
118
+ ty : String ,
119
+ docs : String ,
120
+ is_optional : bool ,
101
121
}
102
122
103
123
const INITIAL_HEAP_VALUES : & [ & str ] = & [ "undefined" , "null" , "true" , "false" ] ;
@@ -1130,27 +1150,8 @@ __wbg_set_wasm(wasm);"
1130
1150
dst. push_str ( & class. contents ) ;
1131
1151
ts_dst. push_str ( & class. typescript ) ;
1132
1152
1133
- let mut fields = class. typescript_fields . keys ( ) . collect :: < Vec < _ > > ( ) ;
1134
- fields. sort ( ) ; // make sure we have deterministic output
1135
- for name in fields {
1136
- let ( ty, docs, has_setter, is_optional, is_static) = & class. typescript_fields [ name] ;
1137
- ts_dst. push_str ( docs) ;
1138
- ts_dst. push_str ( " " ) ;
1139
- if * is_static {
1140
- ts_dst. push_str ( "static " ) ;
1141
- }
1142
- if !has_setter {
1143
- ts_dst. push_str ( "readonly " ) ;
1144
- }
1145
- ts_dst. push_str ( name) ;
1146
- if * is_optional {
1147
- ts_dst. push_str ( "?: " ) ;
1148
- } else {
1149
- ts_dst. push_str ( ": " ) ;
1150
- }
1151
- ts_dst. push_str ( ty) ;
1152
- ts_dst. push_str ( ";\n " ) ;
1153
- }
1153
+ self . write_class_field_types ( class, & mut ts_dst) ;
1154
+
1154
1155
dst. push_str ( "}\n " ) ;
1155
1156
ts_dst. push_str ( "}\n " ) ;
1156
1157
@@ -1164,6 +1165,124 @@ __wbg_set_wasm(wasm);"
1164
1165
Ok ( ( ) )
1165
1166
}
1166
1167
1168
+ fn write_class_field_types ( & mut self , class : & ExportedClass , ts_dst : & mut String ) {
1169
+ let mut fields: Vec < & FieldInfo > = class. typescript_fields . values ( ) . collect ( ) ;
1170
+ fields. sort_by_key ( |f| f. order ) ; // make sure we have deterministic output
1171
+
1172
+ for FieldInfo {
1173
+ name,
1174
+ is_static,
1175
+ getter,
1176
+ setter,
1177
+ ..
1178
+ } in fields
1179
+ {
1180
+ let is_static = if * is_static { "static " } else { "" } ;
1181
+
1182
+ let write_docs = |ts_dst : & mut String , docs : & str | {
1183
+ if docs. is_empty ( ) {
1184
+ return ;
1185
+ }
1186
+ // indent by 2 spaces
1187
+ for line in docs. lines ( ) {
1188
+ ts_dst. push_str ( " " ) ;
1189
+ ts_dst. push_str ( line) ;
1190
+ ts_dst. push ( '\n' ) ;
1191
+ }
1192
+ } ;
1193
+ let write_getter = |ts_dst : & mut String , getter : & FieldAccessor | {
1194
+ write_docs ( ts_dst, & getter. docs ) ;
1195
+ ts_dst. push_str ( " " ) ;
1196
+ ts_dst. push_str ( is_static) ;
1197
+ ts_dst. push_str ( "get " ) ;
1198
+ ts_dst. push_str ( name) ;
1199
+ ts_dst. push_str ( "(): " ) ;
1200
+ ts_dst. push_str ( & getter. ty ) ;
1201
+ ts_dst. push_str ( ";\n " ) ;
1202
+ } ;
1203
+ let write_setter = |ts_dst : & mut String , setter : & FieldAccessor | {
1204
+ write_docs ( ts_dst, & setter. docs ) ;
1205
+ ts_dst. push_str ( " " ) ;
1206
+ ts_dst. push_str ( is_static) ;
1207
+ ts_dst. push_str ( "set " ) ;
1208
+ ts_dst. push_str ( name) ;
1209
+ ts_dst. push_str ( "(value: " ) ;
1210
+ ts_dst. push_str ( & setter. ty ) ;
1211
+ if setter. is_optional {
1212
+ ts_dst. push_str ( " | undefined" ) ;
1213
+ }
1214
+ ts_dst. push_str ( ");\n " ) ;
1215
+ } ;
1216
+
1217
+ match ( getter, setter) {
1218
+ ( None , None ) => unreachable ! ( "field without getter or setter" ) ,
1219
+ ( Some ( getter) , None ) => {
1220
+ // readonly property
1221
+ write_docs ( ts_dst, & getter. docs ) ;
1222
+ ts_dst. push_str ( " " ) ;
1223
+ ts_dst. push_str ( is_static) ;
1224
+ ts_dst. push_str ( "readonly " ) ;
1225
+ ts_dst. push_str ( name) ;
1226
+ ts_dst. push_str ( if getter. is_optional { "?: " } else { ": " } ) ;
1227
+ ts_dst. push_str ( & getter. ty ) ;
1228
+ ts_dst. push_str ( ";\n " ) ;
1229
+ }
1230
+ ( None , Some ( setter) ) => {
1231
+ // write-only property
1232
+
1233
+ // Note: TypeScript does not handle the types of write-only
1234
+ // properties correctly and will allow reads from write-only
1235
+ // properties. This isn't a wasm-bindgen issue, but a
1236
+ // TypeScript issue.
1237
+ write_setter ( ts_dst, setter) ;
1238
+ }
1239
+ ( Some ( getter) , Some ( setter) ) => {
1240
+ // read-write property
1241
+
1242
+ // Here's the tricky part. The getter and setter might have
1243
+ // different types. Obviously, we can only declare a
1244
+ // property as `foo: T` if both the getter and setter have
1245
+ // the same type `T`. If they don't, we have to declare the
1246
+ // getter and setter separately.
1247
+
1248
+ // We current generate types for optional arguments and
1249
+ // return values differently. This is why for the field
1250
+ // `foo: Option<T>`, the setter will have type `T` with
1251
+ // `is_optional` set, while the getter has type
1252
+ // `T | undefined`. Because of this difference, we have to
1253
+ // "normalize" the type of the setter.
1254
+ let same_type = if setter. is_optional {
1255
+ getter. ty == setter. ty . clone ( ) + " | undefined"
1256
+ } else {
1257
+ getter. ty == setter. ty
1258
+ } ;
1259
+
1260
+ if same_type {
1261
+ // simple property, e.g. foo: T
1262
+
1263
+ // Prefer the docs of the getter over the setter's
1264
+ let docs = if !getter. docs . is_empty ( ) {
1265
+ & getter. docs
1266
+ } else {
1267
+ & setter. docs
1268
+ } ;
1269
+ write_docs ( ts_dst, docs) ;
1270
+ ts_dst. push_str ( " " ) ;
1271
+ ts_dst. push_str ( is_static) ;
1272
+ ts_dst. push_str ( name) ;
1273
+ ts_dst. push_str ( if setter. is_optional { "?: " } else { ": " } ) ;
1274
+ ts_dst. push_str ( & setter. ty ) ;
1275
+ ts_dst. push_str ( ";\n " ) ;
1276
+ } else {
1277
+ // separate getter and setter
1278
+ write_getter ( ts_dst, getter) ;
1279
+ write_setter ( ts_dst, setter) ;
1280
+ }
1281
+ }
1282
+ } ;
1283
+ }
1284
+ }
1285
+
1167
1286
fn expose_drop_ref ( & mut self ) {
1168
1287
if !self . should_write_global ( "drop_ref" ) {
1169
1288
return ;
@@ -2743,16 +2862,18 @@ __wbg_set_wasm(wasm);"
2743
2862
prefix += "get " ;
2744
2863
// For getters and setters, we generate a separate TypeScript definition.
2745
2864
if export. generate_typescript {
2746
- exported. push_accessor_ts (
2747
- & ts_docs,
2748
- name,
2865
+ let location = FieldLocation {
2866
+ name : name. clone ( ) ,
2867
+ is_static : receiver. is_static ( ) ,
2868
+ } ;
2869
+ let accessor = FieldAccessor {
2749
2870
// This is only set to `None` when generating a constructor.
2750
- ts_ret_ty
2751
- . as_deref ( )
2752
- . expect ( "missing return type for getter" ) ,
2753
- false ,
2754
- receiver . is_static ( ) ,
2755
- ) ;
2871
+ ty : ts_ret_ty. expect ( "missing return type for getter" ) ,
2872
+ docs : ts_docs . clone ( ) ,
2873
+ is_optional : false ,
2874
+ } ;
2875
+
2876
+ exported . push_accessor_ts ( location , accessor , false ) ;
2756
2877
}
2757
2878
// Add the getter to the list of readable fields (used to generate `toJSON`)
2758
2879
exported. readable_properties . push ( name. clone ( ) ) ;
@@ -2762,15 +2883,17 @@ __wbg_set_wasm(wasm);"
2762
2883
AuxExportedMethodKind :: Setter => {
2763
2884
prefix += "set " ;
2764
2885
if export. generate_typescript {
2765
- let is_optional = exported. push_accessor_ts (
2766
- & ts_docs,
2767
- name,
2768
- & ts_arg_tys[ 0 ] ,
2769
- true ,
2770
- receiver. is_static ( ) ,
2771
- ) ;
2772
- // Set whether the field is optional.
2773
- * is_optional = might_be_optional_field;
2886
+ let location = FieldLocation {
2887
+ name : name. clone ( ) ,
2888
+ is_static : receiver. is_static ( ) ,
2889
+ } ;
2890
+ let accessor = FieldAccessor {
2891
+ ty : ts_arg_tys[ 0 ] . clone ( ) ,
2892
+ docs : ts_docs. clone ( ) ,
2893
+ is_optional : might_be_optional_field,
2894
+ } ;
2895
+
2896
+ exported. push_accessor_ts ( location, accessor, true ) ;
2774
2897
}
2775
2898
None
2776
2899
}
@@ -4427,26 +4550,29 @@ impl ExportedClass {
4427
4550
}
4428
4551
}
4429
4552
4430
- #[ allow( clippy:: assigning_clones) ] // Clippy's suggested fix doesn't work at MSRV.
4431
4553
fn push_accessor_ts (
4432
4554
& mut self ,
4433
- docs : & str ,
4434
- field : & str ,
4435
- ty : & str ,
4555
+ location : FieldLocation ,
4556
+ accessor : FieldAccessor ,
4436
4557
is_setter : bool ,
4437
- is_static : bool ,
4438
- ) -> & mut bool {
4439
- let ( ty_dst, accessor_docs, has_setter, is_optional, is_static_dst) =
4440
- self . typescript_fields . entry ( field. to_string ( ) ) . or_default ( ) ;
4441
-
4442
- * ty_dst = ty. to_string ( ) ;
4443
- // Deterministic output: always use the getter's docs if available
4444
- if !docs. is_empty ( ) && ( accessor_docs. is_empty ( ) || !is_setter) {
4445
- * accessor_docs = docs. to_owned ( ) ;
4558
+ ) {
4559
+ let size = self . typescript_fields . len ( ) ;
4560
+ let field = self
4561
+ . typescript_fields
4562
+ . entry ( location)
4563
+ . or_insert_with_key ( |location| FieldInfo {
4564
+ name : location. name . to_string ( ) ,
4565
+ is_static : location. is_static ,
4566
+ order : size,
4567
+ getter : None ,
4568
+ setter : None ,
4569
+ } ) ;
4570
+
4571
+ if is_setter {
4572
+ field. setter = Some ( accessor) ;
4573
+ } else {
4574
+ field. getter = Some ( accessor) ;
4446
4575
}
4447
- * has_setter |= is_setter;
4448
- * is_static_dst = is_static;
4449
- is_optional
4450
4576
}
4451
4577
}
4452
4578
0 commit comments