shape_ast/error/
suggestions.rs1pub fn find_similar<'a>(
8 name: &str,
9 candidates: impl Iterator<Item = &'a str>,
10 max_distance: usize,
11) -> Vec<&'a str> {
12 let mut similar: Vec<(&str, usize)> = candidates
13 .filter_map(|candidate| {
14 let dist = levenshtein_distance(name, candidate);
15 if dist <= max_distance && dist > 0 {
16 Some((candidate, dist))
17 } else {
18 None
19 }
20 })
21 .collect();
22 similar.sort_by_key(|(_, d)| *d);
23 similar.into_iter().map(|(s, _)| s).collect()
24}
25
26fn levenshtein_distance(a: &str, b: &str) -> usize {
28 let a_chars: Vec<char> = a.chars().collect();
29 let b_chars: Vec<char> = b.chars().collect();
30 let a_len = a_chars.len();
31 let b_len = b_chars.len();
32
33 if a_len == 0 {
34 return b_len;
35 }
36 if b_len == 0 {
37 return a_len;
38 }
39
40 let mut prev_row: Vec<usize> = (0..=b_len).collect();
41 let mut curr_row = vec![0; b_len + 1];
42
43 for (i, a_char) in a_chars.iter().enumerate() {
44 curr_row[0] = i + 1;
45 for (j, b_char) in b_chars.iter().enumerate() {
46 let cost = if a_char == b_char { 0 } else { 1 };
47 curr_row[j + 1] = (prev_row[j + 1] + 1)
48 .min(curr_row[j] + 1)
49 .min(prev_row[j] + cost);
50 }
51 std::mem::swap(&mut prev_row, &mut curr_row);
52 }
53
54 prev_row[b_len]
55}
56
57pub fn did_you_mean<'a>(name: &str, candidates: impl Iterator<Item = &'a str>) -> Option<String> {
59 let similar = find_similar(name, candidates, 3);
60 match similar.len() {
61 0 => None,
62 1 => Some(format!("did you mean `{}`?", similar[0])),
63 _ => Some(format!(
64 "did you mean one of: {}?",
65 similar
66 .iter()
67 .map(|s| format!("`{}`", s))
68 .collect::<Vec<_>>()
69 .join(", ")
70 )),
71 }
72}
73
74pub fn type_conversion_hint(expected: &str, actual: &str) -> Option<String> {
76 match (expected, actual) {
77 ("number", "string") => {
78 Some("try converting with `toNumber()` or `parseFloat()`".to_string())
79 }
80 ("string", "number") => {
81 Some("try converting with `toString()` or string interpolation".to_string())
82 }
83 ("boolean", "number") => {
84 Some("use a comparison like `x != 0` to convert to boolean".to_string())
85 }
86 ("array", other) => Some(format!("wrap the value in an array: `[{}]`", other)),
87 _ => None,
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn test_suggestions_levenshtein() {
97 let candidates = vec!["close", "open", "high", "low", "volume"];
98 let similar = find_similar("clsoe", candidates.iter().copied(), 2);
99 assert!(similar.contains(&"close"));
100 }
101
102 #[test]
103 fn test_suggestions_did_you_mean() {
104 let candidates = vec!["close", "momentum", "bollinger", "macdhistogram"];
106 let hint = did_you_mean("closee", candidates.iter().copied());
107 assert_eq!(hint, Some("did you mean `close`?".to_string()));
108
109 let hint2 = did_you_mean("xyz", candidates.iter().copied());
111 assert_eq!(hint2, None);
112 }
113}