]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/raw_string.rs
New upstream version 1.67.1+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / raw_string.rs
1 use std::borrow::Cow;
2
3 use syntax::{ast, ast::IsString, AstToken, TextRange, TextSize};
4
5 use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7 // Assist: make_raw_string
8 //
9 // Adds `r#` to a plain string literal.
10 //
11 // ```
12 // fn main() {
13 // "Hello,$0 World!";
14 // }
15 // ```
16 // ->
17 // ```
18 // fn main() {
19 // r#"Hello, World!"#;
20 // }
21 // ```
22 pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
23 let token = ctx.find_token_at_offset::<ast::String>()?;
24 if token.is_raw() {
25 return None;
26 }
27 let value = token.value()?;
28 let target = token.syntax().text_range();
29 acc.add(
30 AssistId("make_raw_string", AssistKind::RefactorRewrite),
31 "Rewrite as raw string",
32 target,
33 |edit| {
34 let hashes = "#".repeat(required_hashes(&value).max(1));
35 if matches!(value, Cow::Borrowed(_)) {
36 // Avoid replacing the whole string to better position the cursor.
37 edit.insert(token.syntax().text_range().start(), format!("r{hashes}"));
38 edit.insert(token.syntax().text_range().end(), hashes);
39 } else {
40 edit.replace(token.syntax().text_range(), format!("r{hashes}\"{value}\"{hashes}"));
41 }
42 },
43 )
44 }
45
46 // Assist: make_usual_string
47 //
48 // Turns a raw string into a plain string.
49 //
50 // ```
51 // fn main() {
52 // r#"Hello,$0 "World!""#;
53 // }
54 // ```
55 // ->
56 // ```
57 // fn main() {
58 // "Hello, \"World!\"";
59 // }
60 // ```
61 pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
62 let token = ctx.find_token_at_offset::<ast::String>()?;
63 if !token.is_raw() {
64 return None;
65 }
66 let value = token.value()?;
67 let target = token.syntax().text_range();
68 acc.add(
69 AssistId("make_usual_string", AssistKind::RefactorRewrite),
70 "Rewrite as regular string",
71 target,
72 |edit| {
73 // parse inside string to escape `"`
74 let escaped = value.escape_default().to_string();
75 if let Some(offsets) = token.quote_offsets() {
76 if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped {
77 edit.replace(offsets.quotes.0, "\"");
78 edit.replace(offsets.quotes.1, "\"");
79 return;
80 }
81 }
82
83 edit.replace(token.syntax().text_range(), format!("\"{escaped}\""));
84 },
85 )
86 }
87
88 // Assist: add_hash
89 //
90 // Adds a hash to a raw string literal.
91 //
92 // ```
93 // fn main() {
94 // r#"Hello,$0 World!"#;
95 // }
96 // ```
97 // ->
98 // ```
99 // fn main() {
100 // r##"Hello, World!"##;
101 // }
102 // ```
103 pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
104 let token = ctx.find_token_at_offset::<ast::String>()?;
105 if !token.is_raw() {
106 return None;
107 }
108 let text_range = token.syntax().text_range();
109 let target = text_range;
110 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
111 edit.insert(text_range.start() + TextSize::of('r'), "#");
112 edit.insert(text_range.end(), "#");
113 })
114 }
115
116 // Assist: remove_hash
117 //
118 // Removes a hash from a raw string literal.
119 //
120 // ```
121 // fn main() {
122 // r#"Hello,$0 World!"#;
123 // }
124 // ```
125 // ->
126 // ```
127 // fn main() {
128 // r"Hello, World!";
129 // }
130 // ```
131 pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
132 let token = ctx.find_token_at_offset::<ast::String>()?;
133 if !token.is_raw() {
134 return None;
135 }
136
137 let text = token.text();
138 if !text.starts_with("r#") && text.ends_with('#') {
139 return None;
140 }
141
142 let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
143
144 let text_range = token.syntax().text_range();
145 let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
146
147 if existing_hashes == required_hashes(internal_text) {
148 cov_mark::hit!(cant_remove_required_hash);
149 return None;
150 }
151
152 acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
153 edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
154 edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
155 })
156 }
157
158 fn required_hashes(s: &str) -> usize {
159 let mut res = 0usize;
160 for idx in s.match_indices('"').map(|(i, _)| i) {
161 let (_, sub) = s.split_at(idx + 1);
162 let n_hashes = sub.chars().take_while(|c| *c == '#').count();
163 res = res.max(n_hashes + 1)
164 }
165 res
166 }
167
168 #[cfg(test)]
169 mod tests {
170 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
171
172 use super::*;
173
174 #[test]
175 fn test_required_hashes() {
176 assert_eq!(0, required_hashes("abc"));
177 assert_eq!(0, required_hashes("###"));
178 assert_eq!(1, required_hashes("\""));
179 assert_eq!(2, required_hashes("\"#abc"));
180 assert_eq!(0, required_hashes("#abc"));
181 assert_eq!(3, required_hashes("#ab\"##c"));
182 assert_eq!(5, required_hashes("#ab\"##\"####c"));
183 }
184
185 #[test]
186 fn make_raw_string_target() {
187 check_assist_target(
188 make_raw_string,
189 r#"
190 fn f() {
191 let s = $0"random\nstring";
192 }
193 "#,
194 r#""random\nstring""#,
195 );
196 }
197
198 #[test]
199 fn make_raw_string_works() {
200 check_assist(
201 make_raw_string,
202 r#"
203 fn f() {
204 let s = $0"random\nstring";
205 }
206 "#,
207 r##"
208 fn f() {
209 let s = r#"random
210 string"#;
211 }
212 "##,
213 )
214 }
215
216 #[test]
217 fn make_raw_string_works_inside_macros() {
218 check_assist(
219 make_raw_string,
220 r#"
221 fn f() {
222 format!($0"x = {}", 92)
223 }
224 "#,
225 r##"
226 fn f() {
227 format!(r#"x = {}"#, 92)
228 }
229 "##,
230 )
231 }
232
233 #[test]
234 fn make_raw_string_hashes_inside_works() {
235 check_assist(
236 make_raw_string,
237 r###"
238 fn f() {
239 let s = $0"#random##\nstring";
240 }
241 "###,
242 r####"
243 fn f() {
244 let s = r#"#random##
245 string"#;
246 }
247 "####,
248 )
249 }
250
251 #[test]
252 fn make_raw_string_closing_hashes_inside_works() {
253 check_assist(
254 make_raw_string,
255 r###"
256 fn f() {
257 let s = $0"#random\"##\nstring";
258 }
259 "###,
260 r####"
261 fn f() {
262 let s = r###"#random"##
263 string"###;
264 }
265 "####,
266 )
267 }
268
269 #[test]
270 fn make_raw_string_nothing_to_unescape_works() {
271 check_assist(
272 make_raw_string,
273 r#"
274 fn f() {
275 let s = $0"random string";
276 }
277 "#,
278 r##"
279 fn f() {
280 let s = r#"random string"#;
281 }
282 "##,
283 )
284 }
285
286 #[test]
287 fn make_raw_string_not_works_on_partial_string() {
288 check_assist_not_applicable(
289 make_raw_string,
290 r#"
291 fn f() {
292 let s = "foo$0
293 }
294 "#,
295 )
296 }
297
298 #[test]
299 fn make_usual_string_not_works_on_partial_string() {
300 check_assist_not_applicable(
301 make_usual_string,
302 r#"
303 fn main() {
304 let s = r#"bar$0
305 }
306 "#,
307 )
308 }
309
310 #[test]
311 fn add_hash_target() {
312 check_assist_target(
313 add_hash,
314 r#"
315 fn f() {
316 let s = $0r"random string";
317 }
318 "#,
319 r#"r"random string""#,
320 );
321 }
322
323 #[test]
324 fn add_hash_works() {
325 check_assist(
326 add_hash,
327 r#"
328 fn f() {
329 let s = $0r"random string";
330 }
331 "#,
332 r##"
333 fn f() {
334 let s = r#"random string"#;
335 }
336 "##,
337 )
338 }
339
340 #[test]
341 fn add_more_hash_works() {
342 check_assist(
343 add_hash,
344 r##"
345 fn f() {
346 let s = $0r#"random"string"#;
347 }
348 "##,
349 r###"
350 fn f() {
351 let s = r##"random"string"##;
352 }
353 "###,
354 )
355 }
356
357 #[test]
358 fn add_hash_not_works() {
359 check_assist_not_applicable(
360 add_hash,
361 r#"
362 fn f() {
363 let s = $0"random string";
364 }
365 "#,
366 );
367 }
368
369 #[test]
370 fn remove_hash_target() {
371 check_assist_target(
372 remove_hash,
373 r##"
374 fn f() {
375 let s = $0r#"random string"#;
376 }
377 "##,
378 r##"r#"random string"#"##,
379 );
380 }
381
382 #[test]
383 fn remove_hash_works() {
384 check_assist(
385 remove_hash,
386 r##"fn f() { let s = $0r#"random string"#; }"##,
387 r#"fn f() { let s = r"random string"; }"#,
388 )
389 }
390
391 #[test]
392 fn cant_remove_required_hash() {
393 cov_mark::check!(cant_remove_required_hash);
394 check_assist_not_applicable(
395 remove_hash,
396 r##"
397 fn f() {
398 let s = $0r#"random"str"ing"#;
399 }
400 "##,
401 )
402 }
403
404 #[test]
405 fn remove_more_hash_works() {
406 check_assist(
407 remove_hash,
408 r###"
409 fn f() {
410 let s = $0r##"random string"##;
411 }
412 "###,
413 r##"
414 fn f() {
415 let s = r#"random string"#;
416 }
417 "##,
418 )
419 }
420
421 #[test]
422 fn remove_hash_doesnt_work() {
423 check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0"random string"; }"#);
424 }
425
426 #[test]
427 fn remove_hash_no_hash_doesnt_work() {
428 check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0r"random string"; }"#);
429 }
430
431 #[test]
432 fn make_usual_string_target() {
433 check_assist_target(
434 make_usual_string,
435 r##"
436 fn f() {
437 let s = $0r#"random string"#;
438 }
439 "##,
440 r##"r#"random string"#"##,
441 );
442 }
443
444 #[test]
445 fn make_usual_string_works() {
446 check_assist(
447 make_usual_string,
448 r##"
449 fn f() {
450 let s = $0r#"random string"#;
451 }
452 "##,
453 r#"
454 fn f() {
455 let s = "random string";
456 }
457 "#,
458 )
459 }
460
461 #[test]
462 fn make_usual_string_with_quote_works() {
463 check_assist(
464 make_usual_string,
465 r##"
466 fn f() {
467 let s = $0r#"random"str"ing"#;
468 }
469 "##,
470 r#"
471 fn f() {
472 let s = "random\"str\"ing";
473 }
474 "#,
475 )
476 }
477
478 #[test]
479 fn make_usual_string_more_hash_works() {
480 check_assist(
481 make_usual_string,
482 r###"
483 fn f() {
484 let s = $0r##"random string"##;
485 }
486 "###,
487 r##"
488 fn f() {
489 let s = "random string";
490 }
491 "##,
492 )
493 }
494
495 #[test]
496 fn make_usual_string_not_works() {
497 check_assist_not_applicable(
498 make_usual_string,
499 r#"
500 fn f() {
501 let s = $0"random string";
502 }
503 "#,
504 );
505 }
506 }