Skip to main content

debtmap/risk/lcov/
types.rs

1//! Core data types for LCOV coverage data.
2//!
3//! This module contains the foundational data structures used throughout the LCOV
4//! parsing and querying system. All types are pure data structures with no I/O
5//! operations or side effects.
6//!
7//! # Stillwater Philosophy
8//!
9//! This module represents the "still water" - immutable data definitions that
10//! flow through the system. No dependencies on other lcov modules ensures
11//! this can be the foundation layer.
12//!
13//! # Types
14//!
15//! - [`CoverageProgress`] - Progress state during LCOV file parsing
16//! - [`NormalizedFunctionName`] - Normalized function name with matching variants
17//! - [`FunctionCoverage`] - Coverage data for a single function
18//! - [`LcovData`] - Parsed LCOV coverage data container
19
20use super::super::coverage_index::CoverageIndex;
21use std::collections::HashMap;
22use std::path::PathBuf;
23use std::sync::Arc;
24
25/// Progress state during LCOV file parsing.
26///
27/// Used to report progress to callers during potentially long-running
28/// LCOV file parsing operations.
29///
30/// # Example
31///
32/// ```ignore
33/// use debtmap::risk::lcov::CoverageProgress;
34///
35/// fn report_progress(progress: CoverageProgress) {
36///     match progress {
37///         CoverageProgress::Initializing => println!("Opening file..."),
38///         CoverageProgress::Parsing { current, total } => {
39///             println!("Processing file {} of {}", current, total);
40///         }
41///         CoverageProgress::ComputingStats => println!("Computing statistics..."),
42///         CoverageProgress::Complete => println!("Done!"),
43///     }
44/// }
45/// ```
46#[derive(Debug, Clone, Copy)]
47pub enum CoverageProgress {
48    /// Opening and initializing LCOV file reader
49    Initializing,
50    /// Parsing coverage records with file count progress
51    /// (current_file, total_files_seen_so_far)
52    Parsing { current: usize, total: usize },
53    /// Computing final coverage statistics
54    ComputingStats,
55    /// Parsing complete
56    Complete,
57}
58
59/// Normalized function name with multiple matching variants.
60///
61/// When matching function names between AST-derived names and LCOV coverage data,
62/// various transformations may be needed. This struct captures both the normalized
63/// form and extracted components to enable flexible matching strategies.
64///
65/// # Fields
66///
67/// * `full_path` - Full normalized path like "module::Struct::method"
68/// * `method_name` - Just the method name like "method"
69/// * `original` - Original demangled name for debugging
70///
71/// # Example
72///
73/// ```ignore
74/// use debtmap::risk::lcov::NormalizedFunctionName;
75///
76/// let name = NormalizedFunctionName::simple("my_function");
77/// assert_eq!(name.method_name, "my_function");
78/// ```
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct NormalizedFunctionName {
81    /// Full normalized path: "module::Struct::method"
82    pub full_path: String,
83
84    /// Just the method name: "method"
85    pub method_name: String,
86
87    /// Original demangled name (for debugging)
88    pub original: String,
89}
90
91impl NormalizedFunctionName {
92    /// Create a simple NormalizedFunctionName for testing.
93    ///
94    /// Sets all fields to the same value for simple cases where
95    /// no normalization is needed.
96    ///
97    /// # Example
98    ///
99    /// ```ignore
100    /// let name = NormalizedFunctionName::simple("test_func");
101    /// assert_eq!(name.full_path, "test_func");
102    /// assert_eq!(name.method_name, "test_func");
103    /// assert_eq!(name.original, "test_func");
104    /// ```
105    pub fn simple(name: &str) -> Self {
106        Self {
107            full_path: name.to_string(),
108            method_name: name.to_string(),
109            original: name.to_string(),
110        }
111    }
112}
113
114/// Coverage data for a single function.
115///
116/// Contains all coverage-related information for a function extracted from
117/// LCOV data, including execution counts and line-level coverage details.
118///
119/// # Fields
120///
121/// * `name` - Function name (normalized form)
122/// * `start_line` - Line number where function starts
123/// * `execution_count` - Number of times function was executed
124/// * `coverage_percentage` - Percentage of lines covered (0.0 to 100.0)
125/// * `uncovered_lines` - List of line numbers that weren't covered
126/// * `normalized` - Normalized name variants for matching
127#[derive(Debug, Clone)]
128pub struct FunctionCoverage {
129    /// Function name (normalized form)
130    pub name: String,
131    /// Line number where function starts
132    pub start_line: usize,
133    /// Number of times function was executed
134    pub execution_count: u64,
135    /// Percentage of lines covered (0.0 to 100.0)
136    pub coverage_percentage: f64,
137    /// List of line numbers that weren't covered
138    pub uncovered_lines: Vec<usize>,
139    /// Normalized name variants for matching (not serialized)
140    pub normalized: NormalizedFunctionName,
141}
142
143/// Parsed LCOV coverage data.
144///
145/// Contains all coverage information extracted from an LCOV file, organized
146/// by file path for efficient lookup. Includes a pre-built index for O(1)
147/// function coverage lookups.
148///
149/// # Thread Safety
150///
151/// The `coverage_index` is wrapped in `Arc` for lock-free sharing across
152/// threads during parallel analysis operations.
153///
154/// # Example
155///
156/// ```ignore
157/// use std::path::Path;
158/// use debtmap::risk::lcov::{parse_lcov_file, LcovData};
159///
160/// let data = parse_lcov_file(Path::new("coverage.info"))?;
161/// println!("Total lines: {}", data.total_lines);
162/// println!("Lines hit: {}", data.lines_hit);
163/// println!("Coverage: {:.1}%", data.get_overall_coverage());
164/// ```
165#[derive(Debug, Clone)]
166pub struct LcovData {
167    /// Map of file paths to their function coverage data
168    pub functions: HashMap<PathBuf, Vec<FunctionCoverage>>,
169    /// Total number of executable lines across all files
170    pub total_lines: usize,
171    /// Number of lines that were executed
172    pub lines_hit: usize,
173    /// LOC counter instance for consistent line counting across analysis modes
174    pub(crate) loc_counter: Option<crate::metrics::LocCounter>,
175    /// Pre-built index for O(1) function coverage lookups,
176    /// wrapped in Arc for lock-free sharing across threads
177    pub(crate) coverage_index: Arc<CoverageIndex>,
178}
179
180impl Default for LcovData {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186impl LcovData {
187    /// Create a new empty LcovData instance.
188    ///
189    /// # Example
190    ///
191    /// ```ignore
192    /// let data = LcovData::new();
193    /// assert_eq!(data.total_lines, 0);
194    /// assert!(data.functions.is_empty());
195    /// ```
196    pub fn new() -> Self {
197        Self {
198            functions: HashMap::new(),
199            total_lines: 0,
200            lines_hit: 0,
201            loc_counter: None,
202            coverage_index: Arc::new(CoverageIndex::empty()),
203        }
204    }
205
206    /// Build the coverage index from current function data.
207    ///
208    /// This should be called after modifying the functions HashMap
209    /// to ensure the index is up to date for O(1) lookups.
210    pub fn build_index(&mut self) {
211        self.coverage_index = Arc::new(CoverageIndex::from_coverage(self));
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_normalized_function_name_simple() {
221        let name = NormalizedFunctionName::simple("test_func");
222        assert_eq!(name.full_path, "test_func");
223        assert_eq!(name.method_name, "test_func");
224        assert_eq!(name.original, "test_func");
225    }
226
227    #[test]
228    fn test_lcov_data_default() {
229        let data = LcovData::default();
230        assert_eq!(data.total_lines, 0);
231        assert_eq!(data.lines_hit, 0);
232        assert!(data.functions.is_empty());
233    }
234
235    #[test]
236    fn test_coverage_progress_debug() {
237        // Ensure CoverageProgress variants can be debug printed
238        let progress = CoverageProgress::Parsing {
239            current: 5,
240            total: 10,
241        };
242        let debug_str = format!("{:?}", progress);
243        assert!(debug_str.contains("Parsing"));
244        assert!(debug_str.contains("5"));
245        assert!(debug_str.contains("10"));
246    }
247}