1use crate::{
2 Lrc, Token, TokenStringExt,
3 patterns::{EitherPattern, Pattern, SequencePattern, WordSet},
4};
5
6use super::{Lint, LintKind, PatternLinter, Suggestion};
7
8pub struct ModalOf {
9 pattern: Box<dyn Pattern>,
10}
11
12impl Default for ModalOf {
13 fn default() -> Self {
14 let modals = ["could", "might", "must", "should", "would"];
19 let mut words = WordSet::new(&modals);
20 modals.iter().for_each(|word| {
21 words.add(&format!("{}n't", word));
22 });
23
24 let modal_of = Lrc::new(
25 SequencePattern::default()
26 .then(words)
27 .then_whitespace()
28 .t_aco("of"),
29 );
30
31 let ws_course = Lrc::new(SequencePattern::default().then_whitespace().t_aco("course"));
32
33 let modal_of_course = Lrc::new(
34 SequencePattern::default()
35 .then(modal_of.clone())
36 .then(ws_course.clone()),
37 );
38
39 let anyword_might_of = Lrc::new(
40 SequencePattern::default()
41 .then_any_word()
42 .then_whitespace()
43 .t_aco("might")
44 .then_whitespace()
45 .t_aco("of"),
46 );
47
48 let anyword_might_of_course = Lrc::new(
49 SequencePattern::default()
50 .then(anyword_might_of.clone())
51 .then(ws_course.clone()),
52 );
53
54 Self {
55 pattern: Box::new(EitherPattern::new(vec![
56 Box::new(anyword_might_of_course),
57 Box::new(modal_of_course),
58 Box::new(anyword_might_of),
59 Box::new(modal_of),
60 ])),
61 }
62 }
63}
64
65impl PatternLinter for ModalOf {
66 fn pattern(&self) -> &dyn Pattern {
67 self.pattern.as_ref()
68 }
69
70 fn match_to_lint(&self, matched_toks: &[Token], source_chars: &[char]) -> Option<Lint> {
71 let modal_index = match matched_toks.len() {
72 3 => 0,
74 5 => {
75 let w3_text = matched_toks
77 .last()
78 .unwrap()
79 .span
80 .get_content(source_chars)
81 .iter()
82 .collect::<String>();
83 if w3_text.as_str() != "of" {
84 return None;
85 }
86 let w1_kind = &matched_toks.first().unwrap().kind;
87 if w1_kind.is_adjective() || w1_kind.is_determiner() {
89 return None;
90 }
91 2
93 }
94 7 => return None,
96 _ => unreachable!(),
97 };
98
99 let span_modal_of = matched_toks[modal_index..modal_index + 3].span().unwrap();
100
101 let modal_have = format!(
102 "{} have",
103 matched_toks[modal_index]
104 .span
105 .get_content_string(source_chars)
106 )
107 .chars()
108 .collect();
109
110 Some(Lint {
111 span: span_modal_of,
112 lint_kind: LintKind::WordChoice,
113 suggestions: vec![Suggestion::replace_with_match_case(
114 modal_have,
115 span_modal_of.get_content(source_chars),
116 )],
117 message: "Use `have` rather than `of` here.".to_string(),
118 priority: 126,
119 })
120 }
121
122 fn description(&self) -> &'static str {
123 "Detects `of` mistakenly used with `would`, `could`, `should`, etc."
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::ModalOf;
130 use crate::linting::tests::{assert_lint_count, assert_suggestion_result};
131
132 #[test]
135 fn test_lowercase() {
136 assert_suggestion_result("could of", ModalOf::default(), "could have");
137 }
138
139 #[test]
140 fn test_negative() {
141 assert_suggestion_result("mightn't of", ModalOf::default(), "mightn't have");
142 }
143
144 #[test]
145 fn test_uppercase_negative() {
146 assert_suggestion_result("Mustn't of", ModalOf::default(), "Mustn't have");
147 }
148
149 #[test]
150 fn test_false_positive_of_course() {
151 assert_lint_count("should of course", ModalOf::default(), 0);
152 }
153
154 #[test]
155 fn test_false_positive_the_might_of() {
156 assert_lint_count("the might of", ModalOf::default(), 0);
157 }
158
159 #[test]
160 fn test_false_positive_great_might_of() {
161 assert_lint_count("great might of", ModalOf::default(), 0);
162 }
163
164 #[test]
165 fn test_false_positive_capital_negative() {
166 assert_lint_count("Wouldn't of course", ModalOf::default(), 0);
167 }
168
169 #[test]
172 fn test_buggy_implementation() {
173 assert_lint_count(
174 "... could of just been a buggy implementation",
175 ModalOf::default(),
176 1,
177 );
178 }
179
180 #[test]
181 fn test_missed_one() {
182 assert_lint_count(
183 "We already have a function ... that nedb can understand so we might of missed one.",
184 ModalOf::default(),
185 1,
186 );
187 }
188
189 #[test]
190 fn test_user_option() {
191 assert_lint_count(
192 "im more likely to believe you might of left in the 'user' option",
193 ModalOf::default(),
194 1,
195 );
196 }
197
198 #[test]
199 fn catches_must_of() {
200 assert_suggestion_result(
201 "Ah I must of missed that part.",
202 ModalOf::default(),
203 "Ah I must have missed that part.",
204 );
205 }
206
207 #[test]
208 fn catches_should_of() {
209 assert_lint_count(
210 "Yeah I should of just mentioned it should of been a for of.",
211 ModalOf::default(),
212 2,
213 );
214 }
215
216 #[test]
217 fn catches_would_of() {
218 assert_suggestion_result(
219 "now this issue would of caused hundreds of thousands of extra lines",
220 ModalOf::default(),
221 "now this issue would have caused hundreds of thousands of extra lines",
222 );
223 }
224
225 #[test]
226 fn doesnt_catch_you_could_of_course() {
227 assert_lint_count(
228 "You could of course explicit the else with each possibility",
229 ModalOf::default(),
230 0,
231 );
232 }
233
234 #[test]
235 fn doesnt_catch_compiler_could_of_course() {
236 assert_lint_count(
237 "The compiler could of course detect this too",
238 ModalOf::default(),
239 0,
240 );
241 }
242
243 #[test]
244 fn doesnt_catch_might_of_course_be() {
245 assert_lint_count(
246 "There might of course be other places where not implementing the IMemberSource might break ...",
247 ModalOf::default(),
248 0,
249 );
250 }
251
252 #[test]
253 fn doesnt_catch_not_a_must_of_course() {
254 assert_lint_count(
255 "Not a must of course if the convention should be .ts",
256 ModalOf::default(),
257 0,
258 );
259 }
260
261 #[test]
262 fn doesnt_catch_must_of_course_also() {
263 assert_lint_count(
264 "the schedular must of course also have run through",
265 ModalOf::default(),
266 0,
267 );
268 }
269
270 #[test]
271 fn doesnt_catch_should_of_course_not() {
272 assert_lint_count(
273 "not being local should of course not be supported",
274 ModalOf::default(),
275 0,
276 );
277 }
278
279 #[test]
280 fn doesnt_catch_would_of_course_just() {
281 assert_lint_count(
282 "I would of course just test this by compiling with MATX_MULTI_GPU=ON",
283 ModalOf::default(),
284 0,
285 );
286 }
287
288 #[test]
289 fn doesnt_catch_to_take_on_the_full_might_of_nato() {
290 assert_lint_count("To take on the full might of NATO.", ModalOf::default(), 0);
291 }
292
293 #[test]
294 fn doesnt_catch_mixed_case_of_course() {
295 assert_lint_count(
296 "... for now you could of Course put ...",
297 ModalOf::default(),
298 0,
299 );
300 }
301
302 #[test]
303 fn catches_mixed_case_could_of_put() {
304 assert_lint_count("... for now you could of Put ...", ModalOf::default(), 1);
305 }
306}