]> git.lizzy.rs Git - rust.git/blob - clippy_utils/src/str_utils.rs
Improve heuristics for determining whether eager of lazy evaluation is preferred
[rust.git] / clippy_utils / src / str_utils.rs
1 /// Dealing with sting indices can be hard, this struct ensures that both the
2 /// character and byte index are provided for correct indexing.
3 #[derive(Debug, Default, PartialEq, Eq)]
4 pub struct StrIndex {
5     pub char_index: usize,
6     pub byte_index: usize,
7 }
8
9 impl StrIndex {
10     pub fn new(char_index: usize, byte_index: usize) -> Self {
11         Self { char_index, byte_index }
12     }
13 }
14
15 /// Returns the index of the character after the first camel-case component of `s`.
16 ///
17 /// ```
18 /// assert_eq!(camel_case_until("AbcDef"), StrIndex::new(6, 6));
19 /// assert_eq!(camel_case_until("ABCD"), StrIndex::new(0, 0));
20 /// assert_eq!(camel_case_until("AbcDD"), StrIndex::new(3, 3));
21 /// assert_eq!(camel_case_until("Abc\u{f6}\u{f6}DD"), StrIndex::new(5, 7));
22 /// ```
23 #[must_use]
24 pub fn camel_case_until(s: &str) -> StrIndex {
25     let mut iter = s.char_indices().enumerate();
26     if let Some((_char_index, (_, first))) = iter.next() {
27         if !first.is_uppercase() {
28             return StrIndex::new(0, 0);
29         }
30     } else {
31         return StrIndex::new(0, 0);
32     }
33     let mut up = true;
34     let mut last_index = StrIndex::new(0, 0);
35     for (char_index, (byte_index, c)) in iter {
36         if up {
37             if c.is_lowercase() {
38                 up = false;
39             } else {
40                 return last_index;
41             }
42         } else if c.is_uppercase() {
43             up = true;
44             last_index.byte_index = byte_index;
45             last_index.char_index = char_index;
46         } else if !c.is_lowercase() {
47             return StrIndex::new(char_index, byte_index);
48         }
49     }
50
51     if up {
52         last_index
53     } else {
54         StrIndex::new(s.chars().count(), s.len())
55     }
56 }
57
58 /// Returns index of the last camel-case component of `s`.
59 ///
60 /// ```
61 /// assert_eq!(camel_case_start("AbcDef"), StrIndex::new(0, 0));
62 /// assert_eq!(camel_case_start("abcDef"), StrIndex::new(3, 3));
63 /// assert_eq!(camel_case_start("ABCD"), StrIndex::new(4, 4));
64 /// assert_eq!(camel_case_start("abcd"), StrIndex::new(4, 4));
65 /// assert_eq!(camel_case_start("\u{f6}\u{f6}cd"), StrIndex::new(4, 6));
66 /// ```
67 #[must_use]
68 pub fn camel_case_start(s: &str) -> StrIndex {
69     let char_count = s.chars().count();
70     let range = 0..char_count;
71     let mut iter = range.rev().zip(s.char_indices().rev());
72     if let Some((char_index, (_, first))) = iter.next() {
73         if !first.is_lowercase() {
74             return StrIndex::new(char_index, s.len());
75         }
76     } else {
77         return StrIndex::new(char_count, s.len());
78     }
79     let mut down = true;
80     let mut last_index = StrIndex::new(char_count, s.len());
81     for (char_index, (byte_index, c)) in iter {
82         if down {
83             if c.is_uppercase() {
84                 down = false;
85                 last_index.byte_index = byte_index;
86                 last_index.char_index = char_index;
87             } else if !c.is_lowercase() {
88                 return last_index;
89             }
90         } else if c.is_lowercase() {
91             down = true;
92         } else if c.is_uppercase() {
93             last_index.byte_index = byte_index;
94             last_index.char_index = char_index;
95         } else {
96             return last_index;
97         }
98     }
99     last_index
100 }
101
102 /// Dealing with sting comparison can be complicated, this struct ensures that both the
103 /// character and byte count are provided for correct indexing.
104 #[derive(Debug, Default, PartialEq, Eq)]
105 pub struct StrCount {
106     pub char_count: usize,
107     pub byte_count: usize,
108 }
109
110 impl StrCount {
111     pub fn new(char_count: usize, byte_count: usize) -> Self {
112         Self { char_count, byte_count }
113     }
114 }
115
116 /// Returns the number of chars that match from the start
117 ///
118 /// ```
119 /// assert_eq!(count_match_start("hello_mouse", "hello_penguin"), StrCount::new(6, 6));
120 /// assert_eq!(count_match_start("hello_clippy", "bye_bugs"), StrCount::new(0, 0));
121 /// assert_eq!(count_match_start("hello_world", "hello_world"), StrCount::new(11, 11));
122 /// assert_eq!(count_match_start("T\u{f6}ffT\u{f6}ff", "T\u{f6}ff"), StrCount::new(4, 5));
123 /// ```
124 #[must_use]
125 pub fn count_match_start(str1: &str, str2: &str) -> StrCount {
126     // (char_index, char1)
127     let char_count = str1.chars().count();
128     let iter1 = (0..=char_count).zip(str1.chars());
129     // (byte_index, char2)
130     let iter2 = str2.char_indices();
131
132     iter1
133         .zip(iter2)
134         .take_while(|((_, c1), (_, c2))| c1 == c2)
135         .last()
136         .map_or_else(StrCount::default, |((char_index, _), (byte_index, character))| {
137             StrCount::new(char_index + 1, byte_index + character.len_utf8())
138         })
139 }
140
141 /// Returns the number of chars and bytes that match from the end
142 ///
143 /// ```
144 /// assert_eq!(count_match_end("hello_cat", "bye_cat"), StrCount::new(4, 4));
145 /// assert_eq!(count_match_end("if_item_thing", "enum_value"), StrCount::new(0, 0));
146 /// assert_eq!(count_match_end("Clippy", "Clippy"), StrCount::new(6, 6));
147 /// assert_eq!(count_match_end("MyT\u{f6}ff", "YourT\u{f6}ff"), StrCount::new(4, 5));
148 /// ```
149 #[must_use]
150 pub fn count_match_end(str1: &str, str2: &str) -> StrCount {
151     let char_count = str1.chars().count();
152     if char_count == 0 {
153         return StrCount::default();
154     }
155
156     // (char_index, char1)
157     let iter1 = (0..char_count).rev().zip(str1.chars().rev());
158     // (byte_index, char2)
159     let byte_count = str2.len();
160     let iter2 = str2.char_indices().rev();
161
162     iter1
163         .zip(iter2)
164         .take_while(|((_, c1), (_, c2))| c1 == c2)
165         .last()
166         .map_or_else(StrCount::default, |((char_index, _), (byte_index, _))| {
167             StrCount::new(char_count - char_index, byte_count - byte_index)
168         })
169 }
170
171 #[cfg(test)]
172 mod test {
173     use super::*;
174
175     #[test]
176     fn camel_case_start_full() {
177         assert_eq!(camel_case_start("AbcDef"), StrIndex::new(0, 0));
178         assert_eq!(camel_case_start("Abc"), StrIndex::new(0, 0));
179         assert_eq!(camel_case_start("ABcd"), StrIndex::new(0, 0));
180         assert_eq!(camel_case_start("ABcdEf"), StrIndex::new(0, 0));
181         assert_eq!(camel_case_start("AabABcd"), StrIndex::new(0, 0));
182     }
183
184     #[test]
185     fn camel_case_start_partial() {
186         assert_eq!(camel_case_start("abcDef"), StrIndex::new(3, 3));
187         assert_eq!(camel_case_start("aDbc"), StrIndex::new(1, 1));
188         assert_eq!(camel_case_start("aabABcd"), StrIndex::new(3, 3));
189         assert_eq!(camel_case_start("\u{f6}\u{f6}AabABcd"), StrIndex::new(2, 4));
190     }
191
192     #[test]
193     fn camel_case_start_not() {
194         assert_eq!(camel_case_start("AbcDef_"), StrIndex::new(7, 7));
195         assert_eq!(camel_case_start("AbcDD"), StrIndex::new(5, 5));
196         assert_eq!(camel_case_start("all_small"), StrIndex::new(9, 9));
197         assert_eq!(camel_case_start("\u{f6}_all_small"), StrIndex::new(11, 12));
198     }
199
200     #[test]
201     fn camel_case_start_caps() {
202         assert_eq!(camel_case_start("ABCD"), StrIndex::new(4, 4));
203     }
204
205     #[test]
206     fn camel_case_until_full() {
207         assert_eq!(camel_case_until("AbcDef"), StrIndex::new(6, 6));
208         assert_eq!(camel_case_until("Abc"), StrIndex::new(3, 3));
209         assert_eq!(camel_case_until("Abc\u{f6}\u{f6}\u{f6}"), StrIndex::new(6, 9));
210     }
211
212     #[test]
213     fn camel_case_until_not() {
214         assert_eq!(camel_case_until("abcDef"), StrIndex::new(0, 0));
215         assert_eq!(camel_case_until("aDbc"), StrIndex::new(0, 0));
216     }
217
218     #[test]
219     fn camel_case_until_partial() {
220         assert_eq!(camel_case_until("AbcDef_"), StrIndex::new(6, 6));
221         assert_eq!(camel_case_until("CallTypeC"), StrIndex::new(8, 8));
222         assert_eq!(camel_case_until("AbcDD"), StrIndex::new(3, 3));
223         assert_eq!(camel_case_until("Abc\u{f6}\u{f6}DD"), StrIndex::new(5, 7));
224     }
225
226     #[test]
227     fn until_caps() {
228         assert_eq!(camel_case_until("ABCD"), StrIndex::new(0, 0));
229     }
230 }