wit_bindgen_gen_ts_near/
lib.rs

1use heck::*;
2use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
3use std::mem;
4use wit_bindgen_gen_core::wit_parser::abi::AbiVariant;
5use wit_bindgen_gen_core::{wit_parser::*, Direction, Files, Generator};
6
7mod gen;
8pub use gen::generate_typescript;
9
10#[derive(Default)]
11pub struct Ts {
12    src: Source,
13    imports: HashSet<String>,
14    in_import: bool,
15    opts: Opts,
16    guest_imports: HashMap<String, Imports>,
17    guest_exports: HashMap<String, Exports>,
18    sizes: SizeAlign,
19    #[allow(dead_code)]
20    needs_get_export: bool,
21    #[allow(dead_code)]
22    imported_resources: BTreeSet<ResourceId>,
23    #[allow(dead_code)]
24    exported_resources: BTreeSet<ResourceId>,
25    needs_ty_option: bool,
26    needs_ty_result: bool,
27    needs_ty_push_buffer: bool,
28    needs_ty_pull_buffer: bool,
29}
30
31#[derive(Default)]
32struct Imports {
33    freestanding_funcs: Vec<(String, Source)>,
34    resource_funcs: BTreeMap<ResourceId, Vec<(String, Source)>>,
35}
36
37#[derive(Default)]
38struct Exports {
39    freestanding_funcs: Vec<Source>,
40    arg_types: Vec<Source>,
41    resource_funcs: BTreeMap<ResourceId, Vec<Source>>,
42}
43
44#[derive(Default, Debug, Clone)]
45#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))]
46pub struct Opts {
47    #[cfg_attr(feature = "structopt", structopt(long = "no-typescript"))]
48    pub no_typescript: bool,
49}
50
51impl Opts {
52    pub fn build(self) -> Ts {
53        let mut r = Ts::new();
54        r.opts = self;
55        r
56    }
57}
58
59impl Ts {
60    pub fn new() -> Ts {
61        Ts::default()
62    }
63
64    fn abi_variant(dir: Direction) -> AbiVariant {
65        match dir {
66            Direction::Import => AbiVariant::GuestExport,
67            Direction::Export => AbiVariant::GuestImport,
68        }
69    }
70
71    fn array_ty(&self, iface: &Interface, ty: &Type) -> Option<&'static str> {
72        match ty {
73            Type::U8 | Type::CChar => Some("Uint8Array"),
74            Type::S8 => Some("Int8Array"),
75            Type::U16 => Some("Uint16Array"),
76            Type::S16 => Some("Int16Array"),
77            Type::U32 | Type::Usize => Some("Uint32Array"),
78            Type::S32 => Some("Int32Array"),
79            Type::U64 => Some("BigUint64Array"),
80            Type::S64 => Some("BigInt64Array"),
81            Type::F32 => Some("Float32Array"),
82            Type::F64 => Some("Float64Array"),
83            Type::Char => None,
84            Type::Handle(_) => None,
85            Type::Id(id) => match &iface.types[*id].kind {
86                TypeDefKind::Type(t) => self.array_ty(iface, t),
87                _ => None,
88            },
89        }
90    }
91
92    fn print_ty(&mut self, iface: &Interface, ty: &Type) {
93        match ty {
94            Type::U8 => self.src.ts("u8"),
95            Type::S8 => self.src.ts("i8"),
96            Type::U16 => self.src.ts("u16"),
97            Type::S16 => self.src.ts("i16"),
98            Type::U32 => self.src.ts("u32"),
99            Type::Usize => self.src.ts("usize"),
100            Type::S32 => self.src.ts("i32"),
101            Type::F32 => self.src.ts("f32"),
102            Type::F64 => self.src.ts("f64"),
103            Type::U64 => self.src.ts("u64"),
104            Type::S64 => self.src.ts("i64"),
105            Type::CChar => self.src.ts("number"),
106            Type::Char => self.src.ts("string"),
107            Type::Handle(id) => self.src.ts(&iface.resources[*id].name.to_camel_case()),
108            Type::Id(id) => {
109                let ty = &iface.types[*id];
110                if let Some(name) = &ty.name {
111                    return self.src.ts(&name.to_camel_case());
112                }
113                match &ty.kind {
114                    TypeDefKind::Type(t) => self.print_ty(iface, t),
115                    TypeDefKind::Record(r) if r.is_tuple() => self.print_tuple(iface, r),
116                    TypeDefKind::Record(_) => panic!("anonymous record"),
117                    TypeDefKind::Variant(v) if v.is_bool() => self.src.ts("boolean"),
118                    TypeDefKind::Variant(v) => {
119                        if iface.is_nullable_option(v) {
120                            self.print_ty(iface, v.cases[1].ty.as_ref().unwrap());
121                            self.src.ts(" | null");
122                        } else if let Some(t) = v.as_option() {
123                            self.needs_ty_option = true;
124                            self.src.ts("Option<");
125                            self.print_ty(iface, t);
126                            self.src.ts(">");
127                        } else if let Some((ok, err)) = v.as_expected() {
128                            self.needs_ty_result = true;
129                            self.src.ts("Result<");
130                            match ok {
131                                Some(ok) => self.print_ty(iface, ok),
132                                None => self.src.ts("undefined"),
133                            }
134                            self.src.ts(", ");
135                            match err {
136                                Some(err) => self.print_ty(iface, err),
137                                None => self.src.ts("undefined"),
138                            }
139                            self.src.ts(">");
140                        } else {
141                            panic!("anonymous variant");
142                        }
143                    }
144                    TypeDefKind::List(v) => self.print_list(iface, v),
145                    TypeDefKind::PushBuffer(v) => self.print_buffer(iface, true, v),
146                    TypeDefKind::PullBuffer(v) => self.print_buffer(iface, false, v),
147                    TypeDefKind::Pointer(_) | TypeDefKind::ConstPointer(_) => {
148                        self.src.ts("number");
149                    }
150                }
151            }
152        }
153    }
154
155    fn print_list(&mut self, iface: &Interface, ty: &Type) {
156        if let Some(r) = self.hash_map(iface, ty) {
157            self.src.ts("Record<");
158            self.print_ty(iface, &r.fields[0].ty);
159            self.src.ts(", ");
160            self.print_ty(iface, &r.fields[1].ty);
161            self.src.ts(">");
162        } else {
163            match self.array_ty(iface, ty) {
164                Some(ty) => self.src.ts(ty),
165                None => {
166                    if let Type::Char = ty {
167                        self.src.ts("string");
168                    } else {
169                        self.print_ty(iface, ty);
170                        self.src.ts("[]");
171                    }
172                }
173            }
174        }
175    }
176
177    fn print_tuple(&mut self, iface: &Interface, record: &Record) {
178        self.src.ts("[");
179        for (i, field) in record.fields.iter().enumerate() {
180            if i > 0 {
181                self.src.ts(", ");
182            }
183            self.print_ty(iface, &field.ty);
184        }
185        self.src.ts("]");
186    }
187
188    fn print_buffer(&mut self, iface: &Interface, push: bool, ty: &Type) {
189        match self.array_ty(iface, ty) {
190            Some(ty) => self.src.ts(ty),
191            None => {
192                if push {
193                    self.needs_ty_push_buffer = true;
194                    self.src.ts("PushBuffer");
195                } else {
196                    self.needs_ty_pull_buffer = true;
197                    self.src.ts("PullBuffer");
198                }
199                self.src.ts("<");
200                self.print_ty(iface, ty);
201                self.src.ts(">");
202            }
203        }
204    }
205
206    fn docs(&mut self, docs: &Docs) {
207        self.doc_str(&docs.contents);
208    }
209
210    fn doc_str(&mut self, contents: &Option<String>) {
211        let docs = match &contents {
212            Some(docs) => docs,
213            None => return,
214        };
215        let lines = docs
216            .lines()
217            .filter(|line| *line != "@change" && *line != "@view")
218            .collect::<Vec<&str>>();
219        if !lines.is_empty() {
220            self.src.ts("/**\n");
221            for line in lines {
222                self.src.ts(&format!("* {}\n", line));
223            }
224            self.src.ts("*/\n");
225        }
226    }
227
228    fn print_args_type(&mut self, iface: &Interface, func: &Function, param_start: usize) {
229        let none: Option<String> = None;
230        let arg_fields: Vec<(&str, &Type, &Option<String>)> = func.params[param_start..]
231            .iter()
232            .map(|(name, ty)| (name.as_str(), ty, &none))
233            .collect();
234        self.print_fields(iface, arg_fields);
235    }
236    #[allow(dead_code)]
237    fn print_args(&mut self, iface: &Interface, func: &Function, param_start: usize) {
238        self.src.ts("(args");
239        if func.params.is_empty() {
240            self.src.ts(" = {}");
241        } else {
242            self.src.ts(": {\n");
243            self.print_args_type(iface, func, param_start);
244            self.src.ts("}");
245        }
246        if func.params.is_empty() {};
247        self.src.ts(", options?: ");
248        self.src.ts(if is_change(func) {
249            "ChangeMethodOptions"
250        } else {
251            "ViewFunctionOptions"
252        });
253        self.src.ts("): ");
254    }
255
256    fn ts_func(&mut self, _iface: &Interface, _func: &Function) {
257        // TODO: Make this an option
258        // self.docs(&func.docs);
259        // if is_change(func) {
260        //     self.src.ts("async ");
261        // }
262        // let mut name_printed = false;
263        // if let FunctionKind::Static { .. } = &func.kind {
264        //     // static methods in imports are still wired up to an imported host
265        //     // object, but static methods on exports are actually static
266        //     // methods on the resource object.
267        //     if self.in_import {
268        //         name_printed = true;
269        //         self.src.ts(&func.name.to_snake_case());
270        //     } else {
271        //         self.src.ts("static ");
272        //     }
273        // }
274        // if !name_printed {
275        //     self.src.ts(&func.item_name().to_snake_case());
276        // }
277
278        // let param_start = match &func.kind {
279        //     FunctionKind::Freestanding => 0,
280        //     FunctionKind::Static { .. } if self.in_import => 0,
281        //     FunctionKind::Static { .. } => {
282        //         // the 0th argument for exported static methods will be the
283        //         // instantiated interface
284        //         self.src.ts(&iface.name.to_mixed_case());
285        //         self.src.ts(": ");
286        //         self.src.ts(&iface.name.to_camel_case());
287        //         if !func.params.is_empty() {
288        //             self.src.ts(", ");
289        //         }
290        //         0
291        //     }
292        //     // skip the first parameter on methods which is `this`
293        //     FunctionKind::Method { .. } => 1,
294        // };
295        // let name = func.item_name().to_snake_case();
296
297        // self.print_args(iface, func, param_start);
298
299        // // Always async
300        // self.src.ts("Promise<");
301
302        // self.print_func_result(iface, func);
303
304        // self.src.ts("> {\n");
305
306        // if is_change(func) {
307        //     self.src.ts(&format!(
308        //         "return providers.getTransactionLastResult(await this.{name}Raw(args, options));\n}}\n"
309        //     ));
310        //     self.docs(&func.docs);
311        //     self.src.ts(&format!("{name}Raw"));
312        //     self.print_args(iface, func, param_start);
313        //     self.src.ts("Promise<providers.FinalExecutionOutcome> {\n");
314        //     self.src.ts(&format!("return this.account.functionCall({{contractId: this.contractId, methodName: \"{name}\", args, ...options}});\n}}\n"));
315        //     self.docs(&func.docs);
316        //     self.src.ts(&format!("{name}Tx"));
317        //     self.print_args(iface, func, param_start);
318        //     self.src.ts(&format!("transactions.Action {{\n return transactions.functionCall(\"{name}\", args, options?.gas ?? DEFAULT_FUNCTION_CALL_GAS, options?.attachedDeposit ?? new BN(0))\n}}\n"));
319        // } else {
320        //     self.src.ts(&format!(
321        //         "return this.account.viewFunction(this.contractId, \"{name}\", args, options);\n}}\n"
322        //     ));
323        // }
324    }
325
326    fn print_func_result(&mut self, iface: &Interface, func: &Function) {
327        match func.results.len() {
328            0 => self.src.ts("void"),
329            1 => self.print_ty(iface, &func.results[0].1),
330            _ => {
331                if func.results.iter().any(|(n, _)| n.is_empty()) {
332                    self.src.ts("[");
333                    for (i, (_, ty)) in func.results.iter().enumerate() {
334                        if i > 0 {
335                            self.src.ts(", ");
336                        }
337                        self.print_ty(iface, ty);
338                    }
339                    self.src.ts("]");
340                } else {
341                    self.src.ts("{ ");
342                    for (i, (name, ty)) in func.results.iter().enumerate() {
343                        if i > 0 {
344                            self.src.ts(", ");
345                        }
346                        self.src.ts(&name.to_mixed_case());
347                        self.src.ts(": ");
348                        self.print_ty(iface, ty);
349                    }
350                    self.src.ts(" }");
351                }
352            }
353        }
354    }
355
356    fn print_fields(&mut self, iface: &Interface, fields: Vec<(&str, &Type, &Option<String>)>) {
357        for (name, ty, docs) in fields.iter() {
358            self.doc_str(docs);
359            self.src.ts(&name.to_snake_case());
360            if iface.is_ty_nullable_option(ty) {
361                self.src.ts("?");
362            }
363            self.src.ts(": ");
364            let ty = iface.get_nullable_option(ty).unwrap_or(ty);
365            self.print_ty(iface, ty);
366            self.src.ts(";\n");
367        }
368    }
369}
370
371impl Generator for Ts {
372    fn preprocess_one(&mut self, iface: &Interface, dir: Direction) {
373        let variant = Self::abi_variant(dir);
374        self.sizes.fill(variant, iface);
375        self.in_import = variant == AbiVariant::GuestImport;
376        self.add_preamble()
377    }
378
379    fn type_record(
380        &mut self,
381        iface: &Interface,
382        _id: TypeId,
383        name: &str,
384        record: &Record,
385        docs: &Docs,
386    ) {
387        self.docs(docs);
388        self.imports.insert(name.to_camel_case());
389        if record.is_tuple() {
390            self.src
391                .ts(&format!("export type {} = ", name.to_camel_case()));
392            self.print_tuple(iface, record);
393            self.src.ts(";\n");
394        } else if record.is_flags() {
395            let repr = iface
396                .flags_repr(record)
397                .expect("unsupported number of flags");
398            let suffix = if repr == Int::U64 {
399                self.src
400                    .ts(&format!("export type {} = u64;\n", name.to_camel_case()));
401                "n"
402            } else {
403                self.src
404                    .ts(&format!("export type {} = number;\n", name.to_camel_case()));
405                ""
406            };
407            let name = name.to_shouty_snake_case();
408            for (i, field) in record.fields.iter().enumerate() {
409                let field = field.name.to_shouty_snake_case();
410                self.src.ts(&format!(
411                    "export const {}_{} = {}{};\n",
412                    name,
413                    field,
414                    1u64 << i,
415                    suffix,
416                ));
417            }
418        } else {
419            self.src
420                .ts(&format!("export interface {} {{\n", name.to_camel_case()));
421
422            let fields = record
423                .fields
424                .iter()
425                .map(|f| (f.name.as_str(), &f.ty, &f.docs.contents))
426                .collect();
427            self.print_fields(iface, fields);
428            self.src.ts("}\n");
429        }
430    }
431
432    fn type_variant(
433        &mut self,
434        iface: &Interface,
435        _id: TypeId,
436        name: &str,
437        variant: &Variant,
438        docs: &Docs,
439    ) {
440        self.docs(docs);
441        self.imports.insert(name.to_camel_case());
442        if variant.is_bool() {
443            self.src.ts(&format!(
444                "export type {} = boolean;\n",
445                name.to_camel_case(),
446            ));
447        } else if iface.is_nullable_option(variant) {
448            self.src
449                .ts(&format!("export type {} = ", name.to_camel_case()));
450            self.print_ty(iface, variant.cases[1].ty.as_ref().unwrap());
451            self.src.ts(" | null;\n");
452        } else if variant.is_enum() {
453            self.src
454                .ts(&format!("export enum {} {{\n", name.to_camel_case()));
455            for case in variant.cases.iter() {
456                self.docs(&case.docs);
457                let name = case.name.to_camel_case();
458                self.src.ts(&format!("{} = \"{}\",\n", name, name));
459            }
460            self.src.ts("}\n");
461        } else {
462            self.src
463                .ts(&format!("export type {} = ", name.to_camel_case()));
464            for (i, case) in variant.cases.iter().enumerate() {
465                if i > 0 {
466                    self.src.ts(" | ");
467                }
468                self.src
469                    .ts(&format!("{}_{}", name, case.name).to_camel_case());
470            }
471            self.src.ts(";\n");
472            for case in variant.cases.iter() {
473                self.docs(&case.docs);
474                self.src.ts(&format!(
475                    "export interface {} {{\n",
476                    format!("{}_{}", name, case.name).to_camel_case()
477                ));
478                self.src.ts("tag: \"");
479                self.src.ts(&case.name);
480                self.src.ts("\",\n");
481                if let Some(ty) = &case.ty {
482                    self.src.ts("val: ");
483                    self.print_ty(iface, ty);
484                    self.src.ts(",\n");
485                }
486                self.src.ts("}\n");
487            }
488        }
489    }
490
491    fn type_resource(&mut self, _iface: &Interface, ty: ResourceId) {
492        if !self.in_import {
493            self.exported_resources.insert(ty);
494        }
495    }
496
497    fn type_alias(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) {
498        self.docs(docs);
499        self.src
500            .ts(&format!("export type {} = ", name.to_camel_case()));
501        self.imports.insert(name.to_camel_case());
502        self.print_ty(iface, ty);
503        self.src.ts(";\n");
504    }
505
506    fn type_list(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) {
507        self.docs(docs);
508        self.src
509            .ts(&format!("export type {} = ", name.to_camel_case()));
510        self.imports.insert(name.to_camel_case());
511        self.print_list(iface, ty);
512        self.src.ts(";\n");
513    }
514
515    fn type_pointer(
516        &mut self,
517        iface: &Interface,
518        _id: TypeId,
519        name: &str,
520        const_: bool,
521        ty: &Type,
522        docs: &Docs,
523    ) {
524        #[allow(clippy::drop_copy)]
525        drop((iface, _id, name, const_, ty, docs));
526    }
527
528    fn type_builtin(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) {
529        #[allow(clippy::drop_copy)]
530        drop((iface, _id, name, ty, docs));
531    }
532
533    fn type_push_buffer(
534        &mut self,
535        iface: &Interface,
536        _id: TypeId,
537        name: &str,
538        ty: &Type,
539        docs: &Docs,
540    ) {
541        self.docs(docs);
542        self.src
543            .ts(&format!("export type {} = ", name.to_camel_case()));
544        self.print_buffer(iface, true, ty);
545        self.src.ts(";\n");
546    }
547
548    fn type_pull_buffer(
549        &mut self,
550        iface: &Interface,
551        _id: TypeId,
552        name: &str,
553        ty: &Type,
554        docs: &Docs,
555    ) {
556        self.docs(docs);
557        self.src
558            .ts(&format!("export type {} = ", name.to_camel_case()));
559        self.print_buffer(iface, false, ty);
560        self.src.ts(";\n");
561    }
562
563    // As with `abi_variant` above, we're generating host-side bindings here
564    // so a user "export" uses the "guest import" ABI variant on the inside of
565    // this `Generator` implementation.
566    #[allow(dead_code, unused_variables)]
567    fn export(&mut self, iface: &Interface, func: &Function) {}
568
569    // As with `abi_variant` above, we're generating host-side bindings here
570    // so a user "import" uses the "export" ABI variant on the inside of
571    // this `Generator` implementation.
572    fn import(&mut self, iface: &Interface, func: &Function) {
573        let prev = mem::take(&mut self.src);
574        self.ts_func(iface, func);
575
576        let exports = self
577            .guest_exports
578            .entry(iface.name.to_string())
579            .or_insert_with(Exports::default);
580
581        let func_body = mem::replace(&mut self.src, prev);
582        match &func.kind {
583            FunctionKind::Freestanding => {
584                exports.freestanding_funcs.push(func_body);
585            }
586            FunctionKind::Static { resource, .. } | FunctionKind::Method { resource, .. } => {
587                exports
588                    .resource_funcs
589                    .entry(*resource)
590                    .or_insert_with(Vec::new)
591                    .push(func_body);
592            }
593        };
594        let prev = mem::take(&mut self.src);
595        let is_change_func = is_change(func);
596        let func_type = if is_change_func { "change" } else { "view" };
597        let docs = func
598            .docs
599            .contents
600            .as_ref()
601            .map(|d| d.to_string())
602            .unwrap_or_default();
603        self.doc_str(&Some(format!("{docs}\n@contractMethod {func_type}\n")));
604        self.src.ts(&format!(
605            "export interface {} {{\n",
606            func.name.to_camel_case()
607        ));
608
609        self.src.ts("args: {");
610        if !func.params.is_empty() {
611            self.src.ts("\n");
612            self.print_args_type(iface, func, 0);
613        }
614        self.src.ts("};\n");
615        if is_change_func {
616            self.src.ts("options: CallOptions\n");
617        }
618        self.src.ts("\n}\n");
619        self.src.ts(&format!(
620            "export type {}__Result = ",
621            func.name.to_camel_case()
622        ));
623        self.print_func_result(iface, func);
624        self.src.ts(";\n");
625
626        let func_args = mem::replace(&mut self.src, prev);
627        let exports = self
628            .guest_exports
629            .entry(iface.name.to_string())
630            .or_insert_with(Exports::default);
631
632        exports.arg_types.push(func_args);
633    }
634
635    fn finish_one(&mut self, iface: &Interface, files: &mut Files) {
636        for (module, funcs) in mem::take(&mut self.guest_imports) {
637            self.src
638                .ts(&format!("export interface {} {{\n", module.to_camel_case()));
639
640            for (_, src) in funcs.freestanding_funcs.iter() {
641                self.src.ts(&src.ts);
642            }
643
644            self.src.ts("}\n");
645
646            for (resource, _) in iface.resources.iter() {
647                self.src.ts(&format!(
648                    "export interface {} {{\n",
649                    iface.resources[resource].name.to_camel_case()
650                ));
651                if let Some(funcs) = funcs.resource_funcs.get(&resource) {
652                    for (_, src) in funcs {
653                        self.src.ts(&src.ts);
654                    }
655                }
656                self.src.ts("}\n");
657            }
658        }
659        let imports = mem::take(&mut self.src);
660        let mut main = wit_bindgen_gen_core::Source::default();
661        for (_module, exports) in mem::take(&mut self.guest_exports) {
662            // self.src.ts("\nexport class Contract {
663
664            //       constructor(public account: Account, public readonly contractId: string){}\n\n");
665            for func in exports.freestanding_funcs.iter() {
666                self.src.ts(&func.ts);
667            }
668            // self.src.ts("}\n");
669            for args in exports.arg_types.iter() {
670                main.push_str(&args.ts);
671            }
672        }
673
674        if mem::take(&mut self.needs_ty_option) {
675            self.imports.insert("Option".to_string());
676            self.src
677                .ts("export type Option<T> = { tag: \"none\" } | { tag: \"some\", val; T };\n");
678        }
679        if mem::take(&mut self.needs_ty_result) {
680            self.imports.insert("Result".to_string());
681            self.src.ts(
682                "export type Result<T, E> = { tag: \"ok\", val: T } | { tag: \"err\", val: E };\n",
683            );
684        }
685        let exports = mem::take(&mut self.src);
686
687        self.src.ts(&imports.ts);
688        self.src.ts(&exports.ts);
689
690        self.src.main(&format!(
691            r#"
692import {{
693  u8,
694  i8,
695  u16,
696  i16,
697  u32,
698  i32,
699  u64,
700  i64,
701  f32,
702  f64,
703  CallOptions,
704  {},
705}} from "./types";
706
707"#,
708            self.imports
709                .iter()
710                .cloned()
711                .collect::<Vec<String>>()
712                .join(",\n\t")
713        ));
714        self.src.main(&main);
715
716        let src = mem::take(&mut self.src);
717        let name = iface.name.to_kebab_case();
718        files.push("types.ts", src.ts.as_bytes());
719        files.push(&format!("{}.ts", name), src.main.as_bytes());
720    }
721
722    fn finish_all(&mut self, _files: &mut Files) {
723        assert!(self.src.ts.is_empty());
724    }
725}
726
727impl Ts {
728    fn hash_map<'a>(&self, iface: &'a Interface, ty: &Type) -> Option<&'a Record> {
729        iface
730            .get_record(ty)
731            .filter(|r| r.is_tuple() && r.fields.len() == 2)
732    }
733    fn add_preamble(&mut self) {
734        self.src.ts(HELPER);
735        self.src.ts("\n\n");
736    }
737}
738
739pub fn to_js_ident(name: &str) -> &str {
740    match name {
741        "in" => "in_",
742        "import" => "import_",
743        s => s,
744    }
745}
746
747#[derive(Default)]
748struct Source {
749    main: wit_bindgen_gen_core::Source,
750    ts: wit_bindgen_gen_core::Source,
751}
752
753impl Source {
754    fn ts(&mut self, s: &str) {
755        self.ts.push_str(s);
756    }
757
758    fn main(&mut self, s: &str) {
759        self.main.push_str(s);
760    }
761}
762
763fn is_change(func: &Function) -> bool {
764    if let Some(docs) = &func.docs.contents {
765        let mut x = docs.split('\n').filter(|s| *s == "@change").peekable();
766        if x.peek().is_some() {
767            return true;
768        }
769    }
770    false
771}
772
773const HELPER: &str = "
774/** 
775* @minimum 0
776* @maximum 18446744073709551615
777* @asType integer
778*/
779export type u64 = number;
780/** 
781* @minimum -9223372036854775808
782* @maximum 9223372036854775807
783* @asType integer
784*/
785export type i64 = number;
786
787/**
788* @minimum  0 
789* @maximum 255
790* @asType integer
791* */
792export type u8 = number;
793/**
794* @minimum  -128 
795* @maximum 127
796* @asType integer
797* */
798export type i8 = number;
799/**
800* @minimum  0 
801* @maximum 65535
802* @asType integer
803* */
804export type u16 = number;
805/**
806* @minimum -32768 
807* @maximum 32767
808* @asType integer
809* */
810export type i16 = number;
811/**
812* @minimum 0 
813* @maximum 4294967295
814* @asType integer
815* */
816export type u32 = number;
817/**
818* @minimum 0 
819* @maximum 4294967295
820* @asType integer
821* */
822export type usize = number;
823/**
824* @minimum  -2147483648 
825* @maximum 2147483647
826* @asType integer
827* */
828export type i32 = number;
829
830/**
831* @minimum -3.40282347E+38
832* @maximum 3.40282347E+38
833*/
834export type f32 = number;
835
836/**
837* @minimum -1.7976931348623157E+308
838* @maximum 1.7976931348623157E+308
839*/
840export type f64 = number;
841
842export type CallOptions = {
843  /** Units in gas
844   * @pattern [0-9]+
845   * @default \"30000000000000\"
846   */
847  gas?: string;
848  /** Units in yoctoNear
849   * @default \"0\"
850   */
851  attachedDeposit?: Balance;
852}
853";