diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:19:53 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:20:13 +0100 |
commit | 7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch) | |
tree | bdb47765991cb973b2cd5481a088fac636bd326c /crates/assists/src/handlers/replace_qualified_name_with_use.rs | |
parent | ca464650eeaca6195891199a93f4f76cf3e7e697 (diff) | |
parent | e65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (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.rs | 680 |
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 @@ | |||
1 | use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode, TextRange}; | ||
2 | use test_utils::mark; | ||
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 | 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`. | ||
73 | fn 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 | |||
95 | fn 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 | |||
115 | fn 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)] | ||
136 | mod 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" | ||
146 | std::fmt::Debug<|> | ||
147 | ", | ||
148 | r" | ||
149 | use std::fmt::Debug; | ||
150 | |||
151 | Debug | ||
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" | ||
160 | std::fmt::Debug<|> | ||
161 | |||
162 | fn main() { | ||
163 | } | ||
164 | ", | ||
165 | r" | ||
166 | use std::fmt::Debug; | ||
167 | |||
168 | Debug | ||
169 | |||
170 | fn 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" | ||
181 | fn main() { | ||
182 | } | ||
183 | |||
184 | std::fmt::Debug<|> | ||
185 | ", | ||
186 | r" | ||
187 | use std::fmt::Debug; | ||
188 | |||
189 | fn main() { | ||
190 | } | ||
191 | |||
192 | Debug | ||
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" | ||
202 | std::fmt<|>::Debug | ||
203 | ", | ||
204 | r" | ||
205 | use std::fmt; | ||
206 | |||
207 | fmt::Debug | ||
208 | ", | ||
209 | ); | ||
210 | } | ||
211 | |||
212 | #[test] | ||
213 | fn test_replace_add_use() { | ||
214 | check_assist( | ||
215 | replace_qualified_name_with_use, | ||
216 | r" | ||
217 | use stdx; | ||
218 | |||
219 | impl std::fmt::Debug<|> for Foo { | ||
220 | } | ||
221 | ", | ||
222 | r" | ||
223 | use stdx; | ||
224 | use std::fmt::Debug; | ||
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<|> 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<|> 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<|> for Foo { | ||
274 | } | ||
275 | ", | ||
276 | r" | ||
277 | use std::{io, fmt}; | ||
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<|> 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<|> 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<|> for Foo { | ||
331 | } | ||
332 | ", | ||
333 | r" | ||
334 | use std::fmt::{Debug, nested::{Display, self}}; | ||
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<|> 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<|> for Foo { | ||
369 | } | ||
370 | ", | ||
371 | r" | ||
372 | use std::fmt::{Debug, nested::{Display, Debug}}; | ||
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<|> for Foo { | ||
388 | } | ||
389 | ", | ||
390 | r" | ||
391 | use std::fmt::{nested::Display, Debug}; | ||
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<|> 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<|>::trait_env() } | ||
429 | ", | ||
430 | r" | ||
431 | use crate::{ | ||
432 | ty::{Substs, Ty, lower}, | ||
433 | AssocItem, | ||
434 | }; | ||
435 | |||
436 | fn 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" | ||
446 | use std::fmt as foo; | ||
447 | |||
448 | impl foo::Debug<|> for Foo { | ||
449 | } | ||
450 | ", | ||
451 | r" | ||
452 | use std::fmt as foo; | ||
453 | |||
454 | impl 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" | ||
466 | impl 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" | ||
477 | use 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" | ||
487 | mod foo { | ||
488 | mod bar { | ||
489 | std::fmt::Debug<|> | ||
490 | } | ||
491 | } | ||
492 | ", | ||
493 | r" | ||
494 | mod 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 | |||
512 | fn main() { | ||
513 | std::fmt::Debug<|> | ||
514 | } | ||
515 | ", | ||
516 | r" | ||
517 | #![allow(dead_code)] | ||
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<|>; | ||
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<|>; | ||
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<|>; | ||
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<|>; | ||
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<|> for Foo { | ||
647 | } | ||
648 | ", | ||
649 | r" | ||
650 | use std::io; | ||
651 | |||
652 | pub use std::fmt; | ||
653 | |||
654 | impl 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" | ||
665 | pub(crate) use std::fmt; | ||
666 | |||
667 | impl std::io<|> for Foo { | ||
668 | } | ||
669 | ", | ||
670 | r" | ||
671 | use std::io; | ||
672 | |||
673 | pub(crate) use std::fmt; | ||
674 | |||
675 | impl io for Foo { | ||
676 | } | ||
677 | ", | ||
678 | ); | ||
679 | } | ||
680 | } | ||