harper_core/ignored_lints/
mod.rs1mod lint_context;
2
3use hashbrown::HashSet;
4pub use lint_context::LintContext;
5use serde::{Deserialize, Serialize};
6
7use crate::{Document, linting::Lint};
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
14pub struct IgnoredLints {
15 context_hashes: HashSet<u64>,
16}
17
18impl IgnoredLints {
19 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn append(&mut self, other: Self) {
25 self.context_hashes.extend(other.context_hashes)
26 }
27
28 pub fn ignore_lint(&mut self, lint: &Lint, document: &Document) {
30 let context = LintContext::from_lint(lint, document);
31 let context_hash = context.default_hash();
32
33 self.ignore_hash(context_hash);
34 }
35
36 pub fn ignore_hash(&mut self, hash: u64) {
38 self.context_hashes.insert(hash);
39 }
40
41 pub fn is_ignored(&self, lint: &Lint, document: &Document) -> bool {
42 let context = LintContext::from_lint(lint, document);
43 let hash = context.default_hash();
44
45 self.context_hashes.contains(&hash)
46 }
47
48 pub fn remove_ignored(&self, lints: &mut Vec<Lint>, document: &Document) {
50 if self.context_hashes.is_empty() {
51 return;
52 }
53
54 lints.retain(|lint| !self.is_ignored(lint, document));
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use quickcheck::TestResult;
61 use quickcheck_macros::quickcheck;
62
63 use super::IgnoredLints;
64 use crate::linting::create_test_pool;
65 use crate::spell::FstDictionary;
66 use crate::{
67 Dialect, Document,
68 linting::{LintGroup, Linter},
69 };
70
71 create_test_pool!(
72 LintGroup,
73 LintGroup,
74 LintGroup::new_curated(FstDictionary::curated(), Dialect::American)
75 );
76
77 #[quickcheck]
78 fn can_ignore_all(text: String) -> bool {
79 let document = Document::new_markdown_default_curated(&text);
80
81 let mut lints = test_linter().lint(&document);
82
83 let mut ignored = IgnoredLints::new();
84
85 for lint in &lints {
86 ignored.ignore_lint(lint, &document);
87 }
88
89 ignored.remove_ignored(&mut lints, &document);
90 lints.is_empty()
91 }
92
93 #[quickcheck]
94 fn can_ignore_first(text: String) -> TestResult {
95 let document = Document::new_markdown_default_curated(&text);
96
97 let mut lints = test_linter().lint(&document);
98
99 let Some(first) = lints.first().cloned() else {
100 return TestResult::discard();
101 };
102
103 let mut ignored = IgnoredLints::new();
104 ignored.ignore_lint(&first, &document);
105
106 ignored.remove_ignored(&mut lints, &document);
107
108 TestResult::from_bool(!lints.contains(&first))
109 }
110
111 fn assert_ignore_lint_reduction(source: &str, nth_lint: usize) {
113 let document = Document::new_markdown_default_curated(source);
114
115 let mut lints = test_linter().lint(&document);
116
117 let nth = lints.get(nth_lint).cloned().unwrap_or_else(|| {
118 panic!("If ignoring the lint at {nth_lint}, make sure there are enough problems.")
119 });
120
121 let mut ignored = IgnoredLints::new();
122 ignored.ignore_lint(&nth, &document);
123
124 let prev_count = lints.len();
125
126 ignored.remove_ignored(&mut lints, &document);
127
128 assert_eq!(prev_count, lints.len() + 1);
129 assert!(!lints.contains(&nth));
130 }
131
132 #[test]
133 fn an_a() {
134 let source = "There is an problem in this text. Here is an second one.";
135
136 assert_ignore_lint_reduction(source, 0);
137 assert_ignore_lint_reduction(source, 1);
138 }
139
140 #[test]
141 fn spelling() {
142 let source = "There is a problm in this text. Here is a scond one.";
143
144 assert_ignore_lint_reduction(source, 0);
145 assert_ignore_lint_reduction(source, 1);
146 }
147}