Skip to content

Commit

Permalink
feat: Add functionality to export doc strings on types (#187)
Browse files Browse the repository at this point in the history
* Added implementation and tests for type docs
* Added `docs` to `EnumAttr` and `StrucAttr`
* Added `DOCS` to `TS`
  • Loading branch information
timephy authored Feb 9, 2024
1 parent 4eccada commit e7a462a
Show file tree
Hide file tree
Showing 12 changed files with 338 additions and 3 deletions.
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>> {
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<_>>>()
}

#[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

0 comments on commit e7a462a

Please sign in to comment.