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, 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::{
65 Dialect, Document, FstDictionary,
66 linting::{LintGroup, Linter},
67 };
68
69 #[quickcheck]
70 fn can_ignore_all(text: String) -> bool {
71 let document = Document::new_markdown_default_curated(&text);
72
73 let mut lints =
74 LintGroup::new_curated(FstDictionary::curated(), Dialect::American).lint(&document);
75
76 let mut ignored = IgnoredLints::new();
77
78 for lint in &lints {
79 ignored.ignore_lint(lint, &document);
80 }
81
82 ignored.remove_ignored(&mut lints, &document);
83 lints.is_empty()
84 }
85
86 #[quickcheck]
87 fn can_ignore_first(text: String) -> TestResult {
88 let document = Document::new_markdown_default_curated(&text);
89
90 let mut lints =
91 LintGroup::new_curated(FstDictionary::curated(), Dialect::American).lint(&document);
92
93 let Some(first) = lints.first().cloned() else {
94 return TestResult::discard();
95 };
96
97 let mut ignored = IgnoredLints::new();
98 ignored.ignore_lint(&first, &document);
99
100 ignored.remove_ignored(&mut lints, &document);
101
102 TestResult::from_bool(!lints.contains(&first))
103 }
104
105 fn assert_ignore_lint_reduction(source: &str, nth_lint: usize) {
107 let document = Document::new_markdown_default_curated(source);
108
109 let mut lints =
110 LintGroup::new_curated(FstDictionary::curated(), Dialect::American).lint(&document);
111
112 let nth = lints.get(nth_lint).cloned().unwrap_or_else(|| {
113 panic!("If ignoring the lint at {nth_lint}, make sure there are enough problems.")
114 });
115
116 let mut ignored = IgnoredLints::new();
117 ignored.ignore_lint(&nth, &document);
118
119 let prev_count = lints.len();
120
121 ignored.remove_ignored(&mut lints, &document);
122
123 assert_eq!(prev_count, lints.len() + 1);
124 assert!(!lints.contains(&nth));
125 }
126
127 #[test]
128 fn an_a() {
129 let source = "There is an problem in this text. Here is an second one.";
130
131 assert_ignore_lint_reduction(source, 0);
132 assert_ignore_lint_reduction(source, 1);
133 }
134
135 #[test]
136 fn spelling() {
137 let source = "There is a problm in this text. Here is a scond one.";
138
139 assert_ignore_lint_reduction(source, 0);
140 assert_ignore_lint_reduction(source, 1);
141 }
142}