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#[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}