tiger_lib/report/filter.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
use std::path::PathBuf;
use crate::block::Comparator;
use crate::fileset::FileKind;
use crate::report::{Confidence, ErrorKey, LogReport, Severity};
use crate::token::Loc;
/// Determines whether a given Report should be printed.
/// If a report is matched by both the blacklist and the whitelist, it will not be printed.
#[derive(Default, Debug)]
pub struct ReportFilter {
/// Whether to log errors in vanilla CK3 files
pub show_vanilla: bool,
/// Whether to log errors in other loaded mods
pub show_loaded_mods: bool,
/// A complex trigger that evaluates a report to assess whether it should be printed.
pub predicate: FilterRule,
}
impl ReportFilter {
/// Returns true iff the report should be printed.
/// A print will be rejected if the report matches at least one of the following conditions:
/// - Its Severity or Confidence level is too low.
/// - It's from vanilla or a loaded mod and the program is configured to ignore those locations.
/// - The filter has a trigger, and the report doesn't match it.
pub fn should_print_report(&self, report: &LogReport) -> bool {
if report.key == ErrorKey::Config {
// Any errors concerning the Config should be easy to fix and will fundamentally
// undermine the operation of the application. They must always be printed.
return true;
}
// If every single Loc in the chain is out of scope, the report is out of scope.
let out_of_scope = report.pointers.iter().map(|p| &p.loc).all(|loc| {
(loc.kind.counts_as_vanilla() && !self.show_vanilla)
|| (matches!(loc.kind, FileKind::LoadedMod(_)) && !self.show_loaded_mods)
});
if out_of_scope {
return false;
}
self.predicate.apply(report)
}
/// TODO: Check the filter rules to be more sure.
pub fn should_maybe_print(&self, key: ErrorKey, loc: Loc) -> bool {
if key == ErrorKey::Config {
// Any errors concerning the Config should be easy to fix and will fundamentally
// undermine the operation of the application. They must always be printed.
return true;
}
if (loc.kind.counts_as_vanilla() && !self.show_vanilla)
|| (matches!(loc.kind, FileKind::LoadedMod(_)) && !self.show_loaded_mods)
{
return false;
}
true
}
}
#[derive(Default, Debug)]
pub enum FilterRule {
/// Always true.
#[default]
Tautology,
/// Always false.
Contradiction,
/// Configured by the AND-key. The top-level rule is always a conjunction.
/// Reports must match all enclosed rules to match the conjunction.
Conjunction(Vec<FilterRule>),
/// Configured by the OR-key.
/// Reports must match at least one of the enclosed rules to match the disjunction.
Disjunction(Vec<FilterRule>),
/// Configured by the NOT-key.
/// Reports must not match the enclosed rule to match the negation.
Negation(Box<FilterRule>),
/// Reports must be within the given Severity range to match the rule.
/// The condition is built like `severity >= error` in the filter trigger.
Severity(Comparator, Severity),
/// Reports must be within the given Confidence range to match the rule.
/// The condition is built like `confidence > weak` in the filter trigger.
Confidence(Comparator, Confidence),
/// The report's `ErrorKey` must be the listed key for the report to match the rule.
Key(ErrorKey),
/// The report's pointers must contain the given file for the report to match the rule.
File(PathBuf),
/// The report's msg must contain the given text for the report to match the rule.
Text(String),
}
impl FilterRule {
fn apply(&self, report: &LogReport) -> bool {
match self {
FilterRule::Tautology => true,
FilterRule::Contradiction => false,
FilterRule::Conjunction(children) => children.iter().all(|child| child.apply(report)),
FilterRule::Disjunction(children) => children.iter().any(|child| child.apply(report)),
FilterRule::Negation(child) => !child.apply(report),
FilterRule::Severity(comparator, level) => match comparator {
Comparator::Equals(_) => report.severity == *level,
Comparator::NotEquals => report.severity != *level,
Comparator::GreaterThan => report.severity > *level,
Comparator::AtLeast => report.severity >= *level,
Comparator::LessThan => report.severity < *level,
Comparator::AtMost => report.severity <= *level,
},
FilterRule::Confidence(comparator, level) => match comparator {
Comparator::Equals(_) => report.confidence == *level,
Comparator::NotEquals => report.confidence != *level,
Comparator::GreaterThan => report.confidence > *level,
Comparator::AtLeast => report.confidence >= *level,
Comparator::LessThan => report.confidence < *level,
Comparator::AtMost => report.confidence <= *level,
},
FilterRule::Key(key) => report.key == *key,
FilterRule::File(path) => {
report.pointers.iter().any(|pointer| pointer.loc.pathname().starts_with(path))
}
FilterRule::Text(s) => {
report.msg.to_ascii_lowercase().contains(&s.to_ascii_lowercase())
}
}
}
}