use deps_core::edit::UpdateKind;
use deps_core::lsp_helpers::EcosystemFormatter;
use crate::config::IgnoreRule;
use crate::update::SkipReason;
#[derive(Debug, Default)]
pub struct IgnoreRules {
rules: Vec<(String, Option<Vec<crate::config::UpdateTypeToken>>)>,
}
impl IgnoreRules {
#[must_use]
pub const fn empty() -> Self {
Self { rules: Vec::new() }
}
#[must_use]
pub fn new(rules: Vec<IgnoreRule>, formatter: &dyn EcosystemFormatter) -> Self {
let rules = rules
.into_iter()
.map(|rule| {
let normalized =
formatter.normalize_package_name(&deps_core::PackageName::new(rule.name));
(normalized, rule.update_types)
})
.collect();
Self { rules }
}
#[must_use]
pub fn matches_name(&self, normalized_name: &str) -> bool {
self.rules.iter().any(|(name, _)| name == normalized_name)
}
#[must_use]
pub fn skip_reason(&self, normalized_name: &str, kind: UpdateKind) -> Option<SkipReason> {
let (_, update_types) = self
.rules
.iter()
.find(|(name, _)| name == normalized_name)?;
let matches = match update_types {
None => true,
Some(types) => kind == UpdateKind::Unknown || types.iter().any(|t| t.matches(kind)),
};
matches.then_some(SkipReason::IgnoreRule)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::UpdateTypeToken;
const STUB_FORMATTER: deps_core::test_util::StubFormatter =
deps_core::test_util::StubFormatter::new().with_package_url_prefix("");
#[test]
fn test_empty_rules_never_skip() {
let rules = IgnoreRules::empty();
assert_eq!(rules.skip_reason("tokio", UpdateKind::Major), None);
}
#[test]
fn test_rule_with_no_update_types_matches_every_kind() {
let rules = IgnoreRules::new(
vec![IgnoreRule {
name: "legacy-thing".to_string(),
update_types: None,
}],
&STUB_FORMATTER,
);
assert_eq!(
rules.skip_reason("legacy-thing", UpdateKind::Patch),
Some(SkipReason::IgnoreRule)
);
assert_eq!(
rules.skip_reason("legacy-thing", UpdateKind::Unknown),
Some(SkipReason::IgnoreRule)
);
}
#[test]
fn test_scoped_rule_only_matches_listed_kind() {
let rules = IgnoreRules::new(
vec![IgnoreRule {
name: "tokio".to_string(),
update_types: Some(vec![UpdateTypeToken::Major]),
}],
&STUB_FORMATTER,
);
assert_eq!(
rules.skip_reason("tokio", UpdateKind::Major),
Some(SkipReason::IgnoreRule)
);
assert_eq!(rules.skip_reason("tokio", UpdateKind::Minor), None);
assert_eq!(rules.skip_reason("tokio", UpdateKind::Patch), None);
}
#[test]
fn test_scoped_rule_fail_closed_on_unknown_kind() {
let rules = IgnoreRules::new(
vec![IgnoreRule {
name: "tokio".to_string(),
update_types: Some(vec![UpdateTypeToken::Major]),
}],
&STUB_FORMATTER,
);
assert_eq!(
rules.skip_reason("tokio", UpdateKind::Unknown),
Some(SkipReason::IgnoreRule)
);
}
#[test]
fn test_non_matching_name_is_never_skipped() {
let rules = IgnoreRules::new(
vec![IgnoreRule {
name: "tokio".to_string(),
update_types: None,
}],
&STUB_FORMATTER,
);
assert_eq!(rules.skip_reason("serde", UpdateKind::Major), None);
}
}