From 904ffcd212e5e0b62f7f5d56fa39e5f4ea59a7e6 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Sat, 29 Apr 2023 10:20:42 +0200 Subject: [PATCH] Improve docs generation, add function types --- docs/typedefs/Net.luau | 2 +- packages/cli/src/gen/definitions/builder.rs | 33 ++++-- packages/cli/src/gen/definitions/item.rs | 53 ++++++++- packages/cli/src/gen/definitions/moonwave.rs | 34 +++--- packages/cli/src/gen/definitions/parser.rs | 4 +- .../cli/src/gen/definitions/type_info_ext.rs | 106 +++++++++++------- packages/cli/src/gen/gitbook_dir.rs | 37 +++++- packages/cli/src/gen/selene_defs.rs | 3 +- packages/lib/src/lua/task/ext/resume_ext.rs | 5 +- 9 files changed, 195 insertions(+), 82 deletions(-) diff --git a/docs/typedefs/Net.luau b/docs/typedefs/Net.luau index 6a99809..e6f6e7a 100644 --- a/docs/typedefs/Net.luau +++ b/docs/typedefs/Net.luau @@ -163,7 +163,7 @@ export type NetWebSocket = { local response = net.request({ url = "https://dummyjson.com/products/add", method = "POST", - headers = { ["Content-Type"] = "application/json" }, + headers = { ["Content-Type"] = "application/json" }, body = net.jsonEncode({ title = "Cool Pencil", }) diff --git a/packages/cli/src/gen/definitions/builder.rs b/packages/cli/src/gen/definitions/builder.rs index ac80313..7daac60 100644 --- a/packages/cli/src/gen/definitions/builder.rs +++ b/packages/cli/src/gen/definitions/builder.rs @@ -1,6 +1,9 @@ use anyhow::{bail, Result}; -use super::{item::DefinitionsItem, kind::DefinitionsItemKind}; +use super::{ + item::{DefinitionsItem, DefinitionsItemFunctionArg, DefinitionsItemFunctionRet}, + kind::DefinitionsItemKind, +}; #[derive(Debug, Default, Clone)] pub struct DefinitionsItemBuilder { @@ -10,7 +13,8 @@ pub struct DefinitionsItemBuilder { meta: Option, value: Option, children: Vec, - arg_types: Vec, + args: Vec, + rets: Vec, } #[allow(dead_code)] @@ -57,14 +61,26 @@ impl DefinitionsItemBuilder { self } - pub fn with_arg_type>(mut self, arg_type: S) -> Self { - self.arg_types.push(arg_type.as_ref().to_string()); + pub fn with_arg(mut self, arg: DefinitionsItemFunctionArg) -> Self { + self.args.push(arg); self } - pub fn with_arg_types>(mut self, arg_types: &[S]) -> Self { - for arg_type in arg_types { - self.arg_types.push(arg_type.as_ref().to_string()); + pub fn with_args(mut self, args: &[DefinitionsItemFunctionArg]) -> Self { + for arg in args { + self.args.push(arg.clone()); + } + self + } + + pub fn with_ret(mut self, ret: DefinitionsItemFunctionRet) -> Self { + self.rets.push(ret); + self + } + + pub fn with_rets(mut self, rets: &[DefinitionsItemFunctionRet]) -> Self { + for ret in rets { + self.rets.push(ret.clone()); } self } @@ -80,7 +96,8 @@ impl DefinitionsItemBuilder { meta: self.meta, value: self.value, children, - arg_types: self.arg_types, + args: self.args, + rets: self.rets, }) } else { bail!("Missing doc item kind") diff --git a/packages/cli/src/gen/definitions/item.rs b/packages/cli/src/gen/definitions/item.rs index 2ca5473..d611ff7 100644 --- a/packages/cli/src/gen/definitions/item.rs +++ b/packages/cli/src/gen/definitions/item.rs @@ -4,6 +4,47 @@ use serde::{Deserialize, Serialize}; use super::kind::DefinitionsItemKind; +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DefinitionsItemFunctionArg { + pub name: String, + pub typedef: String, + pub typedef_simple: String, +} + +impl DefinitionsItemFunctionArg { + pub fn new(name: N, typedef: T, typedef_simple: TS) -> Self + where + N: Into, + T: Into, + TS: Into, + { + Self { + name: name.into(), + typedef: typedef.into(), + typedef_simple: typedef_simple.into(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DefinitionsItemFunctionRet { + pub typedef: String, + pub typedef_simple: String, +} + +impl DefinitionsItemFunctionRet { + pub fn new(typedef: T, typedef_simple: TS) -> Self + where + T: Into, + TS: Into, + { + Self { + typedef: typedef.into(), + typedef_simple: typedef_simple.into(), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DefinitionsItem { @@ -19,7 +60,9 @@ pub struct DefinitionsItem { #[serde(skip_serializing_if = "Vec::is_empty")] pub(super) children: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] - pub(super) arg_types: Vec, + pub(super) args: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub(super) rets: Vec, } #[allow(clippy::trivially_copy_pass_by_ref)] @@ -108,7 +151,11 @@ impl DefinitionsItem { &self.children } - pub fn arg_types(&self) -> Vec<&str> { - self.arg_types.iter().map(String::as_str).collect() + pub fn args(&self) -> Vec<&DefinitionsItemFunctionArg> { + self.args.iter().collect() + } + + pub fn rets(&self) -> Vec<&DefinitionsItemFunctionRet> { + self.rets.iter().collect() } } diff --git a/packages/cli/src/gen/definitions/moonwave.rs b/packages/cli/src/gen/definitions/moonwave.rs index 9d5870d..d81800c 100644 --- a/packages/cli/src/gen/definitions/moonwave.rs +++ b/packages/cli/src/gen/definitions/moonwave.rs @@ -39,26 +39,30 @@ fn parse_moonwave_style_tag(line: &str) -> Option { } pub(super) fn parse_moonwave_style_comment(comment: &str) -> Vec { - let lines = comment.lines().map(str::trim).collect::>(); - let indent_len = lines.iter().fold(usize::MAX, |acc, line| { - let first = line.chars().enumerate().find_map(|(idx, ch)| { - if ch.is_alphanumeric() { - Some(idx) + let no_tabs = comment.replace('\t', " "); + let lines = no_tabs.split('\n').collect::>(); + let indent_len = + lines.iter().fold(usize::MAX, |acc, line| { + let first = line.chars().enumerate().find_map(|(idx, ch)| { + if ch.is_whitespace() { + None + } else { + Some(idx) + } + }); + if let Some(first_non_whitespace) = first { + acc.min(first_non_whitespace) } else { - None + acc } }); - if let Some(first_alphanumeric) = first { - if first_alphanumeric > 0 { - acc.min(first_alphanumeric - 1) - } else { - 0 - } + let unindented_lines = lines.iter().map(|line| { + if line.chars().any(|c| !c.is_whitespace()) { + &line[indent_len..] } else { - acc + line } }); - let unindented_lines = lines.iter().map(|line| &line[indent_len..]); let mut doc_items = Vec::new(); let mut doc_lines = Vec::new(); for line in unindented_lines { @@ -72,7 +76,7 @@ pub(super) fn parse_moonwave_style_comment(comment: &str) -> Vec Option { _ => None, }) .last() - .map(|comment| comment.trim().to_string()) + .map(ToString::to_string) } diff --git a/packages/cli/src/gen/definitions/type_info_ext.rs b/packages/cli/src/gen/definitions/type_info_ext.rs index 33d337d..666cae0 100644 --- a/packages/cli/src/gen/definitions/type_info_ext.rs +++ b/packages/cli/src/gen/definitions/type_info_ext.rs @@ -6,7 +6,10 @@ use full_moon::{ ShortString, }; -use super::kind::DefinitionsItemKind; +use super::{ + item::{DefinitionsItemFunctionArg, DefinitionsItemFunctionRet}, + kind::DefinitionsItemKind, +}; pub(crate) trait TypeInfoExt { fn is_fn(&self) -> bool; @@ -20,7 +23,12 @@ pub(crate) trait TypeInfoExt { fn extract_args_normalized( &self, type_lookup_table: &HashMap, - ) -> Option>; + ) -> Option>; + // fn extract_rets(&self) -> Vec; + // fn extract_rets_normalized( + // &self, + // type_lookup_table: &HashMap, + // ) -> Option>; } impl TypeInfoExt for TypeInfo { @@ -200,55 +208,32 @@ impl TypeInfoExt for TypeInfo { fn extract_args_normalized( &self, type_lookup_table: &HashMap, - ) -> Option> { + ) -> Option> { if self.is_fn() { - let separator = format!(" {} ", Symbol::Pipe); let args_stringified_not_normalized = self .extract_args() .iter() .map(|type_arg| { - type_arg - .type_info() - .stringify_simple(Some(self), type_lookup_table) + ( + type_arg + .name() + .map_or_else(|| "_".to_string(), |n| n.0.to_string()), + type_arg.type_info().to_string(), + type_arg + .type_info() + .stringify_simple(Some(self), type_lookup_table), + ) }) .collect::>(); - let mut args_stringified = Vec::new(); - for arg_string in args_stringified_not_normalized { - let arg_parts = arg_string.split(&separator).collect::>(); - // Check if we got any optional arg, if so then the entire possible - // union of args will be optional when merged together / normalized - let is_optional = arg_parts - .iter() - .any(|part| part == &"nil" || part.ends_with('?')); - // Get rid of any nils or optional markers since we keep track of it above - let mut arg_parts_no_nils = arg_parts - .iter() - .filter_map(|arg_part| { - if arg_part == &"nil" { - None - } else { - Some(arg_part.trim_end_matches('?')) - } - }) - .collect::>(); - arg_parts_no_nils.sort_unstable(); // Sort the args to be able to dedup - arg_parts_no_nils.dedup(); // Deduplicate types that are the exact same shape - if is_optional { - if arg_parts_no_nils.len() > 1 { - // A union of args that is nillable should be enclosed in parens to make - // it more clear that the entire arg is nillable and not just the last type - args_stringified.push(format!("({})?", arg_parts_no_nils.join(&separator))); - } else { - // Just one nillable arg, does not need any parens - args_stringified.push(format!("{}?", arg_parts_no_nils.first().unwrap())); - } - } else if arg_parts_no_nils.len() > 1 { - args_stringified.push(arg_parts_no_nils.join(&separator).to_string()); - } else { - args_stringified.push((*arg_parts_no_nils.first().unwrap()).to_string()); - } + let mut args = Vec::new(); + for (arg_name, arg_typedef, arg_typedef_simplified) in args_stringified_not_normalized { + args.push(DefinitionsItemFunctionArg::new( + arg_name, + arg_typedef, + normalize_type(&arg_typedef_simplified), + )); } - Some(args_stringified) + Some(args) } else { None } @@ -311,3 +296,38 @@ fn merge_type_argument_vecs( } result } + +fn normalize_type(simplified: &str) -> String { + let separator = format!(" {} ", Symbol::Pipe); + let arg_parts = simplified.split(&separator).collect::>(); + // Check if we got any optional arg, if so then the entire possible + // union of args will be optional when merged together / normalized + let is_optional = arg_parts + .iter() + .any(|part| part == &"nil" || part.ends_with('?')); + // Get rid of any nils or optional markers since we keep track of it above + let mut arg_parts_no_nils = arg_parts + .iter() + .filter_map(|arg_part| { + if arg_part == &"nil" { + None + } else { + Some(arg_part.trim_end_matches('?')) + } + }) + .collect::>(); + arg_parts_no_nils.sort_unstable(); // Sort the args to be able to dedup + arg_parts_no_nils.dedup(); // Deduplicate types that are the exact same shape + if is_optional { + if arg_parts_no_nils.len() > 1 { + // A union of args that is nillable should be enclosed in parens to make + // it more clear that the entire arg is nillable and not just the last type + format!("({})?", arg_parts_no_nils.join(&separator)) + } else { + // Just one nillable arg, does not need any parens + format!("{}?", arg_parts_no_nils.first().unwrap()) + } + } else { + arg_parts_no_nils.join(&separator) + } +} diff --git a/packages/cli/src/gen/gitbook_dir.rs b/packages/cli/src/gen/gitbook_dir.rs index d8cad39..4e4dc48 100644 --- a/packages/cli/src/gen/gitbook_dir.rs +++ b/packages/cli/src/gen/gitbook_dir.rs @@ -78,7 +78,7 @@ pub async fn generate_from_type_definitions(contents: &str) -> Result<()> { .with_extension("md"); let mut contents = String::new(); write!(contents, "{GENERATED_COMMENT_TAG}\n\n")?; - generate_markdown_documentation(&mut contents, &category_item)?; + generate_markdown_documentation(&mut contents, &category_item, 0)?; files_to_write.push((path, post_process_docs(contents))); } // Write all dirs and files only when we know generation was successful @@ -113,7 +113,11 @@ fn get_name(item: &DefinitionsItem) -> Result { .context("Definitions item is missing a name") } -fn generate_markdown_documentation(contents: &mut String, item: &DefinitionsItem) -> Result<()> { +fn generate_markdown_documentation( + contents: &mut String, + item: &DefinitionsItem, + depth: usize, +) -> Result<()> { match item.kind() { DefinitionsItemKind::Table | DefinitionsItemKind::Property @@ -126,14 +130,35 @@ fn generate_markdown_documentation(contents: &mut String, item: &DefinitionsItem )?; } DefinitionsItemKind::Description => { + let desc = item.get_value().context("Description is missing a value")?; write!( contents, "\n{}\n", - item.get_value().context("Description is missing a value")? + if depth >= 2 { + // HACK: We know our typedefs are formatted like this and + // it looks nicer to have this bolding instead of two + // headers using "###" in the function definition + desc.replace("### Example usage", "**Example usage:**") + } else { + desc.to_string() + } )?; } _ => {} } + if item.kind().is_function() && !item.args().is_empty() { + let args = item + .args() + .iter() + .map(|arg| format!("{}: {}", arg.name, arg.typedef)) + .collect::>(); + write!( + contents, + "\n```lua\nfunction {}({})\n```\n", + item.get_name().unwrap_or("_"), + args.join(", ") + )?; + } let descriptions = item .children() .iter() @@ -150,19 +175,19 @@ fn generate_markdown_documentation(contents: &mut String, item: &DefinitionsItem .filter(|child| child.is_function()) .collect::>(); for description in descriptions { - generate_markdown_documentation(contents, description)?; + generate_markdown_documentation(contents, description, depth + 1)?; } if !properties.is_empty() { write!(contents, "\n\n---\n\n## Properties\n\n")?; } for property in properties { - generate_markdown_documentation(contents, property)?; + generate_markdown_documentation(contents, property, depth + 1)?; } if !functions.is_empty() { write!(contents, "\n\n---\n\n## Functions\n\n")?; } for function in functions { - generate_markdown_documentation(contents, function)?; + generate_markdown_documentation(contents, function, depth + 1)?; } Ok(()) } diff --git a/packages/cli/src/gen/selene_defs.rs b/packages/cli/src/gen/selene_defs.rs index d542a7e..101bd06 100644 --- a/packages/cli/src/gen/selene_defs.rs +++ b/packages/cli/src/gen/selene_defs.rs @@ -115,7 +115,8 @@ fn doc_item_to_selene_yaml_mapping(item: &DefinitionsItem) -> Result (stripped, true), diff --git a/packages/lib/src/lua/task/ext/resume_ext.rs b/packages/lib/src/lua/task/ext/resume_ext.rs index 9fecaae..5dd9e07 100644 --- a/packages/lib/src/lua/task/ext/resume_ext.rs +++ b/packages/lib/src/lua/task/ext/resume_ext.rs @@ -42,7 +42,7 @@ impl TaskSchedulerResumeExt for TaskScheduler<'_> { */ async fn resume_queue(&self) -> TaskSchedulerState { let current = TaskSchedulerState::new(self); - let result = if current.num_blocking > 0 { + if current.num_blocking > 0 { // 1. Blocking tasks resume_next_blocking_task(self, None) } else if current.num_futures > 0 || current.num_background > 0 { @@ -57,8 +57,7 @@ impl TaskSchedulerResumeExt for TaskScheduler<'_> { // a busy loop to prevent cpu usage from going to 100% sleep(Duration::from_millis(1)).await; TaskSchedulerState::new(self) - }; - result + } } }