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