Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add functionality to export doc strings on types #187

Merged
merged 14 commits into from
Feb 9, 2024
Merged
9 changes: 8 additions & 1 deletion macros/src/attr/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use syn::{Attribute, Ident, Result};

use crate::{
attr::{parse_assign_inflection, parse_assign_str, Inflection},
utils::parse_attrs,
utils::{parse_attrs, parse_docs},
};

#[derive(Default)]
Expand All @@ -12,6 +12,7 @@ pub struct EnumAttr {
pub rename: Option<String>,
pub export_to: Option<String>,
pub export: bool,
pub docs: Vec<String>,
tag: Option<String>,
untagged: bool,
content: Option<String>,
Expand Down Expand Up @@ -45,6 +46,10 @@ impl EnumAttr {
pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut result = Self::default();
parse_attrs(attrs)?.for_each(|a| result.merge(a));

let docs = parse_docs(attrs)?;
result.docs = docs;

#[cfg(feature = "serde-compat")]
crate::utils::parse_serde_attrs::<SerdeEnumAttr>(attrs).for_each(|a| result.merge(a.0));
Ok(result)
Expand All @@ -61,6 +66,7 @@ impl EnumAttr {
untagged,
export_to,
export,
docs,
}: EnumAttr,
) {
self.rename = self.rename.take().or(rename);
Expand All @@ -71,6 +77,7 @@ impl EnumAttr {
self.content = self.content.take().or(content);
self.export = self.export || export;
self.export_to = self.export_to.take().or(export_to);
self.docs = docs;
}
}

Expand Down
9 changes: 8 additions & 1 deletion macros/src/attr/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use syn::{Attribute, Ident, Result};

use crate::{
attr::{parse_assign_str, Inflection, VariantAttr},
utils::parse_attrs,
utils::{parse_attrs, parse_docs},
};

#[derive(Default, Clone)]
Expand All @@ -14,6 +14,7 @@ pub struct StructAttr {
pub export_to: Option<String>,
pub export: bool,
pub tag: Option<String>,
pub docs: Vec<String>,
}

#[cfg(feature = "serde-compat")]
Expand All @@ -24,6 +25,10 @@ impl StructAttr {
pub fn from_attrs(attrs: &[Attribute]) -> Result<Self> {
let mut result = Self::default();
parse_attrs(attrs)?.for_each(|a| result.merge(a));

let docs = parse_docs(attrs)?;
result.docs = docs;

#[cfg(feature = "serde-compat")]
crate::utils::parse_serde_attrs::<SerdeStructAttr>(attrs).for_each(|a| result.merge(a.0));
Ok(result)
Expand All @@ -37,13 +42,15 @@ impl StructAttr {
export,
export_to,
tag,
docs,
}: StructAttr,
) {
self.rename = self.rename.take().or(rename);
self.rename_all = self.rename_all.take().or(rename_all);
self.export_to = self.export_to.take().or(export_to);
self.export = self.export || export;
self.tag = self.tag.take().or(tag);
self.docs = docs;
}
}

Expand Down
12 changes: 12 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod types;

struct DerivedTS {
name: String,
docs: Vec<String>,
inline: TokenStream,
decl: TokenStream,
inline_flattened: Option<TokenStream>,
Expand Down Expand Up @@ -64,12 +65,22 @@ impl DerivedTS {

let DerivedTS {
name,
docs,
inline,
decl,
inline_flattened,
dependencies,
..
} = self;

let docs = match docs.is_empty() {
true => None,
false => {
let docs_str = docs.join("\n");
Some(quote!(const DOCS: Option<&'static str> = Some(#docs_str);))
}
};

let inline_flattened = inline_flattened
.map(|t| {
quote! {
Expand All @@ -84,6 +95,7 @@ impl DerivedTS {
quote! {
#impl_start {
const EXPORT_TO: Option<&'static str> = Some(#export_to);
#docs

fn decl() -> String {
#decl
Expand Down
3 changes: 3 additions & 0 deletions macros/src/types/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub(crate) fn r#enum_def(s: &ItemEnum) -> syn::Result<DerivedTS> {
if s.variants.is_empty() {
return Ok(DerivedTS {
name,
docs: enum_attr.docs,
inline: quote!("never".to_owned()),
decl: quote!("type {} = never;"),
inline_flattened: None,
Expand Down Expand Up @@ -55,6 +56,7 @@ pub(crate) fn r#enum_def(s: &ItemEnum) -> syn::Result<DerivedTS> {
)),
dependencies,
name,
docs: enum_attr.docs,
export: enum_attr.export,
export_to: enum_attr.export_to,
})
Expand Down Expand Up @@ -194,6 +196,7 @@ fn empty_enum(name: impl Into<String>, enum_attr: EnumAttr) -> DerivedTS {
inline: quote!("never".to_owned()),
decl: quote!(format!("type {} = never;", #name)),
name,
docs: enum_attr.docs,
inline_flattened: None,
dependencies: Dependencies::default(),
export: enum_attr.export,
Expand Down
1 change: 1 addition & 0 deletions macros/src/types/named.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub(crate) fn named(
decl: quote!(format!("type {}{} = {}", #name, #generic_args, Self::inline())),
inline_flattened: Some(quote!(format!("{{ {} }}", #fields))),
name: name.to_owned(),
docs: attr.docs.clone(),
dependencies,
export: attr.export,
export_to: attr.export_to.clone(),
Expand Down
1 change: 1 addition & 0 deletions macros/src/types/newtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ pub(crate) fn newtype(
inline: inline_def,
inline_flattened: None,
name: name.to_owned(),
docs: attr.docs.clone(),
dependencies,
export: attr.export,
export_to: attr.export_to.clone(),
Expand Down
1 change: 1 addition & 0 deletions macros/src/types/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub(crate) fn tuple(
},
inline_flattened: None,
name: name.to_owned(),
docs: attr.docs.clone(),
dependencies,
export: attr.export,
export_to: attr.export_to.clone(),
Expand Down
3 changes: 3 additions & 0 deletions macros/src/types/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub(crate) fn empty_object(attr: &StructAttr, name: &str) -> Result<DerivedTS> {
decl: quote!(format!("type {} = Record<string, never>;", #name)),
inline_flattened: None,
name: name.to_owned(),
docs: attr.docs.clone(),
dependencies: Dependencies::default(),
export: attr.export,
export_to: attr.export_to.clone(),
Expand All @@ -25,6 +26,7 @@ pub(crate) fn empty_array(attr: &StructAttr, name: &str) -> Result<DerivedTS> {
decl: quote!(format!("type {} = never[];", #name)),
inline_flattened: None,
name: name.to_owned(),
docs: attr.docs.clone(),
dependencies: Dependencies::default(),
export: attr.export,
export_to: attr.export_to.clone(),
Expand All @@ -39,6 +41,7 @@ pub(crate) fn null(attr: &StructAttr, name: &str) -> Result<DerivedTS> {
decl: quote!(format!("type {} = null;", #name)),
inline_flattened: None,
name: name.to_owned(),
docs: attr.docs.clone(),
dependencies: Dependencies::default(),
export: attr.export,
export_to: attr.export_to.clone(),
Expand Down
20 changes: 19 additions & 1 deletion macros/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::convert::TryFrom;

use proc_macro2::Ident;
use syn::{Attribute, Error, Result};
use syn::{spanned::Spanned, Attribute, Error, Expr, ExprLit, Lit, Meta, Result};

macro_rules! syn_err {
($l:literal $(, $a:expr)*) => {
Expand Down Expand Up @@ -116,6 +116,24 @@ pub fn parse_serde_attrs<'a, A: TryFrom<&'a Attribute, Error = Error>>(
.into_iter()
}

/// Return a vector of all lines of doc comments in the given vector of attributes.
pub fn parse_docs(attrs: &[Attribute]) -> Result<Vec<String>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always prefer &[T] over &Vec<T>

attrs
.into_iter()
.filter_map(|a| match a.meta {
Meta::NameValue(ref x) if x.path.is_ident("doc") => Some(x),
_ => None,
})
.map(|attr| match attr.value {
Expr::Lit(ExprLit {
lit: Lit::Str(ref str),
..
}) => Ok(str.value()),
_ => syn_err!(attr.span(); "doc attribute with non literal expression found"),
})
.collect::<Result<Vec<_>>>()
Comment on lines +121 to +134
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Managed to get rid of those panic!s

}

#[cfg(feature = "serde-compat")]
mod warning {
use std::{fmt::Display, io::Write};
Expand Down
21 changes: 21 additions & 0 deletions ts-rs/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ fn output_path<T: TS + ?Sized>() -> Result<PathBuf, ExportError> {

/// Push the declaration of `T`
fn generate_decl<T: TS + ?Sized>(out: &mut String) {
// Type Docs
let docs = &T::DOCS;
if let Some(docs) = docs {
out.push_str(&format_docs(docs));
}

// Type Definition
out.push_str("export ");
out.push_str(&T::decl());
}
Expand Down Expand Up @@ -249,3 +256,17 @@ where
Some(comps.iter().map(|c| c.as_os_str()).collect())
}
}

/// Returns an unindented docstring that has a newline at the end if it has content.
fn format_docs(docs: &str) -> String {
match docs.is_empty() {
true => "".to_string(),
false => {
let lines = docs
.lines()
.map(|doc| format!(" *{doc}"))
.collect::<Vec<_>>();
format!("/**\n{}\n */\n", lines.join("\n"))
}
}
}
1 change: 1 addition & 0 deletions ts-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ pub mod typelist;
/// Skip this variant
pub trait TS {
const EXPORT_TO: Option<&'static str> = None;
const DOCS: Option<&'static str> = None;

/// Declaration of this type, e.g. `interface User { user_id: number, ... }`.
/// This function will panic if the type has no declaration.
Expand Down
Loading
Loading