From f93d67c5b4f5203fa3fc96e1088daf9410d487c0 Mon Sep 17 00:00:00 2001 From: Aumetra Weisman Date: Wed, 30 Oct 2024 15:46:49 +0100 Subject: [PATCH] WIP TypeScript codegen --- Cargo.lock | 1 + packages/cw-schema-codegen/Cargo.toml | 1 + packages/cw-schema-codegen/src/main.rs | 9 +- .../cw-schema-codegen/src/typescript/mod.rs | 144 ++++++++++++++++++ .../src/typescript/template.rs | 36 ++++- .../templates/rust/enum.tpl.rs | 6 +- .../templates/rust/struct.tpl.rs | 6 +- .../templates/typescript/enum.tpl.ts | 38 +++++ .../templates/typescript/struct.tpl.ts | 30 ++++ 9 files changed, 262 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f6175909a..8238042589 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -945,6 +945,7 @@ dependencies = [ "cw-schema", "either", "frunk", + "frunk_core", "heck", "insta", "log", diff --git a/packages/cw-schema-codegen/Cargo.toml b/packages/cw-schema-codegen/Cargo.toml index f31f233f9d..aa004d9507 100644 --- a/packages/cw-schema-codegen/Cargo.toml +++ b/packages/cw-schema-codegen/Cargo.toml @@ -12,6 +12,7 @@ clap = { version = "4.5.18", features = ["derive"] } cw-schema = { version = "=2.2.0-rc.1", path = "../cw-schema" } either = "1.13.0" frunk = "0.4.3" +frunk_core = "0.4.3" heck = "0.5.0" log = "0.4.22" serde_json = "1.0.128" diff --git a/packages/cw-schema-codegen/src/main.rs b/packages/cw-schema-codegen/src/main.rs index e10f534c75..7331768ef0 100644 --- a/packages/cw-schema-codegen/src/main.rs +++ b/packages/cw-schema-codegen/src/main.rs @@ -79,7 +79,14 @@ fn main() -> anyhow::Result<()> { schema.definitions.iter().try_for_each(|node| { debug!("Processing node: {node:?}"); - cw_schema_codegen::rust::process_node(&mut output, &schema, node) + + match opts.language { + Language::Rust => cw_schema_codegen::rust::process_node(&mut output, &schema, node), + Language::Typescript => { + cw_schema_codegen::typescript::process_node(&mut output, &schema, node) + } + Language::Go | Language::Python => todo!(), + } })?; Ok(()) diff --git a/packages/cw-schema-codegen/src/typescript/mod.rs b/packages/cw-schema-codegen/src/typescript/mod.rs index 612b5b95f1..19c6d955a1 100644 --- a/packages/cw-schema-codegen/src/typescript/mod.rs +++ b/packages/cw-schema-codegen/src/typescript/mod.rs @@ -1 +1,145 @@ +use self::template::{ + EnumTemplate, EnumVariantTemplate, FieldTemplate, StructTemplate, TypeTemplate, +}; +use heck::ToPascalCase; +use std::{borrow::Cow, io}; + pub mod template; + +fn expand_node_name<'a>( + schema: &'a cw_schema::SchemaV1, + node: &'a cw_schema::Node, +) -> Cow<'a, str> { + match node.value { + cw_schema::NodeType::Array { items } => { + let items = &schema.definitions[items]; + format!("{}[]", expand_node_name(schema, items)).into() + } + cw_schema::NodeType::Float => "number".into(), + cw_schema::NodeType::Double => "number".into(), + cw_schema::NodeType::Boolean => "boolean".into(), + cw_schema::NodeType::String => "string".into(), + cw_schema::NodeType::Integer { signed, precision } => { + "string".into() + /*let ty = if signed { "i" } else { "u" }; + format!("{ty}{precision}").into()*/ + } + cw_schema::NodeType::Binary => "Uint8Array".into(), + cw_schema::NodeType::Optional { inner } => { + let inner = &schema.definitions[inner]; + format!("{} | null", expand_node_name(schema, inner)).into() + } + cw_schema::NodeType::Struct(..) => node.name.as_ref().into(), + cw_schema::NodeType::Tuple { ref items } => { + let items = items + .iter() + .map(|item| expand_node_name(schema, &schema.definitions[*item])) + .collect::>() + .join(", "); + + format!("[{}]", items).into() + } + cw_schema::NodeType::Enum { .. } => node.name.as_ref().into(), + + cw_schema::NodeType::Decimal { precision, signed } => todo!(), + cw_schema::NodeType::Address => todo!(), + cw_schema::NodeType::Checksum => todo!(), + cw_schema::NodeType::HexBinary => todo!(), + cw_schema::NodeType::Timestamp => todo!(), + cw_schema::NodeType::Unit => Cow::Borrowed("void"), + } +} + +fn prepare_docs(desc: Option<&str>) -> Cow<'_, [Cow<'_, str>]> { + desc.map(|desc| { + desc.lines() + .map(|line| line.replace('"', "\\\"").into()) + .collect() + }) + .unwrap_or(Cow::Borrowed(&[])) +} + +pub fn process_node( + output: &mut O, + schema: &cw_schema::SchemaV1, + node: &cw_schema::Node, +) -> io::Result<()> +where + O: io::Write, +{ + match node.value { + cw_schema::NodeType::Struct(ref sty) => { + let structt = StructTemplate { + name: node.name.clone(), + docs: prepare_docs(node.description.as_deref()), + ty: match sty { + cw_schema::StructType::Unit => TypeTemplate::Unit, + cw_schema::StructType::Named { ref properties } => TypeTemplate::Named { + fields: properties + .iter() + .map(|(name, prop)| FieldTemplate { + name: Cow::Borrowed(name), + docs: prepare_docs(prop.description.as_deref()), + ty: expand_node_name(schema, &schema.definitions[prop.value]), + }) + .collect(), + }, + cw_schema::StructType::Tuple { ref items } => TypeTemplate::Tuple( + items + .iter() + .map(|item| expand_node_name(schema, &schema.definitions[*item])) + .collect(), + ), + }, + }; + + writeln!(output, "{structt}")?; + } + cw_schema::NodeType::Enum { ref cases, .. } => { + let enumm = EnumTemplate { + name: node.name.clone(), + docs: prepare_docs(node.description.as_deref()), + variants: cases + .iter() + .map(|(name, case)| EnumVariantTemplate { + name: name.clone(), + docs: prepare_docs(case.description.as_deref()), + ty: match case.value { + cw_schema::EnumValue::Unit => TypeTemplate::Unit, + cw_schema::EnumValue::Tuple { ref items } => { + let items = items + .iter() + .map(|item| { + expand_node_name(schema, &schema.definitions[*item]) + }) + .collect(); + + TypeTemplate::Tuple(items) + } + cw_schema::EnumValue::Named { ref properties, .. } => { + TypeTemplate::Named { + fields: properties + .iter() + .map(|(name, prop)| FieldTemplate { + name: Cow::Borrowed(name), + docs: prepare_docs(prop.description.as_deref()), + ty: expand_node_name( + schema, + &schema.definitions[prop.value], + ), + }) + .collect(), + } + } + }, + }) + .collect(), + }; + + writeln!(output, "{enumm}")?; + } + _ => (), + } + + Ok(()) +} diff --git a/packages/cw-schema-codegen/src/typescript/template.rs b/packages/cw-schema-codegen/src/typescript/template.rs index bee10e6fd9..5ee51e9de0 100644 --- a/packages/cw-schema-codegen/src/typescript/template.rs +++ b/packages/cw-schema-codegen/src/typescript/template.rs @@ -1,9 +1,41 @@ use askama::Template; +use std::borrow::Cow; + +#[derive(Clone)] +pub struct EnumVariantTemplate<'a> { + pub name: Cow<'a, str>, + pub docs: Cow<'a, [Cow<'a, str>]>, + pub ty: TypeTemplate<'a>, +} #[derive(Template)] #[template(escape = "none", path = "typescript/enum.tpl.ts")] -pub struct EnumTemplate {} +pub struct EnumTemplate<'a> { + pub name: Cow<'a, str>, + pub docs: Cow<'a, [Cow<'a, str>]>, + pub variants: Cow<'a, [EnumVariantTemplate<'a>]>, +} + +#[derive(Clone)] +pub struct FieldTemplate<'a> { + pub name: Cow<'a, str>, + pub docs: Cow<'a, [Cow<'a, str>]>, + pub ty: Cow<'a, str>, +} + +#[derive(Clone)] +pub enum TypeTemplate<'a> { + Unit, + Tuple(Cow<'a, [Cow<'a, str>]>), + Named { + fields: Cow<'a, [FieldTemplate<'a>]>, + }, +} #[derive(Template)] #[template(escape = "none", path = "typescript/struct.tpl.ts")] -pub struct StructTemplate {} +pub struct StructTemplate<'a> { + pub name: Cow<'a, str>, + pub docs: Cow<'a, [Cow<'a, str>]>, + pub ty: TypeTemplate<'a>, +} diff --git a/packages/cw-schema-codegen/templates/rust/enum.tpl.rs b/packages/cw-schema-codegen/templates/rust/enum.tpl.rs index e0768fef19..6898de1af0 100644 --- a/packages/cw-schema-codegen/templates/rust/enum.tpl.rs +++ b/packages/cw-schema-codegen/templates/rust/enum.tpl.rs @@ -1,3 +1,5 @@ +// This code is @generated by cw-schema-codegen. Do not modify this manually. + {% for doc in docs %} #[doc = "{{ doc }}"] {% endfor %} @@ -20,9 +22,7 @@ pub enum {{ name }} { {% when TypeTemplate::Unit %} {% when TypeTemplate::Tuple with (types) %} ( - {% for ty in types %} - {{ ty }}, - {% endfor %} + {{ types|join(", ") }} ) {% when TypeTemplate::Named with { fields } %} { diff --git a/packages/cw-schema-codegen/templates/rust/struct.tpl.rs b/packages/cw-schema-codegen/templates/rust/struct.tpl.rs index 87173daa9b..3d2f8a005b 100644 --- a/packages/cw-schema-codegen/templates/rust/struct.tpl.rs +++ b/packages/cw-schema-codegen/templates/rust/struct.tpl.rs @@ -1,3 +1,5 @@ +// This code is @generated by cw-schema-codegen. Do not modify this manually. + {% for doc in docs %} #[doc = "{{ doc }}"] {% endfor %} @@ -10,9 +12,7 @@ pub struct {{ name }} ; {% when TypeTemplate::Tuple with (types) %} ( - {% for ty in types %} - {{ ty }}, - {% endfor %} + {{ types|join(", ") }} ); {% when TypeTemplate::Named with { fields } %} { diff --git a/packages/cw-schema-codegen/templates/typescript/enum.tpl.ts b/packages/cw-schema-codegen/templates/typescript/enum.tpl.ts index e69de29bb2..8ab50d8e6a 100644 --- a/packages/cw-schema-codegen/templates/typescript/enum.tpl.ts +++ b/packages/cw-schema-codegen/templates/typescript/enum.tpl.ts @@ -0,0 +1,38 @@ +// This code is @generated by cw-schema-codegen. Do not modify this manually. + +/** +{% for doc in docs %} + * {{ doc }} +{% endfor %} + */ + +type {{ name }} = +{% for variant in variants %} + | + + /** + {% for doc in variant.docs %} + * {{ doc }} + {% endfor %} + */ + + {% match variant.ty %} + {% when TypeTemplate::Unit %} + { "{{ variant.name }}": {} } + {% when TypeTemplate::Tuple with (types) %} + { "{{ variant.name }}": [{{ types|join(", ") }}] } + {% when TypeTemplate::Named with { fields } %} + { "{{ variant.name }}": { + {% for field in fields %} + /** + {% for doc in field.docs %} + * {{ doc }} + {% endfor %} + */ + + {{ field.name }}: {{ field.ty }}; + {% endfor %} + } } + {% endmatch %} +{% endfor %} +; \ No newline at end of file diff --git a/packages/cw-schema-codegen/templates/typescript/struct.tpl.ts b/packages/cw-schema-codegen/templates/typescript/struct.tpl.ts index e69de29bb2..8d7eb0a4fc 100644 --- a/packages/cw-schema-codegen/templates/typescript/struct.tpl.ts +++ b/packages/cw-schema-codegen/templates/typescript/struct.tpl.ts @@ -0,0 +1,30 @@ +// This code is @generated by cw-schema-codegen. Do not modify this manually. + +/** +{% for doc in docs %} + * {{ doc }} +{% endfor %} + */ + +type {{ name }} = +{% match ty %} + {% when TypeTemplate::Unit %} + void + {% when TypeTemplate::Tuple with (types) %} + [{{ types|join(", ") }}] + {% when TypeTemplate::Named with { fields } %} + { + {% for field in fields %} + /** + {% for doc in field.docs %} + * {{ doc }} + {% endfor %} + */ + + {{ field.name }}: {{ field.ty }}; + {% endfor %} + } +{% endmatch %} +; + +export { {{ name }} };