use crate::core::glob::Pattern;
use crate::core::target::Target;
pub trait Action: Sized {
fn from_token(token: &str) -> Option<Self>;
}
impl Action for String {
fn from_token(token: &str) -> Option<Self> {
Some(token.to_owned())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Directive<A = String> {
pub action: A,
pub kind: Option<String>,
pub name: Pattern,
pub path: Option<Pattern>,
pub note: Option<String>,
}
impl<A> Directive<A> {
pub fn matches(&self, t: &impl Target) -> bool {
if let Some(k) = &self.kind {
if t.qualifier() != Some(k.as_str()) {
return false;
}
}
if !t.matches_name(&self.name) {
return false;
}
if let Some(p) = &self.path {
if !t.matches_scope(p) {
return false;
}
}
true
}
}
impl<A: std::fmt::Display> std::fmt::Display for Directive<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:", self.action)?;
if let Some(k) = &self.kind {
write!(f, "<{k}>")?;
}
write!(f, "{}", self.name)?;
if let Some(p) = &self.path {
write!(f, "@{p}")?;
}
if let Some(n) = &self.note {
write!(f, "={n}")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::core::glob::Pattern;
use crate::core::parse::parse;
use crate::core::target::Target;
struct T {
q: Option<&'static str>,
names: Vec<&'static str>,
scopes: Vec<&'static str>,
}
impl Target for T {
fn qualifier(&self) -> Option<&str> {
self.q
}
fn matches_name(&self, p: &Pattern) -> bool {
self.names.iter().any(|n| p.matches(n))
}
fn matches_scope(&self, p: &Pattern) -> bool {
self.scopes.iter().any(|s| p.matches(s))
}
}
#[test]
fn matches_any_alias() {
let t = T {
q: None,
names: vec!["Foo.bar", "Baz.bar"],
scopes: vec![],
};
assert!(parse("suppress:*.bar").unwrap().matches(&t));
assert!(!parse("suppress:*.qux").unwrap().matches(&t));
}
#[test]
fn kind_must_match_exactly() {
let t = T {
q: Some("function"),
names: vec!["foo"],
scopes: vec![],
};
assert!(!parse("suppress:<method>foo").unwrap().matches(&t));
assert!(parse("suppress:<function>foo").unwrap().matches(&t));
}
#[test]
fn path_matches_any_scope() {
let t = T {
q: None,
names: vec!["foo"],
scopes: vec!["src/a.rs", "src/tests/b.rs", "src/c.rs"],
};
assert!(parse("suppress:foo@*/tests/*").unwrap().matches(&t));
assert!(!parse("suppress:foo@*/bench/*").unwrap().matches(&t));
}
#[test]
fn name_only_ignores_kind_and_path() {
let t = T {
q: Some("anything"),
names: vec!["foo"],
scopes: vec!["wherever"],
};
assert!(parse("suppress:foo").unwrap().matches(&t));
}
#[test]
fn joined_alias_form_is_targets_choice() {
let t = T {
q: None,
names: vec!["Foo.bar", "Baz.bar", "Foo.bar/Baz.bar"],
scopes: vec![],
};
assert!(parse("suppress:Foo.bar/Baz.bar").unwrap().matches(&t));
}
}