Skip to main content

ide_assists/handlers/
replace_string_with_char.rs

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
10// Assist: replace_string_with_char
11//
12// Replace string literal with char literal.
13//
14// ```
15// fn main() {
16//     find("{$0");
17// }
18// ```
19// ->
20// ```
21// fn main() {
22//     find('{');
23// }
24// ```
25pub(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
55// Assist: replace_char_with_string
56//
57// Replace a char literal with a string literal.
58//
59// ```
60// fn main() {
61//     find('{$0');
62// }
63// ```
64// ->
65// ```
66// fn main() {
67//     find("{");
68// }
69// ```
70pub(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}