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