diff options
Diffstat (limited to 'crates/assists/src/handlers/replace_qualified_name_with_use.rs')
-rw-r--r-- | crates/assists/src/handlers/replace_qualified_name_with_use.rs | 688 |
1 files changed, 688 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs new file mode 100644 index 000000000..011bf1106 --- /dev/null +++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs | |||
@@ -0,0 +1,688 @@ | |||
1 | use hir; | ||
2 | use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode}; | ||
3 | |||
4 | use crate::{ | ||
5 | utils::{find_insert_use_container, insert_use_statement}, | ||
6 | AssistContext, AssistId, AssistKind, Assists, | ||
7 | }; | ||
8 | |||
9 | // Assist: replace_qualified_name_with_use | ||
10 | // | ||
11 | // Adds a use statement for a given fully-qualified name. | ||
12 | // | ||
13 | // ``` | ||
14 | // fn process(map: std::collections::<|>HashMap<String, String>) {} | ||
15 | // ``` | ||
16 | // -> | ||
17 | // ``` | ||
18 | // use std::collections::HashMap; | ||
19 | // | ||
20 | // fn process(map: HashMap<String, String>) {} | ||
21 | // ``` | ||
22 | pub(crate) fn replace_qualified_name_with_use( | ||
23 | acc: &mut Assists, | ||
24 | ctx: &AssistContext, | ||
25 | ) -> Option<()> { | ||
26 | let path: ast::Path = ctx.find_node_at_offset()?; | ||
27 | // We don't want to mess with use statements | ||
28 | if path.syntax().ancestors().find_map(ast::Use::cast).is_some() { | ||
29 | return None; | ||
30 | } | ||
31 | |||
32 | let hir_path = ctx.sema.lower_path(&path)?; | ||
33 | let segments = collect_hir_path_segments(&hir_path)?; | ||
34 | if segments.len() < 2 { | ||
35 | return None; | ||
36 | } | ||
37 | |||
38 | let target = path.syntax().text_range(); | ||
39 | acc.add( | ||
40 | AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), | ||
41 | "Replace qualified path with use", | ||
42 | target, | ||
43 | |builder| { | ||
44 | let path_to_import = hir_path.mod_path().clone(); | ||
45 | let container = match find_insert_use_container(path.syntax(), ctx) { | ||
46 | Some(c) => c, | ||
47 | None => return, | ||
48 | }; | ||
49 | insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder()); | ||
50 | |||
51 | // Now that we've brought the name into scope, re-qualify all paths that could be | ||
52 | // affected (that is, all paths inside the node we added the `use` to). | ||
53 | let mut rewriter = SyntaxRewriter::default(); | ||
54 | let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone()); | ||
55 | shorten_paths(&mut rewriter, syntax, path); | ||
56 | builder.rewrite(rewriter); | ||
57 | }, | ||
58 | ) | ||
59 | } | ||
60 | |||
61 | fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { | ||
62 | let mut ps = Vec::<SmolStr>::with_capacity(10); | ||
63 | match path.kind() { | ||
64 | hir::PathKind::Abs => ps.push("".into()), | ||
65 | hir::PathKind::Crate => ps.push("crate".into()), | ||
66 | hir::PathKind::Plain => {} | ||
67 | hir::PathKind::Super(0) => ps.push("self".into()), | ||
68 | hir::PathKind::Super(lvl) => { | ||
69 | let mut chain = "super".to_string(); | ||
70 | for _ in 0..*lvl { | ||
71 | chain += "::super"; | ||
72 | } | ||
73 | ps.push(chain.into()); | ||
74 | } | ||
75 | hir::PathKind::DollarCrate(_) => return None, | ||
76 | } | ||
77 | ps.extend(path.segments().iter().map(|it| it.name.to_string().into())); | ||
78 | Some(ps) | ||
79 | } | ||
80 | |||
81 | /// Adds replacements to `re` that shorten `path` in all descendants of `node`. | ||
82 | fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: ast::Path) { | ||
83 | for child in node.children() { | ||
84 | match_ast! { | ||
85 | match child { | ||
86 | // Don't modify `use` items, as this can break the `use` item when injecting a new | ||
87 | // import into the use tree. | ||
88 | ast::Use(_it) => continue, | ||
89 | // Don't descend into submodules, they don't have the same `use` items in scope. | ||
90 | ast::Module(_it) => continue, | ||
91 | |||
92 | ast::Path(p) => { | ||
93 | match maybe_replace_path(rewriter, p.clone(), path.clone()) { | ||
94 | Some(()) => {}, | ||
95 | None => shorten_paths(rewriter, p.syntax().clone(), path.clone()), | ||
96 | } | ||
97 | }, | ||
98 | _ => shorten_paths(rewriter, child, path.clone()), | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | } | ||
103 | |||
104 | fn maybe_replace_path( | ||
105 | rewriter: &mut SyntaxRewriter<'static>, | ||
106 | path: ast::Path, | ||
107 | target: ast::Path, | ||
108 | ) -> Option<()> { | ||
109 | if !path_eq(path.clone(), target) { | ||
110 | return None; | ||
111 | } | ||
112 | |||
113 | // Shorten `path`, leaving only its last segment. | ||
114 | if let Some(parent) = path.qualifier() { | ||
115 | rewriter.delete(parent.syntax()); | ||
116 | } | ||
117 | if let Some(double_colon) = path.coloncolon_token() { | ||
118 | rewriter.delete(&double_colon); | ||
119 | } | ||
120 | |||
121 | Some(()) | ||
122 | } | ||
123 | |||
124 | fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool { | ||
125 | let mut lhs_curr = lhs; | ||
126 | let mut rhs_curr = rhs; | ||
127 | loop { | ||
128 | match (lhs_curr.segment(), rhs_curr.segment()) { | ||
129 | (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (), | ||
130 | _ => return false, | ||
131 | } | ||
132 | |||
133 | match (lhs_curr.qualifier(), rhs_curr.qualifier()) { | ||
134 | (Some(lhs), Some(rhs)) => { | ||
135 | lhs_curr = lhs; | ||
136 | rhs_curr = rhs; | ||
137 | } | ||
138 | (None, None) => return true, | ||
139 | _ => return false, | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | |||
144 | #[cfg(test)] | ||
145 | mod tests { | ||
146 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
147 | |||
148 | use super::*; | ||
149 | |||
150 | #[test] | ||
151 | fn test_replace_add_use_no_anchor() { | ||
152 | check_assist( | ||
153 | replace_qualified_name_with_use, | ||
154 | r" | ||
155 | std::fmt::Debug<|> | ||
156 | ", | ||
157 | r" | ||
158 | use std::fmt::Debug; | ||
159 | |||
160 | Debug | ||
161 | ", | ||
162 | ); | ||
163 | } | ||
164 | #[test] | ||
165 | fn test_replace_add_use_no_anchor_with_item_below() { | ||
166 | check_assist( | ||
167 | replace_qualified_name_with_use, | ||
168 | r" | ||
169 | std::fmt::Debug<|> | ||
170 | |||
171 | fn main() { | ||
172 | } | ||
173 | ", | ||
174 | r" | ||
175 | use std::fmt::Debug; | ||
176 | |||
177 | Debug | ||
178 | |||
179 | fn main() { | ||
180 | } | ||
181 | ", | ||
182 | ); | ||
183 | } | ||
184 | |||
185 | #[test] | ||
186 | fn test_replace_add_use_no_anchor_with_item_above() { | ||
187 | check_assist( | ||
188 | replace_qualified_name_with_use, | ||
189 | r" | ||
190 | fn main() { | ||
191 | } | ||
192 | |||
193 | std::fmt::Debug<|> | ||
194 | ", | ||
195 | r" | ||
196 | use std::fmt::Debug; | ||
197 | |||
198 | fn main() { | ||
199 | } | ||
200 | |||
201 | Debug | ||
202 | ", | ||
203 | ); | ||
204 | } | ||
205 | |||
206 | #[test] | ||
207 | fn test_replace_add_use_no_anchor_2seg() { | ||
208 | check_assist( | ||
209 | replace_qualified_name_with_use, | ||
210 | r" | ||
211 | std::fmt<|>::Debug | ||
212 | ", | ||
213 | r" | ||
214 | use std::fmt; | ||
215 | |||
216 | fmt::Debug | ||
217 | ", | ||
218 | ); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_replace_add_use() { | ||
223 | check_assist( | ||
224 | replace_qualified_name_with_use, | ||
225 | r" | ||
226 | use stdx; | ||
227 | |||
228 | impl std::fmt::Debug<|> for Foo { | ||
229 | } | ||
230 | ", | ||
231 | r" | ||
232 | use stdx; | ||
233 | use std::fmt::Debug; | ||
234 | |||
235 | impl Debug for Foo { | ||
236 | } | ||
237 | ", | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn test_replace_file_use_other_anchor() { | ||
243 | check_assist( | ||
244 | replace_qualified_name_with_use, | ||
245 | r" | ||
246 | impl std::fmt::Debug<|> for Foo { | ||
247 | } | ||
248 | ", | ||
249 | r" | ||
250 | use std::fmt::Debug; | ||
251 | |||
252 | impl Debug for Foo { | ||
253 | } | ||
254 | ", | ||
255 | ); | ||
256 | } | ||
257 | |||
258 | #[test] | ||
259 | fn test_replace_add_use_other_anchor_indent() { | ||
260 | check_assist( | ||
261 | replace_qualified_name_with_use, | ||
262 | r" | ||
263 | impl std::fmt::Debug<|> for Foo { | ||
264 | } | ||
265 | ", | ||
266 | r" | ||
267 | use std::fmt::Debug; | ||
268 | |||
269 | impl Debug for Foo { | ||
270 | } | ||
271 | ", | ||
272 | ); | ||
273 | } | ||
274 | |||
275 | #[test] | ||
276 | fn test_replace_split_different() { | ||
277 | check_assist( | ||
278 | replace_qualified_name_with_use, | ||
279 | r" | ||
280 | use std::fmt; | ||
281 | |||
282 | impl std::io<|> for Foo { | ||
283 | } | ||
284 | ", | ||
285 | r" | ||
286 | use std::{io, fmt}; | ||
287 | |||
288 | impl io for Foo { | ||
289 | } | ||
290 | ", | ||
291 | ); | ||
292 | } | ||
293 | |||
294 | #[test] | ||
295 | fn test_replace_split_self_for_use() { | ||
296 | check_assist( | ||
297 | replace_qualified_name_with_use, | ||
298 | r" | ||
299 | use std::fmt; | ||
300 | |||
301 | impl std::fmt::Debug<|> for Foo { | ||
302 | } | ||
303 | ", | ||
304 | r" | ||
305 | use std::fmt::{self, Debug, }; | ||
306 | |||
307 | impl Debug for Foo { | ||
308 | } | ||
309 | ", | ||
310 | ); | ||
311 | } | ||
312 | |||
313 | #[test] | ||
314 | fn test_replace_split_self_for_target() { | ||
315 | check_assist( | ||
316 | replace_qualified_name_with_use, | ||
317 | r" | ||
318 | use std::fmt::Debug; | ||
319 | |||
320 | impl std::fmt<|> for Foo { | ||
321 | } | ||
322 | ", | ||
323 | r" | ||
324 | use std::fmt::{self, Debug}; | ||
325 | |||
326 | impl fmt for Foo { | ||
327 | } | ||
328 | ", | ||
329 | ); | ||
330 | } | ||
331 | |||
332 | #[test] | ||
333 | fn test_replace_add_to_nested_self_nested() { | ||
334 | check_assist( | ||
335 | replace_qualified_name_with_use, | ||
336 | r" | ||
337 | use std::fmt::{Debug, nested::{Display}}; | ||
338 | |||
339 | impl std::fmt::nested<|> for Foo { | ||
340 | } | ||
341 | ", | ||
342 | r" | ||
343 | use std::fmt::{Debug, nested::{Display, self}}; | ||
344 | |||
345 | impl nested for Foo { | ||
346 | } | ||
347 | ", | ||
348 | ); | ||
349 | } | ||
350 | |||
351 | #[test] | ||
352 | fn test_replace_add_to_nested_self_already_included() { | ||
353 | check_assist( | ||
354 | replace_qualified_name_with_use, | ||
355 | r" | ||
356 | use std::fmt::{Debug, nested::{self, Display}}; | ||
357 | |||
358 | impl std::fmt::nested<|> for Foo { | ||
359 | } | ||
360 | ", | ||
361 | r" | ||
362 | use std::fmt::{Debug, nested::{self, Display}}; | ||
363 | |||
364 | impl nested for Foo { | ||
365 | } | ||
366 | ", | ||
367 | ); | ||
368 | } | ||
369 | |||
370 | #[test] | ||
371 | fn test_replace_add_to_nested_nested() { | ||
372 | check_assist( | ||
373 | replace_qualified_name_with_use, | ||
374 | r" | ||
375 | use std::fmt::{Debug, nested::{Display}}; | ||
376 | |||
377 | impl std::fmt::nested::Debug<|> for Foo { | ||
378 | } | ||
379 | ", | ||
380 | r" | ||
381 | use std::fmt::{Debug, nested::{Display, Debug}}; | ||
382 | |||
383 | impl Debug for Foo { | ||
384 | } | ||
385 | ", | ||
386 | ); | ||
387 | } | ||
388 | |||
389 | #[test] | ||
390 | fn test_replace_split_common_target_longer() { | ||
391 | check_assist( | ||
392 | replace_qualified_name_with_use, | ||
393 | r" | ||
394 | use std::fmt::Debug; | ||
395 | |||
396 | impl std::fmt::nested::Display<|> for Foo { | ||
397 | } | ||
398 | ", | ||
399 | r" | ||
400 | use std::fmt::{nested::Display, Debug}; | ||
401 | |||
402 | impl Display for Foo { | ||
403 | } | ||
404 | ", | ||
405 | ); | ||
406 | } | ||
407 | |||
408 | #[test] | ||
409 | fn test_replace_split_common_use_longer() { | ||
410 | check_assist( | ||
411 | replace_qualified_name_with_use, | ||
412 | r" | ||
413 | use std::fmt::nested::Debug; | ||
414 | |||
415 | impl std::fmt::Display<|> for Foo { | ||
416 | } | ||
417 | ", | ||
418 | r" | ||
419 | use std::fmt::{Display, nested::Debug}; | ||
420 | |||
421 | impl Display for Foo { | ||
422 | } | ||
423 | ", | ||
424 | ); | ||
425 | } | ||
426 | |||
427 | #[test] | ||
428 | fn test_replace_use_nested_import() { | ||
429 | check_assist( | ||
430 | replace_qualified_name_with_use, | ||
431 | r" | ||
432 | use crate::{ | ||
433 | ty::{Substs, Ty}, | ||
434 | AssocItem, | ||
435 | }; | ||
436 | |||
437 | fn foo() { crate::ty::lower<|>::trait_env() } | ||
438 | ", | ||
439 | r" | ||
440 | use crate::{ | ||
441 | ty::{Substs, Ty, lower}, | ||
442 | AssocItem, | ||
443 | }; | ||
444 | |||
445 | fn foo() { lower::trait_env() } | ||
446 | ", | ||
447 | ); | ||
448 | } | ||
449 | |||
450 | #[test] | ||
451 | fn test_replace_alias() { | ||
452 | check_assist( | ||
453 | replace_qualified_name_with_use, | ||
454 | r" | ||
455 | use std::fmt as foo; | ||
456 | |||
457 | impl foo::Debug<|> for Foo { | ||
458 | } | ||
459 | ", | ||
460 | r" | ||
461 | use std::fmt as foo; | ||
462 | |||
463 | impl Debug for Foo { | ||
464 | } | ||
465 | ", | ||
466 | ); | ||
467 | } | ||
468 | |||
469 | #[test] | ||
470 | fn test_replace_not_applicable_one_segment() { | ||
471 | check_assist_not_applicable( | ||
472 | replace_qualified_name_with_use, | ||
473 | r" | ||
474 | impl foo<|> for Foo { | ||
475 | } | ||
476 | ", | ||
477 | ); | ||
478 | } | ||
479 | |||
480 | #[test] | ||
481 | fn test_replace_not_applicable_in_use() { | ||
482 | check_assist_not_applicable( | ||
483 | replace_qualified_name_with_use, | ||
484 | r" | ||
485 | use std::fmt<|>; | ||
486 | ", | ||
487 | ); | ||
488 | } | ||
489 | |||
490 | #[test] | ||
491 | fn test_replace_add_use_no_anchor_in_mod_mod() { | ||
492 | check_assist( | ||
493 | replace_qualified_name_with_use, | ||
494 | r" | ||
495 | mod foo { | ||
496 | mod bar { | ||
497 | std::fmt::Debug<|> | ||
498 | } | ||
499 | } | ||
500 | ", | ||
501 | r" | ||
502 | mod foo { | ||
503 | mod bar { | ||
504 | use std::fmt::Debug; | ||
505 | |||
506 | Debug | ||
507 | } | ||
508 | } | ||
509 | ", | ||
510 | ); | ||
511 | } | ||
512 | |||
513 | #[test] | ||
514 | fn inserts_imports_after_inner_attributes() { | ||
515 | check_assist( | ||
516 | replace_qualified_name_with_use, | ||
517 | r" | ||
518 | #![allow(dead_code)] | ||
519 | |||
520 | fn main() { | ||
521 | std::fmt::Debug<|> | ||
522 | } | ||
523 | ", | ||
524 | r" | ||
525 | #![allow(dead_code)] | ||
526 | use std::fmt::Debug; | ||
527 | |||
528 | fn main() { | ||
529 | Debug | ||
530 | } | ||
531 | ", | ||
532 | ); | ||
533 | } | ||
534 | |||
535 | #[test] | ||
536 | fn replaces_all_affected_paths() { | ||
537 | check_assist( | ||
538 | replace_qualified_name_with_use, | ||
539 | r" | ||
540 | fn main() { | ||
541 | std::fmt::Debug<|>; | ||
542 | let x: std::fmt::Debug = std::fmt::Debug; | ||
543 | } | ||
544 | ", | ||
545 | r" | ||
546 | use std::fmt::Debug; | ||
547 | |||
548 | fn main() { | ||
549 | Debug; | ||
550 | let x: Debug = Debug; | ||
551 | } | ||
552 | ", | ||
553 | ); | ||
554 | } | ||
555 | |||
556 | #[test] | ||
557 | fn replaces_all_affected_paths_mod() { | ||
558 | check_assist( | ||
559 | replace_qualified_name_with_use, | ||
560 | r" | ||
561 | mod m { | ||
562 | fn f() { | ||
563 | std::fmt::Debug<|>; | ||
564 | let x: std::fmt::Debug = std::fmt::Debug; | ||
565 | } | ||
566 | fn g() { | ||
567 | std::fmt::Debug; | ||
568 | } | ||
569 | } | ||
570 | |||
571 | fn f() { | ||
572 | std::fmt::Debug; | ||
573 | } | ||
574 | ", | ||
575 | r" | ||
576 | mod m { | ||
577 | use std::fmt::Debug; | ||
578 | |||
579 | fn f() { | ||
580 | Debug; | ||
581 | let x: Debug = Debug; | ||
582 | } | ||
583 | fn g() { | ||
584 | Debug; | ||
585 | } | ||
586 | } | ||
587 | |||
588 | fn f() { | ||
589 | std::fmt::Debug; | ||
590 | } | ||
591 | ", | ||
592 | ); | ||
593 | } | ||
594 | |||
595 | #[test] | ||
596 | fn does_not_replace_in_submodules() { | ||
597 | check_assist( | ||
598 | replace_qualified_name_with_use, | ||
599 | r" | ||
600 | fn main() { | ||
601 | std::fmt::Debug<|>; | ||
602 | } | ||
603 | |||
604 | mod sub { | ||
605 | fn f() { | ||
606 | std::fmt::Debug; | ||
607 | } | ||
608 | } | ||
609 | ", | ||
610 | r" | ||
611 | use std::fmt::Debug; | ||
612 | |||
613 | fn main() { | ||
614 | Debug; | ||
615 | } | ||
616 | |||
617 | mod sub { | ||
618 | fn f() { | ||
619 | std::fmt::Debug; | ||
620 | } | ||
621 | } | ||
622 | ", | ||
623 | ); | ||
624 | } | ||
625 | |||
626 | #[test] | ||
627 | fn does_not_replace_in_use() { | ||
628 | check_assist( | ||
629 | replace_qualified_name_with_use, | ||
630 | r" | ||
631 | use std::fmt::Display; | ||
632 | |||
633 | fn main() { | ||
634 | std::fmt<|>; | ||
635 | } | ||
636 | ", | ||
637 | r" | ||
638 | use std::fmt::{self, Display}; | ||
639 | |||
640 | fn main() { | ||
641 | fmt; | ||
642 | } | ||
643 | ", | ||
644 | ); | ||
645 | } | ||
646 | |||
647 | #[test] | ||
648 | fn does_not_replace_pub_use() { | ||
649 | check_assist( | ||
650 | replace_qualified_name_with_use, | ||
651 | r" | ||
652 | pub use std::fmt; | ||
653 | |||
654 | impl std::io<|> for Foo { | ||
655 | } | ||
656 | ", | ||
657 | r" | ||
658 | use std::io; | ||
659 | |||
660 | pub use std::fmt; | ||
661 | |||
662 | impl io for Foo { | ||
663 | } | ||
664 | ", | ||
665 | ); | ||
666 | } | ||
667 | |||
668 | #[test] | ||
669 | fn does_not_replace_pub_crate_use() { | ||
670 | check_assist( | ||
671 | replace_qualified_name_with_use, | ||
672 | r" | ||
673 | pub(crate) use std::fmt; | ||
674 | |||
675 | impl std::io<|> for Foo { | ||
676 | } | ||
677 | ", | ||
678 | r" | ||
679 | use std::io; | ||
680 | |||
681 | pub(crate) use std::fmt; | ||
682 | |||
683 | impl io for Foo { | ||
684 | } | ||
685 | ", | ||
686 | ); | ||
687 | } | ||
688 | } | ||