Extract doc-comment on plugin functions.

This commit is contained in:
Stephen Chung
2021-12-21 16:14:07 +08:00
parent f74486f904
commit b85a9b3c1c
11 changed files with 275 additions and 21 deletions

View File

@@ -117,18 +117,60 @@ pub fn inner_item_attributes<T: ExportedParams>(
attrs: &mut Vec<syn::Attribute>,
attr_name: &str,
) -> syn::Result<T> {
// Find the #[rhai_fn] attribute which will turn be read for the function parameters.
if let Some(rhai_fn_idx) = attrs
// Find the #[rhai_fn] attribute which will turn be read for function parameters.
if let Some(index) = attrs
.iter()
.position(|a| a.path.get_ident().map(|i| *i == attr_name).unwrap_or(false))
{
let rhai_fn_attr = attrs.remove(rhai_fn_idx);
let rhai_fn_attr = attrs.remove(index);
// Cannot have more than one #[rhai_fn]
if let Some(duplicate) = attrs
.iter()
.find(|a| a.path.get_ident().map(|i| *i == attr_name).unwrap_or(false))
{
return Err(syn::Error::new(
duplicate.span(),
format!("duplicated attribute '{}'", attr_name),
));
}
rhai_fn_attr.parse_args_with(T::parse_stream)
} else {
Ok(T::no_attrs())
}
}
#[cfg(feature = "metadata")]
pub fn doc_attribute(attrs: &mut Vec<syn::Attribute>) -> syn::Result<String> {
// Find the #[doc] attribute which will turn be read for function documentation.
let mut comments = String::new();
while let Some(index) = attrs
.iter()
.position(|attr| attr.path.get_ident().map(|i| *i == "doc").unwrap_or(false))
{
let attr = attrs.remove(index);
let meta = attr.parse_meta()?;
match meta {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(s),
..
}) => {
if !comments.is_empty() {
comments += "\n";
}
comments += &s.value();
}
_ => continue,
}
}
Ok(comments)
}
pub fn collect_cfg_attr(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
attrs
.iter()

View File

@@ -282,6 +282,8 @@ pub struct ExportedFn {
mut_receiver: bool,
params: ExportedFnParams,
cfg_attrs: Vec<syn::Attribute>,
#[cfg(feature = "metadata")]
comment: String,
}
impl Parse for ExportedFn {
@@ -404,6 +406,8 @@ impl Parse for ExportedFn {
mut_receiver,
params: Default::default(),
cfg_attrs,
#[cfg(feature = "metadata")]
comment: Default::default(),
})
}
}
@@ -503,6 +507,16 @@ impl ExportedFn {
}
}
#[cfg(feature = "metadata")]
pub fn comment(&self) -> &str {
&self.comment
}
#[cfg(feature = "metadata")]
pub fn set_comment(&mut self, comment: String) {
self.comment = comment
}
pub fn set_cfg_attrs(&mut self, cfg_attrs: Vec<syn::Attribute>) {
self.cfg_attrs = cfg_attrs
}

View File

@@ -117,18 +117,22 @@ impl Parse for Module {
syn::Item::Fn(f) => Some(f),
_ => None,
})
.try_fold(Vec::new(), |mut vec, item_fn| {
.try_fold(Vec::new(), |mut vec, item_fn| -> syn::Result<_> {
let params =
crate::attrs::inner_item_attributes(&mut item_fn.attrs, "rhai_fn")?;
syn::parse2::<ExportedFn>(item_fn.to_token_stream())
.and_then(|mut f| {
let f =
syn::parse2(item_fn.to_token_stream()).and_then(|mut f: ExportedFn| {
f.set_params(params)?;
f.set_cfg_attrs(crate::attrs::collect_cfg_attr(&item_fn.attrs));
#[cfg(feature = "metadata")]
f.set_comment(crate::attrs::doc_attribute(&mut item_fn.attrs)?);
Ok(f)
})
.map(|f| vec.push(f))
.map(|_| vec)
})?;
vec.push(f);
Ok(vec)
})?;
// Gather and parse constants definitions.
for item in content.iter() {

View File

@@ -166,20 +166,30 @@ pub fn generate_body(
);
#[cfg(feature = "metadata")]
let param_names = quote! {
Some(#fn_token_name::PARAM_NAMES)
};
let (param_names, comment) = (
quote! { Some(#fn_token_name::PARAM_NAMES) },
function.comment(),
);
#[cfg(not(feature = "metadata"))]
let param_names = quote! { None };
let (param_names, comment) = (quote! { None }, "");
set_fn_statements.push(
set_fn_statements.push(if comment.is_empty() {
syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)*
m.set_fn(#fn_literal, FnNamespace::#ns_str, FnAccess::Public,
#param_names, &[#(#fn_input_types),*], #fn_token_name().into());
})
.unwrap(),
);
.unwrap()
} else {
let comment_literal = syn::LitStr::new(comment, Span::call_site());
syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)*
m.set_fn_with_comment(#fn_literal, FnNamespace::#ns_str, FnAccess::Public,
#param_names, &[#(#fn_input_types),*], #comment_literal, #fn_token_name().into());
})
.unwrap()
});
}
gen_fn_tokens.push(quote! {

View File

@@ -37,6 +37,36 @@ mod module_tests {
);
}
#[test]
fn one_factory_fn_with_comment_module() {
let input_tokens: TokenStream = quote! {
pub mod one_fn {
/// This is a doc-comment.
/// Another line.
/** block doc-comment */
// Regular comment
/// Final line.
/** doc-comment
in multiple lines
*/
pub fn get_mystic_number() -> INT {
42
}
}
};
let item_mod = syn::parse2::<Module>(input_tokens).unwrap();
assert!(item_mod.consts().is_empty());
assert_eq!(item_mod.fns().len(), 1);
assert_eq!(item_mod.fns()[0].name().to_string(), "get_mystic_number");
assert_eq!(item_mod.fns()[0].comment(), " This is a doc-comment.\n Another line.\n block doc-comment \n Final line.\n doc-comment\n in multiple lines\n ");
assert_eq!(item_mod.fns()[0].arg_count(), 0);
assert_eq!(
item_mod.fns()[0].return_type().unwrap(),
&syn::parse2::<syn::Type>(quote! { INT }).unwrap()
);
}
#[test]
fn one_single_arg_fn_module() {
let input_tokens: TokenStream = quote! {
@@ -323,6 +353,66 @@ mod generate_tests {
assert_streams_eq(item_mod.generate(), expected_tokens);
}
#[test]
fn one_factory_fn_with_comment_module() {
let input_tokens: TokenStream = quote! {
pub mod one_fn {
/// This is a doc-comment.
/// Another line.
/** block doc-comment */
// Regular comment
/// Final line.
/** doc-comment
in multiple lines
*/
pub fn get_mystic_number() -> INT {
42
}
}
};
let expected_tokens = quote! {
pub mod one_fn {
pub fn get_mystic_number() -> INT {
42
}
#[allow(unused_imports)]
use super::*;
pub fn rhai_module_generate() -> Module {
let mut m = Module::new();
rhai_generate_into_module(&mut m, false);
m.build_index();
m
}
#[allow(unused_mut)]
pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) {
m.set_fn_with_comment("get_mystic_number", FnNamespace::Internal, FnAccess::Public,
Some(get_mystic_number_token::PARAM_NAMES), &[], " This is a doc-comment.\n Another line.\n block doc-comment \n Final line.\n doc-comment\n in multiple lines\n ",
get_mystic_number_token().into());
if flatten {} else {}
}
#[allow(non_camel_case_types)]
pub struct get_mystic_number_token();
impl get_mystic_number_token {
pub const PARAM_NAMES: &'static [&'static str] = &["INT"];
#[inline(always)] pub fn param_types() -> [TypeId; 0usize] { [] }
}
impl PluginFunction for get_mystic_number_token {
#[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::from(get_mystic_number()))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
}
}
};
let item_mod = syn::parse2::<Module>(input_tokens).unwrap();
assert_streams_eq(item_mod.generate(), expected_tokens);
}
#[test]
fn one_single_arg_global_fn_module() {
let input_tokens: TokenStream = quote! {