Skip to main content

dodo_bindgen_codegen/
file.rs

1use std::path::PathBuf;
2
3use crate::util::*;
4use crate::{ClassDefs, FfiLib, FunctionDefs, RustTypeDefs, TsFormat, UserDefinedDefs};
5use dodo_bindgen_rust_ir::{File, Item, ItemMod};
6
7use super::class::ItemImplExt as _;
8use super::fn_::ItemFnExt as _;
9
10/* -------------------------------------------------------------------------- */
11
12#[derive(Clone, Debug)]
13pub struct CodegenOpts {
14    pub file_name: String,
15    pub dylib_path: String,
16    pub lazy: bool,
17    /// If false, uses the opaque representations of rust types with no method
18    /// to interface with rust data structures
19    ///
20    /// If true, uses the extended rust types with methods for interfacing
21    /// with rust data structures, but embeds the ffi symbols on the same dylib
22    pub extended: bool,
23    /// If provided, writes the extended rust types on a  separate
24    /// file and uses the dylib from this path for the typescript representation
25    /// of the extended rust types. Incompatible with `inline=true`
26    pub embedded: Option<PathBuf>,
27}
28
29#[derive(Clone, Debug, Default)]
30pub struct TsModule {
31    pub ffi_lib: FfiLib, // symbol definitions
32    pub type_defs: RustTypeDefs, /* import statements or type definitions if `inline = true`.
33                          * links to standard types */
34    pub user_defs: UserDefinedDefs,
35    pub functions: FunctionDefs,
36    pub class_defs: ClassDefs,
37}
38
39// TODO: use buffer where possible to handle large projects
40
41impl TsModule {
42    pub fn new(file: File, opts: &CodegenOpts) -> Self {
43        let mut module = TsModule::default();
44        module.ffi_lib.dylib_path = opts.dylib_path.clone();
45        module.ffi_lib.lazy = opts.lazy;
46
47        module.unwrap(file.items);
48        module.user_defs.dedup(&module.class_defs);
49        module
50    }
51
52    /// Recursively transforms parsed rust items into their typescript
53    /// representations
54    fn unwrap(&mut self, items: Vec<Item>) {
55        for item in items {
56            match item {
57                Item::Fn(item_fn) => {
58                    let method = item_fn.unwrap(self);
59                    self.functions.push(method);
60                },
61                Item::Impl(item_impl) => {
62                    item_impl.unwrap(self);
63                },
64                Item::Mod(ItemMod { items, .. }) => {
65                    self.unwrap(items);
66                },
67                _ => (),
68            }
69        }
70    }
71
72    /// Generate a single typescript module with rust type definitions inlined
73    pub fn generate_single(self, opts: &CodegenOpts) -> String {
74        let TsModule {
75            mut ffi_lib,
76            mut type_defs,
77            user_defs,
78            functions,
79            class_defs,
80        } = self;
81
82        type_defs.extended = opts.extended;
83        let (type_defs, mut ffi_symbols) = type_defs.print_inline();
84        ffi_lib.interface.append(&mut ffi_symbols);
85
86        let ffi_lib = ffi_lib.to_token_stream().to_string();
87        let user_defs = user_defs.to_token_stream().to_string();
88        let functions = functions.to_token_stream().to_string();
89        let class_defs = class_defs.to_token_stream().to_string();
90
91        TsFormat::format(format!(
92            "// deno-lint-ignore-file\n
93            {ffi_lib}
94            {type_defs}
95            {user_defs}
96            {class_defs}
97            {functions}
98            "
99        ))
100    }
101
102    /// Generate multiple typescript modules with rust type definitions on a
103    /// separate file
104    pub fn generate_multiple(
105        mut self,
106        opts: &CodegenOpts,
107        type_defs_name: &str,
108    ) -> (String, String) {
109        let TsModule {
110            mut ffi_lib,
111            user_defs,
112            functions,
113            class_defs,
114            ..
115        } = self;
116
117        self.type_defs.extended = opts.extended;
118
119        let (raw_type_defs, mut ffi_symbols) = self.type_defs.print_inline();
120        let mut type_defs_builder = String::from(format!("// deno-lint-ignore-file\n"));
121
122        // use extended types that requires symbols
123        if opts.extended {
124            // use a separate dylib file with a dlopen statement
125            if let Some(embedded) = &opts.embedded {
126                let mut embedded_ffi_lib = FfiLib::default();
127
128                embedded_ffi_lib.dylib_path = embedded
129                    .to_str()
130                    .expect("unknown utf8 character on embedded dylib path")
131                    .to_string();
132                embedded_ffi_lib.interface.append(&mut ffi_symbols);
133
134                let embedded_ffi_lib =
135                    TsFormat::format(embedded_ffi_lib.to_token_stream().to_string());
136
137                // append `const { symbols } = Deno.dlopen(...`
138                type_defs_builder.push_str(&embedded_ffi_lib);
139
140            // use an import { symbols } statement
141            } else {
142                ffi_lib.interface.append(&mut ffi_symbols);
143                ffi_lib.export = true;
144
145                let file_name = format!("./{}", opts.file_name);
146
147                type_defs_builder.push_str(&TsFormat::format(
148                    quote! {
149                        import { symbols } from #file_name;
150                    }
151                    .to_string(),
152                ));
153            }
154        }
155
156        type_defs_builder.push_str(&raw_type_defs);
157
158        let (type_defs, imports) = self
159            .type_defs
160            .print_separate(type_defs_builder, type_defs_name);
161
162        let ffi_lib = ffi_lib.to_token_stream().to_string();
163        let user_defs = user_defs.to_token_stream().to_string();
164        let functions = functions.to_token_stream().to_string();
165        let class_defs = class_defs.to_token_stream().to_string();
166
167        let module = TsFormat::format(format!(
168            "
169            {imports}
170            {ffi_lib}
171            {user_defs}
172            {class_defs}
173            {functions}
174            "
175        ));
176
177        (module, type_defs)
178    }
179}