1use crate::{CharString, CharStringExt, DictWordMetadata};
5
6pub use self::dictionary::Dictionary;
7pub use self::fst_dictionary::FstDictionary;
8pub use self::merged_dictionary::MergedDictionary;
9pub use self::mutable_dictionary::MutableDictionary;
10pub use self::word_id::WordId;
11
12mod dictionary;
13mod fst_dictionary;
14mod merged_dictionary;
15mod mutable_dictionary;
16mod rune;
17mod word_id;
18mod word_map;
19
20#[derive(PartialEq, Debug, Hash, Eq)]
21pub struct FuzzyMatchResult<'a> {
22 pub word: &'a [char],
23 pub edit_distance: u8,
24 pub metadata: std::borrow::Cow<'a, DictWordMetadata>,
25}
26
27impl PartialOrd for FuzzyMatchResult<'_> {
28 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
29 self.edit_distance.partial_cmp(&other.edit_distance)
30 }
31}
32
33pub(crate) fn is_ou_misspelling(a: &[char], b: &[char]) -> bool {
38 if a.len().abs_diff(b.len()) != 1 {
39 return false;
40 }
41
42 let mut a_iter = a.iter();
43 let mut b_iter = b.iter();
44
45 loop {
46 match (
47 a_iter.next().map(char::to_ascii_lowercase),
48 b_iter.next().map(char::to_ascii_lowercase),
49 ) {
50 (Some('o'), Some('o')) => {
51 let mut a_next = a_iter.next().map(char::to_ascii_lowercase);
52 let mut b_next = b_iter.next().map(char::to_ascii_lowercase);
53 if a_next != b_next {
54 if a_next == Some('u') {
55 a_next = a_iter.next().map(char::to_ascii_lowercase);
56 } else if b_next == Some('u') {
57 b_next = b_iter.next().map(char::to_ascii_lowercase);
58 }
59
60 if a_next != b_next {
61 return false;
62 }
63 }
64 }
65 (Some(a_char), Some(b_char)) => {
66 if !a_char.eq_ignore_ascii_case(&b_char) {
67 return false;
68 }
69 }
70 (None, None) => return true,
71 _ => return false,
72 }
73 }
74}
75
76pub(crate) fn is_cksz_misspelling(a: &[char], b: &[char]) -> bool {
82 if a.len() != b.len() {
83 return false;
84 }
85 if a.is_empty() {
86 return true;
87 }
88
89 if !a[0].eq_ignore_ascii_case(&b[0]) {
91 return false;
92 }
93
94 let mut found = false;
95 for (a_char, b_char) in a.iter().copied().zip(b.iter().copied()) {
96 let a_char = a_char.to_ascii_lowercase();
97 let b_char = b_char.to_ascii_lowercase();
98
99 if a_char != b_char {
100 if (a_char == 's' && b_char == 'z')
101 || (a_char == 'z' && b_char == 's')
102 || (a_char == 's' && b_char == 'c')
103 || (a_char == 'c' && b_char == 's')
104 || (a_char == 'k' && b_char == 'c')
105 || (a_char == 'c' && b_char == 'k')
106 {
107 if found {
108 return false;
109 }
110 found = true;
111 } else {
112 return false;
113 }
114 }
115 }
116
117 found
118}
119
120pub(crate) fn is_er_misspelling(a: &[char], b: &[char]) -> bool {
125 if a.len() != b.len() || a.len() <= 4 {
126 return false;
127 }
128
129 let len = a.len();
130 let a_suffix = [&a[len - 2], &a[len - 1]].map(char::to_ascii_lowercase);
131 let b_suffix = [&b[len - 2], &b[len - 1]].map(char::to_ascii_lowercase);
132
133 if a_suffix == ['r', 'e'] && b_suffix == ['e', 'r']
134 || a_suffix == ['e', 'r'] && b_suffix == ['r', 'e']
135 {
136 return a[0..len - 2]
137 .iter()
138 .copied()
139 .zip(b[0..len - 2].iter().copied())
140 .all(|(a_char, b_char)| a_char.eq_ignore_ascii_case(&b_char));
141 }
142
143 false
144}
145
146pub(crate) fn is_ll_misspelling(a: &[char], b: &[char]) -> bool {
151 if a.len().abs_diff(b.len()) != 1 {
152 return false;
153 }
154
155 let mut a_iter = a.iter();
156 let mut b_iter = b.iter();
157
158 loop {
159 match (
160 a_iter.next().map(char::to_ascii_lowercase),
161 b_iter.next().map(char::to_ascii_lowercase),
162 ) {
163 (Some('l'), Some('l')) => {
164 let mut a_next = a_iter.next().map(char::to_ascii_lowercase);
165 let mut b_next = b_iter.next().map(char::to_ascii_lowercase);
166 if a_next != b_next {
167 if a_next == Some('l') {
168 a_next = a_iter.next().map(char::to_ascii_lowercase);
169 } else if b_next == Some('l') {
170 b_next = b_iter.next().map(char::to_ascii_lowercase);
171 }
172
173 if a_next != b_next {
174 return false;
175 }
176 }
177 }
178 (Some(a_char), Some(b_char)) => {
179 if !a_char.eq_ignore_ascii_case(&b_char) {
180 return false;
181 }
182 }
183 (None, None) => return true,
184 _ => return false,
185 }
186 }
187}
188
189pub(crate) fn is_ay_ey_misspelling(a: &[char], b: &[char]) -> bool {
194 if a.len() != b.len() {
195 return false;
196 }
197
198 let mut found_ay_ey = false;
199 let mut a_iter = a.iter();
200 let mut b_iter = b.iter();
201
202 while let (Some(&a_char), Some(&b_char)) = (a_iter.next(), b_iter.next()) {
203 if a_char.eq_ignore_ascii_case(&b_char) {
204 continue;
205 }
206
207 if (a_char.eq_ignore_ascii_case(&'a') && b_char.eq_ignore_ascii_case(&'e'))
209 || (a_char.eq_ignore_ascii_case(&'e') && b_char.eq_ignore_ascii_case(&'a'))
210 {
211 if let (Some(&a_next), Some(&b_next)) = (a_iter.next(), b_iter.next())
213 && a_next.eq_ignore_ascii_case(&'y')
214 && b_next.eq_ignore_ascii_case(&'y')
215 {
216 if found_ay_ey {
217 return false; }
219 found_ay_ey = true;
220 continue;
221 }
222 }
223 return false; }
225
226 if !found_ay_ey {
227 return false;
228 }
229 found_ay_ey
230}
231
232pub(crate) fn is_ei_ie_misspelling(a: &[char], b: &[char]) -> bool {
237 if a.len() != b.len() {
238 return false;
239 }
240 let mut found_ei_ie = false;
241 let mut a_iter = a.iter();
242 let mut b_iter = b.iter();
243
244 while let (Some(&a_char), Some(&b_char)) = (a_iter.next(), b_iter.next()) {
245 if a_char.eq_ignore_ascii_case(&b_char) {
246 continue;
247 }
248
249 if a_char.eq_ignore_ascii_case(&'e') && b_char.eq_ignore_ascii_case(&'i') {
251 if let (Some(&a_next), Some(&b_next)) = (a_iter.next(), b_iter.next()) {
252 if a_next.eq_ignore_ascii_case(&'i') && b_next.eq_ignore_ascii_case(&'e') {
254 if found_ei_ie {
255 return false; }
257 found_ei_ie = true;
258 continue;
259 }
260 }
261 }
262 else if a_char.eq_ignore_ascii_case(&'i')
264 && b_char.eq_ignore_ascii_case(&'e')
265 && let (Some(&a_next), Some(&b_next)) = (a_iter.next(), b_iter.next())
266 {
267 if a_next.eq_ignore_ascii_case(&'e') && b_next.eq_ignore_ascii_case(&'i') {
269 if found_ei_ie {
270 return false; }
272 found_ei_ie = true;
273 continue;
274 }
275 }
276 return false;
277 }
278 found_ei_ie
279}
280
281fn score_suggestion(misspelled_word: &[char], sug: &FuzzyMatchResult) -> i32 {
285 if misspelled_word.is_empty() || sug.word.is_empty() {
286 return i32::MAX;
287 }
288
289 let mut score = sug.edit_distance as i32 * 10;
290
291 if misspelled_word
293 .first()
294 .unwrap()
295 .eq_ignore_ascii_case(sug.word.first().unwrap())
296 {
297 score -= 10;
298 }
299
300 if *misspelled_word.last().unwrap() == 's' && *sug.word.last().unwrap() == 's' {
302 score -= 5;
303 }
304
305 if sug.metadata.common {
307 score -= 5;
308 }
309
310 if sug.word.iter().filter(|c| **c == '\'').count() == 1 {
312 score -= 5;
313 }
314
315 if sug.edit_distance == 1
317 && (is_cksz_misspelling(misspelled_word, sug.word)
318 || is_ou_misspelling(misspelled_word, sug.word)
319 || is_ll_misspelling(misspelled_word, sug.word)
320 || is_ay_ey_misspelling(misspelled_word, sug.word))
321 {
322 score -= 6;
323 }
324 if sug.edit_distance == 2 {
325 if is_ei_ie_misspelling(misspelled_word, sug.word) {
326 score -= 11;
327 }
328 if is_er_misspelling(misspelled_word, sug.word) {
329 score -= 15;
330 }
331 }
332
333 score
334}
335
336fn order_suggestions<'b>(
338 misspelled_word: &[char],
339 mut matches: Vec<FuzzyMatchResult<'b>>,
340) -> Vec<&'b [char]> {
341 matches.sort_by_key(|v| score_suggestion(misspelled_word, v));
342
343 matches.into_iter().map(|v| v.word).collect()
344}
345
346pub fn suggest_correct_spelling<'a>(
349 misspelled_word: &[char],
350 result_limit: usize,
351 max_edit_dist: u8,
352 dictionary: &'a impl Dictionary,
353) -> Vec<&'a [char]> {
354 let matches: Vec<FuzzyMatchResult> = dictionary
355 .fuzzy_match(misspelled_word, max_edit_dist, result_limit)
356 .into_iter()
357 .collect();
358
359 order_suggestions(misspelled_word, matches)
360}
361
362pub fn suggest_correct_spelling_str(
365 misspelled_word: impl Into<String>,
366 result_limit: usize,
367 max_edit_dist: u8,
368 dictionary: &impl Dictionary,
369) -> Vec<String> {
370 let chars: CharString = misspelled_word.into().chars().collect();
371 suggest_correct_spelling(&chars, result_limit, max_edit_dist, dictionary)
372 .into_iter()
373 .map(|a| a.to_string())
374 .collect()
375}
376
377#[cfg(test)]
378mod tests {
379 use itertools::Itertools;
380
381 use crate::{
382 CharStringExt, Dialect,
383 linting::{
384 SpellCheck,
385 tests::{assert_suggestion_result, assert_top3_suggestion_result},
386 },
387 };
388
389 use super::{FstDictionary, suggest_correct_spelling_str};
390
391 const RESULT_LIMIT: usize = 100;
392 const MAX_EDIT_DIST: u8 = 2;
393
394 #[test]
395 fn normalizes_weve() {
396 let word = ['w', 'e', '’', 'v', 'e'];
397 let norm = word.normalized();
398
399 assert_eq!(norm.clone(), vec!['w', 'e', '\'', 'v', 'e'])
400 }
401
402 #[test]
403 fn punctation_no_duplicates() {
404 let results = suggest_correct_spelling_str(
405 "punctation",
406 RESULT_LIMIT,
407 MAX_EDIT_DIST,
408 &FstDictionary::curated(),
409 );
410
411 assert!(results.iter().all_unique())
412 }
413
414 #[test]
415 fn youre_contraction() {
416 assert_suggests_correction("youre", "you're");
417 }
418
419 #[test]
420 fn thats_contraction() {
421 assert_suggests_correction("thats", "that's");
422 }
423
424 #[test]
425 fn weve_contraction() {
426 assert_suggests_correction("weve", "we've");
427 }
428
429 #[test]
430 fn this_correction() {
431 assert_suggests_correction("ths", "this");
432 }
433
434 #[test]
435 fn issue_624_no_duplicates() {
436 let results = suggest_correct_spelling_str(
437 "Semantical",
438 RESULT_LIMIT,
439 MAX_EDIT_DIST,
440 &FstDictionary::curated(),
441 );
442
443 dbg!(&results);
444
445 assert!(results.iter().all_unique())
446 }
447
448 #[test]
449 fn issue_182() {
450 assert_suggests_correction("Im", "I'm");
451 }
452
453 #[test]
454 fn fst_spellcheck_hvllo() {
455 let results = suggest_correct_spelling_str(
456 "hvllo",
457 RESULT_LIMIT,
458 MAX_EDIT_DIST,
459 &FstDictionary::curated(),
460 );
461
462 dbg!(&results);
463
464 assert!(results.iter().take(3).contains(&"hello".to_string()));
465 }
466
467 #[track_caller]
470 fn assert_suggests_correction(misspelled_word: &str, correct: &str) {
471 let results = suggest_correct_spelling_str(
472 misspelled_word,
473 RESULT_LIMIT,
474 MAX_EDIT_DIST,
475 &FstDictionary::curated(),
476 );
477
478 dbg!(&results);
479
480 assert!(results.iter().take(3).contains(&correct.to_string()));
481 }
482
483 #[test]
484 fn spellcheck_hvllo() {
485 assert_suggests_correction("hvllo", "hello");
486 }
487
488 #[test]
489 fn spellcheck_aout() {
490 assert_suggests_correction("aout", "about");
491 }
492
493 #[test]
494 fn spellchecking_is_deterministic() {
495 let results1 = suggest_correct_spelling_str(
496 "hello",
497 RESULT_LIMIT,
498 MAX_EDIT_DIST,
499 &FstDictionary::curated(),
500 );
501 let results2 = suggest_correct_spelling_str(
502 "hello",
503 RESULT_LIMIT,
504 MAX_EDIT_DIST,
505 &FstDictionary::curated(),
506 );
507 let results3 = suggest_correct_spelling_str(
508 "hello",
509 RESULT_LIMIT,
510 MAX_EDIT_DIST,
511 &FstDictionary::curated(),
512 );
513
514 assert_eq!(results1, results2);
515 assert_eq!(results1, results3);
516 }
517
518 #[test]
519 fn adviced_correction() {
520 assert_suggests_correction("adviced", "advised");
521 }
522
523 #[test]
524 fn aknowledged_correction() {
525 assert_suggests_correction("aknowledged", "acknowledged");
526 }
527
528 #[test]
529 fn alcaholic_correction() {
530 assert_suggests_correction("alcaholic", "alcoholic");
531 }
532
533 #[test]
534 fn slaves_correction() {
535 assert_suggests_correction("Slaves", "Slavs");
536 }
537
538 #[test]
539 fn conciousness_correction() {
540 assert_suggests_correction("conciousness", "consciousness");
541 }
542
543 #[test]
547 fn suggest_color_for_colour_lowercase() {
548 assert_suggestion_result(
549 "colour",
550 SpellCheck::new(FstDictionary::curated(), Dialect::American),
551 "color",
552 );
553 }
554
555 #[test]
556 fn suggest_colour_for_color_lowercase() {
557 assert_suggestion_result(
558 "color",
559 SpellCheck::new(FstDictionary::curated(), Dialect::British),
560 "colour",
561 );
562 }
563
564 #[test]
566 fn suggest_color_for_colour_titlecase() {
567 assert_suggestion_result(
568 "Colour",
569 SpellCheck::new(FstDictionary::curated(), Dialect::American),
570 "Color",
571 );
572 }
573
574 #[test]
575 #[ignore = "known failure due to bug"]
576 fn suggest_colour_for_color_titlecase() {
577 assert_suggestion_result(
578 "Color",
579 SpellCheck::new(FstDictionary::curated(), Dialect::British),
580 "Colour",
581 );
582 }
583
584 #[test]
586 #[ignore = "known failure due to bug"]
587 fn suggest_color_for_colour_all_caps() {
588 assert_suggestion_result(
589 "COLOUR",
590 SpellCheck::new(FstDictionary::curated(), Dialect::American),
591 "COLOR",
592 );
593 }
594
595 #[test]
596 #[ignore = "known failure due to bug"]
597 fn suggest_colour_for_color_all_caps() {
598 assert_suggestion_result(
599 "COLOR",
600 SpellCheck::new(FstDictionary::curated(), Dialect::British),
601 "COLOUR",
602 );
603 }
604
605 #[test]
609 fn suggest_realise_for_realize() {
610 assert_suggestion_result(
611 "realize",
612 SpellCheck::new(FstDictionary::curated(), Dialect::British),
613 "realise",
614 );
615 }
616
617 #[test]
618 fn suggest_realize_for_realise() {
619 assert_suggestion_result(
620 "realise",
621 SpellCheck::new(FstDictionary::curated(), Dialect::American),
622 "realize",
623 );
624 }
625
626 #[test]
627 fn suggest_realise_for_realize_titlecase() {
628 assert_suggestion_result(
629 "Realize",
630 SpellCheck::new(FstDictionary::curated(), Dialect::British),
631 "Realise",
632 );
633 }
634
635 #[test]
636 #[ignore = "known failure due to bug"]
637 fn suggest_realize_for_realise_titlecase() {
638 assert_suggestion_result(
639 "Realise",
640 SpellCheck::new(FstDictionary::curated(), Dialect::American),
641 "Realize",
642 );
643 }
644
645 #[test]
646 #[ignore = "known failure due to bug"]
647 fn suggest_realise_for_realize_all_caps() {
648 assert_suggestion_result(
649 "REALIZE",
650 SpellCheck::new(FstDictionary::curated(), Dialect::British),
651 "REALISE",
652 );
653 }
654
655 #[test]
656 #[ignore = "known failure due to bug"]
657 fn suggest_realize_for_realise_all_caps() {
658 assert_suggestion_result(
659 "REALISE",
660 SpellCheck::new(FstDictionary::curated(), Dialect::American),
661 "REALIZE",
662 );
663 }
664
665 #[test]
667 fn suggest_defence_for_defense() {
668 assert_suggestion_result(
669 "defense",
670 SpellCheck::new(FstDictionary::curated(), Dialect::British),
671 "defence",
672 );
673 }
674
675 #[test]
676 fn suggest_defense_for_defence() {
677 assert_suggestion_result(
678 "defence",
679 SpellCheck::new(FstDictionary::curated(), Dialect::American),
680 "defense",
681 );
682 }
683
684 #[test]
685 fn suggest_defense_for_defence_titlecase() {
686 assert_suggestion_result(
687 "Defense",
688 SpellCheck::new(FstDictionary::curated(), Dialect::British),
689 "Defence",
690 );
691 }
692
693 #[test]
694 fn suggest_defence_for_defense_titlecase() {
695 assert_suggestion_result(
696 "Defence",
697 SpellCheck::new(FstDictionary::curated(), Dialect::American),
698 "Defense",
699 );
700 }
701
702 #[test]
703 #[ignore = "known failure due to bug"]
704 fn suggest_defense_for_defence_all_caps() {
705 assert_suggestion_result(
706 "DEFENSE",
707 SpellCheck::new(FstDictionary::curated(), Dialect::British),
708 "DEFENCE",
709 );
710 }
711
712 #[test]
713 #[ignore = "known failure due to bug"]
714 fn suggest_defence_for_defense_all_caps() {
715 assert_suggestion_result(
716 "DEFENCE",
717 SpellCheck::new(FstDictionary::curated(), Dialect::American),
718 "DEFENSE",
719 );
720 }
721
722 #[test]
724 fn suggest_sceptic_for_skeptic() {
725 assert_suggestion_result(
726 "skeptic",
727 SpellCheck::new(FstDictionary::curated(), Dialect::British),
728 "sceptic",
729 );
730 }
731
732 #[test]
733 fn suggest_skeptic_for_sceptic() {
734 assert_suggestion_result(
735 "sceptic",
736 SpellCheck::new(FstDictionary::curated(), Dialect::American),
737 "skeptic",
738 );
739 }
740
741 #[test]
742 fn suggest_sceptic_for_skeptic_titlecase() {
743 assert_suggestion_result(
744 "Skeptic",
745 SpellCheck::new(FstDictionary::curated(), Dialect::British),
746 "Sceptic",
747 );
748 }
749
750 #[test]
751 #[ignore = "known failure due to bug"]
752 fn suggest_skeptic_for_sceptic_titlecase() {
753 assert_suggestion_result(
754 "Sceptic",
755 SpellCheck::new(FstDictionary::curated(), Dialect::American),
756 "Skeptic",
757 );
758 }
759
760 #[test]
761 #[ignore = "known failure due to bug"]
762 fn suggest_skeptic_for_sceptic_all_caps() {
763 assert_suggestion_result(
764 "SKEPTIC",
765 SpellCheck::new(FstDictionary::curated(), Dialect::British),
766 "SCEPTIC",
767 );
768 }
769
770 #[test]
771 #[ignore = "known failure due to bug"]
772 fn suggest_sceptic_for_skeptic_all_caps() {
773 assert_suggestion_result(
774 "SCEPTIC",
775 SpellCheck::new(FstDictionary::curated(), Dialect::American),
776 "SKEPTIC",
777 );
778 }
779
780 #[test]
783 fn suggest_centimeter_for_centimetre() {
784 assert_suggestion_result(
785 "centimetre",
786 SpellCheck::new(FstDictionary::curated(), Dialect::American),
787 "centimeter",
788 );
789 }
790
791 #[test]
792 fn suggest_centimetre_for_centimeter() {
793 assert_suggestion_result(
794 "centimeter",
795 SpellCheck::new(FstDictionary::curated(), Dialect::British),
796 "centimetre",
797 );
798 }
799
800 #[test]
801 fn suggest_centimeter_for_centimetre_titlecase() {
802 assert_suggestion_result(
803 "Centimetre",
804 SpellCheck::new(FstDictionary::curated(), Dialect::American),
805 "Centimeter",
806 );
807 }
808
809 #[test]
810 #[ignore = "known failure due to bug"]
811 fn suggest_centimetre_for_centimeter_titlecase() {
812 assert_suggestion_result(
813 "Centimeter",
814 SpellCheck::new(FstDictionary::curated(), Dialect::British),
815 "Centimetre",
816 );
817 }
818
819 #[test]
820 #[ignore = "known failure due to bug"]
821 fn suggest_centimeter_for_centimetre_all_caps() {
822 assert_suggestion_result(
823 "CENTIMETRE",
824 SpellCheck::new(FstDictionary::curated(), Dialect::American),
825 "CENTIMETER",
826 );
827 }
828
829 #[test]
830 #[ignore = "known failure due to bug"]
831 fn suggest_centimetre_for_centimeter_all_caps() {
832 assert_suggestion_result(
833 "CENTIMETER",
834 SpellCheck::new(FstDictionary::curated(), Dialect::British),
835 "CENTIMETRE",
836 );
837 }
838
839 #[test]
842 fn suggest_traveler_for_traveller() {
843 assert_suggestion_result(
844 "traveller",
845 SpellCheck::new(FstDictionary::curated(), Dialect::American),
846 "traveler",
847 );
848 }
849
850 #[test]
851 fn suggest_traveller_for_traveler() {
852 assert_suggestion_result(
853 "traveler",
854 SpellCheck::new(FstDictionary::curated(), Dialect::British),
855 "traveller",
856 );
857 }
858
859 #[test]
860 fn suggest_traveler_for_traveller_titlecase() {
861 assert_suggestion_result(
862 "Traveller",
863 SpellCheck::new(FstDictionary::curated(), Dialect::American),
864 "Traveler",
865 );
866 }
867
868 #[test]
869 #[ignore = "known failure due to bug"]
870 fn suggest_traveller_for_traveler_titlecase() {
871 assert_suggestion_result(
872 "Traveler",
873 SpellCheck::new(FstDictionary::curated(), Dialect::British),
874 "Traveller",
875 );
876 }
877
878 #[test]
879 #[ignore = "known failure due to bug"]
880 fn suggest_traveler_for_traveller_all_caps() {
881 assert_suggestion_result(
882 "TRAVELLER",
883 SpellCheck::new(FstDictionary::curated(), Dialect::American),
884 "TRAVELER",
885 );
886 }
887
888 #[test]
889 #[ignore = "known failure due to bug"]
890 fn suggest_traveller_for_traveler_all_caps() {
891 assert_suggestion_result(
892 "TRAVELER",
893 SpellCheck::new(FstDictionary::curated(), Dialect::British),
894 "TRAVELLER",
895 );
896 }
897
898 #[test]
902 fn suggest_grey_for_gray_in_non_american() {
903 assert_suggestion_result(
904 "I've got a gray cat.",
905 SpellCheck::new(FstDictionary::curated(), Dialect::British),
906 "I've got a grey cat.",
907 );
908 }
909
910 #[test]
911 fn suggest_gray_for_grey_in_american() {
912 assert_suggestion_result(
913 "It's a greyscale photo.",
914 SpellCheck::new(FstDictionary::curated(), Dialect::American),
915 "It's a grayscale photo.",
916 );
917 }
918
919 #[test]
920 #[ignore = "known failure due to bug"]
921 fn suggest_grey_for_gray_in_non_american_titlecase() {
922 assert_suggestion_result(
923 "I've Got a Gray Cat.",
924 SpellCheck::new(FstDictionary::curated(), Dialect::British),
925 "I've Got a Grey Cat.",
926 );
927 }
928
929 #[test]
930 fn suggest_gray_for_grey_in_american_titlecase() {
931 assert_suggestion_result(
932 "It's a Greyscale Photo.",
933 SpellCheck::new(FstDictionary::curated(), Dialect::American),
934 "It's a Grayscale Photo.",
935 );
936 }
937
938 #[test]
939 #[ignore = "known failure due to bug"]
940 fn suggest_grey_for_gray_in_non_american_all_caps() {
941 assert_suggestion_result(
942 "GRAY",
943 SpellCheck::new(FstDictionary::curated(), Dialect::British),
944 "GREY",
945 );
946 }
947
948 #[test]
949 #[ignore = "known failure due to bug"]
950 fn suggest_gray_for_grey_in_american_all_caps() {
951 assert_suggestion_result(
952 "GREY",
953 SpellCheck::new(FstDictionary::curated(), Dialect::American),
954 "GRAY",
955 );
956 }
957
958 #[test]
962 fn fix_cheif_and_recieved() {
963 assert_top3_suggestion_result(
964 "The cheif recieved a letter.",
965 SpellCheck::new(FstDictionary::curated(), Dialect::British),
966 "The chief received a letter.",
967 );
968 }
969
970 #[test]
971 #[ignore = "known failure due to bug"]
972 fn fix_cheif_and_recieved_titlecase() {
973 assert_top3_suggestion_result(
974 "The Cheif Recieved a Letter.",
975 SpellCheck::new(FstDictionary::curated(), Dialect::British),
976 "The Chief Received a Letter.",
977 );
978 }
979
980 #[test]
981 #[ignore = "known failure due to bug"]
982 fn fix_cheif_and_recieved_all_caps() {
983 assert_top3_suggestion_result(
984 "THE CHEIF RECIEVED A LETTER.",
985 SpellCheck::new(FstDictionary::curated(), Dialect::British),
986 "THE CHEIF RECEIVED A LETTER.",
987 );
988 }
989}