postcard_bindgen_core/code_gen/js/
mod.rs1mod des;
2mod general;
3mod generateable;
4mod ser;
5mod type_checks;
6
7use core::borrow::Borrow;
8
9use des::{gen_des_functions, gen_deserialize_func, gen_deserializer_code};
10use genco::{
11 prelude::js::JavaScript,
12 quote_in,
13 tokens::{quoted, FormatInto},
14};
15use general::gen_util;
16use generateable::gen_ts_typings;
17use ser::{gen_ser_functions, gen_serialize_func, gen_serializer_code};
18use type_checks::gen_type_checks;
19
20use crate::{registry::ContainerCollection, ExportFile, Exports};
21
22use super::{export_registry::ExportMode, utils::TokensIterExt};
23
24const JS_ENUM_VARIANT_KEY: &str = "tag";
25const JS_ENUM_VARIANT_VALUE: &str = "value";
26const JS_OBJECT_VARIABLE: &str = "v";
27const JS_LOGIC_AND: &str = "&&";
28const JS_LOGIC_OR: &str = "||";
29
30type Tokens = genco::Tokens<JavaScript>;
31
32type VariablePath = super::variable_path::VariablePath<JavaScript>;
33type VariableAccess = super::variable_path::VariableAccess;
34type FieldAccessor<'a> = super::field_accessor::FieldAccessor<'a>;
35type AvailableCheck = super::available_check::AvailableCheck<JavaScript>;
36type Function = super::function::Function<JavaScript>;
37type FunctionArg = super::function::FunctionArg<JavaScript>;
38type ExportRegistry = super::export_registry::ExportRegistry<JavaScript>;
39type Case = super::switch_case::Case<JavaScript>;
40type DefaultCase = super::switch_case::DefaultCase<JavaScript>;
41type SwitchCase = super::switch_case::SwitchCase<JavaScript>;
42
43#[derive(Debug)]
53pub struct GenerationSettings {
54 ser: bool,
55 des: bool,
56 runtime_type_checks: bool,
57 type_script_types: bool,
58 module_structure: bool,
59 esm_module: bool,
60}
61
62impl GenerationSettings {
63 pub fn enable_all() -> Self {
65 Self {
66 ser: true,
67 des: true,
68 runtime_type_checks: true,
69 type_script_types: true,
70 module_structure: true,
71 esm_module: true,
72 }
73 }
74
75 pub fn serialization(mut self, enabled: bool) -> Self {
77 self.ser = enabled;
78 self
79 }
80
81 pub fn deserialization(mut self, enabled: bool) -> Self {
83 self.des = enabled;
84 self
85 }
86
87 pub fn type_script_types(mut self, enabled: bool) -> Self {
93 self.type_script_types = enabled;
94 self
95 }
96
97 pub fn runtime_type_checks(mut self, enabled: bool) -> Self {
101 self.runtime_type_checks = enabled;
102 self
103 }
104
105 pub fn module_structure(mut self, enabled: bool) -> Self {
114 self.module_structure = enabled;
115 self
116 }
117
118 pub fn esm_module(mut self, enabled: bool) -> Self {
128 self.esm_module = enabled;
129 self
130 }
131}
132
133impl Default for GenerationSettings {
134 fn default() -> Self {
135 Self {
136 ser: false,
137 des: true,
138 runtime_type_checks: false,
139 type_script_types: false,
140 module_structure: true,
141 esm_module: false,
142 }
143 }
144}
145
146pub struct ExportMeta {
151 pub esm_module: bool,
152}
153
154pub fn generate(
155 mut containers: ContainerCollection,
156 gen_settings: impl Borrow<GenerationSettings>,
157) -> (Exports<JavaScript>, ExportMeta) {
158 let gen_settings = gen_settings.borrow();
159
160 if !gen_settings.module_structure {
161 containers.flatten();
162 }
163
164 let export_mode = if gen_settings.esm_module {
165 ExportMode::Esm
166 } else {
167 ExportMode::Cjs
168 };
169
170 let mut export_files = Vec::new();
171
172 export_files.push(ExportFile {
173 content_type: "util".to_owned(),
174 content: gen_util(),
175 });
176
177 if gen_settings.ser {
178 export_files.push(ExportFile {
179 content_type: "serializer".to_owned(),
180 content: gen_serializer_code(),
181 });
182
183 let mut tokens = Tokens::new();
184
185 tokens.append(gen_ser_functions(containers.all_containers()));
186 tokens.line();
187
188 let mut export_registry = ExportRegistry::new(export_mode.clone());
189
190 tokens.append(gen_serialize_func(
191 containers.all_containers(),
192 gen_settings.runtime_type_checks,
193 &mut export_registry,
194 ));
195
196 tokens.line();
197 tokens.append(export_registry);
198
199 export_files.push(ExportFile {
200 content_type: "ser".to_owned(),
201 content: tokens,
202 });
203 }
204
205 if gen_settings.des {
206 export_files.push(ExportFile {
207 content_type: "deserializer".to_owned(),
208 content: gen_deserializer_code(),
209 });
210
211 let mut tokens = Tokens::new();
212
213 tokens.append(gen_des_functions(containers.all_containers()));
214 tokens.line();
215
216 let mut export_registry = ExportRegistry::new(export_mode);
217
218 tokens.append(gen_deserialize_func(
219 containers.all_containers(),
220 &mut export_registry,
221 ));
222 tokens.line();
223
224 tokens.append(export_registry);
225
226 export_files.push(ExportFile {
227 content_type: "des".to_owned(),
228 content: tokens,
229 });
230 }
231
232 if gen_settings.runtime_type_checks {
233 export_files.push(ExportFile {
234 content_type: "runtime_checks".to_owned(),
235 content: gen_type_checks(containers.all_containers()),
236 });
237 }
238
239 if gen_settings.type_script_types {
240 let ts = gen_ts_typings(&containers, gen_settings);
241 export_files.push(ExportFile {
242 content_type: "ts".to_owned(),
243 content: ts,
244 });
245 }
246
247 let export_metadata = ExportMeta {
249 esm_module: gen_settings.esm_module,
250 };
251
252 (
253 Exports {
254 files: export_files,
255 },
256 export_metadata,
257 )
258}
259
260impl<I, F> TokensIterExt<JavaScript, F> for I
261where
262 I: Iterator<Item = F>,
263 F: FormatInto<JavaScript>,
264{
265 const LOGICAL_AND: &'static str = JS_LOGIC_AND;
266 const LOGICAL_OR: &'static str = JS_LOGIC_OR;
267}
268
269impl FormatInto<JavaScript> for FieldAccessor<'_> {
270 fn format_into(self, tokens: &mut Tokens) {
271 quote_in! { *tokens =>
272 $(match self {
273 Self::Array | Self::None => (),
274 Self::Object(n) => $n:$[' '],
275 })
276 }
277 }
278}
279
280impl FormatInto<JavaScript> for VariablePath {
281 fn format_into(self, tokens: &mut Tokens) {
282 quote_in! { *tokens =>
283 $(self.start_variable)
284 }
285 self.parts
286 .into_iter()
287 .for_each(|part| part.format_into(tokens))
288 }
289}
290
291impl Default for VariablePath {
292 fn default() -> Self {
293 Self::new(JS_OBJECT_VARIABLE.to_owned())
294 }
295}
296
297impl FormatInto<JavaScript> for VariableAccess {
298 fn format_into(self, tokens: &mut Tokens) {
299 quote_in! { *tokens =>
300 $(match self {
301 Self::Indexed(index) => [$index],
302 Self::Field(name) => .$name,
303 })
304 }
305 }
306}
307
308impl FormatInto<JavaScript> for AvailableCheck {
309 fn format_into(self, tokens: &mut Tokens) {
310 quote_in! { *tokens =>
311 $(match self {
312 AvailableCheck::Object(path, name) => $(quoted(name)) in $path,
313 AvailableCheck::None => ()
314 })
315 }
316 }
317}
318
319impl FormatInto<JavaScript> for FunctionArg {
320 fn format_into(self, tokens: &mut Tokens) {
321 quote_in! { *tokens =>
322 $(self.name)
323 }
324 }
325}
326
327impl FormatInto<JavaScript> for Function {
328 fn format_into(self, tokens: &mut Tokens) {
329 let doc_string = self.doc_string.map(|doc| {
330 let mut tokens = Tokens::new();
331 tokens.append("/**\n");
332 tokens.append(
333 doc.lines()
334 .map(|line| format!(" * {}", line.trim()))
335 .collect::<Vec<_>>()
336 .join("\n"),
337 );
338 tokens.push();
339 tokens.append(" */");
340 tokens
341 });
342 quote_in! { *tokens =>
343 $(doc_string)
344 function $(self.name)($(for arg in self.args join (, ) => $arg)) {
345 $(self.body)
346 }
347 }
348 }
349}
350
351impl FormatInto<JavaScript> for ExportRegistry {
352 fn format_into(self, tokens: &mut Tokens) {
353 match self.export_mode {
354 ExportMode::Cjs => {
355 quote_in! { *tokens =>
356 $(for export in self.exports join () => exports.$(&export) = $export)
357 }
358 }
359 ExportMode::Esm => {
360 quote_in! { *tokens =>
361 export {
362 $(for export in self.exports join (,) => $export)
363 };
364 }
365 }
366 }
367 }
368}
369
370impl FormatInto<JavaScript> for Case {
371 fn format_into(self, tokens: &mut Tokens) {
372 quote_in! {*tokens =>
373 case $(self.case):
374 $(self.body)
375 $(if self.break_after { break; })
376 }
377 }
378}
379
380impl FormatInto<JavaScript> for DefaultCase {
381 fn format_into(self, tokens: &mut Tokens) {
382 quote_in! { *tokens =>
383 default:
384 $(self.body)
385 $(if self.break_after { break; })
386 }
387 }
388}
389
390impl FormatInto<JavaScript> for SwitchCase {
391 fn format_into(self, tokens: &mut Tokens) {
392 quote_in! { *tokens =>
393 switch ($(self.switch_arg)) {
394 $(for case in self.cases => $case)
395 $(if let Some(default_case) = self.default_case { $default_case })
396 }
397 }
398 }
399}