ktrs_cli/ktlint/baseline/
mod.rs1pub mod xml;
4
5use std::collections::HashMap;
6use std::path::Path;
7
8use crate::ktlint::console::Console;
9use crate::ktlint::logger::{BASELINE, Logger};
10use crate::ktlint::reporter::{KtlintCliError, Status};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum BaselineStatus {
14 Disabled,
16 Valid,
17 NotFound,
19 Invalid,
21}
22
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct Baseline {
25 pub path: Option<String>,
26 pub status: BaselineStatus,
27 pub lint_errors_per_file: HashMap<String, Vec<KtlintCliError>>,
29}
30
31impl Baseline {
32 pub fn disabled() -> Baseline {
33 Baseline { path: None, status: BaselineStatus::Disabled, lint_errors_per_file: HashMap::new() }
34 }
35
36 fn with_status(path: &str, status: BaselineStatus) -> Baseline {
37 Baseline { path: Some(path.to_owned()), status, lint_errors_per_file: HashMap::new() }
38 }
39}
40
41struct LoadError {
43 message: String,
44 fatal_error: Option<xml::XmlError>,
45}
46
47pub fn load_baseline(path: &str, working_dir: &Path, logger: &Logger, console: &Console) -> Baseline {
50 let baseline_path = Some(working_dir.join(path)).filter(|p| p.exists());
51 match load(path, baseline_path.as_deref()) {
52 Ok(baseline) => baseline,
53 Err(e) => {
54 if let Some(fatal) = &e.fatal_error {
55 console.println_err(&format!("[Fatal Error] :{}:{}: {}", fatal.line, fatal.col, fatal.message));
56 }
57 if let Some(file) = &baseline_path {
58 let _ = std::fs::remove_file(file);
59 }
60 logger.error(BASELINE, || e.message);
61 Baseline::with_status(path, BaselineStatus::Invalid)
62 }
63 }
64}
65
66fn load(path: &str, baseline_path: Option<&Path>) -> Result<Baseline, LoadError> {
67 if path.trim().is_empty() {
68 return Err(LoadError {
69 message: "Path for loading baseline may not be blank or empty".to_owned(),
70 fatal_error: None,
71 });
72 }
73 let Some(baseline_file) = baseline_path else {
74 return Ok(Baseline::with_status(path, BaselineStatus::NotFound));
75 };
76 let unable_to_parse = |fatal_error| LoadError { message: format!("Unable to parse baseline file: {path}"), fatal_error };
77 let bytes = std::fs::read(baseline_file).map_err(|_| unable_to_parse(None))?;
78 let document = xml::parse_document(&String::from_utf8_lossy(&bytes)).map_err(|e| unable_to_parse(Some(e)))?;
79 let lint_errors_per_file = parse_baseline(&document)
80 .map_err(|message| LoadError { message, fatal_error: None })?;
81 Ok(Baseline { path: Some(path.to_owned()), status: BaselineStatus::Valid, lint_errors_per_file })
82}
83
84fn parse_baseline(document: &xml::Element) -> Result<HashMap<String, Vec<KtlintCliError>>, String> {
85 let mut lint_errors_per_file = HashMap::new();
86 let mut files = Vec::new();
87 document.elements_by_tag_name("file", &mut files);
88 for file in files {
89 let mut errors = Vec::new();
90 file.elements_by_tag_name("error", &mut errors);
91 let errors = errors.into_iter().map(parse_baseline_error_element).collect::<Result<Vec<_>, _>>()?;
92 lint_errors_per_file.insert(file.attribute("name").to_owned(), errors);
93 }
94 Ok(lint_errors_per_file)
95}
96
97fn parse_baseline_error_element(element: &xml::Element) -> Result<KtlintCliError, String> {
98 Ok(KtlintCliError {
99 line: to_int(element.attribute("line"))?,
100 col: to_int(element.attribute("column"))?,
101 rule_id: element.attribute("source").to_owned(),
102 detail: String::new(),
103 status: Status::BaselineIgnored,
104 })
105}
106
107fn to_int(value: &str) -> Result<usize, String> {
110 value
111 .strip_prefix('+')
112 .unwrap_or(value)
113 .parse::<u32>()
114 .ok()
115 .filter(|&v| v <= i32::MAX as u32 && !value.starts_with("++"))
116 .map(|v| v as usize)
117 .ok_or_else(|| format!("For input string: \"{value}\""))
118}
119
120pub fn contains_lint_error(list: &[KtlintCliError], ktlint_cli_error: &KtlintCliError) -> bool {
122 list.iter().any(|e| is_same_as(e, ktlint_cli_error))
123}
124
125pub fn does_not_contain(list: &[KtlintCliError], ktlint_cli_error: &KtlintCliError) -> bool {
127 !contains_lint_error(list, ktlint_cli_error)
128}
129
130fn is_same_as(a: &KtlintCliError, b: &KtlintCliError) -> bool {
131 a.col == b.col && a.line == b.line && a.rule_id == b.rule_id
132}