1use crate::{CharString, CharStringExt, WordMetadata};
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: &'a WordMetadata,
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 if a_next.eq_ignore_ascii_case(&'y') && b_next.eq_ignore_ascii_case(&'y') {
214 if found_ay_ey {
215 return false; }
217 found_ay_ey = true;
218 continue;
219 }
220 }
221 }
222 return false; }
224
225 if !found_ay_ey {
226 return false;
227 }
228 found_ay_ey
229}
230
231pub(crate) fn is_ei_ie_misspelling(a: &[char], b: &[char]) -> bool {
236 if a.len() != b.len() {
237 return false;
238 }
239 let mut found_ei_ie = false;
240 let mut a_iter = a.iter();
241 let mut b_iter = b.iter();
242
243 while let (Some(&a_char), Some(&b_char)) = (a_iter.next(), b_iter.next()) {
244 if a_char.eq_ignore_ascii_case(&b_char) {
245 continue;
246 }
247
248 if a_char.eq_ignore_ascii_case(&'e') && b_char.eq_ignore_ascii_case(&'i') {
250 if let (Some(&a_next), Some(&b_next)) = (a_iter.next(), b_iter.next()) {
251 if a_next.eq_ignore_ascii_case(&'i') && b_next.eq_ignore_ascii_case(&'e') {
253 if found_ei_ie {
254 return false; }
256 found_ei_ie = true;
257 continue;
258 }
259 }
260 }
261 else if a_char.eq_ignore_ascii_case(&'i') && b_char.eq_ignore_ascii_case(&'e') {
263 if let (Some(&a_next), Some(&b_next)) = (a_iter.next(), b_iter.next()) {
264 if a_next.eq_ignore_ascii_case(&'e') && b_next.eq_ignore_ascii_case(&'i') {
266 if found_ei_ie {
267 return false; }
269 found_ei_ie = true;
270 continue;
271 }
272 }
273 }
274 return false;
275 }
276 found_ei_ie
277}
278
279fn score_suggestion(misspelled_word: &[char], sug: &FuzzyMatchResult) -> i32 {
283 if misspelled_word.is_empty() || sug.word.is_empty() {
284 return i32::MAX;
285 }
286
287 let mut score = sug.edit_distance as i32 * 10;
288
289 if misspelled_word
291 .first()
292 .unwrap()
293 .eq_ignore_ascii_case(sug.word.first().unwrap())
294 {
295 score -= 10;
296 }
297
298 if *misspelled_word.last().unwrap() == 's' && *sug.word.last().unwrap() == 's' {
300 score -= 5;
301 }
302
303 if sug.metadata.common {
305 score -= 5;
306 }
307
308 if sug.word.iter().filter(|c| **c == '\'').count() == 1 {
310 score -= 5;
311 }
312
313 if sug.edit_distance == 1
315 && (is_cksz_misspelling(misspelled_word, sug.word)
316 || is_ou_misspelling(misspelled_word, sug.word)
317 || is_ll_misspelling(misspelled_word, sug.word)
318 || is_ay_ey_misspelling(misspelled_word, sug.word))
319 {
320 score -= 6;
321 }
322 if sug.edit_distance == 2 {
323 if is_ei_ie_misspelling(misspelled_word, sug.word) {
324 score -= 11;
325 }
326 if is_er_misspelling(misspelled_word, sug.word) {
327 score -= 15;
328 }
329 }
330
331 score
332}
333
334fn order_suggestions<'b>(
336 misspelled_word: &[char],
337 mut matches: Vec<FuzzyMatchResult<'b>>,
338) -> Vec<&'b [char]> {
339 matches.sort_by_key(|v| score_suggestion(misspelled_word, v));
340
341 matches.into_iter().map(|v| v.word).collect()
342}
343
344pub fn suggest_correct_spelling<'a>(
347 misspelled_word: &[char],
348 result_limit: usize,
349 max_edit_dist: u8,
350 dictionary: &'a impl Dictionary,
351) -> Vec<&'a [char]> {
352 let matches: Vec<FuzzyMatchResult> = dictionary
353 .fuzzy_match(misspelled_word, max_edit_dist, result_limit)
354 .into_iter()
355 .collect();
356
357 order_suggestions(misspelled_word, matches)
358}
359
360pub fn suggest_correct_spelling_str(
363 misspelled_word: impl Into<String>,
364 result_limit: usize,
365 max_edit_dist: u8,
366 dictionary: &impl Dictionary,
367) -> Vec<String> {
368 let chars: CharString = misspelled_word.into().chars().collect();
369 suggest_correct_spelling(&chars, result_limit, max_edit_dist, dictionary)
370 .into_iter()
371 .map(|a| a.to_string())
372 .collect()
373}
374
375#[cfg(test)]
376mod tests {
377 use itertools::Itertools;
378
379 use crate::{
380 CharStringExt, Dialect,
381 linting::{
382 SpellCheck,
383 tests::{assert_suggestion_result, assert_top3_suggestion_result},
384 },
385 };
386
387 use super::{FstDictionary, suggest_correct_spelling_str};
388
389 const RESULT_LIMIT: usize = 100;
390 const MAX_EDIT_DIST: u8 = 2;
391
392 #[test]
393 fn normalizes_weve() {
394 let word = ['w', 'e', '’', 'v', 'e'];
395 let norm = word.normalized();
396
397 assert_eq!(norm.clone(), vec!['w', 'e', '\'', 'v', 'e'])
398 }
399
400 #[test]
401 fn punctation_no_duplicates() {
402 let results = suggest_correct_spelling_str(
403 "punctation",
404 RESULT_LIMIT,
405 MAX_EDIT_DIST,
406 &FstDictionary::curated(),
407 );
408
409 assert!(results.iter().all_unique())
410 }
411
412 #[test]
413 fn youre_contraction() {
414 assert_suggests_correction("youre", "you're");
415 }
416
417 #[test]
418 fn thats_contraction() {
419 assert_suggests_correction("thats", "that's");
420 }
421
422 #[test]
423 fn weve_contraction() {
424 assert_suggests_correction("weve", "we've");
425 }
426
427 #[test]
428 fn this_correction() {
429 assert_suggests_correction("ths", "this");
430 }
431
432 #[test]
433 fn issue_624_no_duplicates() {
434 let results = suggest_correct_spelling_str(
435 "Semantical",
436 RESULT_LIMIT,
437 MAX_EDIT_DIST,
438 &FstDictionary::curated(),
439 );
440
441 dbg!(&results);
442
443 assert!(results.iter().all_unique())
444 }
445
446 #[test]
447 fn issue_182() {
448 assert_suggests_correction("Im", "I'm");
449 }
450
451 #[test]
452 fn fst_spellcheck_hvllo() {
453 let results = suggest_correct_spelling_str(
454 "hvllo",
455 RESULT_LIMIT,
456 MAX_EDIT_DIST,
457 &FstDictionary::curated(),
458 );
459
460 dbg!(&results);
461
462 assert!(results.iter().take(3).contains(&"hello".to_string()));
463 }
464
465 #[track_caller]
468 fn assert_suggests_correction(misspelled_word: &str, correct: &str) {
469 let results = suggest_correct_spelling_str(
470 misspelled_word,
471 RESULT_LIMIT,
472 MAX_EDIT_DIST,
473 &FstDictionary::curated(),
474 );
475
476 dbg!(&results);
477
478 assert!(results.iter().take(3).contains(&correct.to_string()));
479 }
480
481 #[test]
482 fn spellcheck_hvllo() {
483 assert_suggests_correction("hvllo", "hello");
484 }
485
486 #[test]
487 fn spellcheck_aout() {
488 assert_suggests_correction("aout", "about");
489 }
490
491 #[test]
492 fn spellchecking_is_deterministic() {
493 let results1 = suggest_correct_spelling_str(
494 "hello",
495 RESULT_LIMIT,
496 MAX_EDIT_DIST,
497 &FstDictionary::curated(),
498 );
499 let results2 = suggest_correct_spelling_str(
500 "hello",
501 RESULT_LIMIT,
502 MAX_EDIT_DIST,
503 &FstDictionary::curated(),
504 );
505 let results3 = suggest_correct_spelling_str(
506 "hello",
507 RESULT_LIMIT,
508 MAX_EDIT_DIST,
509 &FstDictionary::curated(),
510 );
511
512 assert_eq!(results1, results2);
513 assert_eq!(results1, results3);
514 }
515
516 #[test]
517 fn adviced_correction() {
518 assert_suggests_correction("adviced", "advised");
519 }
520
521 #[test]
522 fn aknowledged_correction() {
523 assert_suggests_correction("aknowledged", "acknowledged");
524 }
525
526 #[test]
527 fn alcaholic_correction() {
528 assert_suggests_correction("alcaholic", "alcoholic");
529 }
530
531 #[test]
532 fn slaves_correction() {
533 assert_suggests_correction("Slaves", "Slavs");
534 }
535
536 #[test]
537 fn conciousness_correction() {
538 assert_suggests_correction("conciousness", "consciousness");
539 }
540
541 #[test]
545 fn suggest_color_for_colour_lowercase() {
546 assert_suggestion_result(
547 "colour",
548 SpellCheck::new(FstDictionary::curated(), Dialect::American),
549 "color",
550 );
551 }
552
553 #[test]
554 fn suggest_colour_for_color_lowercase() {
555 assert_suggestion_result(
556 "color",
557 SpellCheck::new(FstDictionary::curated(), Dialect::British),
558 "colour",
559 );
560 }
561
562 #[test]
564 fn suggest_color_for_colour_titlecase() {
565 assert_suggestion_result(
566 "Colour",
567 SpellCheck::new(FstDictionary::curated(), Dialect::American),
568 "Color",
569 );
570 }
571
572 #[test]
573 #[ignore = "known failure due to bug"]
574 fn suggest_colour_for_color_titlecase() {
575 assert_suggestion_result(
576 "Color",
577 SpellCheck::new(FstDictionary::curated(), Dialect::British),
578 "Colour",
579 );
580 }
581
582 #[test]
584 #[ignore = "known failure due to bug"]
585 fn suggest_color_for_colour_all_caps() {
586 assert_suggestion_result(
587 "COLOUR",
588 SpellCheck::new(FstDictionary::curated(), Dialect::American),
589 "COLOR",
590 );
591 }
592
593 #[test]
594 #[ignore = "known failure due to bug"]
595 fn suggest_colour_for_color_all_caps() {
596 assert_suggestion_result(
597 "COLOR",
598 SpellCheck::new(FstDictionary::curated(), Dialect::British),
599 "COLOUR",
600 );
601 }
602
603 #[test]
607 fn suggest_realise_for_realize() {
608 assert_suggestion_result(
609 "realize",
610 SpellCheck::new(FstDictionary::curated(), Dialect::British),
611 "realise",
612 );
613 }
614
615 #[test]
616 fn suggest_realize_for_realise() {
617 assert_suggestion_result(
618 "realise",
619 SpellCheck::new(FstDictionary::curated(), Dialect::American),
620 "realize",
621 );
622 }
623
624 #[test]
625 fn suggest_realise_for_realize_titlecase() {
626 assert_suggestion_result(
627 "Realize",
628 SpellCheck::new(FstDictionary::curated(), Dialect::British),
629 "Realise",
630 );
631 }
632
633 #[test]
634 #[ignore = "known failure due to bug"]
635 fn suggest_realize_for_realise_titlecase() {
636 assert_suggestion_result(
637 "Realise",
638 SpellCheck::new(FstDictionary::curated(), Dialect::American),
639 "Realize",
640 );
641 }
642
643 #[test]
644 #[ignore = "known failure due to bug"]
645 fn suggest_realise_for_realize_all_caps() {
646 assert_suggestion_result(
647 "REALIZE",
648 SpellCheck::new(FstDictionary::curated(), Dialect::British),
649 "REALISE",
650 );
651 }
652
653 #[test]
654 #[ignore = "known failure due to bug"]
655 fn suggest_realize_for_realise_all_caps() {
656 assert_suggestion_result(
657 "REALISE",
658 SpellCheck::new(FstDictionary::curated(), Dialect::American),
659 "REALIZE",
660 );
661 }
662
663 #[test]
665 fn suggest_defence_for_defense() {
666 assert_suggestion_result(
667 "defense",
668 SpellCheck::new(FstDictionary::curated(), Dialect::British),
669 "defence",
670 );
671 }
672
673 #[test]
674 fn suggest_defense_for_defence() {
675 assert_suggestion_result(
676 "defence",
677 SpellCheck::new(FstDictionary::curated(), Dialect::American),
678 "defense",
679 );
680 }
681
682 #[test]
683 fn suggest_defense_for_defence_titlecase() {
684 assert_suggestion_result(
685 "Defense",
686 SpellCheck::new(FstDictionary::curated(), Dialect::British),
687 "Defence",
688 );
689 }
690
691 #[test]
692 fn suggest_defence_for_defense_titlecase() {
693 assert_suggestion_result(
694 "Defence",
695 SpellCheck::new(FstDictionary::curated(), Dialect::American),
696 "Defense",
697 );
698 }
699
700 #[test]
701 #[ignore = "known failure due to bug"]
702 fn suggest_defense_for_defence_all_caps() {
703 assert_suggestion_result(
704 "DEFENSE",
705 SpellCheck::new(FstDictionary::curated(), Dialect::British),
706 "DEFENCE",
707 );
708 }
709
710 #[test]
711 #[ignore = "known failure due to bug"]
712 fn suggest_defence_for_defense_all_caps() {
713 assert_suggestion_result(
714 "DEFENCE",
715 SpellCheck::new(FstDictionary::curated(), Dialect::American),
716 "DEFENSE",
717 );
718 }
719
720 #[test]
722 fn suggest_sceptic_for_skeptic() {
723 assert_suggestion_result(
724 "skeptic",
725 SpellCheck::new(FstDictionary::curated(), Dialect::British),
726 "sceptic",
727 );
728 }
729
730 #[test]
731 fn suggest_skeptic_for_sceptic() {
732 assert_suggestion_result(
733 "sceptic",
734 SpellCheck::new(FstDictionary::curated(), Dialect::American),
735 "skeptic",
736 );
737 }
738
739 #[test]
740 fn suggest_sceptic_for_skeptic_titlecase() {
741 assert_suggestion_result(
742 "Skeptic",
743 SpellCheck::new(FstDictionary::curated(), Dialect::British),
744 "Sceptic",
745 );
746 }
747
748 #[test]
749 #[ignore = "known failure due to bug"]
750 fn suggest_skeptic_for_sceptic_titlecase() {
751 assert_suggestion_result(
752 "Sceptic",
753 SpellCheck::new(FstDictionary::curated(), Dialect::American),
754 "Skeptic",
755 );
756 }
757
758 #[test]
759 #[ignore = "known failure due to bug"]
760 fn suggest_skeptic_for_sceptic_all_caps() {
761 assert_suggestion_result(
762 "SKEPTIC",
763 SpellCheck::new(FstDictionary::curated(), Dialect::British),
764 "SCEPTIC",
765 );
766 }
767
768 #[test]
769 #[ignore = "known failure due to bug"]
770 fn suggest_sceptic_for_skeptic_all_caps() {
771 assert_suggestion_result(
772 "SCEPTIC",
773 SpellCheck::new(FstDictionary::curated(), Dialect::American),
774 "SKEPTIC",
775 );
776 }
777
778 #[test]
781 fn suggest_centimeter_for_centimetre() {
782 assert_suggestion_result(
783 "centimetre",
784 SpellCheck::new(FstDictionary::curated(), Dialect::American),
785 "centimeter",
786 );
787 }
788
789 #[test]
790 fn suggest_centimetre_for_centimeter() {
791 assert_suggestion_result(
792 "centimeter",
793 SpellCheck::new(FstDictionary::curated(), Dialect::British),
794 "centimetre",
795 );
796 }
797
798 #[test]
799 fn suggest_centimeter_for_centimetre_titlecase() {
800 assert_suggestion_result(
801 "Centimetre",
802 SpellCheck::new(FstDictionary::curated(), Dialect::American),
803 "Centimeter",
804 );
805 }
806
807 #[test]
808 #[ignore = "known failure due to bug"]
809 fn suggest_centimetre_for_centimeter_titlecase() {
810 assert_suggestion_result(
811 "Centimeter",
812 SpellCheck::new(FstDictionary::curated(), Dialect::British),
813 "Centimetre",
814 );
815 }
816
817 #[test]
818 #[ignore = "known failure due to bug"]
819 fn suggest_centimeter_for_centimetre_all_caps() {
820 assert_suggestion_result(
821 "CENTIMETRE",
822 SpellCheck::new(FstDictionary::curated(), Dialect::American),
823 "CENTIMETER",
824 );
825 }
826
827 #[test]
828 #[ignore = "known failure due to bug"]
829 fn suggest_centimetre_for_centimeter_all_caps() {
830 assert_suggestion_result(
831 "CENTIMETER",
832 SpellCheck::new(FstDictionary::curated(), Dialect::British),
833 "CENTIMETRE",
834 );
835 }
836
837 #[test]
840 fn suggest_traveler_for_traveller() {
841 assert_suggestion_result(
842 "traveller",
843 SpellCheck::new(FstDictionary::curated(), Dialect::American),
844 "traveler",
845 );
846 }
847
848 #[test]
849 fn suggest_traveller_for_traveler() {
850 assert_suggestion_result(
851 "traveler",
852 SpellCheck::new(FstDictionary::curated(), Dialect::British),
853 "traveller",
854 );
855 }
856
857 #[test]
858 fn suggest_traveler_for_traveller_titlecase() {
859 assert_suggestion_result(
860 "Traveller",
861 SpellCheck::new(FstDictionary::curated(), Dialect::American),
862 "Traveler",
863 );
864 }
865
866 #[test]
867 #[ignore = "known failure due to bug"]
868 fn suggest_traveller_for_traveler_titlecase() {
869 assert_suggestion_result(
870 "Traveler",
871 SpellCheck::new(FstDictionary::curated(), Dialect::British),
872 "Traveller",
873 );
874 }
875
876 #[test]
877 #[ignore = "known failure due to bug"]
878 fn suggest_traveler_for_traveller_all_caps() {
879 assert_suggestion_result(
880 "TRAVELLER",
881 SpellCheck::new(FstDictionary::curated(), Dialect::American),
882 "TRAVELER",
883 );
884 }
885
886 #[test]
887 #[ignore = "known failure due to bug"]
888 fn suggest_traveller_for_traveler_all_caps() {
889 assert_suggestion_result(
890 "TRAVELER",
891 SpellCheck::new(FstDictionary::curated(), Dialect::British),
892 "TRAVELLER",
893 );
894 }
895
896 #[test]
900 fn suggest_grey_for_gray_in_non_american() {
901 assert_suggestion_result(
902 "I've got a gray cat.",
903 SpellCheck::new(FstDictionary::curated(), Dialect::British),
904 "I've got a grey cat.",
905 );
906 }
907
908 #[test]
909 fn suggest_gray_for_grey_in_american() {
910 assert_suggestion_result(
911 "It's a greyscale photo.",
912 SpellCheck::new(FstDictionary::curated(), Dialect::American),
913 "It's a grayscale photo.",
914 );
915 }
916
917 #[test]
918 #[ignore = "known failure due to bug"]
919 fn suggest_grey_for_gray_in_non_american_titlecase() {
920 assert_suggestion_result(
921 "I've Got a Gray Cat.",
922 SpellCheck::new(FstDictionary::curated(), Dialect::British),
923 "I've Got a Grey Cat.",
924 );
925 }
926
927 #[test]
928 fn suggest_gray_for_grey_in_american_titlecase() {
929 assert_suggestion_result(
930 "It's a Greyscale Photo.",
931 SpellCheck::new(FstDictionary::curated(), Dialect::American),
932 "It's a Grayscale Photo.",
933 );
934 }
935
936 #[test]
937 #[ignore = "known failure due to bug"]
938 fn suggest_grey_for_gray_in_non_american_all_caps() {
939 assert_suggestion_result(
940 "GRAY",
941 SpellCheck::new(FstDictionary::curated(), Dialect::British),
942 "GREY",
943 );
944 }
945
946 #[test]
947 #[ignore = "known failure due to bug"]
948 fn suggest_gray_for_grey_in_american_all_caps() {
949 assert_suggestion_result(
950 "GREY",
951 SpellCheck::new(FstDictionary::curated(), Dialect::American),
952 "GRAY",
953 );
954 }
955
956 #[test]
960 fn fix_cheif_and_recieved() {
961 assert_top3_suggestion_result(
962 "The cheif recieved a letter.",
963 SpellCheck::new(FstDictionary::curated(), Dialect::British),
964 "The chief received a letter.",
965 );
966 }
967
968 #[test]
969 #[ignore = "known failure due to bug"]
970 fn fix_cheif_and_recieved_titlecase() {
971 assert_top3_suggestion_result(
972 "The Cheif Recieved a Letter.",
973 SpellCheck::new(FstDictionary::curated(), Dialect::British),
974 "The Chief Received a Letter.",
975 );
976 }
977
978 #[test]
979 #[ignore = "known failure due to bug"]
980 fn fix_cheif_and_recieved_all_caps() {
981 assert_top3_suggestion_result(
982 "THE CHEIF RECIEVED A LETTER.",
983 SpellCheck::new(FstDictionary::curated(), Dialect::British),
984 "THE CHEIF RECEIVED A LETTER.",
985 );
986 }
987}