harper_core/linting/compound_nouns/
mod.rs1mod compound_noun_after_det_adj;
2mod compound_noun_after_possessive;
3mod compound_noun_before_aux_verb;
4
5use super::{Lint, LintKind, Suggestion, merge_linters::merge_linters};
6use crate::{CharStringExt, DictWordMetadata, Token};
7
8pub(crate) fn is_content_word(tok: &Token, src: &[char]) -> bool {
10 let Some(Some(meta)) = tok.kind.as_word() else {
11 return false;
12 };
13
14 tok.span.len() > 1
15 && (meta.is_noun() || meta.is_adjective() || meta.is_verb() || meta.is_adverb())
16 && !(meta.is_determiner() || meta.is_conjunction())
17 && (!meta.preposition || tok.span.get_content(src).eq_ignore_ascii_case_str("bar"))
18}
19
20pub(crate) fn predicate(
21 closed: Option<&DictWordMetadata>,
22 open: Option<&DictWordMetadata>,
23) -> bool {
24 open.is_none() && closed.is_some_and(|m| m.is_noun() && !m.is_proper_noun())
25}
26
27use compound_noun_after_det_adj::CompoundNounAfterDetAdj;
28use compound_noun_after_possessive::CompoundNounAfterPossessive;
29use compound_noun_before_aux_verb::CompoundNounBeforeAuxVerb;
30
31merge_linters!(CompoundNouns => CompoundNounAfterDetAdj, CompoundNounBeforeAuxVerb, CompoundNounAfterPossessive => "Detects compound nouns split by a space and suggests merging them when both parts form a valid noun." );
32
33#[cfg(test)]
34mod tests {
35 use super::CompoundNouns;
36 use crate::linting::tests::{assert_lint_count, assert_no_lints, assert_suggestion_result};
37
38 #[test]
39 fn web_cam() {
40 let test_sentence = "The web cam captured a stunning image.";
41 let expected = "The webcam captured a stunning image.";
42 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
43 }
44
45 #[test]
46 fn note_book() {
47 let test_sentence = "She always carries a note book to jot down ideas.";
48 let expected = "She always carries a notebook to jot down ideas.";
49 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
50 }
51
52 #[test]
53 fn mother_board() {
54 let test_sentence = "After the upgrade, the mother board was replaced.";
55 let expected = "After the upgrade, the motherboard was replaced.";
56 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
57 }
58
59 #[test]
60 fn smart_phone() {
61 let test_sentence = "He bought a new smart phone last week.";
62 let expected = "He bought a new smartphone last week.";
63 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
64 }
65
66 #[test]
67 fn firm_ware() {
68 let test_sentence = "The device's firm ware was updated overnight.";
69 let expected = "The device's firmware was updated overnight.";
70 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
71 }
72
73 #[test]
74 fn back_plane() {
75 let test_sentence = "A reliable back plane is essential for high-speed data transfer.";
76 let expected = "A reliable backplane is essential for high-speed data transfer.";
77 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
78 }
79
80 #[test]
81 fn spread_sheet() {
82 let test_sentence = "The accountant reviewed the spread sheet carefully.";
83 let expected = "The accountant reviewed the spreadsheet carefully.";
84 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
85 }
86
87 #[test]
88 fn side_bar() {
89 let test_sentence = "The website's side bar offers quick navigation links.";
90 let expected = "The website's sidebar offers quick navigation links.";
91 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
92 }
93
94 #[test]
95 fn back_pack() {
96 let test_sentence = "I packed my books in my back pack before leaving.";
97 let expected = "I packed my books in my backpack before leaving.";
98 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
99 }
100
101 #[test]
102 fn cup_board() {
103 let test_sentence = "She stored the dishes in the old cup board.";
104 let expected = "She stored the dishes in the old cupboard.";
105 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
106 }
107
108 #[test]
109 fn key_board() {
110 let test_sentence = "My key board stopped working during the meeting.";
111 let expected = "My keyboard stopped working during the meeting.";
112 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
113 }
114
115 #[test]
116 fn touch_screen() {
117 let test_sentence = "The device features a responsive touch screen.";
118 let expected = "The device features a responsive touchscreen.";
119 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
120 }
121
122 #[test]
123 fn head_set() {
124 let test_sentence = "He bought a new head set for his workouts.";
125 let expected = "He bought a new headset for his workouts.";
126 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
127 }
128
129 #[test]
130 fn frame_work() {
131 let test_sentence = "The frame work of the app was built with care.";
132 let expected = "The framework of the app was built with care.";
133 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
134 }
135
136 #[test]
137 fn touch_pad() {
138 let test_sentence = "The touch pad on my laptop is very sensitive.";
139 let expected = "The touchpad on my laptop is very sensitive.";
140 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
141 }
142
143 #[test]
144 fn micro_processor() {
145 let test_sentence = "This micro processor is among the fastest available.";
146 let expected = "This microprocessor is among the fastest available.";
147 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
148 }
149
150 #[test]
151 fn head_phone() {
152 let test_sentence = "I lost my head phone at the gym.";
153 let expected = "I lost my headphone at the gym.";
154 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
155 }
156
157 #[test]
158 fn micro_services() {
159 let test_sentence = "Our architecture now relies on micro services.";
160 let expected = "Our architecture now relies on microservices.";
161 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
162 }
163
164 #[test]
165 fn dash_board() {
166 let test_sentence = "The dash board shows real-time analytics.";
167 let expected = "The dashboard shows real-time analytics.";
168 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
169 }
170
171 #[test]
172 fn site_map() {
173 let test_sentence = "A site map is provided at the footer of the website.";
174 let expected = "A sitemap is provided at the footer of the website.";
175 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
176 }
177
178 #[test]
179 fn fire_wall() {
180 let test_sentence = "A robust fire wall is essential for network security.";
181 let expected = "A robust firewall is essential for network security.";
182 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
183 }
184
185 #[test]
186 fn bit_stream() {
187 let test_sentence = "The bit stream was interrupted during transmission.";
188 let expected = "The bitstream was interrupted during transmission.";
189 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
190 }
191
192 #[test]
193 fn block_chain() {
194 let test_sentence = "The block chain is revolutionizing the financial sector.";
195 let expected = "The blockchain is revolutionizing the financial sector.";
196 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
197 }
198
199 #[test]
200 fn thumb_nail() {
201 let test_sentence = "I saved the image as a thumb nail.";
202 let expected = "I saved the image as a thumbnail.";
203 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
204 }
205
206 #[test]
207 fn bath_room() {
208 let test_sentence = "They remodeled the bath room entirely.";
209 let expected = "They remodeled the bathroom entirely.";
210 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
211 }
212
213 #[test]
214 #[ignore = "\"everyone\" is not a valid compound noun, it's a pronoun"]
215 fn every_one() {
216 let test_sentence = "Every one should have access to quality education.";
217 let expected = "Everyone should have access to quality education.";
218 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
219 }
220
221 #[test]
222 fn play_ground() {
223 let test_sentence = "The kids spent the afternoon at the play ground.";
224 let expected = "The kids spent the afternoon at the playground.";
225 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
226 }
227
228 #[test]
229 fn run_way() {
230 let test_sentence = "The airplane taxied along the run way.";
231 let expected = "The airplane taxied along the runway.";
232 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
233 }
234
235 #[test]
236 fn cyber_space() {
237 let test_sentence = "Hackers roam the cyber space freely.";
238 let expected = "Hackers roam the cyberspace freely.";
239 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
240 }
241
242 #[test]
243 fn cyber_attack() {
244 let test_sentence = "The network was hit by a cyber attack.";
245 let expected = "The network was hit by a cyberattack.";
246 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
247 }
248
249 #[test]
250 fn web_socket() {
251 let test_sentence = "Real-time updates are sent via a web socket.";
252 let expected = "Real-time updates are sent via a websocket.";
253 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
254 }
255
256 #[test]
257 fn finger_print() {
258 let test_sentence = "The detective collected a finger print as evidence.";
259 let expected = "The detective collected a fingerprint as evidence.";
260 assert_suggestion_result(test_sentence, CompoundNouns::default(), expected);
261 }
262
263 #[test]
264 fn got_is_not_possessive() {
265 assert_lint_count("I got here by car...", CompoundNouns::default(), 0);
266 }
267
268 #[test]
269 fn allow_issue_662() {
270 assert_lint_count(
271 "They are as old as *modern* computers ",
272 CompoundNouns::default(),
273 0,
274 );
275 }
276
277 #[test]
278 fn allow_issue_661() {
279 assert_lint_count("I may be wrong.", CompoundNouns::default(), 0);
280 }
281
282 #[test]
283 fn allow_issue_704() {
284 assert_lint_count(
285 "Here are some ways to do that:",
286 CompoundNouns::default(),
287 0,
288 );
289 }
290
291 #[test]
292 fn allows_issue_721() {
293 assert_lint_count(
294 "So if you adjust any one of these adjusters that can have a negative or a positive effect.",
295 CompoundNouns::default(),
296 0,
297 );
298 }
299
300 #[test]
301 fn allows_678() {
302 assert_lint_count(
303 "they can't catch all the bugs.",
304 CompoundNouns::default(),
305 0,
306 );
307 }
308
309 #[test]
310 fn ina_not_suggested() {
311 assert_lint_count(
312 "past mistakes or a character in a looping reality facing personal challenges.",
313 CompoundNouns::default(),
314 0,
315 );
316 }
317
318 #[test]
319 fn allow_suppress_or() {
320 assert_lint_count(
321 "He must decide whether to suppress or coexist with his doppelgänger.",
322 CompoundNouns::default(),
323 0,
324 );
325 }
326
327 #[test]
328 fn allow_an_arm_and_a_leg() {
329 assert_lint_count(
330 "I have to pay an arm and a leg get a worker to come and be my assistant baker.",
331 CompoundNouns::default(),
332 0,
333 );
334 }
335
336 #[test]
337 fn allow_well_and_723() {
338 assert_lint_count(
339 "I understood very well and decided to go.",
340 CompoundNouns::default(),
341 0,
342 );
343 }
344
345 #[test]
346 fn allow_can_not() {
347 assert_lint_count("Size can not be determined.", CompoundNouns::default(), 0);
348 }
349
350 #[test]
351 fn dont_flag_lot_to() {
352 assert_lint_count(
353 "but you'd have to raise taxes a lot to do it.",
354 CompoundNouns::default(),
355 0,
356 );
357 }
358
359 #[test]
360 fn dont_flag_to_me() {
361 assert_lint_count(
362 "There's no massive damage to the rockers or anything that to me would indicate that like the whole front of the car was off",
363 CompoundNouns::default(),
364 0,
365 );
366 }
367
368 #[test]
369 fn allow_issue_1553() {
370 assert_no_lints(
371 "I'm not sure if there's anyone else that may be interested in more fine-grained control, but as it stands, having the domain level toggle is sufficient for me.",
372 CompoundNouns::default(),
373 );
374 }
375
376 #[test]
377 fn allow_issue_1496() {
378 assert_no_lints(
379 "I am not able to respond to messages.",
380 CompoundNouns::default(),
381 );
382 }
383
384 #[test]
385 fn allow_issue_1298() {
386 assert_no_lints(
387 "A series of tests that cover all possible cases.",
388 CompoundNouns::default(),
389 );
390 }
391
392 #[test]
393 fn dont_flag_project_or() {
394 assert_no_lints(
395 "You can star or watch this project or follow author to get release notifications in time.",
396 CompoundNouns::default(),
397 );
398 }
399}