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