use std::collections::BTreeMap;
use crate::TsMethod;
use crate::util::*;
use syn::Ident;
#[derive(Clone, Debug, Default)]
pub struct TsClass {
pub methods: Vec<TsMethod>,
pub drop_symbol: Option<Ident>,
pub snippets: Vec<String>,
}
#[derive(Clone, Debug, Default)]
pub struct ClassDefs {
pub store: BTreeMap<Ident, TsClass>,
}
impl ClassDefs {
pub fn print(&self) -> String {
let mut tokens = proc_macro2::TokenStream::new();
for (ident, class) in &self.store {
let dispose = class.drop_symbol.as_ref().map(|drop_symbol| {
quote! {
[Symbol.dispose]() {
__dodo_dispose(this, symbols.#drop_symbol);
}
}
});
let methods = class.methods.iter().map(TsMethod::print);
let snippets = class
.snippets
.iter()
.map(|snippet| raw(format!("\n{snippet}\n")));
tokens.extend(quote! {
export class #ident extends RustPrototype<#ident> {
#dispose
#(#methods)*
#(#snippets)*
}
});
}
tokens.to_string()
}
}
#[cfg(test)]
mod tests {
use crate::{CodegenOpts, TsModule};
use dodo_bindgen_rust_ir::{
Abi, Arg, HelperSymbol, Item, ItemClass, ItemFn, ItemHelper, ItemMethod, ItemTs, Receiver,
Type, TypeNumeric,
};
fn generate(items: Vec<Item>) -> String {
crate::util::deno_fmt(TsModule::new(items, &opts()).generate_single(&opts()))
}
fn opts() -> CodegenOpts {
CodegenOpts {
file_name: "mod.ts".to_string(),
lib_name: "lib".to_string(),
lazy: false,
}
}
fn class(name: &str, ty: Type) -> Item {
Item::Class(ItemClass {
module_path: "crate".to_string(),
name: name.to_string(),
drop_symbol: format!("__dodo_crate_{name}_drop"),
doc: String::new(),
ty,
})
}
fn method(class: &str, name: &str, receiver: Receiver, inputs: Vec<Arg>, output: Type) -> Item {
Item::Method(ItemMethod {
class: class.to_string(),
receiver,
func: ItemFn {
name: name.to_string(),
symbol: format!("__dodo_crate_{class}_{name}"),
doc: String::new(),
non_blocking: false,
inputs,
output_abi: if output == Type::Void {
Abi::Void
} else {
Abi::Pointer
},
output,
},
})
}
fn arg(ty: Type, abi: Abi) -> Arg {
Arg {
name: "arg".to_string(),
ty,
abi,
}
}
fn object(name: &str) -> Type {
Type::Object {
module_path: "crate".to_string(),
name: name.to_string(),
}
}
#[test]
fn test_print() {
let items = vec![
class("CustomType", object("CustomType")),
method(
"CustomType",
"test_fn",
Receiver::Ref,
vec![arg(Type::Ref(Box::new(object("CustomType"))), Abi::Pointer)],
Type::Void,
),
Item::Ts(ItemTs {
class: "CustomType".to_string(),
code: "get answer(): number { return 42; }".to_string(),
}),
];
insta::assert_snapshot!(generate(items));
}
fn echo(ty: Type) -> Item {
Item::Fn(ItemFn {
name: "echo".to_string(),
symbol: "__dodo_crate_echo".to_string(),
doc: String::new(),
non_blocking: false,
inputs: vec![arg(ty, Abi::Pointer)],
output: Type::Void,
output_abi: Abi::Void,
})
}
#[test]
fn foreign_classes_map_types_and_prune() {
let items = vec![
class("RustString", Type::String),
method("RustString", "new", Receiver::Static, vec![], Type::String),
class(
"RustBytes",
Type::Vec(Box::new(Type::Numeric(TypeNumeric::U8))),
),
method("RustBytes", "new", Receiver::Static, vec![], Type::Void),
echo(Type::Box(Box::new(Type::String))),
];
insta::assert_snapshot!(generate(items));
}
#[test]
fn new_becomes_constructor() {
let items = vec![
class("Counter", object("Counter")),
method(
"Counter",
"new",
Receiver::Static,
vec![arg(Type::Numeric(TypeNumeric::U32), Abi::U32)],
object("Counter"),
),
method(
"Counter",
"fork",
Receiver::Ref,
vec![arg(Type::Ref(Box::new(object("Counter"))), Abi::Pointer)],
object("Counter"),
),
];
insta::assert_snapshot!(generate(items));
}
#[test]
fn receivers_reject_moved_values() {
let this = || arg(object("Encoder"), Abi::Pointer);
let items = vec![
class("Encoder", object("Encoder")),
method(
"Encoder",
"len",
Receiver::Ref,
vec![this()],
Type::Numeric(TypeNumeric::U32),
),
method(
"Encoder",
"append_frame",
Receiver::RefMut,
vec![this()],
Type::Void,
),
method(
"Encoder",
"finish",
Receiver::Owned,
vec![this()],
Type::Void,
),
];
insta::assert_snapshot!(generate(items));
}
fn non_blocking(mut item: Item) -> Item {
if let Item::Method(method) = &mut item {
method.func.non_blocking = true;
}
item
}
#[test]
fn non_blocking_receivers_borrow() {
let this = || arg(object("Decoder"), Abi::Pointer);
let items = vec![
class("Decoder", object("Decoder")),
non_blocking(method(
"Decoder",
"next",
Receiver::Ref,
vec![this()],
Type::Void,
)),
non_blocking(method(
"Decoder",
"seek",
Receiver::RefMut,
vec![this()],
Type::Void,
)),
non_blocking(method(
"Decoder",
"finish",
Receiver::Owned,
vec![this()],
Type::Void,
)),
];
insta::assert_snapshot!(generate(items));
}
}