1use syntax::{
2 AstToken,
3 SyntaxKind::{CHAR, STRING},
4 TextRange, TextSize, ast,
5 ast::IsString,
6};
7
8use crate::{AssistContext, AssistId, Assists, utils::string_suffix};
9
10pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
26 let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?;
27 let value = token.value().ok()?;
28 let target = token.syntax().text_range();
29
30 if value.chars().take(2).count() != 1 {
31 return None;
32 }
33 let quote_offsets = token.quote_offsets()?;
34
35 acc.add(
36 AssistId::refactor_rewrite("replace_string_with_char"),
37 "Replace string with char",
38 target,
39 |edit| {
40 let (left, right) = quote_offsets.quotes;
41 let suffix = TextSize::of(string_suffix(token.text()).unwrap_or_default());
42 let right = TextRange::new(right.start(), right.end() - suffix);
43 edit.replace(left, '\'');
44 edit.replace(right, '\'');
45 if token.text_without_quotes() == "'" {
46 edit.insert(left.end(), '\\');
47 }
48 },
49 )
50}
51
52pub(crate) fn replace_char_with_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
68 let token = ctx.find_token_syntax_at_offset(CHAR)?;
69 let target = token.text_range();
70
71 acc.add(
72 AssistId::refactor_rewrite("replace_char_with_string"),
73 "Replace char with string",
74 target,
75 |edit| {
76 let suffix = string_suffix(token.text()).unwrap_or_default();
77 if token.text().starts_with("'\"'") {
78 edit.replace(token.text_range(), format!(r#""\""{suffix}"#));
79 } else {
80 let len = TextSize::of('\'');
81 let suffix = TextSize::of(suffix);
82 edit.replace(TextRange::at(target.start(), len), '"');
83 edit.replace(TextRange::at(target.end() - suffix - len, len), '"');
84 }
85 },
86 )
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::tests::{check_assist, check_assist_not_applicable};
92
93 use super::*;
94
95 #[test]
96 fn replace_string_with_char_assist() {
97 check_assist(
98 replace_string_with_char,
99 r#"
100fn f() {
101 let s = "$0c";
102}
103"#,
104 r##"
105fn f() {
106 let s = 'c';
107}
108"##,
109 )
110 }
111
112 #[test]
113 fn replace_string_with_char_has_suffix() {
114 check_assist(
115 replace_string_with_char,
116 r#"
117fn f() {
118 let s = "$0c"i32;
119}
120"#,
121 r##"
122fn f() {
123 let s = 'c'i32;
124}
125"##,
126 )
127 }
128
129 #[test]
130 fn replace_string_with_char_assist_with_multi_byte_char() {
131 check_assist(
132 replace_string_with_char,
133 r#"
134fn f() {
135 let s = "$0😀";
136}
137"#,
138 r##"
139fn f() {
140 let s = '😀';
141}
142"##,
143 )
144 }
145
146 #[test]
147 fn replace_string_with_char_multiple_chars() {
148 check_assist_not_applicable(
149 replace_string_with_char,
150 r#"
151fn f() {
152 let s = "$0test";
153}
154"#,
155 )
156 }
157
158 #[test]
159 fn replace_string_with_char_works_inside_macros() {
160 check_assist(
161 replace_string_with_char,
162 r#"
163fn f() {
164 format!($0"x", 92)
165}
166"#,
167 r##"
168fn f() {
169 format!('x', 92)
170}
171"##,
172 )
173 }
174
175 #[test]
176 fn replace_string_with_char_newline() {
177 check_assist(
178 replace_string_with_char,
179 r#"
180fn f() {
181 find($0"\n");
182}
183"#,
184 r##"
185fn f() {
186 find('\n');
187}
188"##,
189 )
190 }
191
192 #[test]
193 fn replace_string_with_char_unicode_escape() {
194 check_assist(
195 replace_string_with_char,
196 r#"
197fn f() {
198 find($0"\u{7FFF}");
199}
200"#,
201 r##"
202fn f() {
203 find('\u{7FFF}');
204}
205"##,
206 )
207 }
208
209 #[test]
210 fn replace_raw_string_with_char() {
211 check_assist(
212 replace_string_with_char,
213 r##"
214fn f() {
215 $0r#"X"#
216}
217"##,
218 r##"
219fn f() {
220 'X'
221}
222"##,
223 )
224 }
225
226 #[test]
227 fn replace_char_with_string_assist() {
228 check_assist(
229 replace_char_with_string,
230 r"
231fn f() {
232 let s = '$0c';
233}
234",
235 r#"
236fn f() {
237 let s = "c";
238}
239"#,
240 )
241 }
242
243 #[test]
244 fn replace_char_with_string_assist_with_multi_byte_char() {
245 check_assist(
246 replace_char_with_string,
247 r"
248fn f() {
249 let s = '$0😀';
250}
251",
252 r#"
253fn f() {
254 let s = "😀";
255}
256"#,
257 )
258 }
259
260 #[test]
261 fn replace_char_with_string_newline() {
262 check_assist(
263 replace_char_with_string,
264 r"
265fn f() {
266 find($0'\n');
267}
268",
269 r#"
270fn f() {
271 find("\n");
272}
273"#,
274 )
275 }
276
277 #[test]
278 fn replace_char_with_string_unicode_escape() {
279 check_assist(
280 replace_char_with_string,
281 r"
282fn f() {
283 find($0'\u{7FFF}');
284}
285",
286 r#"
287fn f() {
288 find("\u{7FFF}");
289}
290"#,
291 )
292 }
293
294 #[test]
295 fn replace_char_with_string_quote() {
296 check_assist(
297 replace_char_with_string,
298 r#"
299fn f() {
300 find($0'"');
301}
302"#,
303 r#"
304fn f() {
305 find("\"");
306}
307"#,
308 )
309 }
310
311 #[test]
312 fn replace_char_with_string_quote_has_suffix() {
313 check_assist(
314 replace_char_with_string,
315 r#"
316fn f() {
317 find($0'"'i32);
318}
319"#,
320 r#"
321fn f() {
322 find("\""i32);
323}
324"#,
325 )
326 }
327
328 #[test]
329 fn replace_char_with_string_escaped_quote_has_suffix() {
330 check_assist(
331 replace_char_with_string,
332 r#"
333fn f() {
334 find($0'\"'i32);
335}
336"#,
337 r#"
338fn f() {
339 find("\""i32);
340}
341"#,
342 )
343 }
344
345 #[test]
346 fn replace_string_with_char_quote() {
347 check_assist(
348 replace_string_with_char,
349 r#"
350fn f() {
351 find($0"'");
352}
353"#,
354 r#"
355fn f() {
356 find('\'');
357}
358"#,
359 )
360 }
361
362 #[test]
363 fn replace_string_with_escaped_char_quote() {
364 check_assist(
365 replace_string_with_char,
366 r#"
367fn f() {
368 find($0"\'");
369}
370"#,
371 r#"
372fn f() {
373 find('\'');
374}
375"#,
376 )
377 }
378
379 #[test]
380 fn replace_string_with_char_quote_has_suffix() {
381 check_assist(
382 replace_string_with_char,
383 r#"
384fn f() {
385 find($0"'"i32);
386}
387"#,
388 r#"
389fn f() {
390 find('\''i32);
391}
392"#,
393 )
394 }
395
396 #[test]
397 fn replace_string_with_escaped_char_quote_has_suffix() {
398 check_assist(
399 replace_string_with_char,
400 r#"
401fn f() {
402 find($0"\'"i32);
403}
404"#,
405 r#"
406fn f() {
407 find('\''i32);
408}
409"#,
410 )
411 }
412
413 #[test]
414 fn replace_raw_string_with_char_quote() {
415 check_assist(
416 replace_string_with_char,
417 r#"
418fn f() {
419 find($0r"'");
420}
421"#,
422 r#"
423fn f() {
424 find('\'');
425}
426"#,
427 )
428 }
429
430 #[test]
431 fn replace_string_with_code_escaped_char_quote() {
432 check_assist(
433 replace_string_with_char,
434 r#"
435fn f() {
436 find($0"\x27");
437}
438"#,
439 r#"
440fn f() {
441 find('\x27');
442}
443"#,
444 )
445 }
446}