Skip to main content

dodo_bindgen_codegen/
class.rs

1use std::collections::BTreeMap;
2
3use crate::TsMethod;
4use crate::util::*;
5use syn::Ident;
6
7#[derive(Clone, Debug, Default)]
8pub struct TsClass {
9    pub methods: Vec<TsMethod>,
10    pub drop_symbol: Option<Ident>,
11    pub snippets: Vec<String>,
12}
13
14#[derive(Clone, Debug, Default)]
15pub struct ClassDefs {
16    pub store: BTreeMap<Ident, TsClass>,
17}
18
19impl ClassDefs {
20    pub fn print(&self) -> String {
21        let mut tokens = proc_macro2::TokenStream::new();
22
23        for (ident, class) in &self.store {
24            let dispose = class.drop_symbol.as_ref().map(|drop_symbol| {
25                quote! {
26                    [Symbol.dispose]() {
27                        __dodo_dispose(this, symbols.#drop_symbol);
28                    }
29                }
30            });
31            let methods = class.methods.iter().map(TsMethod::print);
32            let snippets = class
33                .snippets
34                .iter()
35                .map(|snippet| raw(format!("\n{snippet}\n")));
36
37            tokens.extend(quote! {
38                export class #ident extends RustPrototype<#ident> {
39                    #dispose
40                    #(#methods)*
41                    #(#snippets)*
42                }
43            });
44        }
45
46        tokens.to_string()
47    }
48}
49
50/* -------------------------------------------------------------------------- */
51
52// MARK: tests
53
54#[cfg(test)]
55mod tests {
56    use crate::{CodegenOpts, TsModule};
57    use dodo_bindgen_rust_ir::{
58        Abi, Arg, HelperSymbol, Item, ItemClass, ItemFn, ItemHelper, ItemMethod, ItemTs, Receiver,
59        Type, TypeNumeric,
60    };
61
62    fn generate(items: Vec<Item>) -> String {
63        crate::util::deno_fmt(TsModule::new(items, &opts()).generate_single(&opts()))
64    }
65
66    fn opts() -> CodegenOpts {
67        CodegenOpts {
68            file_name: "mod.ts".to_string(),
69            lib_name: "lib".to_string(),
70            lazy: false,
71        }
72    }
73
74    fn class(name: &str, ty: Type) -> Item {
75        Item::Class(ItemClass {
76            module_path: "crate".to_string(),
77            name: name.to_string(),
78            drop_symbol: format!("__dodo_crate_{name}_drop"),
79            doc: String::new(),
80            ty,
81        })
82    }
83
84    fn method(class: &str, name: &str, receiver: Receiver, inputs: Vec<Arg>, output: Type) -> Item {
85        Item::Method(ItemMethod {
86            class: class.to_string(),
87            receiver,
88            func: ItemFn {
89                name: name.to_string(),
90                symbol: format!("__dodo_crate_{class}_{name}"),
91                doc: String::new(),
92                non_blocking: false,
93                inputs,
94                output_abi: if output == Type::Void {
95                    Abi::Void
96                } else {
97                    Abi::Pointer
98                },
99                output,
100            },
101        })
102    }
103
104    fn arg(ty: Type, abi: Abi) -> Arg {
105        Arg {
106            name: "arg".to_string(),
107            ty,
108            abi,
109        }
110    }
111
112    fn object(name: &str) -> Type {
113        Type::Object {
114            module_path: "crate".to_string(),
115            name: name.to_string(),
116        }
117    }
118
119    #[test]
120    fn test_print() {
121        let items = vec![
122            class("CustomType", object("CustomType")),
123            method(
124                "CustomType",
125                "test_fn",
126                Receiver::Ref,
127                vec![arg(Type::Ref(Box::new(object("CustomType"))), Abi::Pointer)],
128                Type::Void,
129            ),
130            Item::Ts(ItemTs {
131                class: "CustomType".to_string(),
132                code: "get answer(): number { return 42; }".to_string(),
133            }),
134        ];
135        insta::assert_snapshot!(generate(items));
136    }
137
138    fn echo(ty: Type) -> Item {
139        Item::Fn(ItemFn {
140            name: "echo".to_string(),
141            symbol: "__dodo_crate_echo".to_string(),
142            doc: String::new(),
143            non_blocking: false,
144            inputs: vec![arg(ty, Abi::Pointer)],
145            output: Type::Void,
146            output_abi: Abi::Void,
147        })
148    }
149
150    #[test]
151    fn foreign_classes_map_types_and_prune() {
152        let items = vec![
153            class("RustString", Type::String),
154            method("RustString", "new", Receiver::Static, vec![], Type::String),
155            class(
156                "RustBytes",
157                Type::Vec(Box::new(Type::Numeric(TypeNumeric::U8))),
158            ),
159            method("RustBytes", "new", Receiver::Static, vec![], Type::Void),
160            echo(Type::Box(Box::new(Type::String))),
161        ];
162        insta::assert_snapshot!(generate(items));
163    }
164
165    #[test]
166    fn new_becomes_constructor() {
167        let items = vec![
168            class("Counter", object("Counter")),
169            method(
170                "Counter",
171                "new",
172                Receiver::Static,
173                vec![arg(Type::Numeric(TypeNumeric::U32), Abi::U32)],
174                object("Counter"),
175            ),
176            method(
177                "Counter",
178                "fork",
179                Receiver::Ref,
180                vec![arg(Type::Ref(Box::new(object("Counter"))), Abi::Pointer)],
181                object("Counter"),
182            ),
183        ];
184        insta::assert_snapshot!(generate(items));
185    }
186
187    #[test]
188    fn receivers_reject_moved_values() {
189        let this = || arg(object("Encoder"), Abi::Pointer);
190        let items = vec![
191            class("Encoder", object("Encoder")),
192            method(
193                "Encoder",
194                "len",
195                Receiver::Ref,
196                vec![this()],
197                Type::Numeric(TypeNumeric::U32),
198            ),
199            method(
200                "Encoder",
201                "append_frame",
202                Receiver::RefMut,
203                vec![this()],
204                Type::Void,
205            ),
206            method(
207                "Encoder",
208                "finish",
209                Receiver::Owned,
210                vec![this()],
211                Type::Void,
212            ),
213        ];
214        insta::assert_snapshot!(generate(items));
215    }
216
217    fn non_blocking(mut item: Item) -> Item {
218        if let Item::Method(method) = &mut item {
219            method.func.non_blocking = true;
220        }
221        item
222    }
223
224    #[test]
225    fn non_blocking_receivers_borrow() {
226        let this = || arg(object("Decoder"), Abi::Pointer);
227        let items = vec![
228            class("Decoder", object("Decoder")),
229            non_blocking(method(
230                "Decoder",
231                "next",
232                Receiver::Ref,
233                vec![this()],
234                Type::Void,
235            )),
236            non_blocking(method(
237                "Decoder",
238                "seek",
239                Receiver::RefMut,
240                vec![this()],
241                Type::Void,
242            )),
243            non_blocking(method(
244                "Decoder",
245                "finish",
246                Receiver::Owned,
247                vec![this()],
248                Type::Void,
249            )),
250        ];
251        insta::assert_snapshot!(generate(items));
252    }
253}