deps_cli/update/
ignore.rs1use deps_core::edit::UpdateKind;
4use deps_core::lsp_helpers::EcosystemFormatter;
5
6use crate::config::IgnoreRule;
7use crate::update::SkipReason;
8
9#[derive(Debug, Default)]
15pub struct IgnoreRules {
16 rules: Vec<(String, Option<Vec<crate::config::UpdateTypeToken>>)>,
17}
18
19impl IgnoreRules {
20 #[must_use]
22 pub const fn empty() -> Self {
23 Self { rules: Vec::new() }
24 }
25
26 #[must_use]
29 pub fn new(rules: Vec<IgnoreRule>, formatter: &dyn EcosystemFormatter) -> Self {
30 let rules = rules
31 .into_iter()
32 .map(|rule| {
33 let normalized =
34 formatter.normalize_package_name(&deps_core::PackageName::new(rule.name));
35 (normalized, rule.update_types)
36 })
37 .collect();
38 Self { rules }
39 }
40
41 #[must_use]
46 pub fn matches_name(&self, normalized_name: &str) -> bool {
47 self.rules.iter().any(|(name, _)| name == normalized_name)
48 }
49
50 #[must_use]
59 pub fn skip_reason(&self, normalized_name: &str, kind: UpdateKind) -> Option<SkipReason> {
60 let (_, update_types) = self
61 .rules
62 .iter()
63 .find(|(name, _)| name == normalized_name)?;
64
65 let matches = match update_types {
66 None => true,
67 Some(types) => kind == UpdateKind::Unknown || types.iter().any(|t| t.matches(kind)),
68 };
69 matches.then_some(SkipReason::IgnoreRule)
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::config::UpdateTypeToken;
77
78 const STUB_FORMATTER: deps_core::test_util::StubFormatter =
79 deps_core::test_util::StubFormatter::new().with_package_url_prefix("");
80
81 #[test]
82 fn test_empty_rules_never_skip() {
83 let rules = IgnoreRules::empty();
84 assert_eq!(rules.skip_reason("tokio", UpdateKind::Major), None);
85 }
86
87 #[test]
88 fn test_rule_with_no_update_types_matches_every_kind() {
89 let rules = IgnoreRules::new(
90 vec![IgnoreRule {
91 name: "legacy-thing".to_string(),
92 update_types: None,
93 }],
94 &STUB_FORMATTER,
95 );
96 assert_eq!(
97 rules.skip_reason("legacy-thing", UpdateKind::Patch),
98 Some(SkipReason::IgnoreRule)
99 );
100 assert_eq!(
101 rules.skip_reason("legacy-thing", UpdateKind::Unknown),
102 Some(SkipReason::IgnoreRule)
103 );
104 }
105
106 #[test]
107 fn test_scoped_rule_only_matches_listed_kind() {
108 let rules = IgnoreRules::new(
109 vec![IgnoreRule {
110 name: "tokio".to_string(),
111 update_types: Some(vec![UpdateTypeToken::Major]),
112 }],
113 &STUB_FORMATTER,
114 );
115 assert_eq!(
116 rules.skip_reason("tokio", UpdateKind::Major),
117 Some(SkipReason::IgnoreRule)
118 );
119 assert_eq!(rules.skip_reason("tokio", UpdateKind::Minor), None);
120 assert_eq!(rules.skip_reason("tokio", UpdateKind::Patch), None);
121 }
122
123 #[test]
126 fn test_scoped_rule_fail_closed_on_unknown_kind() {
127 let rules = IgnoreRules::new(
128 vec![IgnoreRule {
129 name: "tokio".to_string(),
130 update_types: Some(vec![UpdateTypeToken::Major]),
131 }],
132 &STUB_FORMATTER,
133 );
134 assert_eq!(
135 rules.skip_reason("tokio", UpdateKind::Unknown),
136 Some(SkipReason::IgnoreRule)
137 );
138 }
139
140 #[test]
141 fn test_non_matching_name_is_never_skipped() {
142 let rules = IgnoreRules::new(
143 vec![IgnoreRule {
144 name: "tokio".to_string(),
145 update_types: None,
146 }],
147 &STUB_FORMATTER,
148 );
149 assert_eq!(rules.skip_reason("serde", UpdateKind::Major), None);
150 }
151}