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}