harper_core/linting/compound_nouns/
mod.rs

1mod 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
8// Helper function to check if a token is a content word (not a function word)
9pub(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}