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}
52
53impl<'a> DocPackageBuilder<'a> {
54 pub fn new(stub_info: &'a StubInfo) -> Self {
55 let export_resolver = ExportResolver::new(&stub_info.modules);
56 let export_map = export_resolver.build_export_map();
57
58 Self {
59 stub_info,
60 export_resolver,
61 export_map,
62 }
63 }
64
65 fn create_context<'b>(&'b self, module: &'b str) -> DocBuildContext<'b> {
67 let link_resolver = crate::docgen::link::LinkResolver::new(&self.export_map);
68 DocBuildContext {
69 link_resolver,
70 module,
71 }
72 }
73
74 pub fn build(self) -> Result<DocPackage> {
75 let mut modules = BTreeMap::new();
76
77 for (module_name, module) in &self.stub_info.modules {
78 if is_hidden_module(module_name) {
80 continue;
81 }
82
83 let doc_module = self.build_module(module_name, module)?;
84 modules.insert(module_name.clone(), doc_module);
85 }
86
87 let export_map = self.export_map;
88
89 let mut json_config = self.stub_info.config.doc_gen.clone().unwrap_or_default();
91 if let Some(pyproject_dir) = &self.stub_info.pyproject_dir {
92 let relative_posix = json_config.to_relative_posix_path(pyproject_dir);
93 json_config.output_dir = PathBuf::from(relative_posix);
94 }
95
96 Ok(DocPackage {
97 name: self.stub_info.project_name.clone(),
98 modules,
99 export_map,
100 config: json_config,
101 })
102 }
103
104 fn build_module(&self, name: &str, module: &crate::generate::Module) -> Result<DocModule> {
105 let exports = self.export_resolver.resolve_exports(module);
106 let mut items = Vec::new();
107
108 for (func_name, func_defs) in &module.function {
110 if exports.contains(*func_name) {
111 items.push(self.build_function(name, func_defs)?);
112 }
113 }
114
115 for (alias_name, alias_def) in &module.type_aliases {
117 if exports.contains(*alias_name) {
118 items.push(self.build_type_alias(name, alias_def)?);
119 }
120 }
121
122 let mut classes: Vec<_> = module.class.values().collect();
124 classes.sort_by_key(|c| c.name);
125 for class_def in classes {
126 if exports.contains(class_def.name) {
127 items.push(self.build_class(name, class_def)?);
128 }
129 }
130
131 let mut enums: Vec<_> = module.enum_.values().collect();
133 enums.sort_by_key(|e| e.name);
134 for enum_def in enums {
135 if exports.contains(enum_def.name) {
136 items.push(self.build_enum_as_class(name, enum_def)?);
137 }
138 }
139
140 for (var_name, var_def) in &module.variables {
142 if exports.contains(*var_name) {
143 items.push(self.build_variable(name, var_def)?);
144 }
145 }
146
147 for re_export in &module.module_re_exports {
149 if let Some(source_module) = self.stub_info.modules.get(&re_export.source_module) {
150 for item_name in &re_export.items {
151 if items.iter().any(|item| matches_item_name(item, item_name)) {
153 continue;
154 }
155
156 if let Some(item) = self.build_reexported_item(
158 &re_export.source_module,
159 source_module,
160 item_name,
161 )? {
162 items.push(item);
163 }
164 }
165 }
166 }
167
168 for submod_name in &module.submodules {
170 if !submod_name.starts_with('_') && exports.contains(submod_name) {
171 let submod_fqn = if name.is_empty() {
173 submod_name.clone()
174 } else {
175 format!("{}.{}", name, submod_name)
176 };
177
178 let submod_doc = self
180 .stub_info
181 .modules
182 .get(&submod_fqn)
183 .map(|m| m.doc.clone())
184 .unwrap_or_default();
185
186 items.push(DocItem::Module(DocSubmodule {
187 name: submod_name.clone(),
188 doc: submod_doc,
189 fqn: submod_fqn,
190 }));
191 }
192 }
193
194 for item in &mut items {
197 self.correct_link_targets(item, name);
198 }
199
200 Ok(DocModule {
201 name: name.to_string(),
202 doc: module.doc.clone(),
203 items,
204 submodules: module
205 .submodules
206 .iter()
207 .filter(|s| !s.starts_with('_'))
208 .cloned()
209 .collect(),
210 })
211 }
212
213 fn build_function(
214 &self,
215 module: &str,
216 func_defs: &[crate::generate::FunctionDef],
217 ) -> Result<DocItem> {
218 let mut sorted_defs = func_defs.to_vec();
220 sorted_defs.sort_by_key(|func| (func.file, func.line, func.column, func.index));
221
222 let signatures: Vec<DocSignature> = sorted_defs
224 .iter()
225 .map(|def| self.build_signature(module, def))
226 .collect::<Result<_>>()?;
227
228 let doc = sorted_defs
230 .first()
231 .map(|d| d.doc.to_string())
232 .unwrap_or_default();
233
234 let deprecated = sorted_defs.first().and_then(|d| {
235 d.deprecated.as_ref().map(|dep| DeprecatedInfo {
236 since: dep.since.map(|s| s.to_string()),
237 note: dep.note.map(|s| s.to_string()),
238 })
239 });
240
241 Ok(DocItem::Function(DocFunction {
242 name: sorted_defs[0].name.to_string(),
243 doc,
244 signatures,
245 is_async: sorted_defs[0].is_async,
246 deprecated,
247 }))
248 }
249
250 fn build_signature_from_params(
251 &self,
252 module: &str,
253 parameters: &crate::generate::Parameters,
254 return_type: &crate::TypeInfo,
255 ) -> Result<DocSignature> {
256 let ctx = self.create_context(module);
257 let type_renderer = ctx.type_renderer();
258 let default_parser = ctx.default_parser();
259
260 let params: Vec<DocParameter> = parameters
261 .positional_only
262 .iter()
263 .chain(parameters.positional_or_keyword.iter())
264 .chain(parameters.keyword_only.iter())
265 .chain(parameters.varargs.iter())
266 .chain(parameters.varkw.iter())
267 .map(|param| DocParameter {
268 name: param.name.to_string(),
269 type_: type_renderer.render_type(¶m.type_info),
270 default: match ¶m.default {
271 crate::generate::ParameterDefault::None => None,
272 crate::generate::ParameterDefault::Expr { value, .. } => {
273 Some(default_parser.parse(value, ¶m.type_info))
275 }
276 },
277 })
278 .collect();
279
280 let ret_type = Some(type_renderer.render_type(return_type));
281
282 Ok(DocSignature {
283 parameters: params,
284 return_type: ret_type,
285 })
286 }
287
288 fn build_signature(
289 &self,
290 module: &str,
291 def: &crate::generate::FunctionDef,
292 ) -> Result<DocSignature> {
293 self.build_signature_from_params(module, &def.parameters, &def.r#return)
294 }
295
296 fn build_signature_from_method(
297 &self,
298 module: &str,
299 def: &crate::generate::MethodDef,
300 ) -> Result<DocSignature> {
301 self.build_signature_from_params(module, &def.parameters, &def.r#return)
302 }
303
304 fn build_type_alias(
305 &self,
306 module: &str,
307 alias: &crate::generate::TypeAliasDef,
308 ) -> Result<DocItem> {
309 let ctx = self.create_context(module);
311 let type_renderer = ctx.type_renderer();
312
313 Ok(DocItem::TypeAlias(DocTypeAlias {
314 name: alias.name.to_string(),
315 doc: alias.doc.to_string(),
316 definition: type_renderer.render_type(&alias.type_),
317 }))
318 }
319
320 fn build_class(&self, module: &str, class: &crate::generate::ClassDef) -> Result<DocItem> {
321 let ctx = self.create_context(module);
322 let type_renderer = ctx.type_renderer();
323
324 let bases: Vec<DocTypeExpr> = class
325 .bases
326 .iter()
327 .map(|base| type_renderer.render_type(base))
328 .collect();
329
330 let mut methods = Vec::new();
331 for (method_name, method_overloads) in &class.methods {
332 let signatures: Vec<DocSignature> = method_overloads
333 .iter()
334 .map(|method| self.build_signature_from_method(module, method))
335 .collect::<Result<_>>()?;
336
337 let deprecated = method_overloads.first().and_then(|d| {
338 d.deprecated.as_ref().map(|dep| DeprecatedInfo {
339 since: dep.since.map(|s| s.to_string()),
340 note: dep.note.map(|s| s.to_string()),
341 })
342 });
343
344 methods.push(DocFunction {
345 name: method_name.to_string(),
346 doc: method_overloads
347 .first()
348 .map(|m| m.doc.to_string())
349 .unwrap_or_default(),
350 signatures,
351 is_async: method_overloads
352 .first()
353 .map(|m| m.is_async)
354 .unwrap_or(false),
355 deprecated,
356 });
357 }
358
359 let mut attributes: Vec<DocAttribute> = class
361 .attrs
362 .iter()
363 .map(|attr| DocAttribute {
364 name: attr.name.to_string(),
365 doc: attr.doc.to_string(),
366 type_: Some(type_renderer.render_type(&attr.r#type)),
367 is_property: false,
368 is_readonly: false,
369 deprecated: attr.deprecated.as_ref().map(|dep| DeprecatedInfo {
370 since: dep.since.map(|s| s.to_string()),
371 note: dep.note.map(|s| s.to_string()),
372 }),
373 })
374 .collect();
375
376 for (prop_name, (getter, setter)) in &class.getter_setters {
378 let member = getter.as_ref().or(setter.as_ref());
379 if let Some(member) = member {
380 let deprecated = getter
382 .as_ref()
383 .and_then(|g| g.deprecated.as_ref())
384 .or_else(|| setter.as_ref().and_then(|s| s.deprecated.as_ref()))
385 .map(|dep| DeprecatedInfo {
386 since: dep.since.map(|s| s.to_string()),
387 note: dep.note.map(|s| s.to_string()),
388 });
389 attributes.push(DocAttribute {
390 name: prop_name.clone(),
391 doc: member.doc.to_string(),
392 type_: Some(type_renderer.render_type(&member.r#type)),
393 is_property: true,
394 is_readonly: setter.is_none(),
395 deprecated,
396 });
397 }
398 }
399
400 Ok(DocItem::Class(DocClass {
401 name: class.name.to_string(),
402 doc: class.doc.to_string(),
403 bases,
404 methods,
405 attributes,
406 deprecated: None, }))
408 }
409
410 fn build_enum_as_class(
411 &self,
412 module: &str,
413 enum_def: &crate::generate::EnumDef,
414 ) -> Result<DocItem> {
415 let ctx = self.create_context(module);
416 let type_renderer = ctx.type_renderer();
417
418 let mut attributes: Vec<DocAttribute> = enum_def
420 .variants
421 .iter()
422 .map(|(variant_name, variant_doc)| DocAttribute {
423 name: (*variant_name).to_string(),
424 doc: (*variant_doc).to_string(),
425 type_: None, is_property: false,
427 is_readonly: false,
428 deprecated: None,
429 })
430 .collect();
431
432 let setter_names: std::collections::HashSet<&str> =
434 enum_def.setters.iter().map(|s| s.name).collect();
435
436 for getter in &enum_def.getters {
438 attributes.push(DocAttribute {
439 name: getter.name.to_string(),
440 doc: getter.doc.to_string(),
441 type_: Some(type_renderer.render_type(&getter.r#type)),
442 is_property: true,
443 is_readonly: !setter_names.contains(getter.name),
444 deprecated: getter.deprecated.as_ref().map(|dep| DeprecatedInfo {
445 since: dep.since.map(|s| s.to_string()),
446 note: dep.note.map(|s| s.to_string()),
447 }),
448 });
449 }
450
451 Ok(DocItem::Class(DocClass {
452 name: enum_def.name.to_string(),
453 doc: enum_def.doc.to_string(),
454 bases: Vec::new(), methods: Vec::new(),
456 attributes,
457 deprecated: None,
458 }))
459 }
460
461 fn build_variable(&self, module: &str, var: &crate::generate::VariableDef) -> Result<DocItem> {
462 let ctx = self.create_context(module);
463 let type_renderer = ctx.type_renderer();
464
465 Ok(DocItem::Variable(DocVariable {
466 name: var.name.to_string(),
467 doc: String::new(), type_: Some(type_renderer.render_type(&var.type_)),
469 }))
470 }
471
472 fn build_reexported_item(
474 &self,
475 source_module_name: &str,
476 source_module: &crate::generate::Module,
477 item_name: &str,
478 ) -> Result<Option<DocItem>> {
479 if let Some(func_defs) = source_module.function.get(item_name) {
481 return Ok(Some(self.build_function(source_module_name, func_defs)?));
482 }
483
484 for class_def in source_module.class.values() {
486 if class_def.name == item_name {
487 return Ok(Some(self.build_class(source_module_name, class_def)?));
488 }
489 }
490
491 for enum_def in source_module.enum_.values() {
493 if enum_def.name == item_name {
494 return Ok(Some(
495 self.build_enum_as_class(source_module_name, enum_def)?,
496 ));
497 }
498 }
499
500 if let Some(alias_def) = source_module.type_aliases.get(item_name) {
502 return Ok(Some(self.build_type_alias(source_module_name, alias_def)?));
503 }
504
505 if let Some(var_def) = source_module.variables.get(item_name) {
507 return Ok(Some(self.build_variable(source_module_name, var_def)?));
508 }
509
510 Ok(None)
511 }
512
513 fn correct_link_targets(&self, item: &mut DocItem, _target_module: &str) {
515 match item {
516 DocItem::Function(func) => {
517 for sig in &mut func.signatures {
518 if let Some(ret) = &mut sig.return_type {
519 self.correct_type_expr(ret);
520 }
521 for param in &mut sig.parameters {
522 self.correct_type_expr(&mut param.type_);
523 }
524 }
525 }
526 DocItem::Class(cls) => {
527 for base in &mut cls.bases {
528 self.correct_type_expr(base);
529 }
530 for method in &mut cls.methods {
531 for sig in &mut method.signatures {
532 if let Some(ret) = &mut sig.return_type {
533 self.correct_type_expr(ret);
534 }
535 for param in &mut sig.parameters {
536 self.correct_type_expr(&mut param.type_);
537 }
538 }
539 }
540 for attr in &mut cls.attributes {
541 if let Some(type_) = &mut attr.type_ {
542 self.correct_type_expr(type_);
543 }
544 }
545 }
546 DocItem::TypeAlias(alias) => {
547 self.correct_type_expr(&mut alias.definition);
548 }
549 DocItem::Variable(var) => {
550 if let Some(type_) = &mut var.type_ {
551 self.correct_type_expr(type_);
552 }
553 }
554 DocItem::Module(_) => {}
555 }
556 }
557
558 fn correct_type_expr(&self, type_expr: &mut DocTypeExpr) {
560 if let Some(link_target) = &mut type_expr.link_target {
561 let (exported_fqn, exported_module) =
564 if let Some(module) = self.export_map.get(&link_target.fqn) {
565 (link_target.fqn.clone(), module.clone())
566 } else {
567 if let Some(type_name) = link_target.fqn.split('.').next_back() {
570 if let Some((fqn, module)) = self
572 .export_map
573 .iter()
574 .find(|(fqn, _)| fqn.ends_with(&format!(".{}", type_name)))
575 {
576 (fqn.clone(), module.clone())
577 } else {
578 (link_target.fqn.clone(), link_target.doc_module.clone())
579 }
580 } else {
581 (link_target.fqn.clone(), link_target.doc_module.clone())
582 }
583 };
584
585 link_target.fqn = exported_fqn;
586 link_target.doc_module = exported_module;
587
588 type_expr.display = self.strip_internal_module_prefix(&type_expr.display);
591 }
592 for child in &mut type_expr.children {
593 self.correct_type_expr(child);
594 }
595 }
596
597 fn strip_internal_module_prefix(&self, display: &str) -> String {
600 prefix_stripper::strip_internal_prefixes(display)
601 }
602}