1#![cfg_attr(not(debug_assertions),allow(non_snake_case,non_upper_case_globals,non_camel_case_types))]
2#![cfg_attr( debug_assertions ,allow(non_snake_case,non_upper_case_globals,non_camel_case_types,unused_imports,unused_mut,unused_variables,dead_code,unused_assignments,unused_macros))]
3#![doc= include_str!("../Readme.md")]
4use quote::{quote, ToTokens};
26use syn::{parse_macro_input, Attribute, ItemFn, ItemStruct, ItemEnum, Ident};
27use util::{
28 extract_documented_generics, extract_documented_parameters, extract_fn_doc_attrs, make_doc_block,
29 extract_struct_doc_attrs
30};
31use docpos_fn::docpos_fn;
32use docpos_struct::docpos_struct;
33use docpos_enum::docpos_enum;
34use helper::*;
35mod util;
36mod helper;
37mod util_struct;
38mod util_fn;
39mod util_enum;
40mod docpos_struct;
41mod docpos_enum;
42mod docpos_fn;
43
44use indoc::formatdoc;
45
46const PARAM_SECTION: &str = "parameters_section";
48const ROXYGEN_MACRO: &str = "roxygen";
50
51mod mhelp {
52 macro_rules! try2 {
54 ($ex:expr) => {
55 match $ex {
56 Ok(val) => val,
57 Err(err) => return err.into_compile_error().into(),
58 }
59 };
60 }
61 pub(crate) use try2;
62}
63use mhelp::try2 as try2;
64
65#[proc_macro_attribute]
66pub fn roxygen(
68 _attr: proc_macro::TokenStream,
69 item: proc_macro::TokenStream,
70) -> proc_macro::TokenStream {
71 let mut function: ItemFn = parse_macro_input!(item as ItemFn);
72
73 try2!(function.attrs.iter_mut().try_for_each(|attr| {
74 if is_roxygen_main(attr) {
75 Err(syn::Error::new_spanned(
76 attr,
77 "Duplicate attribute. This attribute must only appear once.",
78 ))
79 } else {
80 Ok(())
81 }
82 }));
83
84 let function_docs = try2!(extract_fn_doc_attrs(&mut function.attrs));
86
87 let documented_params = try2!(extract_documented_parameters(
88 function.sig.inputs.iter_mut()
89 ));
90
91 let documented_generics = try2!(extract_documented_generics(&mut function.sig.generics));
92
93 let has_documented_params = !documented_params.is_empty();
94 let has_documented_generics = !documented_generics.is_empty();
95
96 if !has_documented_params && !has_documented_generics {
97 return syn::Error::new_spanned(
98 function.sig.ident,
99 "Function has no documented parameters or generics.\nDocument at least one function parameter or generic.",
100 )
101 .into_compile_error()
102 .into();
103 }
104
105 let parameter_doc_block = make_doc_block("Parameters", documented_params);
106 let generics_doc_block = make_doc_block("Generics", documented_generics);
107
108 let docs_before = function_docs.before_args_section;
109 let docs_after = function_docs.after_args_section;
110 let maybe_empty_doc_line = if !docs_after.is_empty() {
111 Some(quote! {#[doc=""]})
112 } else {
113 None
114 };
115
116 quote! {
117 #(#docs_before)*
118 #parameter_doc_block
119 #generics_doc_block
120 #maybe_empty_doc_line
121 #(#docs_after)*
122 #function
123 }
124 .into()
125}
126
127use syn::Result;
128use syn::ext::IdentExt;
129use syn::parse::{Parse,ParseStream};
130use core::fmt;
131#[derive(Debug)]
132struct IdentAny {pub ident:String}
133impl fmt::Display for IdentAny {fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "{}", self.ident)}}
134impl AsRef<str> for IdentAny {fn as_ref(&self ) -> &str { &self.ident}}
135impl Parse for IdentAny {fn parse(input:ParseStream) -> Result<IdentAny> {
136 let lookahead = input.lookahead1();
137 if lookahead.peek(Ident::peek_any) {
138 let name = input.call(Ident::parse_any)?;
139 Ok(Self {ident:name.to_string()})
140 } else {Err(lookahead.error())} }
141}
142
143#[proc_macro_attribute]
144pub fn docpos(attr: proc_macro::TokenStream , item: proc_macro::TokenStream,
148 ) -> proc_macro::TokenStream {
149 match syn::parse::<IdentAny>(attr) {
150 Ok (id) => {match id.to_string().as_ref() {"struct" => {return docpos_struct(parse_macro_input!(item as ItemStruct),false)},
152 "struct_sect"=> {return docpos_struct(parse_macro_input!(item as ItemStruct),true )},
153 "enum" => {return docpos_enum (parse_macro_input!(item as ItemEnum ),false)},
154 "enum_sect" => {return docpos_enum (parse_macro_input!(item as ItemEnum ),true )},
155 "fn" => {return docpos_fn (parse_macro_input!(item as ItemFn ))},
156 _ => {let errmsg=format!("Expected either 'struct','fn','enum', got '{}'\n(or use '#[docpos]' without an argument for auto-detection)",id);
157 return quote! {compile_error!(#errmsg)}.into();}
158 }},
159 Err(_err ) => {let (e_struct, e_enum, e_fn); match syn::parse::<ItemStruct>(item.clone()) {Ok(item)=>{return docpos_struct(item,false)}, Err(err)=>{e_struct=err},};
161 match syn::parse::<ItemEnum >(item.clone()) {Ok(item)=>{return docpos_enum (item,false)}, Err(err)=>{e_enum =err},};
162 match syn::parse::<ItemFn >(item ) {Ok(item)=>{return docpos_fn (item )}, Err(err)=>{e_fn =err},};
163 let errmsg = formatdoc!(r#"Parsing ℯ as
164 Struct: {e_struct},
165 Enum : {e_enum},
166 Fn : {e_fn}"#); return quote!{compile_error!(#errmsg)}.into();
167 }
168 }
169}
170
171
172#[proc_macro_attribute]
179pub fn parameters_section(
180 _attr: proc_macro::TokenStream,
181 item: proc_macro::TokenStream,
182) -> proc_macro::TokenStream {
183 let function: ItemFn = parse_macro_input!(item as ItemFn);
184
185 let maybe_roxygen = function.attrs.iter().find(|attr| is_roxygen_main(attr));
188 if let Some(attr) = maybe_roxygen {
189 syn::Error::new_spanned(attr,"The #[roxygen] attribute must come before the parameters_section attribute.\nPlace it before any of the doc comments for the function.").into_compile_error().into()
190 } else {
191 function.to_token_stream().into()
192 }
193}
194
195#[inline(always)]
199fn is_parameters_section(attr: &Attribute) -> bool {
200 let path = attr.path();
201
202 if path.is_ident(PARAM_SECTION) {
203 true
204 } else {
205 path.segments.len() == 2
207 && path.segments[0].ident == DOCPOS_CRATE
208 && path.segments[1].ident == PARAM_SECTION
209 }
210}
211
212#[inline(always)]
216fn is_roxygen_main(attr: &Attribute) -> bool {
217 let path = attr.path();
218
219 if path.is_ident(ROXYGEN_MACRO) {
220 true
221 } else {
222 path.segments.len() == 2
224 && path.segments[0].ident == DOCPOS_CRATE
225 && path.segments[1].ident == ROXYGEN_MACRO
226 }
227}