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