From 33ce45dcef6493a6af867aaf766b4e1ad8806f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren?= Date: Mon, 14 Aug 2023 13:36:23 +0200 Subject: [PATCH 1/3] Extract and copy rustdoc comments into type definitions --- tsify-macros/src/attrs.rs | 6 +++ tsify-macros/src/comments.rs | 69 ++++++++++++++++++++++++++++++++++ tsify-macros/src/decl.rs | 52 ++++++++++++++++++++++--- tsify-macros/src/lib.rs | 1 + tsify-macros/src/parser.rs | 9 +++++ tsify-macros/src/type_alias.rs | 5 ++- tsify-macros/src/typescript.rs | 34 +++++++++++++++++ 7 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 tsify-macros/src/comments.rs diff --git a/tsify-macros/src/attrs.rs b/tsify-macros/src/attrs.rs index 3e64cc3..34e0c1b 100644 --- a/tsify-macros/src/attrs.rs +++ b/tsify-macros/src/attrs.rs @@ -1,10 +1,13 @@ use serde_derive_internals::ast::Field; +use crate::comments::extract_doc_comments; + #[derive(Debug, Default)] pub struct TsifyContainerAttars { pub into_wasm_abi: bool, pub from_wasm_abi: bool, pub namespace: bool, + pub comments: Vec, } impl TsifyContainerAttars { @@ -13,6 +16,7 @@ impl TsifyContainerAttars { into_wasm_abi: false, from_wasm_abi: false, namespace: false, + comments: extract_doc_comments(&input.attrs), }; for attr in &input.attrs { @@ -60,6 +64,7 @@ impl TsifyContainerAttars { pub struct TsifyFieldAttrs { pub type_override: Option, pub optional: bool, + pub comments: Vec, } impl TsifyFieldAttrs { @@ -67,6 +72,7 @@ impl TsifyFieldAttrs { let mut attrs = Self { type_override: None, optional: false, + comments: extract_doc_comments(&field.original.attrs), }; for attr in &field.original.attrs { diff --git a/tsify-macros/src/comments.rs b/tsify-macros/src/comments.rs new file mode 100644 index 0000000..e9aede3 --- /dev/null +++ b/tsify-macros/src/comments.rs @@ -0,0 +1,69 @@ +use proc_macro2::TokenTree; +use quote::ToTokens; + +use crate::typescript::TsType; + +/// Extract the documentation comments from a Vec of attributes +pub fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec { + attrs + .iter() + .filter_map(|a| { + // if the path segments include an ident of "doc" we know this + // this is a doc comment + if a.path() + .segments + .iter() + .any(|s| s.ident.to_string() == "doc") + { + Some(a.to_token_stream().into_iter().filter_map(|t| match t { + TokenTree::Group(group) => { + // this will return the inner tokens of the group + // which will be the doc comments + Some( + group + .stream() + .into_iter() + .filter_map(|t| match t { + TokenTree::Literal(lit) => { + // this will always return the quoted string, we deal with + // that in the cli when we read in the comments + Some(lit.to_string()) + } + _ => None, + }) + .collect::>() + .join(""), + ) + } + _ => None, + })) + } else { + None + } + }) + //Fold up the [[String]] iter we created into Vec + .fold(vec![], |mut acc, a| { + acc.extend(a); + acc + }) +} + +pub fn format_doc_comments(comments: &Vec) -> String { + let comment = comments + .iter() + .map(|line| format!(" *{}\n", line.trim_matches('"'))) + .collect::>() + .join(""); + + format!("/**\n{} */\n", comment) +} + +pub fn clean_comments(typ: &mut TsType) -> () { + if let TsType::TypeLit(ref mut lit) = typ { + lit.members.iter_mut().for_each(|elem| { + elem.comments = vec![]; + // Recurse + clean_comments(&mut elem.type_ann); + }); + } +} diff --git a/tsify-macros/src/decl.rs b/tsify-macros/src/decl.rs index 9be3a9e..5984585 100644 --- a/tsify-macros/src/decl.rs +++ b/tsify-macros/src/decl.rs @@ -1,7 +1,11 @@ -use std::fmt::Display; use std::ops::Deref; +use std::{fmt::Display, vec}; -use crate::typescript::{TsType, TsTypeElement, TsTypeLit}; +use crate::comments::clean_comments; +use crate::{ + comments::format_doc_comments, + typescript::{TsType, TsTypeElement, TsTypeLit}, +}; #[derive(Clone)] pub struct TsTypeAliasDecl { @@ -9,6 +13,18 @@ pub struct TsTypeAliasDecl { pub export: bool, pub type_params: Vec, pub type_ann: TsType, + pub comments: Vec, +} + +impl TsTypeAliasDecl { + pub fn to_string_with_indent(&self, indent: usize) -> String { + let out = self.to_string(); + let indent_str = " ".repeat(indent); + out.split("\n") + .map(|line| format!("{}{}", indent_str, line)) + .collect::>() + .join("\n") + } } impl Display for TsTypeAliasDecl { @@ -20,6 +36,10 @@ impl Display for TsTypeAliasDecl { format!("{}<{}>", self.id, type_params) }; + if !self.comments.is_empty() { + write!(f, "{}", format_doc_comments(&self.comments))?; + } + if self.export { write!(f, "export ")?; } @@ -32,10 +52,15 @@ pub struct TsInterfaceDecl { pub type_params: Vec, pub extends: Vec, pub body: Vec, + pub comments: Vec, } impl Display for TsInterfaceDecl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if !self.comments.is_empty() { + write!(f, "{}", format_doc_comments(&self.comments))?; + } + write!(f, "export interface {}", self.id)?; if !self.type_params.is_empty() { @@ -60,7 +85,7 @@ impl Display for TsInterfaceDecl { let members = self .body .iter() - .map(|elem| format!("\n {elem};")) + .map(|elem| format!("\n{};", elem.to_string_with_indent(4))) .collect::>() .join(""); @@ -74,6 +99,7 @@ pub struct TsEnumDecl { pub type_params: Vec, pub members: Vec, pub namespace: bool, + pub comments: Vec, } const ALPHABET_UPPER: [char; 26] = [ @@ -141,6 +167,7 @@ impl TsEnumDecl { key: t.key.clone(), optional: t.optional, type_ann: TsEnumDecl::replace_type_params(t.type_ann.clone(), type_args), + comments: vec![], }) .collect(), }), @@ -187,6 +214,7 @@ impl Display for TsEnumDecl { export: false, type_params: type_refs, type_ann: ts_type, + comments: vec![], } }) .collect::>() @@ -197,6 +225,11 @@ impl Display for TsEnumDecl { for type_ref in type_refs { writeln!(f, "{}", type_ref)?; } + + if !self.comments.is_empty() { + write!(f, "{}", format_doc_comments(&self.comments))?; + } + write!(f, "declare namespace {}", self.id)?; if self.members.is_empty() { @@ -214,8 +247,9 @@ impl Display for TsEnumDecl { .type_ann .clone() .prefix_type_refs(&prefix, &self.type_params), + comments: elem.comments.clone(), }) - .map(|elem| format!("\n {elem}")) + .map(|elem| format!("\n{}", elem.to_string_with_indent(4))) .collect::>() .join(""); @@ -232,9 +266,17 @@ impl Display for TsEnumDecl { type_ann: TsType::Union( self.members .iter() - .map(|member| member.type_ann.clone()) + .map(|member| { + // let mut type_refs = Vec::new(); + // TsEnumDecl::replace_type_params(member.type_ann.clone(), &mut type_refs) + + let mut clone = member.type_ann.clone(); + clean_comments(&mut clone); + clone + }) .collect(), ), + comments: self.comments.clone(), } .fmt(f) } diff --git a/tsify-macros/src/lib.rs b/tsify-macros/src/lib.rs index a48e847..0e261e6 100644 --- a/tsify-macros/src/lib.rs +++ b/tsify-macros/src/lib.rs @@ -1,4 +1,5 @@ mod attrs; +mod comments; mod container; mod ctxt; mod decl; diff --git a/tsify-macros/src/parser.rs b/tsify-macros/src/parser.rs index 66c6c2d..9b55e09 100644 --- a/tsify-macros/src/parser.rs +++ b/tsify-macros/src/parser.rs @@ -7,6 +7,7 @@ use serde_derive_internals::{ use crate::{ attrs::TsifyFieldAttrs, + comments::extract_doc_comments, container::Container, decl::{Decl, TsEnumDecl, TsInterfaceDecl, TsTypeAliasDecl}, typescript::{TsType, TsTypeElement, TsTypeLit}, @@ -74,6 +75,7 @@ impl<'a> Parser<'a> { export: true, type_params: self.create_relevant_type_params(type_ann.type_ref_names()), type_ann, + comments: extract_doc_comments(&self.container.serde_container.original.attrs), }) } @@ -95,6 +97,7 @@ impl<'a> Parser<'a> { type_params, extends, body: members, + comments: extract_doc_comments(&self.container.serde_container.original.attrs), }) } else { let extra = TsType::Intersection( @@ -124,6 +127,7 @@ impl<'a> Parser<'a> { key: tag.clone(), type_ann: TsType::Lit(name), optional: false, + comments: vec![], }; let mut vec = Vec::with_capacity(members.len() + 1); @@ -224,10 +228,13 @@ impl<'a> Parser<'a> { type_ann }; + let comments = extract_doc_comments(&field.original.attrs); + TsTypeElement { key, type_ann, optional: optional || !default_is_none, + comments, } }) .collect(); @@ -248,6 +255,7 @@ impl<'a> Parser<'a> { let decl = self.create_type_alias_decl(self.parse_variant(variant)); if let Decl::TsTypeAlias(mut type_alias) = decl { type_alias.id = variant.attrs.name().serialize_name(); + type_alias.comments = extract_doc_comments(&variant.original.attrs); type_alias } else { @@ -268,6 +276,7 @@ impl<'a> Parser<'a> { type_params: relevant_type_params, members, namespace: self.container.attrs.namespace, + comments: extract_doc_comments(&self.container.serde_container.original.attrs), }) } diff --git a/tsify-macros/src/type_alias.rs b/tsify-macros/src/type_alias.rs index 3efdcca..45e85b9 100644 --- a/tsify-macros/src/type_alias.rs +++ b/tsify-macros/src/type_alias.rs @@ -1,7 +1,9 @@ use proc_macro2::TokenStream; use quote::quote; -use crate::{ctxt::Ctxt, decl::TsTypeAliasDecl, typescript::TsType}; +use crate::{ + comments::extract_doc_comments, ctxt::Ctxt, decl::TsTypeAliasDecl, typescript::TsType, +}; pub fn expend(item: syn::ItemType) -> syn::Result { let ctxt = Ctxt::new(); @@ -17,6 +19,7 @@ pub fn expend(item: syn::ItemType) -> syn::Result { .map(|ty| ty.ident.to_string()) .collect(), type_ann, + comments: extract_doc_comments(&item.attrs), }; let decl_str = decl.to_string(); diff --git a/tsify-macros/src/typescript.rs b/tsify-macros/src/typescript.rs index 25fd6ae..5518b0a 100644 --- a/tsify-macros/src/typescript.rs +++ b/tsify-macros/src/typescript.rs @@ -2,6 +2,8 @@ use std::{collections::HashSet, fmt::Display}; use serde_derive_internals::{ast::Style, attr::TagType}; +use crate::comments::format_doc_comments; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TsKeywordTypeKind { Number, @@ -19,6 +21,7 @@ pub struct TsTypeElement { pub key: String, pub type_ann: TsType, pub optional: bool, + pub comments: Vec, } impl From for TsTypeLit { @@ -101,6 +104,7 @@ macro_rules! type_lit { key: stringify!($k).to_string(), type_ann: $t, optional: false, + comments: vec![], } ),*], }) @@ -383,6 +387,7 @@ impl TsType { key: name, type_ann, optional: false, + comments: vec![], } .into() } @@ -393,6 +398,7 @@ impl TsType { key: tag.clone(), type_ann: TsType::Lit(name), optional: false, + comments: vec![], } .into(); @@ -402,6 +408,7 @@ impl TsType { key: tag.clone(), type_ann: TsType::Lit(name), optional: false, + comments: vec![], } .into(); @@ -413,6 +420,7 @@ impl TsType { key: tag.clone(), type_ann: TsType::Lit(name), optional: false, + comments: vec![], }; if matches!(style, Style::Unit) { @@ -422,6 +430,7 @@ impl TsType { key: content.clone(), type_ann, optional: false, + comments: vec![], }; TsTypeLit { @@ -519,6 +528,7 @@ impl TsType { key: t.key.clone(), optional: t.optional, type_ann: t.type_ann.clone().prefix_type_refs(prefix, exceptions), + comments: t.comments.clone(), }) .collect(), }), @@ -578,6 +588,17 @@ fn is_js_ident(string: &str) -> bool { !string.contains('-') } +impl TsTypeElement { + pub fn to_string_with_indent(&self, indent: usize) -> String { + let out = self.to_string(); + let indent_str = " ".repeat(indent); + out.split("\n") + .map(|line| format!("{}{}", indent_str, line)) + .collect::>() + .join("\n") + } +} + impl Display for TsTypeElement { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let key = &self.key; @@ -585,6 +606,10 @@ impl Display for TsTypeElement { let optional_ann = if self.optional { "?" } else { "" }; + if !self.comments.is_empty() { + write!(f, "{}", format_doc_comments(&self.comments))?; + } + if is_js_ident(key) { write!(f, "{key}{optional_ann}: {type_ann}") } else { @@ -682,6 +707,15 @@ impl Display for TsType { .iter() .map(|ty| match ty { TsType::Union(_) => format!("({ty})"), + TsType::TypeLit(tl) => { + // Intersections are formatted as single lines, so we need to remove + // any comments as they are multi-line and will break the formatting. + let mut copy = tl.clone(); + copy.members.iter_mut().for_each(|elem| { + elem.comments = vec![]; + }); + copy.to_string() + } _ => ty.to_string(), }) .collect::>() From 1816f9ebc2ba0090c8dc81efdab5113e0d18513f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren?= Date: Mon, 14 Aug 2023 13:35:02 +0200 Subject: [PATCH 2/3] Adapt tests to test comment generation --- tests/enum.rs | 272 +++++++++++++++++++++++++++++++++++++++-- tests/flatten.rs | 21 ++++ tests/generics.rs | 62 +++++++++- tests/optional.rs | 63 ++++++++++ tests/rename.rs | 69 ++++++++++- tests/skip.rs | 38 +++++- tests/struct.rs | 109 ++++++++++++++++- tests/transparent.rs | 25 +++- tests/type_override.rs | 44 ++++++- 9 files changed, 682 insertions(+), 21 deletions(-) diff --git a/tests/enum.rs b/tests/enum.rs index 7d3453f..ef9c529 100644 --- a/tests/enum.rs +++ b/tests/enum.rs @@ -11,17 +11,27 @@ struct Foo { #[test] fn test_externally_tagged_enum() { + /// Comment for External #[derive(Tsify)] enum External { + /// Comment for Struct Struct { x: String, y: i32 }, + /// Comment for EmptyStruct EmptyStruct {}, + /// Comment for Tuple Tuple(i32, String), + /// Comment for EmptyTuple EmptyTuple(), + /// Comment for Newtype Newtype(Foo), + /// Comment for Unit Unit, } let expected = indoc! {r#" + /** + * Comment for External + */ export type External = { Struct: { x: string; y: number } } | { EmptyStruct: {} } | { Tuple: [number, string] } | { EmptyTuple: [] } | { Newtype: Foo } | "Unit";"# }; @@ -30,28 +40,59 @@ fn test_externally_tagged_enum() { #[test] fn test_externally_tagged_enum_with_namespace() { + /// Comment for External #[derive(Tsify)] #[tsify(namespace)] enum External { + /// Comment for Struct Struct { x: String, y: i32 }, + /// Comment for EmptyStruct EmptyStruct {}, + /// Comment for Tuple Tuple(i32, String), + /// Comment for EmptyTuple EmptyTuple(), + /// Comment for Newtype Newtype(Foo), + /// Comment for Unit Unit, } let expected = indoc! {r#" type __ExternalFoo = Foo; + /** + * Comment for External + */ declare namespace External { + /** + * Comment for Struct + */ export type Struct = { Struct: { x: string; y: number } }; + /** + * Comment for EmptyStruct + */ export type EmptyStruct = { EmptyStruct: {} }; + /** + * Comment for Tuple + */ export type Tuple = { Tuple: [number, string] }; + /** + * Comment for EmptyTuple + */ export type EmptyTuple = { EmptyTuple: [] }; + /** + * Comment for Newtype + */ export type Newtype = { Newtype: __ExternalFoo }; + /** + * Comment for Unit + */ export type Unit = "Unit"; } - + + /** + * Comment for External + */ export type External = { Struct: { x: string; y: number } } | { EmptyStruct: {} } | { Tuple: [number, string] } | { EmptyTuple: [] } | { Newtype: Foo } | "Unit";"# }; @@ -60,16 +101,24 @@ fn test_externally_tagged_enum_with_namespace() { #[test] fn test_internally_tagged_enum() { + /// Comment for Internal #[derive(Tsify)] #[serde(tag = "t")] enum Internal { + /// Comment for Struct Struct { x: String, y: i32 }, + /// Comment for EmptyStruct EmptyStruct {}, + /// Comment for Newtype Newtype(Foo), + /// Comment for Unit Unit, } let expected = indoc! {r#" + /** + * Comment for Internal + */ export type Internal = { t: "Struct"; x: string; y: number } | { t: "EmptyStruct" } | ({ t: "Newtype" } & Foo) | { t: "Unit" };"# }; @@ -78,25 +127,48 @@ fn test_internally_tagged_enum() { #[test] fn test_internally_tagged_enum_with_namespace() { + /// Comment for Internal #[derive(Tsify)] #[serde(tag = "t")] #[tsify(namespace)] enum Internal { + /// Comment for Struct Struct { x: String, y: i32 }, + /// Comment for EmptyStruct EmptyStruct {}, + /// Comment for Newtype Newtype(Foo), + /// Comment for Unit Unit, } let expected = indoc! {r#" type __InternalFoo = Foo; + /** + * Comment for Internal + */ declare namespace Internal { + /** + * Comment for Struct + */ export type Struct = { t: "Struct"; x: string; y: number }; + /** + * Comment for EmptyStruct + */ export type EmptyStruct = { t: "EmptyStruct" }; + /** + * Comment for Newtype + */ export type Newtype = { t: "Newtype" } & __InternalFoo; + /** + * Comment for Unit + */ export type Unit = { t: "Unit" }; } - + + /** + * Comment for Internal + */ export type Internal = { t: "Struct"; x: string; y: number } | { t: "EmptyStruct" } | ({ t: "Newtype" } & Foo) | { t: "Unit" };"# }; @@ -105,19 +177,29 @@ fn test_internally_tagged_enum_with_namespace() { #[test] fn test_adjacently_tagged_enum() { + /// Comment for Adjacent #[derive(Tsify)] #[serde(tag = "t", content = "c")] enum Adjacent { + /// Comment for Struct Struct { x: String, y: i32 }, + /// Comment for EmptyStruct EmptyStruct {}, + /// Comment for Tuple Tuple(i32, String), + /// Comment for EmptyTuple EmptyTuple(), + /// Comment for Newtype Newtype(Foo), + /// Comment for Unit Unit, } let expected = indoc! {r#" - export type Adjacent = { t: "Struct"; c: { x: string; y: number } } | { t: "EmptyStruct"; c: {} } | { t: "Tuple"; c: [number, string] } | { t: "EmptyTuple"; c: [] } | { t: "Newtype"; c: Foo } | { t: "Unit" };"# + /** + * Comment for Adjacent + */ + export type Adjacent = { t: "Struct"; c: { x: string; y: number } } | { t: "EmptyStruct"; c: {} } | { t: "Tuple"; c: [number, string] } | { t: "EmptyTuple"; c: [] } | { t: "Newtype"; c: Foo } | { t: "Unit" };"# }; assert_eq!(Adjacent::DECL, expected); @@ -125,29 +207,60 @@ fn test_adjacently_tagged_enum() { #[test] fn test_adjacently_tagged_enum_with_namespace() { + /// Comment for Adjacent #[derive(Tsify)] #[serde(tag = "t", content = "c")] #[tsify(namespace)] enum Adjacent { + /// Comment for Struct Struct { x: String, y: i32 }, + /// Comment for EmptyStruct EmptyStruct {}, + /// Comment for Tuple Tuple(i32, String), + /// Comment for EmptyTuple EmptyTuple(), + /// Comment for Newtype Newtype(Foo), + /// Comment for Unit Unit, } let expected = indoc! {r#" type __AdjacentFoo = Foo; + /** + * Comment for Adjacent + */ declare namespace Adjacent { + /** + * Comment for Struct + */ export type Struct = { t: "Struct"; c: { x: string; y: number } }; + /** + * Comment for EmptyStruct + */ export type EmptyStruct = { t: "EmptyStruct"; c: {} }; + /** + * Comment for Tuple + */ export type Tuple = { t: "Tuple"; c: [number, string] }; + /** + * Comment for EmptyTuple + */ export type EmptyTuple = { t: "EmptyTuple"; c: [] }; + /** + * Comment for Newtype + */ export type Newtype = { t: "Newtype"; c: __AdjacentFoo }; + /** + * Comment for Unit + */ export type Unit = { t: "Unit" }; } - + + /** + * Comment for Adjacent + */ export type Adjacent = { t: "Struct"; c: { x: string; y: number } } | { t: "EmptyStruct"; c: {} } | { t: "Tuple"; c: [number, string] } | { t: "EmptyTuple"; c: [] } | { t: "Newtype"; c: Foo } | { t: "Unit" };"# }; @@ -156,23 +269,36 @@ fn test_adjacently_tagged_enum_with_namespace() { #[test] fn test_untagged_enum() { + /// Comment for Untagged #[derive(Tsify)] #[serde(untagged)] enum Untagged { + /// Comment for Struct Struct { x: String, y: i32 }, + /// Comment for EmptyStruct EmptyStruct {}, + /// Comment for Tuple Tuple(i32, String), + /// Comment for EmptyTuple EmptyTuple(), + /// Comment for Newtype Newtype(Foo), + /// Comment for Unit Unit, } let expected = if cfg!(feature = "js") { indoc! {r#" + /** + * Comment for Untagged + */ export type Untagged = { x: string; y: number } | {} | [number, string] | [] | Foo | undefined;"# } } else { indoc! {r#" + /** + * Comment for Untagged + */ export type Untagged = { x: string; y: number } | {} | [number, string] | [] | Foo | null;"# } }; @@ -182,44 +308,99 @@ fn test_untagged_enum() { #[test] fn test_untagged_enum_with_namespace() { + /// Comment for Untagged #[derive(Tsify)] #[serde(untagged)] #[tsify(namespace)] enum Untagged { + /// Comment for Struct Struct { x: String, y: i32 }, + /// Comment for EmptyStruct EmptyStruct {}, + /// Comment for Tuple Tuple(i32, String), + /// Comment for EmptyTuple EmptyTuple(), + /// Comment for Newtype Newtype(Foo), + /// Comment for Unit Unit, } let expected = if cfg!(feature = "js") { indoc! {r#" type __UntaggedFoo = Foo; + /** + * Comment for Untagged + */ declare namespace Untagged { + /** + * Comment for Struct + */ export type Struct = { x: string; y: number }; + /** + * Comment for EmptyStruct + */ export type EmptyStruct = {}; + /** + * Comment for Tuple + */ export type Tuple = [number, string]; + /** + * Comment for EmptyTuple + */ export type EmptyTuple = []; + /** + * Comment for Newtype + */ export type Newtype = __UntaggedFoo; + /** + * Comment for Unit + */ export type Unit = undefined; } - + + /** + * Comment for Untagged + */ export type Untagged = { x: string; y: number } | {} | [number, string] | [] | Foo | undefined;"# } } else { indoc! {r#" type __UntaggedFoo = Foo; + /** + * Comment for Untagged + */ declare namespace Untagged { + /** + * Comment for Struct + */ export type Struct = { x: string; y: number }; + /** + * Comment for EmptyStruct + */ export type EmptyStruct = {}; + /** + * Comment for Tuple + */ export type Tuple = [number, string]; + /** + * Comment for EmptyTuple + */ export type EmptyTuple = []; + /** + * Comment for Newtype + */ export type Newtype = __UntaggedFoo; + /** + * Comment for Unit + */ export type Unit = null; } - + + /** + * Comment for Untagged + */ export type Untagged = { x: string; y: number } | {} | [number, string] | [] | Foo | null;"# } }; @@ -229,30 +410,65 @@ fn test_untagged_enum_with_namespace() { #[test] fn test_module_reimport_enum() { + /// Comment for Internal #[derive(Tsify)] #[tsify(namespace)] enum Internal { + /// Comment for Struct Struct { x: String, y: i32 }, + /// Comment for EmptyStruct EmptyStruct {}, + /// Comment for Tuple Tuple(i32, String), + /// Comment for EmptyTuple EmptyTuple(), + /// Comment for Newtype Newtype(Foo), + /// Comment for Newtype2 Newtype2(Foo), + /// Comment for Unit Unit, } let expected = indoc! {r#" type __InternalFoo = Foo; + /** + * Comment for Internal + */ declare namespace Internal { + /** + * Comment for Struct + */ export type Struct = { Struct: { x: string; y: number } }; + /** + * Comment for EmptyStruct + */ export type EmptyStruct = { EmptyStruct: {} }; + /** + * Comment for Tuple + */ export type Tuple = { Tuple: [number, string] }; + /** + * Comment for EmptyTuple + */ export type EmptyTuple = { EmptyTuple: [] }; + /** + * Comment for Newtype + */ export type Newtype = { Newtype: __InternalFoo }; + /** + * Comment for Newtype2 + */ export type Newtype2 = { Newtype2: __InternalFoo }; + /** + * Comment for Unit + */ export type Unit = "Unit"; } + /** + * Comment for Internal + */ export type Internal = { Struct: { x: string; y: number } } | { EmptyStruct: {} } | { Tuple: [number, string] } | { EmptyTuple: [] } | { Newtype: Foo } | { Newtype2: Foo } | "Unit";"# }; @@ -261,28 +477,53 @@ fn test_module_reimport_enum() { #[test] fn test_module_template_enum() { + /// Comment for Test struct Test { + /// Comment for inner inner: T, } + /// Comment for Internal #[derive(Tsify)] #[tsify(namespace)] enum Internal { + /// Comment for Newtype Newtype(Test), + /// Comment for NewtypeF NewtypeF(Test), + /// Comment for NewtypeL NewtypeL(Test), + /// Comment for Unit Unit, } let expected = indoc! {r#" type __InternalFoo = Foo; type __InternalTest = Test; + /** + * Comment for Internal + */ declare namespace Internal { + /** + * Comment for Newtype + */ export type Newtype = { Newtype: __InternalTest }; + /** + * Comment for NewtypeF + */ export type NewtypeF = { NewtypeF: __InternalTest<__InternalFoo> }; + /** + * Comment for NewtypeL + */ export type NewtypeL = { NewtypeL: __InternalTest<__InternalFoo> }; + /** + * Comment for Unit + */ export type Unit = "Unit"; } + /** + * Comment for Internal + */ export type Internal = { Newtype: Test } | { NewtypeF: Test } | { NewtypeL: Test } | "Unit";"# }; @@ -295,25 +536,42 @@ struct Test { #[test] fn test_module_template_enum_inner() { + /// Comment for Test struct Test { + /// Comment for inner inner: T, } + /// Comment for Internal #[derive(Tsify)] #[tsify(namespace)] enum Internal { + /// Comment for Newtype Newtype(Test), + /// Comment for Unit Unit, } let expected = indoc! {r#" type __InternalFoo = Foo; type __InternalTest = Test; + /** + * Comment for Internal + */ declare namespace Internal { + /** + * Comment for Newtype + */ export type Newtype = { Newtype: __InternalTest<__InternalFoo> }; + /** + * Comment for Unit + */ export type Unit = "Unit"; } - + + /** + * Comment for Internal + */ export type Internal = { Newtype: Test } | "Unit";"# }; diff --git a/tests/flatten.rs b/tests/flatten.rs index 38cede3..86300d3 100644 --- a/tests/flatten.rs +++ b/tests/flatten.rs @@ -6,23 +6,35 @@ use tsify::Tsify; #[test] fn test_flatten() { + /// Comment for A #[derive(Tsify)] struct A { + /// Comment for a a: i32, + /// Comment for b b: String, } + /// Comment for B #[derive(Tsify)] struct B { + /// Comment for extra #[serde(flatten)] extra: A, + /// Comment for c c: i32, } assert_eq!( B::DECL, indoc! {" + /** + * Comment for B + */ export interface B extends A { + /** + * Comment for c + */ c: number; }" } @@ -31,22 +43,31 @@ fn test_flatten() { #[test] fn test_flatten_option() { + /// Comment for A #[derive(Tsify)] struct A { + /// Comment for a a: i32, + /// Comment for b b: String, } + /// Comment for B #[derive(Tsify)] struct B { + /// Comment for extra #[serde(flatten)] extra: Option, + /// Comment for c c: i32, } assert_eq!( B::DECL, indoc! {" + /** + * Comment for B + */ export type B = { c: number } & (A | {});" } ); diff --git a/tests/generics.rs b/tests/generics.rs index 9a0213a..bbb1414 100644 --- a/tests/generics.rs +++ b/tests/generics.rs @@ -6,45 +6,75 @@ use tsify::Tsify; #[test] fn test_generic_struct() { + /// Comment for GenericStruct #[derive(Tsify)] pub struct GenericStruct<'a, A, B, C, D> { + /// Comment for a a: A, + /// Comment for b b: B, + /// Comment for c #[serde(skip)] c: &'a C, + /// Comment for d d: D, } assert_eq!( GenericStruct::<(), (), (), ()>::DECL, indoc! {" + /** + * Comment for GenericStruct + */ export interface GenericStruct { + /** + * Comment for a + */ a: A; + /** + * Comment for b + */ b: B; + /** + * Comment for d + */ d: D; }" } ); + /// Comment for GenericNewtype #[derive(Tsify)] pub struct GenericNewtype(T); assert_eq!( GenericNewtype::<()>::DECL, - "export type GenericNewtype = T;" + indoc! {" + /** + * Comment for GenericNewtype + */ + export type GenericNewtype = T;" + }, ); + /// Comment for GenericTuple #[derive(Tsify)] pub struct GenericTuple<'a, A, B, C, D>(A, #[serde(skip)] &'a B, C, D); assert_eq!( GenericTuple::<(), (), (), ()>::DECL, - "export type GenericTuple = [A, C, D];" + indoc! {" + /** + * Comment for GenericTuple + */ + export type GenericTuple = [A, C, D];" + ,} ); } #[test] fn test_generic_enum() { + /// Comment for GenericEnum #[derive(Tsify)] pub enum GenericEnum { Unit, @@ -54,6 +84,9 @@ fn test_generic_enum() { } let expected = indoc! {r#" + /** + * Comment for GenericEnum + */ export type GenericEnum = "Unit" | { NewType: T } | { Seq: [T, U] } | { Map: { x: T; y: U } };"# }; @@ -62,23 +95,46 @@ fn test_generic_enum() { #[test] fn test_generic_enum_with_namespace() { + /// Comment for GenericEnum #[derive(Tsify)] #[tsify(namespace)] pub enum GenericEnum { + /// Comment for Unit Unit, + /// Comment for NewType NewType(T), + /// Comment for Seq Seq(T, U), + /// Comment for Map Map { x: T, y: U }, } let expected = indoc! {r#" + /** + * Comment for GenericEnum + */ declare namespace GenericEnum { + /** + * Comment for Unit + */ export type Unit = "Unit"; + /** + * Comment for NewType + */ export type NewType = { NewType: T }; + /** + * Comment for Seq + */ export type Seq = { Seq: [T, U] }; + /** + * Comment for Map + */ export type Map = { Map: { x: T; y: U } }; } - + + /** + * Comment for GenericEnum + */ export type GenericEnum = "Unit" | { NewType: T } | { Seq: [T, U] } | { Map: { x: T; y: U } };"# }; diff --git a/tests/optional.rs b/tests/optional.rs index 8ea3182..2d91f69 100644 --- a/tests/optional.rs +++ b/tests/optional.rs @@ -6,23 +6,32 @@ use tsify::Tsify; #[test] fn test_optional() { + /// Comment for Optional #[derive(Tsify)] struct Optional { + /// Comment for a #[tsify(optional)] a: Option, + /// Comment for b #[serde(skip_serializing_if = "Option::is_none")] b: Option, + /// Comment for c #[serde(default)] c: i32, + /// Comment for d #[serde(default)] d: Option, } + /// Comment for OptionalAll #[derive(Tsify)] #[serde(default)] struct OptionalAll { + /// Comment for a a: i32, + /// Comment for b b: i32, + /// Comment for c c: Option, } @@ -30,10 +39,25 @@ fn test_optional() { assert_eq!( Optional::DECL, indoc! {" + /** + * Comment for Optional + */ export interface Optional { + /** + * Comment for a + */ a?: number; + /** + * Comment for b + */ b?: string; + /** + * Comment for c + */ c?: number; + /** + * Comment for d + */ d?: string | undefined; }" } @@ -41,9 +65,21 @@ fn test_optional() { assert_eq!( OptionalAll::DECL, indoc! {" + /** + * Comment for OptionalAll + */ export interface OptionalAll { + /** + * Comment for a + */ a?: number; + /** + * Comment for b + */ b?: number; + /** + * Comment for c + */ c?: number | undefined; }" } @@ -52,10 +88,25 @@ fn test_optional() { assert_eq!( Optional::DECL, indoc! {" + /** + * Comment for Optional + */ export interface Optional { + /** + * Comment for a + */ a?: number; + /** + * Comment for b + */ b?: string; + /** + * Comment for c + */ c?: number; + /** + * Comment for d + */ d?: string | null; }" } @@ -63,9 +114,21 @@ fn test_optional() { assert_eq!( OptionalAll::DECL, indoc! {" + /** + * Comment for OptionalAll + */ export interface OptionalAll { + /** + * Comment for a + */ a?: number; + /** + * Comment for b + */ b?: number; + /** + * Comment for c + */ c?: number | null; }" } diff --git a/tests/rename.rs b/tests/rename.rs index cb55db1..2626963 100644 --- a/tests/rename.rs +++ b/tests/rename.rs @@ -6,10 +6,13 @@ use tsify::Tsify; #[test] fn test_rename() { + /// Comment for RenamedStruct #[derive(Tsify)] struct RenamedStruct { + /// Comment for X #[serde(rename = "X")] x: i32, + /// Comment for Y #[serde(rename = "Y")] y: i32, } @@ -17,26 +20,43 @@ fn test_rename() { assert_eq!( RenamedStruct::DECL, indoc! {" + /** + * Comment for RenamedStruct + */ export interface RenamedStruct { + /** + * Comment for X + */ X: number; + /** + * Comment for Y + */ Y: number; }" } ); + /// Comment for RenamedEnum #[derive(Tsify)] enum RenamedEnum { + /// Comment for X #[serde(rename = "X")] A(bool), + /// Comment for Y #[serde(rename = "Y")] B(i64), + /// Comment for Z #[serde(rename = "Z")] C(String), + /// Comment for D #[serde(skip)] D(i32), } let expected = indoc! {r#" + /** + * Comment for RenamedEnum + */ export type RenamedEnum = { X: boolean } | { Y: number } | { Z: string };"# }; @@ -46,25 +66,30 @@ fn test_rename() { #[test] fn test_rename_all() { + /// Comment for Enum #[allow(clippy::enum_variant_names)] #[derive(Tsify)] #[serde(rename_all = "snake_case")] #[tsify(namespace)] enum Enum { + /// Comment for snake_case SnakeCase { foo: bool, foo_bar: bool, }, + /// Comment for camel_case #[serde(rename_all = "camelCase")] CamelCase { foo: bool, foo_bar: bool, }, + /// Comment for kebab_case #[serde(rename_all = "kebab-case")] KebabCase { foo: bool, foo_bar: bool, }, + /// Comment for screaming_snake_case #[serde(rename_all = "SCREAMING_SNAKE_CASE")] ScreamingSnakeCase { foo: bool, @@ -72,28 +97,52 @@ fn test_rename_all() { }, } + /// Comment for PascalCase #[derive(Tsify)] #[serde(rename_all = "PascalCase")] struct PascalCase { + /// Comment for Foo foo: bool, + /// Comment for FooBar foo_bar: bool, } + /// Comment for ScreamingKebab #[derive(Tsify)] #[serde(rename_all = "SCREAMING-KEBAB-CASE")] struct ScreamingKebab { + /// Comment for FOO foo: bool, + /// Comment for FOO-BAR foo_bar: bool, } let expected = indoc! {r#" + /** + * Comment for Enum + */ declare namespace Enum { + /** + * Comment for snake_case + */ export type snake_case = { snake_case: { foo: boolean; foo_bar: boolean } }; + /** + * Comment for camel_case + */ export type camel_case = { camel_case: { foo: boolean; fooBar: boolean } }; + /** + * Comment for kebab_case + */ export type kebab_case = { kebab_case: { foo: boolean; "foo-bar": boolean } }; + /** + * Comment for screaming_snake_case + */ export type screaming_snake_case = { screaming_snake_case: { FOO: boolean; FOO_BAR: boolean } }; } - + + /** + * Comment for Enum + */ export type Enum = { snake_case: { foo: boolean; foo_bar: boolean } } | { camel_case: { foo: boolean; fooBar: boolean } } | { kebab_case: { foo: boolean; "foo-bar": boolean } } | { screaming_snake_case: { FOO: boolean; FOO_BAR: boolean } };"# }; @@ -102,8 +151,17 @@ fn test_rename_all() { assert_eq!( PascalCase::DECL, indoc! {" + /** + * Comment for PascalCase + */ export interface PascalCase { + /** + * Comment for Foo + */ Foo: boolean; + /** + * Comment for FooBar + */ FooBar: boolean; }" } @@ -112,8 +170,17 @@ fn test_rename_all() { assert_eq!( ScreamingKebab::DECL, indoc! {r#" + /** + * Comment for ScreamingKebab + */ export interface ScreamingKebab { + /** + * Comment for FOO + */ FOO: boolean; + /** + * Comment for FOO-BAR + */ "FOO-BAR": boolean; }"# } diff --git a/tests/skip.rs b/tests/skip.rs index 5f02a74..2d65f50 100644 --- a/tests/skip.rs +++ b/tests/skip.rs @@ -6,13 +6,18 @@ use tsify::Tsify; #[test] fn test_skip() { + /// Comment for Struct #[derive(Tsify)] struct Struct { + /// Comment for a a: i32, + /// Comment for b #[serde(skip)] b: i32, + /// Comment for c #[serde(skip_serializing)] c: i32, + /// Comment for d #[serde(skip_deserializing)] d: i32, } @@ -20,34 +25,63 @@ fn test_skip() { assert_eq!( Struct::DECL, indoc! {" + /** + * Comment for Struct + */ export interface Struct { + /** + * Comment for a + */ a: number; }" } ); + /// Comment for Tuple #[derive(Tsify)] struct Tuple(#[serde(skip)] String, i32); - assert_eq!(Tuple::DECL, "export type Tuple = [number];"); + assert_eq!( + Tuple::DECL, + indoc! {" + /** + * Comment for Tuple + */ + export type Tuple = [number];" + } + ); + /// Comment for Enum #[derive(Tsify)] #[tsify(namespace)] enum Enum { + /// Comment for A #[serde(skip)] A, + /// Comment for B #[serde(skip_serializing)] B, + /// Comment for C #[serde(skip_deserializing)] C, + /// Comment for D D, } let expected = indoc! {r#" + /** + * Comment for Enum + */ declare namespace Enum { + /** + * Comment for D + */ export type D = "D"; } - + + /** + * Comment for Enum + */ export type Enum = "D";"# }; diff --git a/tests/struct.rs b/tests/struct.rs index a4479b4..abf7a49 100644 --- a/tests/struct.rs +++ b/tests/struct.rs @@ -8,35 +8,73 @@ use tsify::Tsify; #[test] fn test_unit() { + /// Comment for Unit #[derive(Tsify)] struct Unit; if cfg!(feature = "js") { - assert_eq!(Unit::DECL, "export type Unit = undefined;"); + assert_eq!( + Unit::DECL, + indoc! {" + /** + * Comment for Unit + */ + export type Unit = undefined;" + } + ); } else { - assert_eq!(Unit::DECL, "export type Unit = null;"); + assert_eq!( + Unit::DECL, + indoc! {" + /** + * Comment for Unit + */ + export type Unit = null;" + } + ); }; } #[test] fn test_named_fields() { + /// Comment for Struct #[derive(Tsify)] struct A { + /// Comment for a a: (usize, u64), + /// Comment for b b: HashMap, } let expected = if cfg!(feature = "js") { indoc! {" + /** + * Comment for Struct + */ export interface A { + /** + * Comment for a + */ a: [number, number]; + /** + * Comment for b + */ b: Map; }" } } else { indoc! {" + /** + * Comment for Struct + */ export interface A { + /** + * Comment for a + */ a: [number, number]; + /** + * Comment for b + */ b: Record; }" } @@ -47,39 +85,76 @@ fn test_named_fields() { #[test] fn test_newtype_struct() { + /// Comment for Newtype #[derive(Tsify)] struct Newtype(i32); - assert_eq!(Newtype::DECL, "export type Newtype = number;"); + assert_eq!( + Newtype::DECL, + indoc! {" + /** + * Comment for Newtype + */ + export type Newtype = number;" + } + ); } #[test] fn test_tuple_struct() { + /// Comment for Tuple #[derive(Tsify)] struct Tuple(i32, String); + /// Comment for EmptyTuple #[derive(Tsify)] struct EmptyTuple(); - assert_eq!(Tuple::DECL, "export type Tuple = [number, string];"); - assert_eq!(EmptyTuple::DECL, "export type EmptyTuple = [];"); + assert_eq!( + Tuple::DECL, + indoc! {" + /** + * Comment for Tuple + */ + export type Tuple = [number, string];" + } + ); + assert_eq!( + EmptyTuple::DECL, + indoc! {" + /** + * Comment for EmptyTuple + */ + export type EmptyTuple = [];" + } + ); } #[test] fn test_nested_struct() { + /// Comment for A #[derive(Tsify)] struct A { + /// Comment for x x: f64, } + /// Comment for B #[derive(Tsify)] struct B { + /// Comment for a a: A, } assert_eq!( B::DECL, indoc! {" + /** + * Comment for B + */ export interface B { + /** + * Comment for a + */ a: A; }" } @@ -90,17 +165,29 @@ fn test_nested_struct() { fn test_struct_with_borrowed_fields() { use std::borrow::Cow; + /// Comment for Borrow #[derive(Tsify)] struct Borrow<'a> { + /// Comment for raw raw: &'a str, + /// Comment for cow cow: Cow<'a, str>, } assert_eq!( Borrow::DECL, indoc! {" + /** + * Comment for Borrow + */ export interface Borrow { + /** + * Comment for raw + */ raw: string; + /** + * Comment for cow + */ cow: string; }" } @@ -109,19 +196,31 @@ fn test_struct_with_borrowed_fields() { #[test] fn test_tagged_struct() { + /// Comment for TaggedStruct #[derive(Tsify)] #[serde(tag = "type")] struct TaggedStruct { + /// Comment for x x: i32, + /// Comment for y y: i32, } assert_eq!( TaggedStruct::DECL, indoc! {r#" + /** + * Comment for TaggedStruct + */ export interface TaggedStruct { type: "TaggedStruct"; + /** + * Comment for x + */ x: number; + /** + * Comment for y + */ y: number; }"# } diff --git a/tests/transparent.rs b/tests/transparent.rs index 01f5915..4667a43 100644 --- a/tests/transparent.rs +++ b/tests/transparent.rs @@ -1,22 +1,43 @@ #![allow(dead_code)] +use indoc::indoc; use pretty_assertions::assert_eq; use tsify::Tsify; #[test] fn test_transparent() { + /// Comment for A #[derive(Tsify)] #[serde(transparent)] struct A(String, #[serde(skip)] f64); + /// Comment for B #[derive(Tsify)] #[serde(transparent)] struct B { + /// Comment for x #[serde(skip)] x: String, + /// Comment for y y: f64, } - assert_eq!("export type A = string;", A::DECL); - assert_eq!("export type B = number;", B::DECL); + assert_eq!( + A::DECL, + indoc! {" + /** + * Comment for A + */ + export type A = string;" + } + ); + assert_eq!( + B::DECL, + indoc! {" + /** + * Comment for B + */ + export type B = number;" + } + ); } diff --git a/tests/type_override.rs b/tests/type_override.rs index 6058899..4cbee82 100644 --- a/tests/type_override.rs +++ b/tests/type_override.rs @@ -8,50 +8,84 @@ struct Unsupported; #[test] fn test_struct_with_type_override() { + /// Comment for Struct #[derive(Tsify)] struct Struct { + /// Comment for a a: i32, + /// Comment for b #[tsify(type = "0 | 1 | 2")] b: i32, + /// Comment for c #[tsify(type = "string | null")] c: Unsupported, } + /// Comment for Newtype #[derive(Tsify)] struct Newtype(#[tsify(type = "string | null")] Unsupported); assert_eq!( Struct::DECL, indoc! {r#" + /** + * Comment for Struct + */ export interface Struct { + /** + * Comment for a + */ a: number; + /** + * Comment for b + */ b: 0 | 1 | 2; + /** + * Comment for c + */ c: string | null; }"# } ); - assert_eq!(Newtype::DECL, "export type Newtype = string | null;"); + assert_eq!( + Newtype::DECL, + indoc! {" + /** + * Comment for Newtype + */ + export type Newtype = string | null;" + } + ); } #[test] fn test_enum_with_type_override() { + /// Comment for Enum #[derive(Tsify)] enum Enum { + /// Comment for Struct Struct { + /// Comment for x #[tsify(type = "`tpl_lit_${string}`")] x: String, + /// Comment for y #[tsify(type = "0 | 1 | 2")] y: i32, }, + /// Comment for Tuple Tuple( #[tsify(type = "`tpl_lit_${string}`")] String, #[tsify(type = "0 | 1 | 2")] i32, ), + /// Comment for Newtype Newtype(#[tsify(type = "number")] Unsupported), } let expected = indoc! {r#" + /** + * Comment for Enum + */ export type Enum = { Struct: { x: `tpl_lit_${string}`; y: 0 | 1 | 2 } } | { Tuple: [`tpl_lit_${string}`, 0 | 1 | 2] } | { Newtype: number };"# }; @@ -60,14 +94,22 @@ fn test_enum_with_type_override() { #[test] fn test_generic_struct_with_type_override() { + /// Comment for Foo #[derive(Tsify)] pub struct Foo { + /// Comment for bar #[tsify(type = "[T, ...T[]]")] bar: Vec, } let expected = indoc! {r#" + /** + * Comment for Foo + */ export interface Foo { + /** + * Comment for bar + */ bar: [T, ...T[]]; }"# }; From e36e55bcf3c9ac7c1d8185e5ad994885f4a2eb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren?= Date: Mon, 14 Aug 2023 13:54:17 +0200 Subject: [PATCH 3/3] Clean up, reduce repetition when writing comments --- tsify-macros/src/comments.rs | 11 +++++++++-- tsify-macros/src/decl.rs | 17 ++++------------- tsify-macros/src/typescript.rs | 6 ++---- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/tsify-macros/src/comments.rs b/tsify-macros/src/comments.rs index e9aede3..1364bd6 100644 --- a/tsify-macros/src/comments.rs +++ b/tsify-macros/src/comments.rs @@ -48,14 +48,21 @@ pub fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec { }) } -pub fn format_doc_comments(comments: &Vec) -> String { +pub fn write_doc_comments( + f: &mut std::fmt::Formatter<'_>, + comments: &Vec, +) -> Result<(), std::fmt::Error> { + if comments.is_empty() { + return Ok(()); + } + let comment = comments .iter() .map(|line| format!(" *{}\n", line.trim_matches('"'))) .collect::>() .join(""); - format!("/**\n{} */\n", comment) + write!(f, "{}", format!("/**\n{} */\n", comment)) } pub fn clean_comments(typ: &mut TsType) -> () { diff --git a/tsify-macros/src/decl.rs b/tsify-macros/src/decl.rs index 5984585..6d1b990 100644 --- a/tsify-macros/src/decl.rs +++ b/tsify-macros/src/decl.rs @@ -3,7 +3,7 @@ use std::{fmt::Display, vec}; use crate::comments::clean_comments; use crate::{ - comments::format_doc_comments, + comments::write_doc_comments, typescript::{TsType, TsTypeElement, TsTypeLit}, }; @@ -36,9 +36,7 @@ impl Display for TsTypeAliasDecl { format!("{}<{}>", self.id, type_params) }; - if !self.comments.is_empty() { - write!(f, "{}", format_doc_comments(&self.comments))?; - } + write_doc_comments(f, &self.comments)?; if self.export { write!(f, "export ")?; @@ -57,9 +55,7 @@ pub struct TsInterfaceDecl { impl Display for TsInterfaceDecl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if !self.comments.is_empty() { - write!(f, "{}", format_doc_comments(&self.comments))?; - } + write_doc_comments(f, &self.comments)?; write!(f, "export interface {}", self.id)?; @@ -226,9 +222,7 @@ impl Display for TsEnumDecl { writeln!(f, "{}", type_ref)?; } - if !self.comments.is_empty() { - write!(f, "{}", format_doc_comments(&self.comments))?; - } + write_doc_comments(f, &self.comments)?; write!(f, "declare namespace {}", self.id)?; @@ -267,9 +261,6 @@ impl Display for TsEnumDecl { self.members .iter() .map(|member| { - // let mut type_refs = Vec::new(); - // TsEnumDecl::replace_type_params(member.type_ann.clone(), &mut type_refs) - let mut clone = member.type_ann.clone(); clean_comments(&mut clone); clone diff --git a/tsify-macros/src/typescript.rs b/tsify-macros/src/typescript.rs index 5518b0a..4b0a938 100644 --- a/tsify-macros/src/typescript.rs +++ b/tsify-macros/src/typescript.rs @@ -2,7 +2,7 @@ use std::{collections::HashSet, fmt::Display}; use serde_derive_internals::{ast::Style, attr::TagType}; -use crate::comments::format_doc_comments; +use crate::comments::write_doc_comments; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TsKeywordTypeKind { @@ -606,9 +606,7 @@ impl Display for TsTypeElement { let optional_ann = if self.optional { "?" } else { "" }; - if !self.comments.is_empty() { - write!(f, "{}", format_doc_comments(&self.comments))?; - } + write_doc_comments(f, &self.comments)?; if is_js_ident(key) { write!(f, "{key}{optional_ann}: {type_ann}")