From 35b16e545b4fce299edbc00c9a9754179fa51634 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Sat, 17 Dec 2022 16:24:29 +0100 Subject: Document parameters in comment --- macros/src/castable.rs | 2 +- macros/src/func.rs | 122 ++++++++++++++++++++++++++++++++++++++++--------- macros/src/lib.rs | 11 ++++- macros/src/node.rs | 47 +++++++++---------- 4 files changed, 132 insertions(+), 50 deletions(-) (limited to 'macros/src') diff --git a/macros/src/castable.rs b/macros/src/castable.rs index 48cdf9e1..6738ee1d 100644 --- a/macros/src/castable.rs +++ b/macros/src/castable.rs @@ -150,7 +150,7 @@ fn create_describe_func(castable: &Castable) -> TokenStream { let mut infos = vec![]; for cast in &castable.casts { - let docs = doc_comment(&cast.attrs); + let docs = documentation(&cast.attrs); infos.push(match &cast.pattern { Pattern::Str(lit) => { quote! { ::typst::model::CastInfo::Value(#lit.into(), #docs) } diff --git a/macros/src/func.rs b/macros/src/func.rs index 4523d48a..98fca6a4 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -1,36 +1,26 @@ +use proc_macro2::Span; +use unscanny::Scanner; + use super::*; /// Expand the `#[func]` macro. pub fn func(item: syn::Item) -> Result { - let doc_comment = match &item { - syn::Item::Struct(item) => doc_comment(&item.attrs), - syn::Item::Enum(item) => doc_comment(&item.attrs), - syn::Item::Fn(item) => doc_comment(&item.attrs), + let mut docs = match &item { + syn::Item::Struct(item) => documentation(&item.attrs), + syn::Item::Enum(item) => documentation(&item.attrs), + syn::Item::Fn(item) => documentation(&item.attrs), _ => String::new(), }; - let mut tags = vec![]; - let mut kept = vec![]; - for line in doc_comment.lines() { - let line = line.trim(); - if let Some(suffix) = line.trim_end_matches(".").strip_prefix("Tags: ") { - tags.extend(suffix.split(", ")); - } else { - kept.push(line); - } - } - - while kept.last().map_or(false, |line| line.is_empty()) { - kept.pop(); - } - - let docs = kept.join("\n"); + let tags = tags(&mut docs); + let params = params(&mut docs)?; + let docs = docs.trim(); let info = quote! { ::typst::model::FuncInfo { name, docs: #docs, tags: &[#(#tags),*], - params: ::std::vec![], + params: ::std::vec![#(#params),*], } }; @@ -83,3 +73,93 @@ pub fn func(item: syn::Item) -> Result { }) } } + +/// Extract a section. +pub fn section(docs: &mut String, title: &str) -> Option { + let needle = format!("# {title}\n"); + let start = docs.find(&needle)?; + let rest = &docs[start..]; + let len = rest[1..].find('#').map(|x| 1 + x).unwrap_or(rest.len()); + let end = start + len; + let section = docs[start + needle.len()..].to_owned(); + docs.replace_range(start..end, ""); + Some(section) +} + +/// Parse the tag section. +pub fn tags(docs: &mut String) -> Vec { + section(docs, "Tags") + .unwrap_or_default() + .lines() + .filter_map(|line| line.strip_prefix('-')) + .map(|s| s.trim().into()) + .collect() +} + +/// Parse the parameter section. +pub fn params(docs: &mut String) -> Result> { + let Some(section) = section(docs, "Parameters") else { return Ok(vec![]) }; + let mut s = Scanner::new(§ion); + let mut infos = vec![]; + + while s.eat_if('-') { + s.eat_whitespace(); + let name = s.eat_until(':'); + s.expect(": "); + let ty: syn::Type = syn::parse_str(s.eat_until(char::is_whitespace))?; + s.eat_whitespace(); + let mut named = false; + let mut positional = false; + let mut required = false; + let mut variadic = false; + let mut settable = false; + s.expect('('); + for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) { + match part { + "named" => named = true, + "positional" => positional = true, + "required" => required = true, + "variadic" => variadic = true, + "settable" => settable = true, + _ => { + return Err(syn::Error::new( + Span::call_site(), + format!("unknown parameter flag {:?}", part), + )) + } + } + } + + if (!named && !positional) + || (variadic && !positional) + || (named && variadic) + || (required && variadic) + { + return Err(syn::Error::new( + Span::call_site(), + "invalid combination of parameter flags", + )); + } + + s.expect(')'); + let docs = dedent(s.eat_until("\n-").trim()); + infos.push(quote! { + ::typst::model::ParamInfo { + name: #name, + docs: #docs, + cast: <#ty as ::typst::model::Cast< + ::typst::syntax::Spanned<::typst::model::Value> + >>::describe(), + named: #named, + positional: #positional, + required: #required, + variadic: #variadic, + settable: #settable, + } + }); + + s.eat_whitespace(); + } + + Ok(infos) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 15dc3ee7..a03dc30e 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -64,7 +64,7 @@ pub fn castable(stream: BoundaryStream) -> BoundaryStream { } /// Extract documentation comments from an attribute list. -fn doc_comment(attrs: &[syn::Attribute]) -> String { +fn documentation(attrs: &[syn::Attribute]) -> String { let mut doc = String::new(); // Parse doc comments. @@ -72,7 +72,9 @@ fn doc_comment(attrs: &[syn::Attribute]) -> String { if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() { if meta.path.is_ident("doc") { if let syn::Lit::Str(string) = &meta.lit { - doc.push_str(&string.value()); + let full = string.value(); + let line = full.strip_prefix(' ').unwrap_or(&full); + doc.push_str(line); doc.push('\n'); } } @@ -81,3 +83,8 @@ fn doc_comment(attrs: &[syn::Attribute]) -> String { doc.trim().into() } + +/// Dedent documentation text. +fn dedent(text: &str) -> String { + text.lines().map(str::trim).collect::>().join("\n") +} diff --git a/macros/src/node.rs b/macros/src/node.rs index ad079c0e..1e03c441 100644 --- a/macros/src/node.rs +++ b/macros/src/node.rs @@ -36,7 +36,6 @@ struct Property { shorthand: Option, resolve: bool, fold: bool, - reflect: bool, } /// The shorthand form of a style property. @@ -118,7 +117,6 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result { let mut referenced = false; let mut resolve = false; let mut fold = false; - let mut reflect = false; // Parse the `#[property(..)]` attribute. let mut stream = tokens.into_iter().peekable(); @@ -152,7 +150,6 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result { "referenced" => referenced = true, "resolve" => resolve = true, "fold" => fold = true, - "reflect" => reflect = true, _ => bail!(ident, "invalid attribute"), } } @@ -192,7 +189,6 @@ fn prepare_property(item: &syn::ImplItemConst) -> Result { referenced, resolve, fold, - reflect, }) } @@ -275,9 +271,9 @@ fn create_node_construct_func(node: &Node) -> syn::ImplItemMethod { parse_quote! { fn construct( _: &::typst::model::Vm, - _: &mut ::typst::model::Args, + args: &mut ::typst::model::Args, ) -> ::typst::diag::SourceResult<::typst::model::Content> { - unimplemented!() + ::typst::diag::bail!(args.span, "cannot be constructed manually"); } } }) @@ -335,27 +331,26 @@ fn create_node_set_func(node: &Node) -> syn::ImplItemMethod { /// Create the node's `properties` function. fn create_node_properties_func(node: &Node) -> syn::ImplItemMethod { - let infos = node - .properties - .iter() - .filter(|p| !p.skip || p.reflect) - .map(|property| { - let name = property.name.to_string().replace('_', "-").to_lowercase(); - let docs = doc_comment(&property.attrs); - let value_ty = &property.value_ty; - let shorthand = matches!(property.shorthand, Some(Shorthand::Positional)); - quote! { - ::typst::model::ParamInfo { - name: #name, - docs: #docs, - settable: true, - shorthand: #shorthand, - cast: <#value_ty as ::typst::model::Cast< - ::typst::syntax::Spanned<::typst::model::Value> - >>::describe(), - } + let infos = node.properties.iter().filter(|p| !p.skip).map(|property| { + let name = property.name.to_string().replace('_', "-").to_lowercase(); + let docs = documentation(&property.attrs); + let value_ty = &property.value_ty; + let shorthand = matches!(property.shorthand, Some(Shorthand::Positional)); + quote! { + ::typst::model::ParamInfo { + name: #name, + docs: #docs, + cast: <#value_ty as ::typst::model::Cast< + ::typst::syntax::Spanned<::typst::model::Value> + >>::describe(), + named: true, + positional: #shorthand, + required: false, + variadic: false, + settable: true, } - }); + } + }); parse_quote! { fn properties() -> ::std::vec::Vec<::typst::model::ParamInfo> -- cgit v1.2.3