doc_macro/
generated_doc.rs1use proc_macro::TokenStream;
2use quote::ToTokens;
3use syn::{
4 AngleBracketedGenericArguments, Attribute, Expr, GenericArgument, ItemFn, Path, PathArguments,
5 ReturnType, Type, TypePath,
6};
7
8pub fn generated_doc_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
9 let mut stream = TokenStream::new();
10 stream.extend(
11 format!("#[cfg_attr(all(doc, not(doctest)), generated_doc_inner({attr}))]")
12 .parse::<TokenStream>()
13 .unwrap(),
14 );
15 stream.extend(item);
16 stream
17}
18
19pub fn generated_doc_inner_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
20 let mut item = syn::parse_macro_input!(item as ItemFn);
21
22 let storage;
23 let (arg, generate_docs_for) = if attr.is_empty() {
24 process_return_type(&item)
25 } else {
26 storage = syn::parse_macro_input!(attr as Type);
27 (&storage, GenerateDocsFor::JsonAndTs)
28 };
29 let windows_safe_name = arg
30 .to_token_stream()
31 .to_string()
32 .replace('<', "(")
33 .replace('>', ")");
34 let expr: Expr = syn::parse_str(&windows_safe_name).unwrap_or_else(|err| {
35 panic!("Failed to parse windows safe name {windows_safe_name}: {err}")
36 });
37 let attr: Attribute = match generate_docs_for {
38 GenerateDocsFor::Ts => syn::parse_quote!(#[doc = generated_docs!(#expr, ts)]),
39 GenerateDocsFor::JsonAndTs => {
40 syn::parse_quote!(#[doc = generated_docs!(#expr)])
41 }
42 };
43
44 item.attrs.push(attr);
45
46 item.into_token_stream().into()
47}
48
49#[derive(Clone, Copy)]
50enum GenerateDocsFor {
51 Ts,
52 JsonAndTs,
53}
54
55fn process_return_type(item: &ItemFn) -> (&Type, GenerateDocsFor) {
56 if let ReturnType::Type(_, ty) = &item.sig.output {
58 if let Type::Path(TypePath {
59 path: Path { segments, .. },
60 ..
61 }) = ty.as_ref()
62 {
63 let segment = segments
65 .last()
66 .expect("return type path shouldn't be empty");
67 let mut inner = (ty.as_ref(), segments);
69
70 if segment.ident != "Json" {
72 if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
73 args, ..
74 }) = &segment.arguments
75 {
76 let arg = args
78 .first()
79 .expect("return type generic list shouldn't be empty");
80 if let GenericArgument::Type(
81 ty @ Type::Path(TypePath {
82 path: Path { segments, .. },
83 ..
84 }),
85 ) = arg
86 {
87 inner = (ty, segments);
88 }
89 }
90 };
91
92 if let Some(segments) = inner.1.last() {
94 if segments.ident == "Bytes" {
95 return (inner.0, GenerateDocsFor::Ts);
96 }
97 if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
99 args, ..
100 }) = &segments.arguments
101 {
102 if let Some(GenericArgument::Type(t)) = args.first() {
103 return (t, GenerateDocsFor::JsonAndTs);
104 }
105 }
106 }
107 }
108 }
109 panic!(
110 "return type was expected to be `Json<_>` or `SomeGenericType<Json<_>>`, but it was `{:#?}`",
111 item.sig.output
112 )
113}