aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/replace_qualified_name_with_use.rs
diff options
context:
space:
mode:
authorZac Pullar-Strecker <[email protected]>2020-08-24 10:19:53 +0100
committerZac Pullar-Strecker <[email protected]>2020-08-24 10:20:13 +0100
commit7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch)
treebdb47765991cb973b2cd5481a088fac636bd326c /crates/assists/src/handlers/replace_qualified_name_with_use.rs
parentca464650eeaca6195891199a93f4f76cf3e7e697 (diff)
parente65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff)
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
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.rs680
1 files changed, 680 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..470e5f8ff
--- /dev/null
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -0,0 +1,680 @@
1use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode, TextRange};
2use test_utils::mark;
3
4use 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// ```
22pub(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 if path.qualifier().is_none() {
32 mark::hit!(dont_import_trivial_paths);
33 return None;
34 }
35 let path_to_import = path.to_string().clone();
36 let path_to_import = match path.segment()?.generic_arg_list() {
37 Some(generic_args) => {
38 let generic_args_start =
39 generic_args.syntax().text_range().start() - path.syntax().text_range().start();
40 &path_to_import[TextRange::up_to(generic_args_start)]
41 }
42 None => path_to_import.as_str(),
43 };
44
45 let target = path.syntax().text_range();
46 acc.add(
47 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
48 "Replace qualified path with use",
49 target,
50 |builder| {
51 let container = match find_insert_use_container(path.syntax(), ctx) {
52 Some(c) => c,
53 None => return,
54 };
55 insert_use_statement(
56 path.syntax(),
57 &path_to_import.to_string(),
58 ctx,
59 builder.text_edit_builder(),
60 );
61
62 // Now that we've brought the name into scope, re-qualify all paths that could be
63 // affected (that is, all paths inside the node we added the `use` to).
64 let mut rewriter = SyntaxRewriter::default();
65 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
66 shorten_paths(&mut rewriter, syntax, path);
67 builder.rewrite(rewriter);
68 },
69 )
70}
71
72/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
73fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: ast::Path) {
74 for child in node.children() {
75 match_ast! {
76 match child {
77 // Don't modify `use` items, as this can break the `use` item when injecting a new
78 // import into the use tree.
79 ast::Use(_it) => continue,
80 // Don't descend into submodules, they don't have the same `use` items in scope.
81 ast::Module(_it) => continue,
82
83 ast::Path(p) => {
84 match maybe_replace_path(rewriter, p.clone(), path.clone()) {
85 Some(()) => {},
86 None => shorten_paths(rewriter, p.syntax().clone(), path.clone()),
87 }
88 },
89 _ => shorten_paths(rewriter, child, path.clone()),
90 }
91 }
92 }
93}
94
95fn maybe_replace_path(
96 rewriter: &mut SyntaxRewriter<'static>,
97 path: ast::Path,
98 target: ast::Path,
99) -> Option<()> {
100 if !path_eq(path.clone(), target) {
101 return None;
102 }
103
104 // Shorten `path`, leaving only its last segment.
105 if let Some(parent) = path.qualifier() {
106 rewriter.delete(parent.syntax());
107 }
108 if let Some(double_colon) = path.coloncolon_token() {
109 rewriter.delete(&double_colon);
110 }
111
112 Some(())
113}
114
115fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool {
116 let mut lhs_curr = lhs;
117 let mut rhs_curr = rhs;
118 loop {
119 match (lhs_curr.segment(), rhs_curr.segment()) {
120 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
121 _ => return false,
122 }
123
124 match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
125 (Some(lhs), Some(rhs)) => {
126 lhs_curr = lhs;
127 rhs_curr = rhs;
128 }
129 (None, None) => return true,
130 _ => return false,
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use crate::tests::{check_assist, check_assist_not_applicable};
138
139 use super::*;
140
141 #[test]
142 fn test_replace_add_use_no_anchor() {
143 check_assist(
144 replace_qualified_name_with_use,
145 r"
146std::fmt::Debug<|>
147 ",
148 r"
149use std::fmt::Debug;
150
151Debug
152 ",
153 );
154 }
155 #[test]
156 fn test_replace_add_use_no_anchor_with_item_below() {
157 check_assist(
158 replace_qualified_name_with_use,
159 r"
160std::fmt::Debug<|>
161
162fn main() {
163}
164 ",
165 r"
166use std::fmt::Debug;
167
168Debug
169
170fn main() {
171}
172 ",
173 );
174 }
175
176 #[test]
177 fn test_replace_add_use_no_anchor_with_item_above() {
178 check_assist(
179 replace_qualified_name_with_use,
180 r"
181fn main() {
182}
183
184std::fmt::Debug<|>
185 ",
186 r"
187use std::fmt::Debug;
188
189fn main() {
190}
191
192Debug
193 ",
194 );
195 }
196
197 #[test]
198 fn test_replace_add_use_no_anchor_2seg() {
199 check_assist(
200 replace_qualified_name_with_use,
201 r"
202std::fmt<|>::Debug
203 ",
204 r"
205use std::fmt;
206
207fmt::Debug
208 ",
209 );
210 }
211
212 #[test]
213 fn test_replace_add_use() {
214 check_assist(
215 replace_qualified_name_with_use,
216 r"
217use stdx;
218
219impl std::fmt::Debug<|> for Foo {
220}
221 ",
222 r"
223use stdx;
224use std::fmt::Debug;
225
226impl 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"
237impl std::fmt::Debug<|> for Foo {
238}
239 ",
240 r"
241use std::fmt::Debug;
242
243impl 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<|> 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"
271use std::fmt;
272
273impl std::io<|> for Foo {
274}
275 ",
276 r"
277use std::{io, fmt};
278
279impl 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"
290use std::fmt;
291
292impl std::fmt::Debug<|> for Foo {
293}
294 ",
295 r"
296use std::fmt::{self, Debug, };
297
298impl 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"
309use std::fmt::Debug;
310
311impl std::fmt<|> for Foo {
312}
313 ",
314 r"
315use std::fmt::{self, Debug};
316
317impl 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"
328use std::fmt::{Debug, nested::{Display}};
329
330impl std::fmt::nested<|> for Foo {
331}
332",
333 r"
334use std::fmt::{Debug, nested::{Display, self}};
335
336impl 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"
347use std::fmt::{Debug, nested::{self, Display}};
348
349impl std::fmt::nested<|> for Foo {
350}
351",
352 r"
353use std::fmt::{Debug, nested::{self, Display}};
354
355impl 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"
366use std::fmt::{Debug, nested::{Display}};
367
368impl std::fmt::nested::Debug<|> for Foo {
369}
370",
371 r"
372use std::fmt::{Debug, nested::{Display, Debug}};
373
374impl 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"
385use std::fmt::Debug;
386
387impl std::fmt::nested::Display<|> for Foo {
388}
389",
390 r"
391use std::fmt::{nested::Display, Debug};
392
393impl 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"
404use std::fmt::nested::Debug;
405
406impl std::fmt::Display<|> for Foo {
407}
408",
409 r"
410use std::fmt::{Display, nested::Debug};
411
412impl 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"
423use crate::{
424 ty::{Substs, Ty},
425 AssocItem,
426};
427
428fn foo() { crate::ty::lower<|>::trait_env() }
429",
430 r"
431use crate::{
432 ty::{Substs, Ty, lower},
433 AssocItem,
434};
435
436fn foo() { lower::trait_env() }
437",
438 );
439 }
440
441 #[test]
442 fn test_replace_alias() {
443 check_assist(
444 replace_qualified_name_with_use,
445 r"
446use std::fmt as foo;
447
448impl foo::Debug<|> for Foo {
449}
450",
451 r"
452use std::fmt as foo;
453
454impl Debug for Foo {
455}
456",
457 );
458 }
459
460 #[test]
461 fn dont_import_trivial_paths() {
462 mark::check!(dont_import_trivial_paths);
463 check_assist_not_applicable(
464 replace_qualified_name_with_use,
465 r"
466impl foo<|> for Foo {
467}
468",
469 );
470 }
471
472 #[test]
473 fn test_replace_not_applicable_in_use() {
474 check_assist_not_applicable(
475 replace_qualified_name_with_use,
476 r"
477use std::fmt<|>;
478",
479 );
480 }
481
482 #[test]
483 fn test_replace_add_use_no_anchor_in_mod_mod() {
484 check_assist(
485 replace_qualified_name_with_use,
486 r"
487mod foo {
488 mod bar {
489 std::fmt::Debug<|>
490 }
491}
492 ",
493 r"
494mod foo {
495 mod bar {
496 use std::fmt::Debug;
497
498 Debug
499 }
500}
501 ",
502 );
503 }
504
505 #[test]
506 fn inserts_imports_after_inner_attributes() {
507 check_assist(
508 replace_qualified_name_with_use,
509 r"
510#![allow(dead_code)]
511
512fn main() {
513 std::fmt::Debug<|>
514}
515 ",
516 r"
517#![allow(dead_code)]
518use std::fmt::Debug;
519
520fn 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"
532fn main() {
533 std::fmt::Debug<|>;
534 let x: std::fmt::Debug = std::fmt::Debug;
535}
536 ",
537 r"
538use std::fmt::Debug;
539
540fn 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"
553mod m {
554 fn f() {
555 std::fmt::Debug<|>;
556 let x: std::fmt::Debug = std::fmt::Debug;
557 }
558 fn g() {
559 std::fmt::Debug;
560 }
561}
562
563fn f() {
564 std::fmt::Debug;
565}
566 ",
567 r"
568mod 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
580fn 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"
592fn main() {
593 std::fmt::Debug<|>;
594}
595
596mod sub {
597 fn f() {
598 std::fmt::Debug;
599 }
600}
601 ",
602 r"
603use std::fmt::Debug;
604
605fn main() {
606 Debug;
607}
608
609mod 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"
623use std::fmt::Display;
624
625fn main() {
626 std::fmt<|>;
627}
628 ",
629 r"
630use std::fmt::{self, Display};
631
632fn 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"
644pub use std::fmt;
645
646impl std::io<|> for Foo {
647}
648 ",
649 r"
650use std::io;
651
652pub use std::fmt;
653
654impl io for Foo {
655}
656 ",
657 );
658 }
659
660 #[test]
661 fn does_not_replace_pub_crate_use() {
662 check_assist(
663 replace_qualified_name_with_use,
664 r"
665pub(crate) use std::fmt;
666
667impl std::io<|> for Foo {
668}
669 ",
670 r"
671use std::io;
672
673pub(crate) use std::fmt;
674
675impl io for Foo {
676}
677 ",
678 );
679 }
680}