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