use crate::core::{Action, Directive, Ladder, Target};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum LintAction {
Suppress,
Deescalate,
Escalate,
Note,
Set,
}
impl Action for LintAction {
fn from_token(token: &str) -> Option<Self> {
match token.trim().to_ascii_lowercase().replace('-', "").as_str() {
"suppress" => Some(Self::Suppress),
"deescalate" => Some(Self::Deescalate),
"escalate" => Some(Self::Escalate),
"note" => Some(Self::Note),
"settings" | "set" => Some(Self::Set),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Severity {
Error,
Warning,
Info,
}
impl Severity {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Error => "ERROR",
Self::Warning => "WARNING",
Self::Info => "INFO",
}
}
}
#[must_use]
pub fn ladder() -> Ladder<Severity> {
Ladder::new(vec![Severity::Error, Severity::Warning, Severity::Info])
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Folded {
pub severity: Severity,
pub dropped: bool,
pub notes: Vec<String>,
}
#[must_use]
pub fn fold(
current: Severity,
target: &impl Target,
directives: &[Directive<LintAction>],
) -> Folded {
let mut notes = Vec::new();
let mut step = 0i32;
let mut dropped = false;
for d in directives {
if d.action == LintAction::Set || !d.matches(target) {
continue;
}
if let Some(n) = &d.note {
notes.push(n.clone());
}
match d.action {
LintAction::Deescalate => step += 1,
LintAction::Escalate => step -= 1,
LintAction::Suppress => dropped = true,
LintAction::Note | LintAction::Set => {}
}
}
Folded {
severity: ladder().step(¤t, step),
dropped,
notes,
}
}
#[must_use]
pub fn extract_settings(directives: &[Directive<LintAction>]) -> Vec<(String, String)> {
directives
.iter()
.filter(|d| d.action == LintAction::Set)
.map(|d| {
(
d.name.as_str().to_owned(),
d.note.clone().unwrap_or_default(),
)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::{extract_settings, fold, ladder, LintAction, Severity};
use crate::core::{parse_as, Action, Directive, Pattern, Target};
struct T {
names: Vec<&'static str>,
}
impl Target for T {
fn matches_name(&self, p: &Pattern) -> bool {
self.names.iter().any(|n| p.matches(n))
}
}
fn dirs(specs: &[&str]) -> Vec<Directive<LintAction>> {
specs
.iter()
.map(|s| parse_as::<LintAction>(s).unwrap())
.collect()
}
#[test]
fn from_token_alias_matrix() {
for (tok, want) in [
("suppress", LintAction::Suppress),
("Suppress", LintAction::Suppress),
("de-escalate", LintAction::Deescalate),
("deescalate", LintAction::Deescalate),
("DE-ESCALATE", LintAction::Deescalate),
("escalate", LintAction::Escalate),
("note", LintAction::Note),
("settings", LintAction::Set),
("set", LintAction::Set),
] {
assert_eq!(LintAction::from_token(tok), Some(want), "{tok}");
}
assert_eq!(LintAction::from_token("supress"), None); }
#[test]
fn ladder_directions() {
let l = ladder();
assert_eq!(l.step(&Severity::Error, 1), Severity::Warning);
assert_eq!(l.step(&Severity::Warning, -1), Severity::Error);
}
#[test]
fn notes_accumulate_including_suppress() {
let t = T { names: vec!["foo"] };
let f = fold(
Severity::Error,
&t,
&dirs(&["note:foo=keep", "suppress:foo=because"]),
);
assert_eq!(f.notes, vec!["keep".to_owned(), "because".to_owned()]);
assert!(f.dropped);
}
#[test]
fn severity_steps_sum_and_clamp() {
let t = T { names: vec!["foo"] };
let f = fold(
Severity::Error,
&t,
&dirs(&["de-escalate:foo", "de-escalate:foo"]),
);
assert_eq!(f.severity, Severity::Info);
let f = fold(
Severity::Warning,
&t,
&dirs(&["escalate:foo", "de-escalate:foo"]),
);
assert_eq!(f.severity, Severity::Warning);
}
#[test]
fn set_directives_skipped_in_fold() {
let t = T { names: vec!["foo"] };
let f = fold(Severity::Error, &t, &dirs(&["set:foo=1"]));
assert_eq!(f.severity, Severity::Error);
assert!(!f.dropped);
assert!(f.notes.is_empty());
}
#[test]
fn extract_settings_pairs() {
let d = dirs(&["set:max-name-group=256", "suppress:foo", "set:gpu=on"]);
assert_eq!(
extract_settings(&d),
vec![
("max-name-group".to_owned(), "256".to_owned()),
("gpu".to_owned(), "on".to_owned()),
]
);
}
#[test]
fn duplicate_directive_compounds() {
let t = T { names: vec!["foo"] };
let single = fold(Severity::Error, &t, &dirs(&["de-escalate:foo"]));
let double = fold(
Severity::Error,
&t,
&dirs(&["de-escalate:foo", "de-escalate:foo"]),
);
assert_eq!(single.severity, Severity::Warning);
assert_eq!(double.severity, Severity::Info);
}
}