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}