Skip to content

Commit

Permalink
fix: improve ts codegen and formatting for ts/tsx files (#311)
Browse files Browse the repository at this point in the history
* refactor: replace tempalte derived types.ts files with rust-geneted ones

* fix typo

* add programatic formatting

* fix incorrectly named variable in tests

* ignore formatting EntryTypes

* fix invalid enum types

* fix: improve code formatting in ts/tsx files

* fix clippy warning

* fix failing codgegen tests

* rename tests module
  • Loading branch information
c12i committed Jun 17, 2024
1 parent c56bdf8 commit 3fc6f21
Show file tree
Hide file tree
Showing 28 changed files with 878 additions and 149 deletions.
486 changes: 483 additions & 3 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ include_dir = "0.7.3"
serde = "1"
semver = "1.0"
itertools = "0.10"
colored = "2.1.0"
colored = "2.1.0"
dprint-plugin-typescript = "0.91.1"
dprint-plugin-vue = { git = "https://github.com/c12i/dprint-plugin-vue.git", version = "0.6.0"}
2 changes: 2 additions & 0 deletions src/scaffold/entry_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ pub fn scaffold_entry_type(
fields,
reference_entry_hash,
};
let entry_def_ts_types = entry_def.ts_type_codegen();

let integrity_zome_name = zome_file_tree.zome_manifest.name.0.to_string();

Expand Down Expand Up @@ -217,6 +218,7 @@ pub fn scaffold_entry_type(
&dna_manifest.name(),
&coordinator_zome,
&entry_def,
&entry_def_ts_types,
&crud,
link_from_original_to_each_update,
no_ui,
Expand Down
188 changes: 188 additions & 0 deletions src/scaffold/entry_type/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ impl FieldType {
}
}

pub fn ts_type(&self) -> &str {
use FieldType::*;

match self {
Bool => "boolean",
String => "string",
U32 => "number",
I32 => "number",
F32 => "number",
Timestamp => "number",
AgentPubKey => "AgentPubKey",
ActionHash => "ActionHash",
EntryHash => "EntryHash",
DnaHash => "DnaHash",
Enum { label, .. } => label,
}
}

// Define a non-primitive rust type for this widget
pub fn rust_type_definition(&self) -> Option<TokenStream> {
match self {
Expand Down Expand Up @@ -351,4 +369,174 @@ impl EntryDefinition {
pub fn camel_case_name(&self) -> String {
self.name.to_case(Case::Camel)
}

/// Generate entry definition as typescript interface
pub fn ts_type_codegen(&self) -> String {
let mut ts_interface = format!("export interface {} {{\n", &self.pascal_case_name());
let mut ts_enums = String::new();

for field in &self.fields {
let ts_type = field.field_type.ts_type();
if let FieldType::Enum { label, variants } = &field.field_type {
let enum_definition = format!(
"export type {label} = {};\n",
variants
.iter()
.map(|v| format!("{{type: '{}'}}", v))
.collect::<Vec<_>>()
.join(" | ")
);
ts_enums.push_str(&enum_definition);
}
let ts_field = match field.cardinality {
Cardinality::Single => {
format!(" {}: {};", &field.field_name.to_case(Case::Snake), ts_type)
}
Cardinality::Option => format!(
" {}: {} | undefined;",
&field.field_name.to_case(Case::Snake),
ts_type
),
Cardinality::Vector => {
format!(
" {}: Array<{}>;",
&field.field_name.to_case(Case::Snake),
ts_type
)
}
};
ts_interface.push_str(&ts_field);
ts_interface.push('\n');
}
ts_interface.push('}');
ts_enums
.is_empty()
.then_some(ts_interface.clone())
.unwrap_or(format!("{ts_enums}\n{}", ts_interface.clone()))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_entry_def_ts_codegen_with_primitive_fields() {
let post_entry = EntryDefinition {
name: "post".to_string(),
fields: vec![
FieldDefinition {
field_name: "title".to_string(),
field_type: FieldType::String,
widget: Some("TextField".to_string()),
cardinality: Cardinality::Single,
linked_from: None,
},
FieldDefinition {
field_name: "content".to_string(),
field_type: FieldType::String,
widget: Some("TextArea".to_string()),
cardinality: Cardinality::Single,
linked_from: None,
},
],
reference_entry_hash: false,
};

let comment_entry = EntryDefinition {
name: "post".to_string(),
fields: vec![
FieldDefinition {
field_name: "comment".to_string(),
field_type: FieldType::String,
widget: Some("TextArea".to_string()),
cardinality: Cardinality::Single,
linked_from: None,
},
FieldDefinition {
field_name: "post_hash".to_string(),
field_type: FieldType::ActionHash,
widget: None,
cardinality: Cardinality::Single,
linked_from: Some(Referenceable::EntryType(EntryTypeReference {
entry_type: post_entry.name.to_string(),
reference_entry_hash: false,
})),
},
],
reference_entry_hash: false,
};

let post_ts_interface = &post_entry.ts_type_codegen();
let expected_post_ts_interface = r#"export interface Post {
title: string;
content: string;
}"#;
assert_eq!(expected_post_ts_interface, post_ts_interface);

let comment_ts_interface = &comment_entry.ts_type_codegen();
let expected_comment_ts_inteface = r#"export interface Post {
comment: string;
post_hash: ActionHash;
}"#;
assert_eq!(expected_comment_ts_inteface, comment_ts_interface)
}

#[test]
fn test_entry_def_ts_codegen_with_enums_arrays_arrays_and_options() {
let other_entry = EntryDefinition {
name: "example_entry".to_string(),
fields: vec![
FieldDefinition {
field_name: "field_one".to_string(),
field_type: FieldType::String,
widget: None,
cardinality: Cardinality::Single,
linked_from: None,
},
FieldDefinition {
field_name: "field_two".to_string(),
field_type: FieldType::U32,
widget: None,
cardinality: Cardinality::Option,
linked_from: None,
},
FieldDefinition {
field_name: "field_three".to_string(),
field_type: FieldType::Bool,
widget: None,
cardinality: Cardinality::Vector,
linked_from: None,
},
FieldDefinition {
field_name: "enum_field".to_string(),
field_type: FieldType::Enum {
label: "ExampleEnum".to_string(),
variants: vec![
"Variant1".to_string(),
"Variant2".to_string(),
"Variant3".to_string(),
],
},
widget: None,
cardinality: Cardinality::Single,
linked_from: None,
},
],
reference_entry_hash: false,
};

let ts_interface = &other_entry.ts_type_codegen();

let expected_ts_interface = r#"export type ExampleEnum = {type: 'Variant1'} | {type: 'Variant2'} | {type: 'Variant3'};
export interface ExampleEntry {
field_one: string;
field_two: number | undefined;
field_three: Array<boolean>;
enum_field: ExampleEnum;
}"#;

assert_eq!(ts_interface, expected_ts_interface);
}
}
12 changes: 8 additions & 4 deletions src/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::error::ScaffoldResult;
use crate::file_tree::{
file_content, find_files, flatten_file_tree, unflatten_file_tree, FileTree,
};
use crate::utils::format_code;

pub mod helpers;

Expand Down Expand Up @@ -167,9 +168,9 @@ pub fn render_template_file_tree<T: Serialize>(

for (i, f) in files_to_create.into_iter().enumerate() {
let target_path = PathBuf::from(path_prefix.clone()).join(f);
let formatted_contents = format_code(&new_contents_split[i], &target_path)?;

transformed_templates
.insert(target_path, Some(new_contents_split[i].clone()));
transformed_templates.insert(target_path, Some(formatted_contents));
}
}
} else if if_regex.is_match(path.to_str().unwrap()) {
Expand All @@ -191,7 +192,9 @@ pub fn render_template_file_tree<T: Serialize>(
&contents,
&value,
)?;
transformed_templates.insert(target_path, Some(new_contents));
let formatted_contents = format_code(&new_contents, file_name)?;

transformed_templates.insert(target_path, Some(formatted_contents));
}
} else if let Some(e) = path.extension() {
if e == "hbs" {
Expand All @@ -205,8 +208,9 @@ pub fn render_template_file_tree<T: Serialize>(
&contents,
&value,
)?;
let formatted_contents = format_code(&new_contents, &target_path)?;

transformed_templates.insert(target_path, Some(new_contents));
transformed_templates.insert(target_path, Some(formatted_contents));
}
}
} else {
Expand Down
5 changes: 4 additions & 1 deletion src/templates/entry_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ use super::{
};

#[derive(Serialize, Debug)]
pub struct ScaffoldEntryTypeData {
pub struct ScaffoldEntryTypeData<'a> {
pub app_name: String,
pub dna_role_name: String,
pub coordinator_zome_manifest: ZomeManifest,
pub entry_type: EntryDefinition,
pub entry_type_ts_types: &'a str,
pub crud: Crud,
pub link_from_original_to_each_update: bool,
}
Expand All @@ -32,6 +33,7 @@ pub fn scaffold_entry_type_templates(
dna_role_name: &str,
coordinator_zome: &ZomeManifest,
entry_type: &EntryDefinition,
entry_type_ts_types: &str,
crud: &Crud,
link_from_original_to_each_update: bool,
no_ui: bool,
Expand All @@ -41,6 +43,7 @@ pub fn scaffold_entry_type_templates(
dna_role_name: dna_role_name.to_owned(),
coordinator_zome_manifest: coordinator_zome.clone(),
entry_type: entry_type.clone(),
entry_type_ts_types,
crud: crud.clone(),
link_from_original_to_each_update,
};
Expand Down
Loading

0 comments on commit 3fc6f21

Please sign in to comment.