Skip to main content

covguard_paths/
lib.rs

1//! Shared path normalization utilities used by multiple adapters.
2//!
3//! The crate intentionally keeps behavior small and deterministic:
4//! - convert backslashes to forward slashes
5//! - normalize diff-like `a/` and `b/` prefixes
6//! - handle leading `./` and common absolute-path stripping cases
7
8const COMMON_SOURCE_MARKERS: [&str; 4] = ["/src/", "/lib/", "/test/", "/tests/"];
9
10/// Normalize a repository-relative path as seen in unified diff headers.
11///
12/// Behavior:
13/// - trim outer whitespace
14/// - convert backslashes to forward slashes
15/// - strip `a/` or `b/` prefix
16/// - strip leading `./` (once)
17pub fn normalize_diff_path(path: &str) -> String {
18    let path = path.trim();
19    let path = path.replace('\\', "/");
20
21    let path = path
22        .strip_prefix("b/")
23        .or_else(|| path.strip_prefix("a/"))
24        .unwrap_or(&path);
25
26    path.strip_prefix("./").unwrap_or(path).to_string()
27}
28
29/// Normalize an LCOV/coverage path with optional configured strip prefixes.
30pub fn normalize_coverage_path_with_strip(path: &str, strip_prefixes: &[String]) -> String {
31    let mut normalized = path.replace('\\', "/");
32
33    for prefix in strip_prefixes {
34        let prefix_norm = prefix.replace('\\', "/");
35        if normalized.starts_with(&prefix_norm) {
36            normalized = normalized[prefix_norm.len()..].to_string();
37            break;
38        }
39    }
40
41    while normalized.starts_with("./") {
42        normalized = normalized[2..].to_string();
43    }
44
45    if normalized.starts_with('/') {
46        for marker in &COMMON_SOURCE_MARKERS {
47            if let Some(pos) = normalized.find(marker) {
48                normalized = normalized[pos + 1..].to_string();
49                break;
50            }
51        }
52    }
53
54    if normalized.len() > 2 && normalized.chars().nth(1) == Some(':') {
55        for marker in &COMMON_SOURCE_MARKERS {
56            if let Some(pos) = normalized.find(marker) {
57                normalized = normalized[pos + 1..].to_string();
58                break;
59            }
60        }
61    }
62
63    normalized
64}
65
66/// Normalize a simple coverage path without configured prefixes.
67pub fn normalize_coverage_path(path: &str) -> String {
68    normalize_coverage_path_with_strip(path, &[])
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_normalize_diff_path() {
77        assert_eq!(normalize_diff_path("b/src/lib.rs"), "src/lib.rs");
78        assert_eq!(normalize_diff_path("a/src/lib.rs"), "src/lib.rs");
79        assert_eq!(normalize_diff_path("./src/lib.rs"), "src/lib.rs");
80        assert_eq!(normalize_diff_path("src\\lib.rs"), "src/lib.rs");
81        assert_eq!(normalize_diff_path("b/./src/lib.rs"), "src/lib.rs");
82        assert_eq!(normalize_diff_path("./b/src/lib.rs"), "b/src/lib.rs");
83    }
84
85    #[test]
86    fn test_normalize_coverage_paths() {
87        assert_eq!(normalize_coverage_path("src/lib.rs"), "src/lib.rs");
88        assert_eq!(normalize_coverage_path("./src/lib.rs"), "src/lib.rs");
89        assert_eq!(normalize_coverage_path("././src/lib.rs"), "src/lib.rs");
90        assert_eq!(normalize_coverage_path("src\\lib.rs"), "src/lib.rs");
91        assert_eq!(
92            normalize_coverage_path("src\\sub\\lib.rs"),
93            "src/sub/lib.rs"
94        );
95        assert_eq!(
96            normalize_coverage_path("/home/user/project/src/lib.rs"),
97            "src/lib.rs"
98        );
99        assert_eq!(
100            normalize_coverage_path("C:\\Users\\user\\project\\src\\lib.rs"),
101            "src/lib.rs"
102        );
103    }
104}