serde_generate/
typescript.rs

1// Copyright (c) Facebook, Inc. and its affiliates
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{
5    common,
6    indent::{IndentConfig, IndentedWriter},
7    CodeGeneratorConfig,
8};
9use heck::CamelCase;
10use include_dir::include_dir as include_directory;
11use serde_reflection::{ContainerFormat, Format, FormatHolder, Named, Registry, VariantFormat};
12use std::{
13    collections::{BTreeMap, HashMap},
14    io::{Result, Write},
15    path::PathBuf,
16};
17
18/// Main configuration object for code-generation in TypeScript, powered by
19/// the Deno runtime.
20pub struct CodeGenerator<'a> {
21    /// Language-independent configuration.
22    config: &'a CodeGeneratorConfig,
23    /// Mapping from external type names to fully-qualified class names (e.g. "MyClass" -> "com.my_org.my_package.MyClass").
24    /// Derived from `config.external_definitions`.
25    external_qualified_names: HashMap<String, String>,
26    /// vector of namespaces to import
27    namespaces_to_import: Vec<String>,
28}
29
30/// Shared state for the code generation of a TypeScript source file.
31struct TypeScriptEmitter<'a, T> {
32    /// Writer.
33    out: IndentedWriter<T>,
34    /// Generator.
35    generator: &'a CodeGenerator<'a>,
36}
37
38impl<'a> CodeGenerator<'a> {
39    /// Create a TypeScript code generator for the given config.
40    pub fn new(config: &'a CodeGeneratorConfig) -> Self {
41        if config.c_style_enums {
42            panic!("TypeScript does not support generating c-style enums");
43        }
44        let mut external_qualified_names = HashMap::new();
45        for (namespace, names) in &config.external_definitions {
46            for name in names {
47                external_qualified_names.insert(
48                    name.to_string(),
49                    format!("{}.{}", namespace.to_camel_case(), name),
50                );
51            }
52        }
53        Self {
54            config,
55            external_qualified_names,
56            namespaces_to_import: config
57                .external_definitions
58                .keys()
59                .map(|k| k.to_string())
60                .collect::<Vec<_>>(),
61        }
62    }
63
64    /// Output class definitions for `registry` in a single source file.
65    pub fn output(&self, out: &mut dyn Write, registry: &Registry) -> Result<()> {
66        let mut emitter = TypeScriptEmitter {
67            out: IndentedWriter::new(out, IndentConfig::Space(2)),
68            generator: self,
69        };
70
71        emitter.output_preamble()?;
72
73        for (name, format) in registry {
74            emitter.output_container(name, format)?;
75        }
76
77        if self.config.serialization {
78            emitter.output_helpers(registry)?;
79        }
80
81        Ok(())
82    }
83}
84
85impl<'a, T> TypeScriptEmitter<'a, T>
86where
87    T: Write,
88{
89    fn output_preamble(&mut self) -> Result<()> {
90        writeln!(
91            self.out,
92            r#"
93import {{ Serializer, Deserializer }} from '../serde/mod.ts';
94import {{ BcsSerializer, BcsDeserializer }} from '../bcs/mod.ts';
95import {{ Optional, Seq, Tuple, ListTuple, unit, bool, int8, int16, int32, int64, int128, uint8, uint16, uint32, uint64, uint128, float32, float64, char, str, bytes }} from '../serde/mod.ts';
96"#,
97        )?;
98        for namespace in self.generator.namespaces_to_import.iter() {
99            writeln!(
100                self.out,
101                "import * as {} from '../{}/mod.ts';\n",
102                namespace.to_camel_case(),
103                namespace
104            )?;
105        }
106
107        Ok(())
108    }
109
110    fn quote_qualified_name(&self, name: &str) -> String {
111        self.generator
112            .external_qualified_names
113            .get(name)
114            .cloned()
115            .unwrap_or_else(|| name.to_string())
116    }
117
118    fn output_comment(&mut self, name: &str) -> std::io::Result<()> {
119        let path = vec![name.to_string()];
120        if let Some(doc) = self.generator.config.comments.get(&path) {
121            let text = textwrap::indent(doc, " * ").replace("\n\n", "\n *\n");
122            writeln!(self.out, "/**\n{} */", text)?;
123        }
124        Ok(())
125    }
126
127    fn quote_type(&self, format: &Format) -> String {
128        use Format::*;
129        match format {
130            TypeName(x) => self.quote_qualified_name(x),
131            Unit => "unit".into(),
132            Bool => "bool".into(),
133            I8 => "int8".into(),
134            I16 => "int16".into(),
135            I32 => "int32".into(),
136            I64 => "int64".into(),
137            I128 => "int128".into(),
138            U8 => "uint8".into(),
139            U16 => "uint16".into(),
140            U32 => "uint32".into(),
141            U64 => "uint64".into(),
142            U128 => "uint128".into(),
143            F32 => "float32".into(),
144            F64 => "float64".into(),
145            Char => "char".into(),
146            Str => "str".into(),
147            Bytes => "bytes".into(),
148
149            Option(format) => format!("Optional<{}>", self.quote_type(format)),
150            Seq(format) => format!("Seq<{}>", self.quote_type(format)),
151            Map { key, value } => {
152                format!("Map<{},{}>", self.quote_type(key), self.quote_type(value))
153            }
154            Tuple(formats) => format!("Tuple<[{}]>", self.quote_types(formats, ", ")),
155            TupleArray {
156                content,
157                size: _size,
158            } => format!("ListTuple<[{}]>", self.quote_type(content),),
159            Variable(_) => panic!("unexpected value"),
160        }
161    }
162
163    fn quote_types(&self, formats: &[Format], sep: &str) -> String {
164        formats
165            .iter()
166            .map(|f| self.quote_type(f))
167            .collect::<Vec<_>>()
168            .join(sep)
169    }
170
171    fn output_helpers(&mut self, registry: &Registry) -> Result<()> {
172        let mut subtypes = BTreeMap::new();
173        for format in registry.values() {
174            format
175                .visit(&mut |f| {
176                    if Self::needs_helper(f) {
177                        subtypes.insert(common::mangle_type(f), f.clone());
178                    }
179                    Ok(())
180                })
181                .unwrap();
182        }
183
184        writeln!(self.out, "export class Helpers {{")?;
185        self.out.indent();
186        for (mangled_name, subtype) in &subtypes {
187            self.output_serialization_helper(mangled_name, subtype)?;
188            self.output_deserialization_helper(mangled_name, subtype)?;
189        }
190        self.out.unindent();
191        writeln!(self.out, "}}")?;
192        writeln!(self.out)
193    }
194
195    fn needs_helper(format: &Format) -> bool {
196        use Format::*;
197        matches!(
198            format,
199            Option(_) | Seq(_) | Map { .. } | Tuple(_) | TupleArray { .. }
200        )
201    }
202
203    fn quote_serialize_value(&self, value: &str, format: &Format, use_this: bool) -> String {
204        use Format::*;
205        let this_str = if use_this { "this." } else { "" };
206
207        match format {
208            TypeName(_) => format!("{}{}.serialize(serializer);", this_str, value),
209            Unit => format!("serializer.serializeUnit({}{});", this_str, value),
210            Bool => format!("serializer.serializeBool({}{});", this_str, value),
211            I8 => format!("serializer.serializeI8({}{});", this_str, value),
212            I16 => format!("serializer.serializeI16({}{});", this_str, value),
213            I32 => format!("serializer.serializeI32({}{});", this_str, value),
214            I64 => format!("serializer.serializeI64({}{});", this_str, value),
215            I128 => format!("serializer.serializeI128({}{});", this_str, value),
216            U8 => format!("serializer.serializeU8({}{});", this_str, value),
217            U16 => format!("serializer.serializeU16({}{});", this_str, value),
218            U32 => format!("serializer.serializeU32({}{});", this_str, value),
219            U64 => format!("serializer.serializeU64({}{});", this_str, value),
220            U128 => format!("serializer.serializeU128({}{});", this_str, value),
221            F32 => format!("serializer.serializeF32({}{});", this_str, value),
222            F64 => format!("serializer.serializeF64({}{});", this_str, value),
223            Char => format!("serializer.serializeChar({}{});", this_str, value),
224            Str => format!("serializer.serializeStr({}{});", this_str, value),
225            Bytes => format!("serializer.serializeBytes({}{});", this_str, value),
226            _ => format!(
227                "Helpers.serialize{}({}{}, serializer);",
228                common::mangle_type(format).to_camel_case(),
229                this_str,
230                value
231            ),
232        }
233    }
234
235    fn quote_deserialize(&self, format: &Format) -> String {
236        use Format::*;
237        match format {
238            TypeName(name) => format!(
239                "{}.deserialize(deserializer)",
240                self.quote_qualified_name(name)
241            ),
242            Unit => "deserializer.deserializeUnit()".to_string(),
243            Bool => "deserializer.deserializeBool()".to_string(),
244            I8 => "deserializer.deserializeI8()".to_string(),
245            I16 => "deserializer.deserializeI16()".to_string(),
246            I32 => "deserializer.deserializeI32()".to_string(),
247            I64 => "deserializer.deserializeI64()".to_string(),
248            I128 => "deserializer.deserializeI128()".to_string(),
249            U8 => "deserializer.deserializeU8()".to_string(),
250            U16 => "deserializer.deserializeU16()".to_string(),
251            U32 => "deserializer.deserializeU32()".to_string(),
252            U64 => "deserializer.deserializeU64()".to_string(),
253            U128 => "deserializer.deserializeU128()".to_string(),
254            F32 => "deserializer.deserializeF32()".to_string(),
255            F64 => "deserializer.deserializeF64()".to_string(),
256            Char => "deserializer.deserializeChar()".to_string(),
257            Str => "deserializer.deserializeStr()".to_string(),
258            Bytes => "deserializer.deserializeBytes()".to_string(),
259            _ => format!(
260                "Helpers.deserialize{}(deserializer)",
261                common::mangle_type(format).to_camel_case(),
262            ),
263        }
264    }
265
266    fn output_serialization_helper(&mut self, name: &str, format0: &Format) -> Result<()> {
267        use Format::*;
268
269        write!(
270            self.out,
271            "static serialize{}(value: {}, serializer: Serializer): void {{",
272            name.to_camel_case(),
273            self.quote_type(format0)
274        )?;
275        self.out.indent();
276        match format0 {
277            Option(format) => {
278                write!(
279                    self.out,
280                    r#"
281if (value) {{
282    serializer.serializeOptionTag(true);
283    {}
284}} else {{
285    serializer.serializeOptionTag(false);
286}}
287"#,
288                    self.quote_serialize_value("value", format, false)
289                )?;
290            }
291
292            Seq(format) => {
293                write!(
294                    self.out,
295                    r#"
296serializer.serializeLen(value.length);
297value.forEach((item: {}) => {{
298    {}
299}});
300"#,
301                    self.quote_type(format),
302                    self.quote_serialize_value("item", format, false)
303                )?;
304            }
305
306            Map { key, value } => {
307                write!(
308                    self.out,
309                    r#"
310serializer.serializeLen(value.size);
311const offsets: number[] = [];
312for (const [k, v] of value.entries()) {{
313  offsets.push(serializer.getBufferOffset());
314  {}
315  {}
316}}
317serializer.sortMapEntries(offsets);
318"#,
319                    self.quote_serialize_value("k", key, false),
320                    self.quote_serialize_value("v", value, false)
321                )?;
322            }
323
324            Tuple(formats) => {
325                writeln!(self.out)?;
326                for (index, format) in formats.iter().enumerate() {
327                    let expr = format!("value[{}]", index);
328                    writeln!(
329                        self.out,
330                        "{}",
331                        self.quote_serialize_value(&expr, format, false)
332                    )?;
333                }
334            }
335
336            TupleArray {
337                content,
338                size: _size,
339            } => {
340                write!(
341                    self.out,
342                    r#"
343value.forEach((item) =>{{
344    {}
345}});
346"#,
347                    self.quote_serialize_value("item[0]", content, false)
348                )?;
349            }
350
351            _ => panic!("unexpected case"),
352        }
353        self.out.unindent();
354        writeln!(self.out, "}}\n")
355    }
356
357    fn output_deserialization_helper(&mut self, name: &str, format0: &Format) -> Result<()> {
358        use Format::*;
359
360        write!(
361            self.out,
362            "static deserialize{}(deserializer: Deserializer): {} {{",
363            name.to_camel_case(),
364            self.quote_type(format0),
365        )?;
366        self.out.indent();
367        match format0 {
368            Option(format) => {
369                write!(
370                    self.out,
371                    r#"
372const tag = deserializer.deserializeOptionTag();
373if (!tag) {{
374    return null;
375}} else {{
376    return {};
377}}
378"#,
379                    self.quote_deserialize(format),
380                )?;
381            }
382
383            Seq(format) => {
384                write!(
385                    self.out,
386                    r#"
387const length = deserializer.deserializeLen();
388const list: {} = [];
389for (let i = 0; i < length; i++) {{
390    list.push({});
391}}
392return list;
393"#,
394                    self.quote_type(format0),
395                    self.quote_deserialize(format)
396                )?;
397            }
398
399            Map { key, value } => {
400                write!(
401                    self.out,
402                    r#"
403const length = deserializer.deserializeLen();
404const obj = new Map<{0}, {1}>();
405let previousKeyStart = 0;
406let previousKeyEnd = 0;
407for (let i = 0; i < length; i++) {{
408    const keyStart = deserializer.getBufferOffset();
409    const key = {2};
410    const keyEnd = deserializer.getBufferOffset();
411    if (i > 0) {{
412        deserializer.checkThatKeySlicesAreIncreasing(
413            [previousKeyStart, previousKeyEnd],
414            [keyStart, keyEnd]);
415    }}
416    previousKeyStart = keyStart;
417    previousKeyEnd = keyEnd;
418    const value = {3};
419    obj.set(key, value);
420}}
421return obj;
422"#,
423                    self.quote_type(key),
424                    self.quote_type(value),
425                    self.quote_deserialize(key),
426                    self.quote_deserialize(value),
427                )?;
428            }
429
430            Tuple(formats) => {
431                write!(
432                    self.out,
433                    r#"
434return [{}
435];
436"#,
437                    formats
438                        .iter()
439                        .map(|f| format!("\n    {}", self.quote_deserialize(f)))
440                        .collect::<Vec<_>>()
441                        .join(",")
442                )?;
443            }
444
445            TupleArray { content, size } => {
446                write!(
447                    self.out,
448                    r#"
449const list: {} = [];
450for (let i = 0; i < {}; i++) {{
451    list.push([{}]);
452}}
453return list;
454"#,
455                    self.quote_type(format0),
456                    size,
457                    self.quote_deserialize(content)
458                )?;
459            }
460
461            _ => panic!("unexpected case"),
462        }
463        self.out.unindent();
464        writeln!(self.out, "}}\n")
465    }
466
467    fn output_variant(
468        &mut self,
469        base: &str,
470        index: u32,
471        name: &str,
472        variant: &VariantFormat,
473    ) -> Result<()> {
474        use VariantFormat::*;
475        let fields = match variant {
476            Unit => Vec::new(),
477            NewType(format) => vec![Named {
478                name: "value".to_string(),
479                value: format.as_ref().clone(),
480            }],
481            Tuple(formats) => formats
482                .iter()
483                .enumerate()
484                .map(|(i, f)| Named {
485                    name: format!("field{}", i),
486                    value: f.clone(),
487                })
488                .collect(),
489            Struct(fields) => fields.clone(),
490            Variable(_) => panic!("incorrect value"),
491        };
492        self.output_struct_or_variant_container(Some(base), Some(index), name, &fields)
493    }
494
495    fn output_variants(
496        &mut self,
497        base: &str,
498        variants: &BTreeMap<u32, Named<VariantFormat>>,
499    ) -> Result<()> {
500        for (index, variant) in variants {
501            self.output_variant(base, *index, &variant.name, &variant.value)?;
502        }
503        Ok(())
504    }
505
506    fn output_struct_or_variant_container(
507        &mut self,
508        variant_base: Option<&str>,
509        variant_index: Option<u32>,
510        name: &str,
511        fields: &[Named<Format>],
512    ) -> Result<()> {
513        let mut variant_base_name = String::new();
514
515        // Beginning of class
516        if let Some(base) = variant_base {
517            writeln!(self.out)?;
518            self.output_comment(name)?;
519            writeln!(
520                self.out,
521                "export class {0}Variant{1} extends {0} {{",
522                base, name
523            )?;
524            variant_base_name = format!("{0}Variant", base);
525        } else {
526            self.output_comment(name)?;
527            writeln!(self.out, "export class {} {{", name)?;
528        }
529        if !fields.is_empty() {
530            writeln!(self.out)?;
531        }
532        // Constructor.
533        writeln!(
534            self.out,
535            "constructor ({}) {{",
536            fields
537                .iter()
538                .map(|f| { format!("public {}: {}", &f.name, self.quote_type(&f.value)) })
539                .collect::<Vec<_>>()
540                .join(", ")
541        )?;
542        if let Some(_base) = variant_base {
543            self.out.indent();
544            writeln!(self.out, "super();")?;
545            self.out.unindent();
546        }
547        writeln!(self.out, "}}\n")?;
548        // Serialize
549        if self.generator.config.serialization {
550            writeln!(
551                self.out,
552                "public serialize(serializer: Serializer): void {{",
553            )?;
554            self.out.indent();
555            if let Some(index) = variant_index {
556                writeln!(self.out, "serializer.serializeVariantIndex({});", index)?;
557            }
558            for field in fields {
559                writeln!(
560                    self.out,
561                    "{}",
562                    self.quote_serialize_value(&field.name, &field.value, true)
563                )?;
564            }
565            self.out.unindent();
566            writeln!(self.out, "}}\n")?;
567        }
568        // Deserialize (struct) or Load (variant)
569        if self.generator.config.serialization {
570            if variant_index.is_none() {
571                writeln!(
572                    self.out,
573                    "static deserialize(deserializer: Deserializer): {} {{",
574                    name,
575                )?;
576            } else {
577                writeln!(
578                    self.out,
579                    "static load(deserializer: Deserializer): {}{} {{",
580                    variant_base_name, name,
581                )?;
582            }
583            self.out.indent();
584            for field in fields {
585                writeln!(
586                    self.out,
587                    "const {} = {};",
588                    field.name,
589                    self.quote_deserialize(&field.value)
590                )?;
591            }
592            writeln!(
593                self.out,
594                r#"return new {0}{1}({2});"#,
595                variant_base_name,
596                name,
597                fields
598                    .iter()
599                    .map(|f| f.name.to_string())
600                    .collect::<Vec<_>>()
601                    .join(",")
602            )?;
603            self.out.unindent();
604            writeln!(self.out, "}}\n")?;
605        }
606        writeln!(self.out, "}}")
607    }
608
609    fn output_enum_container(
610        &mut self,
611        name: &str,
612        variants: &BTreeMap<u32, Named<VariantFormat>>,
613    ) -> Result<()> {
614        self.output_comment(name)?;
615        writeln!(self.out, "export abstract class {} {{", name)?;
616        if self.generator.config.serialization {
617            writeln!(
618                self.out,
619                "abstract serialize(serializer: Serializer): void;\n"
620            )?;
621            write!(
622                self.out,
623                "static deserialize(deserializer: Deserializer): {} {{",
624                name
625            )?;
626            self.out.indent();
627            writeln!(
628                self.out,
629                r#"
630const index = deserializer.deserializeVariantIndex();
631switch (index) {{"#,
632            )?;
633            self.out.indent();
634            for (index, variant) in variants {
635                writeln!(
636                    self.out,
637                    "case {}: return {}Variant{}.load(deserializer);",
638                    index, name, variant.name,
639                )?;
640            }
641            writeln!(
642                self.out,
643                "default: throw new Error(\"Unknown variant index for {}: \" + index);",
644                name,
645            )?;
646            self.out.unindent();
647            writeln!(self.out, "}}")?;
648            self.out.unindent();
649            writeln!(self.out, "}}")?;
650        }
651        writeln!(self.out, "}}\n")?;
652        self.output_variants(name, variants)?;
653        Ok(())
654    }
655
656    fn output_container(&mut self, name: &str, format: &ContainerFormat) -> Result<()> {
657        use ContainerFormat::*;
658        let fields = match format {
659            UnitStruct => Vec::new(),
660            NewTypeStruct(format) => vec![Named {
661                name: "value".to_string(),
662                value: format.as_ref().clone(),
663            }],
664            TupleStruct(formats) => formats
665                .iter()
666                .enumerate()
667                .map(|(i, f)| Named {
668                    name: format!("field{}", i),
669                    value: f.clone(),
670                })
671                .collect::<Vec<_>>(),
672            Struct(fields) => fields.clone(),
673            Enum(variants) => {
674                self.output_enum_container(name, variants)?;
675                return Ok(());
676            }
677        };
678        self.output_struct_or_variant_container(None, None, name, &fields)
679    }
680}
681
682/// Installer for generated source files in TypeScript.
683pub struct Installer {
684    install_dir: PathBuf,
685}
686
687impl Installer {
688    pub fn new(install_dir: PathBuf) -> Self {
689        Installer { install_dir }
690    }
691
692    fn install_runtime(
693        &self,
694        source_dir: include_dir::Dir,
695        path: &str,
696    ) -> std::result::Result<(), Box<dyn std::error::Error>> {
697        let dir_path = self.install_dir.join(path);
698        std::fs::create_dir_all(&dir_path)?;
699        for entry in source_dir.files() {
700            let mut file = std::fs::File::create(dir_path.join(entry.path()))?;
701            file.write_all(entry.contents())?;
702        }
703        Ok(())
704    }
705}
706
707impl crate::SourceInstaller for Installer {
708    type Error = Box<dyn std::error::Error>;
709
710    fn install_module(
711        &self,
712        config: &CodeGeneratorConfig,
713        registry: &Registry,
714    ) -> std::result::Result<(), Self::Error> {
715        let dir_path = self.install_dir.join(&config.module_name);
716        std::fs::create_dir_all(&dir_path)?;
717        let source_path = dir_path.join("mod.ts");
718        let mut file = std::fs::File::create(source_path)?;
719
720        let generator = CodeGenerator::new(config);
721        generator.output(&mut file, registry)?;
722        Ok(())
723    }
724
725    fn install_serde_runtime(&self) -> std::result::Result<(), Self::Error> {
726        self.install_runtime(include_directory!("runtime/typescript/serde"), "serde")
727    }
728
729    fn install_bincode_runtime(&self) -> std::result::Result<(), Self::Error> {
730        self.install_runtime(include_directory!("runtime/typescript/bincode"), "bincode")
731    }
732
733    fn install_bcs_runtime(&self) -> std::result::Result<(), Self::Error> {
734        self.install_runtime(include_directory!("runtime/typescript/bcs"), "bcs")
735    }
736}