diff --git a/macros/src/attr/field.rs b/macros/src/attr/field.rs index 13daef55d..64a4ab569 100644 --- a/macros/src/attr/field.rs +++ b/macros/src/attr/field.rs @@ -11,6 +11,7 @@ pub struct FieldAttr { pub skip: bool, pub optional: bool, pub flatten: bool, + pub doc_string: Option, } #[cfg(feature = "serde-compat")] @@ -35,6 +36,7 @@ impl FieldAttr { skip, optional, flatten, + doc_string, }: FieldAttr, ) { self.rename = self.rename.take().or(rename); @@ -43,6 +45,7 @@ impl FieldAttr { self.skip = self.skip || skip; self.optional |= optional; self.flatten |= flatten; + self.doc_string = self.doc_string.take().or(doc_string); } } @@ -54,6 +57,7 @@ impl_parse! { "skip" => out.skip = true, "optional" => out.optional = true, "flatten" => out.flatten = true, + "doc_string" => out.doc_string = Some(parse_assign_str(input)?), } } diff --git a/macros/src/types/enum.rs b/macros/src/types/enum.rs index 94486dfaa..41f768d2d 100644 --- a/macros/src/types/enum.rs +++ b/macros/src/types/enum.rs @@ -72,6 +72,7 @@ fn format_variant( skip, optional, flatten, + doc_string, } = FieldAttr::from_attrs(&variant.attrs)?; match (skip, &type_override, inline, optional, flatten) { @@ -97,25 +98,31 @@ fn format_variant( )?; let variant_dependencies = variant_type.dependencies; let inline_type = variant_type.inline; + + let doc_string = match doc_string { + Some(s) => format!("\n/**\n* {}\n*/\n", s), + None => "".to_string(), + }; let formatted = match enum_attr.tagged()? { Tagged::Untagged => quote!(#inline_type), Tagged::Externally => match &variant.fields { Fields::Unit => quote!(format!("\"{}\"", #name)), - _ => quote!(format!("{{ {}: {} }}", #name, #inline_type)), + _ => quote!(format!("{}{{ {}: {} }}", #doc_string, #name, #inline_type)), }, Tagged::Adjacently { tag, content } => match &variant.fields { Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => { let ty = format_type(&unnamed.unnamed[0].ty, dependencies, generics); - quote!(format!("{{ {}: \"{}\", {}: {} }}", #tag, #name, #content, #ty)) + quote!(format!("{}{{ {}: \"{}\", {}: {} }}", #doc_string, #tag, #name, #content, #ty)) } - Fields::Unit => quote!(format!("{{ {}: \"{}\" }}", #tag, #name)), - _ => quote!(format!("{{ {}: \"{}\", {}: {} }}", #tag, #name, #content, #inline_type)), + Fields::Unit => quote!(format!("{}{{ {}: \"{}\" }}", #doc_string, #tag, #name)), + _ => quote!(format!("{}{{ {}: \"{}\", {}: {} }}", #doc_string, #tag, #name, #content, #inline_type)), }, Tagged::Internally { tag } => match variant_type.inline_flattened { Some(inline_flattened) => quote! { format!( - "{{ {}: \"{}\", {} }}", + "{}{{ {}: \"{}\", {} }}", + #doc_string, #tag, #name, #inline_flattened @@ -124,11 +131,11 @@ fn format_variant( None => match &variant.fields { Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => { let ty = format_type(&unnamed.unnamed[0].ty, dependencies, generics); - quote!(format!("{{ {}: \"{}\" }} & {}", #tag, #name, #ty)) + quote!(format!("{}{{ {}: \"{}\" }} & {}", #doc_string, #tag, #name, #ty)) } - Fields::Unit => quote!(format!("{{ {}: \"{}\" }}", #tag, #name)), + Fields::Unit => quote!(format!("{}{{ {}: \"{}\" }}", #doc_string, #tag, #name)), _ => { - quote!(format!("{{ {}: \"{}\" }} & {}", #tag, #name, #inline_type)) + quote!(format!("{}{{ {}: \"{}\" }} & {}", #doc_string, #tag, #name, #inline_type)) } }, }, diff --git a/macros/src/types/named.rs b/macros/src/types/named.rs index c942aaa2e..ec42cc5b8 100644 --- a/macros/src/types/named.rs +++ b/macros/src/types/named.rs @@ -69,6 +69,7 @@ fn format_field( skip, optional, flatten, + doc_string, } = FieldAttr::from_attrs(&field.attrs)?; if skip { @@ -109,8 +110,13 @@ fn format_field( }; let valid_name = raw_name_to_ts_field(name); + let doc_string = match doc_string { + Some(s) => format!("\n/**\n* {}\n*/\n", s), + None => "".to_string(), + }; + formatted_fields.push(quote! { - format!("{}{}: {},", #valid_name, #optional_annotation, #formatted_ty) + format!("{}{}{}: {},", #doc_string, #valid_name, #optional_annotation, #formatted_ty) }); Ok(()) diff --git a/macros/src/types/newtype.rs b/macros/src/types/newtype.rs index b5070d75a..796cd3e24 100644 --- a/macros/src/types/newtype.rs +++ b/macros/src/types/newtype.rs @@ -28,6 +28,7 @@ pub(crate) fn newtype( skip, optional, flatten, + doc_string, } = FieldAttr::from_attrs(&inner.attrs)?; match (&rename_inner, skip, optional, flatten) { @@ -51,9 +52,14 @@ pub(crate) fn newtype( None => format_type(inner_ty, &mut dependencies, generics), }; + let doc_string = match doc_string { + Some(s) => format!("\n/**\n* {}\n*/\n", s), + None => "".to_string(), + }; + let generic_args = format_generics(&mut dependencies, generics); Ok(DerivedTS { - decl: quote!(format!("type {}{} = {};", #name, #generic_args, #inline_def)), + decl: quote!(format!("{}type {}{} = {};", #doc_string, #name, #generic_args, #inline_def)), inline: inline_def, inline_flattened: None, name: name.to_owned(), diff --git a/macros/src/types/tuple.rs b/macros/src/types/tuple.rs index ac763af29..df231132d 100644 --- a/macros/src/types/tuple.rs +++ b/macros/src/types/tuple.rs @@ -29,6 +29,7 @@ pub(crate) fn tuple( } let generic_args = format_generics(&mut dependencies, generics); + Ok(DerivedTS { inline: quote! { format!( @@ -66,6 +67,7 @@ fn format_field( skip, optional, flatten, + doc_string, } = FieldAttr::from_attrs(&field.attrs)?; if skip { @@ -80,6 +82,10 @@ fn format_field( if flatten { syn_err!("`flatten` is not applicable to tuple fields") } + if doc_string.is_some() { + syn_err!("`doc_string` is not applicable to tuple fields") + } + formatted_fields.push(match &type_override { Some(o) => quote!(#o.to_owned()), diff --git a/ts-rs/src/lib.rs b/ts-rs/src/lib.rs index 32ea04623..0fe21bd0f 100644 --- a/ts-rs/src/lib.rs +++ b/ts-rs/src/lib.rs @@ -205,6 +205,9 @@ mod export; /// - `#[ts(flatten)]`: /// Flatten this field (only works if the field is a struct) /// +/// - `#[ts(doc_string = "..")]`: +/// Add typescript docstring to this field +/// /// ### enum attributes /// /// - `#[ts(tag = "..")]`: diff --git a/ts-rs/tests/doc_string.rs b/ts-rs/tests/doc_string.rs new file mode 100644 index 000000000..9ca83b7f6 --- /dev/null +++ b/ts-rs/tests/doc_string.rs @@ -0,0 +1,16 @@ +#![allow(dead_code)] + +use ts_rs::TS; + +#[derive(TS)] +struct DocString { + #[ts(doc_string="@mydoc")] + a: i32, + #[ts(doc_string="@mydoc2")] + b: String, +} + +#[test] +fn test() { + assert_eq!(DocString::inline(), "{ \n/**\n* @mydoc\n*/\na: number, \n/**\n* @mydoc2\n*/\nb: string, }"); +}