qlty_coverage/
transformer.rs

1use anyhow::Result;
2use globset::{Glob, GlobSet, GlobSetBuilder};
3use qlty_analysis::utils::fs::path_to_string;
4use qlty_types::tests::v1::{CoverageMetadata, CoverageSummary, FileCoverage};
5use std::{
6    env::current_dir,
7    fmt::Debug,
8    path::{PathBuf, MAIN_SEPARATOR},
9};
10
11pub trait Transformer: Debug + Send + Sync + 'static {
12    fn transform(&self, file_coverage: FileCoverage) -> Option<FileCoverage>;
13    fn clone_box(&self) -> Box<dyn Transformer>;
14}
15
16impl Clone for Box<dyn Transformer> {
17    fn clone(&self) -> Self {
18        self.clone_box()
19    }
20}
21
22#[derive(Debug, Clone)]
23pub struct ComputeSummary {}
24
25impl ComputeSummary {
26    pub fn new() -> Self {
27        Self {}
28    }
29}
30
31impl Transformer for ComputeSummary {
32    fn transform(&self, file_coverage: FileCoverage) -> Option<FileCoverage> {
33        let mut covered = 0;
34        let mut missed = 0;
35        let mut omit = 0;
36
37        for hit in &file_coverage.hits {
38            match hit {
39                -1 => omit += 1,
40                0 => missed += 1,
41                _ => covered += 1,
42            }
43        }
44
45        let mut file_coverage = file_coverage;
46
47        file_coverage.summary = Some(CoverageSummary {
48            covered,
49            missed,
50            omit,
51            total: covered + missed + omit,
52        });
53
54        Some(file_coverage)
55    }
56
57    fn clone_box(&self) -> Box<dyn Transformer> {
58        Box::new(self.clone())
59    }
60}
61
62#[derive(Debug, Clone)]
63pub struct AppendMetadata {
64    metadata: CoverageMetadata,
65}
66
67impl AppendMetadata {
68    pub fn new(metadata: &CoverageMetadata) -> Self {
69        Self {
70            metadata: metadata.clone(),
71        }
72    }
73}
74
75impl Transformer for AppendMetadata {
76    fn transform(&self, file_coverage: FileCoverage) -> Option<FileCoverage> {
77        let mut file_coverage = file_coverage;
78        file_coverage.build_id = self.metadata.build_id.clone();
79        file_coverage.tag = self.metadata.tag.clone();
80        file_coverage.branch = self.metadata.branch.clone();
81        file_coverage.commit_sha = Some(self.metadata.commit_sha.clone());
82        file_coverage.uploaded_at = self.metadata.uploaded_at.clone();
83
84        if self.metadata.pull_request_number != String::default() {
85            file_coverage.pull_request_number = Some(self.metadata.pull_request_number.clone());
86        }
87
88        Some(file_coverage)
89    }
90
91    fn clone_box(&self) -> Box<dyn Transformer> {
92        Box::new(self.clone())
93    }
94}
95
96#[derive(Debug, Clone)]
97pub struct IgnorePaths {
98    glob_set: GlobSet,
99}
100
101impl IgnorePaths {
102    pub fn new(paths: &[String]) -> Result<Self> {
103        let mut builder = GlobSetBuilder::new();
104
105        for glob in paths {
106            builder.add(Glob::new(&glob)?);
107        }
108
109        Ok(Self {
110            glob_set: builder.build()?,
111        })
112    }
113}
114
115impl Transformer for IgnorePaths {
116    fn transform(&self, file_coverage: FileCoverage) -> Option<FileCoverage> {
117        if self.glob_set.is_match(&file_coverage.path) {
118            None
119        } else {
120            Some(file_coverage)
121        }
122    }
123
124    fn clone_box(&self) -> Box<dyn Transformer> {
125        Box::new(self.clone())
126    }
127}
128
129#[derive(Debug, Clone)]
130pub struct AddPrefix {
131    prefix: String,
132}
133
134impl AddPrefix {
135    pub fn new(prefix: &str) -> Self {
136        Self {
137            prefix: prefix.to_owned(),
138        }
139    }
140}
141
142impl Transformer for AddPrefix {
143    fn transform(&self, file_coverage: FileCoverage) -> Option<FileCoverage> {
144        let mut file_coverage = file_coverage;
145        file_coverage.path = format!("{}{}", self.prefix, file_coverage.path);
146        Some(file_coverage)
147    }
148
149    fn clone_box(&self) -> Box<dyn Transformer> {
150        Box::new(self.clone())
151    }
152}
153
154#[derive(Debug, Clone)]
155pub struct StripPrefix {
156    prefix: String,
157}
158
159impl Default for StripPrefix {
160    fn default() -> Self {
161        Self {
162            prefix: format!(
163                "{}{}",
164                path_to_string(current_dir().unwrap_or_else(|_| PathBuf::from("."))),
165                MAIN_SEPARATOR
166            ),
167        }
168    }
169}
170
171impl StripPrefix {
172    pub fn new(prefix: String) -> Self {
173        Self { prefix: prefix }
174    }
175}
176
177impl Transformer for StripPrefix {
178    fn transform(&self, file_coverage: FileCoverage) -> Option<FileCoverage> {
179        let mut file_coverage = file_coverage;
180        file_coverage.path = file_coverage.path.replacen(&self.prefix, "", 1);
181        Some(file_coverage)
182    }
183
184    fn clone_box(&self) -> Box<dyn Transformer> {
185        Box::new(self.clone())
186    }
187}
188
189#[derive(Debug, Clone)]
190pub struct StripDotSlashPrefix;
191
192impl Transformer for StripDotSlashPrefix {
193    fn transform(&self, file_coverage: FileCoverage) -> Option<FileCoverage> {
194        let mut file_coverage = file_coverage;
195        if file_coverage.path.starts_with("./") {
196            file_coverage.path = file_coverage.path[2..].to_string();
197        }
198        Some(file_coverage)
199    }
200
201    fn clone_box(&self) -> Box<dyn Transformer> {
202        Box::new(Self)
203    }
204}