diff --git a/derives/src/container.rs b/derives/src/container.rs index 59ce8ee..fd3c370 100644 --- a/derives/src/container.rs +++ b/derives/src/container.rs @@ -15,6 +15,7 @@ pub struct Container<'a> { pub rename_all: Option, pub rename: Option, pub ident: &'a Ident, + pub comments: Vec, } impl<'a> Container<'a> { @@ -22,6 +23,7 @@ impl<'a> Container<'a> { let mut rename_all: Option = None; let mut file_name: Option = None; let mut rename: Option = None; + let comments = parse_comments(&item.attrs); for meta_item in item .attrs .iter() @@ -62,6 +64,7 @@ impl<'a> Container<'a> { rename_all, ident: &item.ident, rename, + comments, } } syn::Data::Enum(e) => { @@ -77,6 +80,7 @@ impl<'a> Container<'a> { rename_all, ident: &item.ident, rename, + comments, } } _ => panic!("gents does not support the union type currently, use struct instead"), @@ -89,20 +93,24 @@ pub struct Field<'a> { pub ident: &'a Ident, pub ty: Option<&'a Type>, // enum ty can be None. pub skip: bool, + pub comments: Vec, } impl<'a> Field<'a> { pub fn from_field(f: &'a syn::Field) -> Self { + let comments = parse_comments(&f.attrs); let attrs = parse_attrs(&f.attrs); Field { rename: attrs.rename, ident: f.ident.as_ref().unwrap(), ty: Some(&f.ty), skip: attrs.skip, + comments, } } pub fn from_variant(v: &'a syn::Variant) -> Self { + let comments = parse_comments(&v.attrs); let attrs = parse_attrs(&v.attrs); if v.fields.len() > 1 { panic!("not implemented yet") @@ -117,6 +125,7 @@ impl<'a> Field<'a> { ident: &v.ident, ty, skip: attrs.skip, + comments, } } } @@ -184,3 +193,19 @@ fn get_lit_bool<'a>(lit: &'a syn::Lit) -> Result { Err(()) } } + +fn parse_comments(attrs: &[Attribute]) -> Vec { + let mut result = Vec::new(); + + attrs.iter().for_each(|attr| { + if attr.path.is_ident("doc") { + if let Ok(NameValue(nv)) = &attr.parse_meta() { + if let Ok(s) = get_lit_str(&nv.lit) { + let comment = s.value(); + result.push(comment.trim().to_string()); + } + } + } + }); + result +} diff --git a/derives/src/lib.rs b/derives/src/lib.rs index b2e9867..9c644e4 100644 --- a/derives/src/lib.rs +++ b/derives/src/lib.rs @@ -26,11 +26,13 @@ fn get_impl_block(input: DeriveInput) -> proc_macro2::TokenStream { Some(s) => s, None => ident.to_string(), }; + let comments = container.comments; let register_func = { let field_ds = fields.into_iter().filter(|f| !f.skip).map(|s| { let fi = s.ident; let rename = s.rename; let ty = s.ty; + let field_comments = s.comments; let name = match (rename, &rename_all) { (None, None) => fi.to_string(), (None, Some(RenameAll::CamelCase)) => { @@ -51,6 +53,7 @@ fn get_impl_block(input: DeriveInput) -> proc_macro2::TokenStream { ident: #name.to_string(), optional: <#ty as ::gents::TS>::_is_optional(), ts_ty: <#ty as ::gents::TS>::_ts_name(), + comments: vec![#(#field_comments.to_string()),*], }; fields.push(fd); } @@ -60,6 +63,7 @@ fn get_impl_block(input: DeriveInput) -> proc_macro2::TokenStream { ident: #name.to_string(), optional: false, ts_ty: String::from(""), + comments: vec![#(#field_comments.to_string()),*], }; fields.push(fd); } @@ -72,6 +76,7 @@ fn get_impl_block(input: DeriveInput) -> proc_macro2::TokenStream { fields, file_name: #file_name.to_string(), ts_name: #ts_name.to_string(), + comments: vec![#(#comments.to_string()),*], }; let descriptor = ::gents::Descriptor::Enum(_enum); } @@ -83,6 +88,7 @@ fn get_impl_block(input: DeriveInput) -> proc_macro2::TokenStream { fields, file_name: #file_name.to_string(), ts_name: #ts_name.to_string(), + comments: vec![#(#comments.to_string()),*], }; let descriptor = ::gents::Descriptor::Interface(_interface); } diff --git a/src/descriptor.rs b/src/descriptor.rs index 8581b0f..319ed04 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -62,6 +62,8 @@ impl DescriptorManager { } result }; + let comments = get_comment_string(&d.comments, false); + let fields_strings = d.fields.iter().fold(Vec::::new(), |mut prev, fd| { let optional = if fd.optional { String::from(" | null") @@ -70,14 +72,16 @@ impl DescriptorManager { }; let ident = fd.ident.to_string(); let ty = fd.ts_ty.to_string(); - let f = format!(" {}: {}{}", ident, ty, optional); + let c = get_comment_string(&fd.comments, true); + let f = format!("{} {}: {}{}", c, ident, ty, optional); prev.push(f); prev }); let fields_string = fields_strings.join("\n"); let content = format!( - "{}\nexport interface {} {{\n{}\n}}\n", + "{}\n{}export interface {} {{\n{}\n}}\n", import_string, + comments, d.ts_name.to_string(), fields_string ); @@ -106,6 +110,8 @@ impl DescriptorManager { } result }; + let comments = get_comment_string(&e.comments, false); + let fields_strings = e.fields.iter().fold(Vec::::new(), |mut prev, fd| { let ident = fd.ident.to_string(); let ty = fd.ts_ty.to_string(); @@ -121,8 +127,8 @@ impl DescriptorManager { let fields_string = fields_strings.join("\n "); let ts_name = e.ts_name.to_string(); let content = format!( - "{}\nexport type {} =\n {}\n", - import_string, ts_name, fields_string + "{}\n{}export type {} =\n {}\n", + import_string, comments, ts_name, fields_string ); result.push((e.file_name.to_string(), content)) } @@ -132,6 +138,18 @@ impl DescriptorManager { } } +fn get_comment_string(v: &[String], indent: bool) -> String { + if v.is_empty() { + String::from("") + } else { + if !indent { + format!("// {}\n", v.join("\n// ")) + } else { + format!(" // {}\n", v.join("\n // ")) + } + } +} + // todo: InterfaceDescriptor and EnumDescriptor are the same now. // Remove one of it. #[derive(Debug)] @@ -160,6 +178,7 @@ pub struct EnumDescriptor { pub fields: Vec, pub file_name: String, pub ts_name: String, + pub comments: Vec, } /// Describe how to generate a ts interface. @@ -170,6 +189,7 @@ pub struct InterfaceDescriptor { pub fields: Vec, pub file_name: String, pub ts_name: String, + pub comments: Vec, } #[derive(Debug)] @@ -177,6 +197,7 @@ pub struct FieldDescriptor { pub ident: String, pub optional: bool, pub ts_ty: String, + pub comments: Vec, } macro_rules! impl_builtin { diff --git a/tests/test.rs b/tests/test.rs index 7f831ee..9573555 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -126,4 +126,36 @@ export interface Group { | 'None'"# ); } + + #[test] + fn gen_with_comments_test() { + /// This is a doc comment. + /// Another Comment + /** + Block Comment + */ + #[derive(TS)] + #[ts(file_name = "struct_with_comments.ts", rename_all = "camelCase")] + pub struct StructWithComments { + /// field comment1 + /// field comment2 + pub field_with_comment: u32, + } + + let mut manager = DescriptorManager::default(); + StructWithComments::_register(&mut manager); + let (file_name, content) = manager.gen_data().into_iter().next().unwrap(); + assert_eq!(file_name, "struct_with_comments.ts"); + assert_eq!( + content.trim(), + r#"// This is a doc comment. +// Another Comment +// Block Comment +export interface StructWithComments { + // field comment1 + // field comment2 + fieldWithComment: number +}"# + ); + } }