diff --git a/crates/axl-docgen/src/renderer.rs b/crates/axl-docgen/src/renderer.rs index 50fff24bc..32b5da102 100644 --- a/crates/axl-docgen/src/renderer.rs +++ b/crates/axl-docgen/src/renderer.rs @@ -1,6 +1,6 @@ use crate::traversal::{DocPage, DocPageItem}; use crate::type_linker::TypeLinker; -use starlark::docs::{DocFunction, DocProperty}; +use starlark::docs::{DocFunction, DocParam, DocProperty}; const PRELUDE: &str = r#"
"#;
 const POSTLUDE: &str = r#"
"#; @@ -105,6 +105,13 @@ impl<'a> Renderer<'a> { output.push_str(&render_docstring(docs)); } + // Parameter documentation + if let Some(params_md) = render_param_docs(func) { + output.push_str("**Parameters**\n\n"); + output.push_str(¶ms_md); + output.push('\n'); + } + output } @@ -276,6 +283,53 @@ impl<'a> Renderer<'a> { } } +/// Render per-parameter documentation as a markdown list. +/// Returns `None` if no parameter has docs attached. +fn render_param_docs(func: &DocFunction) -> Option { + let p = &func.params; + + // Collect (display_name, DocParam) in declaration order, matching starlark-rust's + // doc_params_with_starred_names() logic (which is pub(crate)). + let mut pairs: Vec<(String, &DocParam)> = Vec::new(); + for param in &p.pos_only { + pairs.push((param.name.clone(), param)); + } + for param in &p.pos_or_named { + pairs.push((param.name.clone(), param)); + } + if let Some(args) = &p.args { + pairs.push((format!("*{}", args.name), args)); + } + for param in &p.named_only { + pairs.push((param.name.clone(), param)); + } + if let Some(kwargs) = &p.kwargs { + pairs.push((format!("**{}", kwargs.name), kwargs)); + } + + let mut output = String::new(); + for (display_name, param) in &pairs { + let Some(docs) = ¶m.docs else { continue }; + let doc_text = match &docs.details { + Some(details) => format!("{}\n\n{}", docs.summary, details), + None => docs.summary.clone(), + }; + let mut lines = doc_text.lines(); + if let Some(first) = lines.next() { + output.push_str(&format!("* `{}`: {}\n", display_name, first)); + for line in lines { + output.push_str(&format!(" {}\n", line)); + } + } + } + + if output.is_empty() { + None + } else { + Some(output) + } +} + /// Escape underscores in markdown headings to prevent interpretation as emphasis. fn escape_underscores(s: &str) -> String { s.replace('_', "\\_")