sphinx_ultra/
domains.rs

1pub mod parser;
2/// Domain System & Cross-Reference Validation
3///
4/// This module implements a domain-based validation system inspired by Sphinx domains.
5/// It provides:
6/// - Domain registration and management
7/// - Cross-reference tracking and validation
8/// - Domain object registry (functions, classes, modules, etc.)
9/// - Reference resolution and dangling reference detection
10pub mod python;
11pub mod rst;
12
13use crate::error::BuildError;
14use std::collections::HashMap;
15
16/// Represents the type of a cross-reference
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub enum ReferenceType {
19    /// Document reference (:doc:)
20    Document,
21    /// Section reference (:ref:)
22    Section,
23    /// Function reference (:func:)
24    Function,
25    /// Class reference (:class:)
26    Class,
27    /// Module reference (:mod:)
28    Module,
29    /// Method reference (:meth:)
30    Method,
31    /// Attribute reference (:attr:)
32    Attribute,
33    /// Data reference (:data:)
34    Data,
35    /// Exception reference (:exc:)
36    Exception,
37    /// Custom reference type
38    Custom(String),
39}
40
41impl std::fmt::Display for ReferenceType {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            ReferenceType::Document => write!(f, "Document"),
45            ReferenceType::Section => write!(f, "Section"),
46            ReferenceType::Function => write!(f, "Function"),
47            ReferenceType::Class => write!(f, "Class"),
48            ReferenceType::Module => write!(f, "Module"),
49            ReferenceType::Method => write!(f, "Method"),
50            ReferenceType::Attribute => write!(f, "Attribute"),
51            ReferenceType::Data => write!(f, "Data"),
52            ReferenceType::Exception => write!(f, "Exception"),
53            ReferenceType::Custom(name) => write!(f, "{}", name),
54        }
55    }
56}
57
58/// Represents a cross-reference found in documentation
59#[derive(Debug, Clone)]
60pub struct CrossReference {
61    /// The type of reference
62    pub ref_type: ReferenceType,
63    /// The target being referenced
64    pub target: String,
65    /// Optional display text (different from target)
66    pub display_text: Option<String>,
67    /// Location where the reference was found
68    pub source_location: ReferenceLocation,
69    /// Whether this reference is external (outside current project)
70    pub is_external: bool,
71}
72
73/// Location information for a reference
74#[derive(Debug, Clone)]
75pub struct ReferenceLocation {
76    /// Document name where reference appears
77    pub docname: String,
78    /// Line number in source file
79    pub lineno: Option<usize>,
80    /// Character position in line
81    pub column: Option<usize>,
82    /// Source file path
83    pub source_path: Option<String>,
84}
85
86/// Represents an object within a domain (function, class, etc.)
87#[derive(Debug, Clone)]
88pub struct DomainObject {
89    /// Unique identifier for the object
90    pub id: String,
91    /// Human-readable name
92    pub name: String,
93    /// Type of object
94    pub object_type: String,
95    /// Domain this object belongs to
96    pub domain: String,
97    /// Location where object is defined
98    pub definition_location: ReferenceLocation,
99    /// Full qualified name (e.g., module.class.method)
100    pub qualified_name: String,
101    /// Additional metadata specific to object type
102    pub metadata: HashMap<String, String>,
103    /// Signature for functions/methods
104    pub signature: Option<String>,
105    /// Documentation string
106    pub docstring: Option<String>,
107}
108
109/// Result of reference validation
110#[derive(Debug, Clone)]
111pub struct ReferenceValidationResult {
112    /// The original reference
113    pub reference: CrossReference,
114    /// Whether the reference is valid
115    pub is_valid: bool,
116    /// Target object if reference resolves
117    pub target_object: Option<DomainObject>,
118    /// Error message if validation failed
119    pub error_message: Option<String>,
120    /// Suggestions for fixing broken reference
121    pub suggestions: Vec<String>,
122}
123
124/// Domain validation statistics
125#[derive(Debug, Default)]
126pub struct DomainValidationStats {
127    /// Total references processed
128    pub total_references: usize,
129    /// Valid references
130    pub valid_references: usize,
131    /// Invalid/broken references
132    pub broken_references: usize,
133    /// External references (not validated)
134    pub external_references: usize,
135    /// References by type
136    pub references_by_type: HashMap<ReferenceType, usize>,
137}
138
139/// Core trait for domain validation
140pub trait DomainValidator {
141    /// Get the name of this domain
142    fn domain_name(&self) -> &str;
143
144    /// Get the reference types this domain handles
145    fn supported_reference_types(&self) -> Vec<ReferenceType>;
146
147    /// Register a domain object
148    fn register_object(&mut self, object: DomainObject) -> Result<(), BuildError>;
149
150    /// Resolve a reference to a domain object
151    fn resolve_reference(&self, reference: &CrossReference) -> Option<DomainObject>;
152
153    /// Validate a cross-reference
154    fn validate_reference(&self, reference: &CrossReference) -> ReferenceValidationResult;
155
156    /// Get all objects in this domain
157    fn get_all_objects(&self) -> Vec<&DomainObject>;
158
159    /// Clear all registered objects (for rebuilds)
160    fn clear_objects(&mut self);
161}
162
163/// Registry for managing multiple domains
164pub struct DomainRegistry {
165    /// Map of domain name to validator
166    domains: HashMap<String, Box<dyn DomainValidator>>,
167    /// All cross-references found during parsing
168    cross_references: Vec<CrossReference>,
169    /// Global object registry across all domains
170    global_objects: HashMap<String, DomainObject>,
171}
172
173impl Default for DomainRegistry {
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179impl DomainRegistry {
180    /// Create a new domain registry
181    pub fn new() -> Self {
182        Self {
183            domains: HashMap::new(),
184            cross_references: Vec::new(),
185            global_objects: HashMap::new(),
186        }
187    }
188
189    /// Register a domain validator
190    pub fn register_domain(
191        &mut self,
192        validator: Box<dyn DomainValidator>,
193    ) -> Result<(), BuildError> {
194        let domain_name = validator.domain_name().to_string();
195
196        if self.domains.contains_key(&domain_name) {
197            return Err(BuildError::ValidationError(format!(
198                "Domain '{}' is already registered",
199                domain_name
200            )));
201        }
202
203        self.domains.insert(domain_name, validator);
204        Ok(())
205    }
206
207    /// Add a cross-reference for later validation
208    pub fn add_cross_reference(&mut self, reference: CrossReference) {
209        self.cross_references.push(reference);
210    }
211
212    /// Register an object in the global registry and appropriate domain
213    pub fn register_object(&mut self, object: DomainObject) -> Result<(), BuildError> {
214        // Add to global registry
215        let key = format!("{}:{}", object.domain, object.qualified_name);
216        self.global_objects.insert(key, object.clone());
217
218        // Add to domain-specific registry
219        if let Some(domain) = self.domains.get_mut(&object.domain) {
220            domain.register_object(object)?;
221        } else {
222            return Err(BuildError::ValidationError(format!(
223                "Domain '{}' not found for object '{}'",
224                object.domain, object.name
225            )));
226        }
227
228        Ok(())
229    }
230
231    /// Validate all cross-references
232    pub fn validate_all_references(&self) -> Vec<ReferenceValidationResult> {
233        let mut results = Vec::new();
234
235        for reference in &self.cross_references {
236            let result = self.validate_reference(reference);
237            results.push(result);
238        }
239
240        results
241    }
242
243    /// Validate a single cross-reference
244    pub fn validate_reference(&self, reference: &CrossReference) -> ReferenceValidationResult {
245        // Skip external references
246        if reference.is_external {
247            return ReferenceValidationResult {
248                reference: reference.clone(),
249                is_valid: true, // Assume external refs are valid
250                target_object: None,
251                error_message: None,
252                suggestions: Vec::new(),
253            };
254        }
255
256        // Find appropriate domain validator
257        for domain in self.domains.values() {
258            if domain
259                .supported_reference_types()
260                .contains(&reference.ref_type)
261            {
262                return domain.validate_reference(reference);
263            }
264        }
265
266        // No domain found for this reference type
267        ReferenceValidationResult {
268            reference: reference.clone(),
269            is_valid: false,
270            target_object: None,
271            error_message: Some(format!(
272                "No domain validator found for reference type {:?}",
273                reference.ref_type
274            )),
275            suggestions: Vec::new(),
276        }
277    }
278
279    /// Get validation statistics
280    pub fn get_validation_stats(&self) -> DomainValidationStats {
281        let results = self.validate_all_references();
282        let mut stats = DomainValidationStats {
283            total_references: results.len(),
284            ..Default::default()
285        };
286
287        for result in results {
288            if result.reference.is_external {
289                stats.external_references += 1;
290            } else if result.is_valid {
291                stats.valid_references += 1;
292            } else {
293                stats.broken_references += 1;
294            }
295
296            *stats
297                .references_by_type
298                .entry(result.reference.ref_type.clone())
299                .or_insert(0) += 1;
300        }
301
302        stats
303    }
304
305    /// Get all broken references
306    pub fn get_broken_references(&self) -> Vec<ReferenceValidationResult> {
307        self.validate_all_references()
308            .into_iter()
309            .filter(|r| !r.is_valid && !r.reference.is_external)
310            .collect()
311    }
312
313    /// Clear all data (for rebuilds)
314    pub fn clear(&mut self) {
315        self.cross_references.clear();
316        self.global_objects.clear();
317
318        for domain in self.domains.values_mut() {
319            domain.clear_objects();
320        }
321    }
322
323    /// Get object by qualified name across all domains
324    pub fn get_object(&self, qualified_name: &str) -> Option<&DomainObject> {
325        // Try exact match first
326        if let Some(obj) = self
327            .global_objects
328            .values()
329            .find(|o| o.qualified_name == qualified_name)
330        {
331            return Some(obj);
332        }
333
334        // Try name match without domain prefix
335        self.global_objects
336            .values()
337            .find(|o| o.name == qualified_name)
338    }
339
340    /// Search for objects by name pattern
341    pub fn search_objects(&self, pattern: &str) -> Vec<&DomainObject> {
342        self.global_objects
343            .values()
344            .filter(|obj| obj.name.contains(pattern) || obj.qualified_name.contains(pattern))
345            .collect()
346    }
347}