aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/replace_qualified_name_with_use.rs
diff options
context:
space:
mode:
authorDmitry <[email protected]>2020-08-14 19:32:05 +0100
committerDmitry <[email protected]>2020-08-14 19:32:05 +0100
commit178c3e135a2a249692f7784712492e7884ae0c00 (patch)
treeac6b769dbf7162150caa0c1624786a4dd79ff3be /crates/assists/src/handlers/replace_qualified_name_with_use.rs
parent06ff8e6c760ff05f10e868b5d1f9d79e42fbb49c (diff)
parentc2594daf2974dbd4ce3d9b7ec72481764abaceb5 (diff)
Merge remote-tracking branch 'origin/master'
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.rs688
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 @@
1use hir;
2use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode};
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
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
61fn 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`.
82fn 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
104fn 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
124fn 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)]
145mod 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"
155std::fmt::Debug<|>
156 ",
157 r"
158use std::fmt::Debug;
159
160Debug
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"
169std::fmt::Debug<|>
170
171fn main() {
172}
173 ",
174 r"
175use std::fmt::Debug;
176
177Debug
178
179fn 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"
190fn main() {
191}
192
193std::fmt::Debug<|>
194 ",
195 r"
196use std::fmt::Debug;
197
198fn main() {
199}
200
201Debug
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"
211std::fmt<|>::Debug
212 ",
213 r"
214use std::fmt;
215
216fmt::Debug
217 ",
218 );
219 }
220
221 #[test]
222 fn test_replace_add_use() {
223 check_assist(
224 replace_qualified_name_with_use,
225 r"
226use stdx;
227
228impl std::fmt::Debug<|> for Foo {
229}
230 ",
231 r"
232use stdx;
233use std::fmt::Debug;
234
235impl 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"
246impl std::fmt::Debug<|> for Foo {
247}
248 ",
249 r"
250use std::fmt::Debug;
251
252impl 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"
280use std::fmt;
281
282impl std::io<|> for Foo {
283}
284 ",
285 r"
286use std::{io, fmt};
287
288impl 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"
299use std::fmt;
300
301impl std::fmt::Debug<|> for Foo {
302}
303 ",
304 r"
305use std::fmt::{self, Debug, };
306
307impl 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"
318use std::fmt::Debug;
319
320impl std::fmt<|> for Foo {
321}
322 ",
323 r"
324use std::fmt::{self, Debug};
325
326impl 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"
337use std::fmt::{Debug, nested::{Display}};
338
339impl std::fmt::nested<|> for Foo {
340}
341",
342 r"
343use std::fmt::{Debug, nested::{Display, self}};
344
345impl 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"
356use std::fmt::{Debug, nested::{self, Display}};
357
358impl std::fmt::nested<|> for Foo {
359}
360",
361 r"
362use std::fmt::{Debug, nested::{self, Display}};
363
364impl 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"
375use std::fmt::{Debug, nested::{Display}};
376
377impl std::fmt::nested::Debug<|> for Foo {
378}
379",
380 r"
381use std::fmt::{Debug, nested::{Display, Debug}};
382
383impl 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"
394use std::fmt::Debug;
395
396impl std::fmt::nested::Display<|> for Foo {
397}
398",
399 r"
400use std::fmt::{nested::Display, Debug};
401
402impl 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"
413use std::fmt::nested::Debug;
414
415impl std::fmt::Display<|> for Foo {
416}
417",
418 r"
419use std::fmt::{Display, nested::Debug};
420
421impl 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"
432use crate::{
433 ty::{Substs, Ty},
434 AssocItem,
435};
436
437fn foo() { crate::ty::lower<|>::trait_env() }
438",
439 r"
440use crate::{
441 ty::{Substs, Ty, lower},
442 AssocItem,
443};
444
445fn 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"
455use std::fmt as foo;
456
457impl foo::Debug<|> for Foo {
458}
459",
460 r"
461use std::fmt as foo;
462
463impl 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"
474impl 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"
485use 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"
495mod foo {
496 mod bar {
497 std::fmt::Debug<|>
498 }
499}
500 ",
501 r"
502mod 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
520fn main() {
521 std::fmt::Debug<|>
522}
523 ",
524 r"
525#![allow(dead_code)]
526use std::fmt::Debug;
527
528fn 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"
540fn main() {
541 std::fmt::Debug<|>;
542 let x: std::fmt::Debug = std::fmt::Debug;
543}
544 ",
545 r"
546use std::fmt::Debug;
547
548fn 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"
561mod 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
571fn f() {
572 std::fmt::Debug;
573}
574 ",
575 r"
576mod 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
588fn 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"
600fn main() {
601 std::fmt::Debug<|>;
602}
603
604mod sub {
605 fn f() {
606 std::fmt::Debug;
607 }
608}
609 ",
610 r"
611use std::fmt::Debug;
612
613fn main() {
614 Debug;
615}
616
617mod 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"
631use std::fmt::Display;
632
633fn main() {
634 std::fmt<|>;
635}
636 ",
637 r"
638use std::fmt::{self, Display};
639
640fn 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"
652pub use std::fmt;
653
654impl std::io<|> for Foo {
655}
656 ",
657 r"
658use std::io;
659
660pub use std::fmt;
661
662impl 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"
673pub(crate) use std::fmt;
674
675impl std::io<|> for Foo {
676}
677 ",
678 r"
679use std::io;
680
681pub(crate) use std::fmt;
682
683impl io for Foo {
684}
685 ",
686 );
687 }
688}