declint_core/
suppressions.rs1use std::collections::HashMap;
16
17const DISABLE: &str = "declint:disable";
18const DISABLE_NEXT_LINE: &str = "declint:disable-next-line";
19
20#[derive(Debug, Clone, Default)]
22pub struct Suppressions {
23 by_line: HashMap<usize, Vec<String>>,
26}
27
28impl Suppressions {
29 pub fn scan(source: &str) -> Self {
31 let mut by_line: HashMap<usize, Vec<String>> = HashMap::new();
32 for (index, line) in source.lines().enumerate() {
33 let Some((offset, next_line)) = find_marker(line) else {
34 continue;
35 };
36 let line_no = if next_line { index + 2 } else { index + 1 };
37 let rule_ids = parse_ids(&line[offset..]);
38 by_line.entry(line_no).or_default().extend(rule_ids);
39 }
40 Self { by_line }
41 }
42
43 pub fn is_suppressed(
46 &self,
47 rule_id: &str,
48 source: &str,
49 start: usize,
50 end: usize,
51 ) -> bool {
52 let (start_line, _) = crate::line_col(source, start);
53 let last = end.saturating_sub(1).max(start);
54 let (end_line, _) = crate::line_col(source, last);
55 (start_line..=end_line).any(|line| {
56 self.by_line
57 .get(&line)
58 .is_some_and(|ids| ids.is_empty() || ids.iter().any(|id| id == rule_id))
59 })
60 }
61
62 pub fn is_empty(&self) -> bool {
64 self.by_line.is_empty()
65 }
66}
67
68fn find_marker(line: &str) -> Option<(usize, bool)> {
71 let plain = line.find(DISABLE)?;
72 if line[plain..].starts_with(DISABLE_NEXT_LINE) {
73 Some((plain + DISABLE_NEXT_LINE.len(), true))
74 } else {
75 Some((plain + DISABLE.len(), false))
76 }
77}
78
79fn parse_ids(rest: &str) -> Vec<String> {
82 let Some(list) = rest.strip_prefix('=').map(str::trim) else {
83 return Vec::new();
84 };
85 list.split(',')
86 .map(str::trim)
87 .filter(|id| !id.is_empty())
88 .map(str::to_string)
89 .collect()
90}
91
92pub fn apply(source: &str, violations: &mut Vec<crate::Violation>) -> usize {
95 let suppressions = Suppressions::scan(source);
96 if suppressions.is_empty() {
97 return 0;
98 }
99 let before = violations.len();
100 violations.retain(|violation| {
101 !suppressions.is_suppressed(
102 &violation.rule_id,
103 source,
104 violation.span.start,
105 violation.span.end,
106 )
107 });
108 before - violations.len()
109}