1use crate::docgen::{
4 export::ExportResolver,
5 ir::{
6 DeprecatedInfo, DocAttribute, DocClass, DocFunction, DocItem, DocModule, DocPackage,
7 DocParameter, DocSignature, DocSubmodule, DocTypeAlias, DocTypeExpr, DocVariable,
8 },
9 types::TypeRenderer,
10 util::{is_hidden_module, prefix_stripper},
11};
12use crate::generate::StubInfo;
13use crate::Result;
14use std::collections::BTreeMap;
15use std::path::PathBuf;
16
17fn matches_item_name(item: &DocItem, name: &str) -> bool {
19 match item {
20 DocItem::Function(f) => f.name == name,
21 DocItem::Class(c) => c.name == name,
22 DocItem::TypeAlias(t) => t.name == name,
23 DocItem::Variable(v) => v.name == name,
24 DocItem::Module(m) => m.name == name,
25 }
26}
27
28struct DocBuildContext<'a> {
30 link_resolver: crate::docgen::link::LinkResolver<'a>,
31 module: &'a str,
32}
33
34impl<'a> DocBuildContext<'a> {
35 fn type_renderer(&self) -> TypeRenderer<'_> {
37 TypeRenderer::new(&self.link_resolver, self.module)
38 }
39
40 fn default_parser(&self) -> crate::docgen::default_parser::DefaultValueParser<'_> {
42 crate::docgen::default_parser::DefaultValueParser::new(&self.link_resolver, self.module)
43 }
44}
45
46pub struct DocPackageBuilder<'a> {
48 stub_info: &'a StubInfo,
49 export_resolver: ExportResolver<'a>,
50 export_map: BTreeMap<String, String>,
51 default_module_name: String,
52}
53
54impl<'a> DocPackageBuilder<'a> {
55 pub fn new(stub_info: &'a StubInfo) -> Self {
56 let export_resolver = ExportResolver::new(&stub_info.modules);
57 let export_map = export_resolver.build_export_map();
58
59 let default_module_name = stub_info
61 .modules
62 .values()
63 .next()
64 .map(|m| m.default_module_name.clone())
65 .unwrap_or_default();
66
67 Self {
68 stub_info,
69 export_resolver,
70 export_map,
71 default_module_name,
72 }
73 }
74
75 fn create_context<'b>(&'b self, module: &'b str) -> DocBuildContext<'b> {
77 let link_resolver = crate::docgen::link::LinkResolver::new(&self.export_map);
78 DocBuildContext {
79 link_resolver,
80 module,
81 }
82 }
83
84 pub fn build(self) -> Result<DocPackage> {
85 let mut modules = BTreeMap::new();
86
87 for (module_name, module) in &self.stub_info.modules {
88 if is_hidden_module(module_name) {
90 continue;
91 }
92
93 let doc_module = self.build_module(module_name, module)?;
94 modules.insert(module_name.clone(), doc_module);
95 }
96
97 let export_map = self.export_map;
98
99 let mut json_config = self.stub_info.config.doc_gen.clone().unwrap_or_default();
101 if let Some(pyproject_dir) = &self.stub_info.pyproject_dir {
102 let relative_posix = json_config.to_relative_posix_path(pyproject_dir);
103 json_config.output_dir = PathBuf::from(relative_posix);
104 }
105
106 Ok(DocPackage {
107 name: self.default_module_name.clone(),
108 modules,
109 export_map,
110 config: json_config,
111 })
112 }
113
114 fn build_module(&self, name: &str, module: &crate::generate::Module) -> Result<DocModule> {
115 let exports = self.export_resolver.resolve_exports(module);
116 let mut items = Vec::new();
117
118 for (func_name, func_defs) in &module.function {
120 if exports.contains(*func_name) {
121 items.push(self.build_function(name, func_defs)?);
122 }
123 }
124
125 for (alias_name, alias_def) in &module.type_aliases {
127 if exports.contains(*alias_name) {
128 items.push(self.build_type_alias(name, alias_def)?);
129 }
130 }
131
132 let mut classes: Vec<_> = module.class.values().collect();
134 classes.sort_by_key(|c| c.name);
135 for class_def in classes {
136 if exports.contains(class_def.name) {
137 items.push(self.build_class(name, class_def)?);
138 }
139 }
140
141 let mut enums: Vec<_> = module.enum_.values().collect();
143 enums.sort_by_key(|e| e.name);
144 for enum_def in enums {
145 if exports.contains(enum_def.name) {
146 items.push(self.build_enum_as_class(name, enum_def)?);
147 }
148 }
149
150 for (var_name, var_def) in &module.variables {
152 if exports.contains(*var_name) {
153 items.push(self.build_variable(name, var_def)?);
154 }
155 }
156
157 for re_export in &module.module_re_exports {
159 if let Some(source_module) = self.stub_info.modules.get(&re_export.source_module) {
160 for item_name in &re_export.items {
161 if items.iter().any(|item| matches_item_name(item, item_name)) {
163 continue;
164 }
165
166 if let Some(item) = self.build_reexported_item(
168 &re_export.source_module,
169 source_module,
170 item_name,
171 )? {
172 items.push(item);
173 }
174 }
175 }
176 }
177
178 for submod_name in &module.submodules {
180 if !submod_name.starts_with('_') && exports.contains(submod_name) {
181 let submod_fqn = if name.is_empty() {
183 submod_name.clone()
184 } else {
185 format!("{}.{}", name, submod_name)
186 };
187
188 let submod_doc = self
190 .stub_info
191 .modules
192 .get(&submod_fqn)
193 .map(|m| m.doc.clone())
194 .unwrap_or_default();
195
196 items.push(DocItem::Module(DocSubmodule {
197 name: submod_name.clone(),
198 doc: submod_doc,
199 fqn: submod_fqn,
200 }));
201 }
202 }
203
204 for item in &mut items {
207 self.correct_link_targets(item, name);
208 }
209
210 Ok(DocModule {
211 name: name.to_string(),
212 doc: module.doc.clone(),
213 items,
214 submodules: module
215 .submodules
216 .iter()
217 .filter(|s| !s.starts_with('_'))
218 .cloned()
219 .collect(),
220 })
221 }
222
223 fn build_function(
224 &self,
225 module: &str,
226 func_defs: &[crate::generate::FunctionDef],
227 ) -> Result<DocItem> {
228 let mut sorted_defs = func_defs.to_vec();
230 sorted_defs.sort_by_key(|func| (func.file, func.line, func.column, func.index));
231
232 let signatures: Vec<DocSignature> = sorted_defs
234 .iter()
235 .map(|def| self.build_signature(module, def))
236 .collect::<Result<_>>()?;
237
238 let doc = sorted_defs
240 .first()
241 .map(|d| d.doc.to_string())
242 .unwrap_or_default();
243
244 let deprecated = sorted_defs.first().and_then(|d| {
245 d.deprecated.as_ref().map(|dep| DeprecatedInfo {
246 since: dep.since.map(|s| s.to_string()),
247 note: dep.note.map(|s| s.to_string()),
248 })
249 });
250
251 Ok(DocItem::Function(DocFunction {
252 name: sorted_defs[0].name.to_string(),
253 doc,
254 signatures,
255 is_async: sorted_defs[0].is_async,
256 deprecated,
257 }))
258 }
259
260 fn build_signature_from_params(
261 &self,
262 module: &str,
263 parameters: &crate::generate::Parameters,
264 return_type: &crate::TypeInfo,
265 ) -> Result<DocSignature> {
266 let ctx = self.create_context(module);
267 let type_renderer = ctx.type_renderer();
268 let default_parser = ctx.default_parser();
269
270 let params: Vec<DocParameter> = parameters
271 .positional_only
272 .iter()
273 .chain(parameters.positional_or_keyword.iter())
274 .chain(parameters.keyword_only.iter())
275 .chain(parameters.varargs.iter())
276 .chain(parameters.varkw.iter())
277 .map(|param| DocParameter {
278 name: param.name.to_string(),
279 type_: type_renderer.render_type(¶m.type_info),
280 default: match ¶m.default {
281 crate::generate::ParameterDefault::None => None,
282 crate::generate::ParameterDefault::Expr(s) => {
283 Some(default_parser.parse(s, ¶m.type_info))
285 }
286 },
287 })
288 .collect();
289
290 let ret_type = Some(type_renderer.render_type(return_type));
291
292 Ok(DocSignature {
293 parameters: params,
294 return_type: ret_type,
295 })
296 }
297
298 fn build_signature(
299 &self,
300 module: &str,
301 def: &crate::generate::FunctionDef,
302 ) -> Result<DocSignature> {
303 self.build_signature_from_params(module, &def.parameters, &def.r#return)
304 }
305
306 fn build_signature_from_method(
307 &self,
308 module: &str,
309 def: &crate::generate::MethodDef,
310 ) -> Result<DocSignature> {
311 self.build_signature_from_params(module, &def.parameters, &def.r#return)
312 }
313
314 fn build_type_alias(
315 &self,
316 module: &str,
317 alias: &crate::generate::TypeAliasDef,
318 ) -> Result<DocItem> {
319 let ctx = self.create_context(module);
321 let type_renderer = ctx.type_renderer();
322
323 Ok(DocItem::TypeAlias(DocTypeAlias {
324 name: alias.name.to_string(),
325 doc: alias.doc.to_string(),
326 definition: type_renderer.render_type(&alias.type_),
327 }))
328 }
329
330 fn build_class(&self, module: &str, class: &crate::generate::ClassDef) -> Result<DocItem> {
331 let ctx = self.create_context(module);
332 let type_renderer = ctx.type_renderer();
333
334 let bases: Vec<DocTypeExpr> = class
335 .bases
336 .iter()
337 .map(|base| type_renderer.render_type(base))
338 .collect();
339
340 let mut methods = Vec::new();
341 for (method_name, method_overloads) in &class.methods {
342 let signatures: Vec<DocSignature> = method_overloads
343 .iter()
344 .map(|method| self.build_signature_from_method(module, method))
345 .collect::<Result<_>>()?;
346
347 let deprecated = method_overloads.first().and_then(|d| {
348 d.deprecated.as_ref().map(|dep| DeprecatedInfo {
349 since: dep.since.map(|s| s.to_string()),
350 note: dep.note.map(|s| s.to_string()),
351 })
352 });
353
354 methods.push(DocFunction {
355 name: method_name.to_string(),
356 doc: method_overloads
357 .first()
358 .map(|m| m.doc.to_string())
359 .unwrap_or_default(),
360 signatures,
361 is_async: method_overloads
362 .first()
363 .map(|m| m.is_async)
364 .unwrap_or(false),
365 deprecated,
366 });
367 }
368
369 let attributes = Vec::new(); Ok(DocItem::Class(DocClass {
372 name: class.name.to_string(),
373 doc: class.doc.to_string(),
374 bases,
375 methods,
376 attributes,
377 deprecated: None, }))
379 }
380
381 fn build_enum_as_class(
382 &self,
383 _module: &str,
384 enum_def: &crate::generate::EnumDef,
385 ) -> Result<DocItem> {
386 let attributes: Vec<DocAttribute> = enum_def
391 .variants
392 .iter()
393 .map(|(variant_name, variant_doc)| DocAttribute {
394 name: (*variant_name).to_string(),
395 doc: (*variant_doc).to_string(),
396 type_: None, })
398 .collect();
399
400 Ok(DocItem::Class(DocClass {
401 name: enum_def.name.to_string(),
402 doc: enum_def.doc.to_string(),
403 bases: Vec::new(), methods: Vec::new(),
405 attributes,
406 deprecated: None,
407 }))
408 }
409
410 fn build_variable(&self, module: &str, var: &crate::generate::VariableDef) -> Result<DocItem> {
411 let ctx = self.create_context(module);
412 let type_renderer = ctx.type_renderer();
413
414 Ok(DocItem::Variable(DocVariable {
415 name: var.name.to_string(),
416 doc: String::new(), type_: Some(type_renderer.render_type(&var.type_)),
418 }))
419 }
420
421 fn build_reexported_item(
423 &self,
424 source_module_name: &str,
425 source_module: &crate::generate::Module,
426 item_name: &str,
427 ) -> Result<Option<DocItem>> {
428 if let Some(func_defs) = source_module.function.get(item_name) {
430 return Ok(Some(self.build_function(source_module_name, func_defs)?));
431 }
432
433 for class_def in source_module.class.values() {
435 if class_def.name == item_name {
436 return Ok(Some(self.build_class(source_module_name, class_def)?));
437 }
438 }
439
440 for enum_def in source_module.enum_.values() {
442 if enum_def.name == item_name {
443 return Ok(Some(
444 self.build_enum_as_class(source_module_name, enum_def)?,
445 ));
446 }
447 }
448
449 if let Some(alias_def) = source_module.type_aliases.get(item_name) {
451 return Ok(Some(self.build_type_alias(source_module_name, alias_def)?));
452 }
453
454 if let Some(var_def) = source_module.variables.get(item_name) {
456 return Ok(Some(self.build_variable(source_module_name, var_def)?));
457 }
458
459 Ok(None)
460 }
461
462 fn correct_link_targets(&self, item: &mut DocItem, _target_module: &str) {
464 match item {
465 DocItem::Function(func) => {
466 for sig in &mut func.signatures {
467 if let Some(ret) = &mut sig.return_type {
468 self.correct_type_expr(ret);
469 }
470 for param in &mut sig.parameters {
471 self.correct_type_expr(&mut param.type_);
472 }
473 }
474 }
475 DocItem::Class(cls) => {
476 for base in &mut cls.bases {
477 self.correct_type_expr(base);
478 }
479 for method in &mut cls.methods {
480 for sig in &mut method.signatures {
481 if let Some(ret) = &mut sig.return_type {
482 self.correct_type_expr(ret);
483 }
484 for param in &mut sig.parameters {
485 self.correct_type_expr(&mut param.type_);
486 }
487 }
488 }
489 for attr in &mut cls.attributes {
490 if let Some(type_) = &mut attr.type_ {
491 self.correct_type_expr(type_);
492 }
493 }
494 }
495 DocItem::TypeAlias(alias) => {
496 self.correct_type_expr(&mut alias.definition);
497 }
498 DocItem::Variable(var) => {
499 if let Some(type_) = &mut var.type_ {
500 self.correct_type_expr(type_);
501 }
502 }
503 DocItem::Module(_) => {}
504 }
505 }
506
507 fn correct_type_expr(&self, type_expr: &mut DocTypeExpr) {
509 if let Some(link_target) = &mut type_expr.link_target {
510 let (exported_fqn, exported_module) =
513 if let Some(module) = self.export_map.get(&link_target.fqn) {
514 (link_target.fqn.clone(), module.clone())
515 } else {
516 if let Some(type_name) = link_target.fqn.split('.').next_back() {
519 if let Some((fqn, module)) = self
521 .export_map
522 .iter()
523 .find(|(fqn, _)| fqn.ends_with(&format!(".{}", type_name)))
524 {
525 (fqn.clone(), module.clone())
526 } else {
527 (link_target.fqn.clone(), link_target.doc_module.clone())
528 }
529 } else {
530 (link_target.fqn.clone(), link_target.doc_module.clone())
531 }
532 };
533
534 link_target.fqn = exported_fqn;
535 link_target.doc_module = exported_module;
536
537 type_expr.display = self.strip_internal_module_prefix(&type_expr.display);
540 }
541 for child in &mut type_expr.children {
542 self.correct_type_expr(child);
543 }
544 }
545
546 fn strip_internal_module_prefix(&self, display: &str) -> String {
549 prefix_stripper::strip_internal_prefixes(display)
550 }
551}