pyo3_stub_gen/docgen/link.rs
1//! Haddock-style link resolution for documentation
2
3use crate::docgen::ir::{ItemKind, LinkTarget};
4use std::collections::BTreeMap;
5
6/// Link resolver implementing Haddock-style resolution
7pub struct LinkResolver<'a> {
8 export_map: &'a BTreeMap<String, String>,
9}
10
11impl<'a> LinkResolver<'a> {
12 pub fn new(export_map: &'a BTreeMap<String, String>) -> Self {
13 Self { export_map }
14 }
15
16 /// Get the export map for resolving type links
17 pub fn export_map(&self) -> &BTreeMap<String, String> {
18 self.export_map
19 }
20
21 /// Resolve a link using Haddock rules:
22 /// 1. If exported from current_module (incl. re-exports): link to current
23 /// 2. Else if exported from public module: link there
24 /// 3. Else (private module only): no link
25 ///
26 /// Returns (doc_module, item_kind) if linkable, None otherwise
27 pub fn resolve_link(&self, item_fqn: &str, current_module: &str) -> Option<(String, ItemKind)> {
28 // Check if item is exported from current module
29 if let Some(export_module) = self.export_map.get(item_fqn) {
30 if export_module == current_module {
31 // Item is exported from current module - link to it
32 return Some((current_module.to_string(), ItemKind::Class)); // TODO: determine actual kind
33 }
34
35 // Check if the export module is public
36 if !self.is_private_module(export_module) {
37 return Some((export_module.clone(), ItemKind::Class)); // TODO: determine actual kind
38 }
39 }
40
41 // Item is only in private modules - no link
42 None
43 }
44
45 /// Check if a module is private (has underscore segments)
46 fn is_private_module(&self, module_name: &str) -> bool {
47 module_name.split('.').any(|part| part.starts_with('_'))
48 }
49
50 /// Resolve a link to a class attribute (e.g., "C.C1" → link to C1 variant of class C)
51 ///
52 /// This is used for linking to enum variants or class attributes in default values.
53 pub fn resolve_attribute_link(
54 &self,
55 class_name: &str,
56 attribute_name: &str,
57 current_module: &str,
58 ) -> Option<LinkTarget> {
59 // 1. Resolve the class itself
60 let (doc_module, kind) = self.resolve_link(class_name, current_module)?;
61
62 // 2. Verify it's a class
63 if kind != ItemKind::Class {
64 return None;
65 }
66
67 // 3. Build FQN with attribute: "module.Class.attribute"
68 let fqn = format!("{}.{}", class_name, attribute_name);
69
70 Some(LinkTarget {
71 fqn,
72 doc_module,
73 kind: ItemKind::Class,
74 attribute: Some(attribute_name.to_string()),
75 })
76 }
77}