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