Skip to content

Commit

Permalink
feat: preserve the document comments in ts files (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
ImJeremyHe authored Oct 13, 2023
1 parent 1ef5e76 commit 2e88e5e
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 4 deletions.
25 changes: 25 additions & 0 deletions derives/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ pub struct Container<'a> {
pub rename_all: Option<RenameAll>,
pub rename: Option<String>,
pub ident: &'a Ident,
pub comments: Vec<String>,
}

impl<'a> Container<'a> {
pub fn from_ast(item: &'a syn::DeriveInput) -> Self {
let mut rename_all: Option<RenameAll> = None;
let mut file_name: Option<String> = None;
let mut rename: Option<String> = None;
let comments = parse_comments(&item.attrs);
for meta_item in item
.attrs
.iter()
Expand Down Expand Up @@ -62,6 +64,7 @@ impl<'a> Container<'a> {
rename_all,
ident: &item.ident,
rename,
comments,
}
}
syn::Data::Enum(e) => {
Expand All @@ -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"),
Expand All @@ -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<String>,
}

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")
Expand All @@ -117,6 +125,7 @@ impl<'a> Field<'a> {
ident: &v.ident,
ty,
skip: attrs.skip,
comments,
}
}
}
Expand Down Expand Up @@ -184,3 +193,19 @@ fn get_lit_bool<'a>(lit: &'a syn::Lit) -> Result<bool, ()> {
Err(())
}
}

fn parse_comments(attrs: &[Attribute]) -> Vec<String> {
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
}
6 changes: 6 additions & 0 deletions derives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)) => {
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand Down
29 changes: 25 additions & 4 deletions src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ impl DescriptorManager {
}
result
};
let comments = get_comment_string(&d.comments, false);

let fields_strings = d.fields.iter().fold(Vec::<String>::new(), |mut prev, fd| {
let optional = if fd.optional {
String::from(" | null")
Expand All @@ -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
);
Expand Down Expand Up @@ -106,6 +110,8 @@ impl DescriptorManager {
}
result
};
let comments = get_comment_string(&e.comments, false);

let fields_strings = e.fields.iter().fold(Vec::<String>::new(), |mut prev, fd| {
let ident = fd.ident.to_string();
let ty = fd.ts_ty.to_string();
Expand All @@ -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))
}
Expand All @@ -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)]
Expand Down Expand Up @@ -160,6 +178,7 @@ pub struct EnumDescriptor {
pub fields: Vec<FieldDescriptor>,
pub file_name: String,
pub ts_name: String,
pub comments: Vec<String>,
}

/// Describe how to generate a ts interface.
Expand All @@ -170,13 +189,15 @@ pub struct InterfaceDescriptor {
pub fields: Vec<FieldDescriptor>,
pub file_name: String,
pub ts_name: String,
pub comments: Vec<String>,
}

#[derive(Debug)]
pub struct FieldDescriptor {
pub ident: String,
pub optional: bool,
pub ts_ty: String,
pub comments: Vec<String>,
}

macro_rules! impl_builtin {
Expand Down
32 changes: 32 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}"#
);
}
}

0 comments on commit 2e88e5e

Please sign in to comment.