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('_', "\\_")