1use hir::{AsAssocItem, ModuleDef, PathResolution};
2use ide_db::{
3 helpers::mod_path_to_ast,
4 imports::insert_use::{ImportScope, insert_use},
5};
6use syntax::{
7 AstNode, Edition, SyntaxNode,
8 ast::{self, HasGenericArgs, make},
9 match_ast,
10 syntax_editor::SyntaxEditor,
11};
12
13use crate::{AssistContext, AssistId, Assists};
14
15pub(crate) fn replace_qualified_name_with_use(
31 acc: &mut Assists,
32 ctx: &AssistContext<'_>,
33) -> Option<()> {
34 let original_path: ast::Path = ctx.find_node_at_offset()?;
35 if original_path.syntax().ancestors().find_map(ast::UseTree::cast).is_some() {
37 cov_mark::hit!(not_applicable_in_use);
38 return None;
39 }
40
41 let original_path = target_path(ctx, original_path)?;
42
43 let module = match ctx.sema.resolve_path(&original_path.first_qualifier_or_self())? {
46 PathResolution::Def(module @ ModuleDef::Module(_)) => module,
47 _ => return None,
48 };
49
50 let starts_with_name_ref = !matches!(
51 original_path.first_segment().and_then(|it| it.kind()),
52 Some(
53 ast::PathSegmentKind::CrateKw
54 | ast::PathSegmentKind::SuperKw
55 | ast::PathSegmentKind::SelfKw
56 )
57 );
58 let path_to_qualifier = starts_with_name_ref
59 .then(|| {
60 let mod_ = ctx.sema.scope(original_path.syntax())?.module();
61 let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(mod_.krate(ctx.sema.db)));
62 mod_.find_use_path(ctx.sema.db, module, ctx.config.insert_use.prefix_kind, cfg)
63 })
64 .flatten();
65
66 let scope = ImportScope::find_insert_use_container(original_path.syntax(), &ctx.sema)?;
67 let target = original_path.syntax().text_range();
68 acc.add(
69 AssistId::refactor_rewrite("replace_qualified_name_with_use"),
70 "Replace qualified path with use",
71 target,
72 |builder| {
73 let scope_node = scope.as_syntax_node();
76 let mut editor = builder.make_editor(scope_node);
77 shorten_paths(&mut editor, scope_node, &original_path);
78 builder.add_file_edits(ctx.vfs_file_id(), editor);
79 let path = drop_generic_args(&original_path);
80 let edition = ctx
81 .sema
82 .scope(original_path.syntax())
83 .map(|semantics_scope| semantics_scope.krate().edition(ctx.db()))
84 .unwrap_or(Edition::CURRENT);
85 let path =
87 match path_to_qualifier.and_then(|it| mod_path_to_ast(&it, edition).qualifier()) {
88 Some(qualifier) => make::path_concat(qualifier, path),
89 None => path,
90 };
91 let scope = builder.make_import_scope_mut(scope);
92 insert_use(&scope, path, &ctx.config.insert_use);
93 },
94 )
95}
96
97fn target_path(ctx: &AssistContext<'_>, mut original_path: ast::Path) -> Option<ast::Path> {
98 let on_first = original_path.qualifier().is_none();
99
100 if on_first {
101 original_path = original_path.top_path();
102 }
103
104 match ctx.sema.resolve_path(&original_path)? {
105 PathResolution::Def(ModuleDef::Variant(_)) if on_first => original_path.qualifier(),
106 PathResolution::Def(def) if def.as_assoc_item(ctx.db()).is_some() => {
107 on_first.then_some(original_path.qualifier()?)
108 }
109 _ => Some(original_path),
110 }
111}
112
113fn drop_generic_args(path: &ast::Path) -> ast::Path {
114 let path = path.clone_subtree();
115 let mut editor = SyntaxEditor::new(path.syntax().clone());
116 if let Some(segment) = path.segment()
117 && let Some(generic_args) = segment.generic_arg_list()
118 {
119 editor.delete(generic_args.syntax());
120 }
121
122 ast::Path::cast(editor.finish().new_root().clone()).unwrap()
123}
124
125fn shorten_paths(editor: &mut SyntaxEditor, node: &SyntaxNode, path: &ast::Path) {
127 for child in node.children() {
128 match_ast! {
129 match child {
130 ast::Use(_) => continue,
133 ast::Module(_) => continue,
136 ast::Path(p) => if maybe_replace_path(editor, p.clone(), path.clone()).is_none() {
137 shorten_paths(editor, p.syntax(), path);
138 },
139 _ => shorten_paths(editor, &child, path),
140 }
141 }
142 }
143}
144
145fn maybe_replace_path(editor: &mut SyntaxEditor, path: ast::Path, target: ast::Path) -> Option<()> {
146 if !path_eq_no_generics(path.clone(), target) {
147 return None;
148 }
149
150 if let Some(parent) = path.qualifier() {
152 editor.delete(parent.syntax());
153 }
154 if let Some(double_colon) = path.coloncolon_token() {
155 editor.delete(double_colon);
156 }
157
158 Some(())
159}
160
161fn path_eq_no_generics(lhs: ast::Path, rhs: ast::Path) -> bool {
162 let mut lhs_curr = lhs;
163 let mut rhs_curr = rhs;
164 loop {
165 match lhs_curr.segment().zip(rhs_curr.segment()) {
166 Some((lhs, rhs))
167 if lhs.coloncolon_token().is_some() == rhs.coloncolon_token().is_some()
168 && lhs
169 .name_ref()
170 .zip(rhs.name_ref())
171 .is_some_and(|(lhs, rhs)| lhs.text() == rhs.text()) => {}
172 _ => return false,
173 }
174
175 match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
176 (Some(lhs), Some(rhs)) => {
177 lhs_curr = lhs;
178 rhs_curr = rhs;
179 }
180 (None, None) => return true,
181 _ => return false,
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use crate::tests::{check_assist, check_assist_not_applicable};
189
190 use super::*;
191
192 #[test]
193 fn test_replace_already_imported() {
194 check_assist(
195 replace_qualified_name_with_use,
196 r"
197mod std { pub mod fs { pub struct Path; } }
198use std::fs;
199
200fn main() {
201 std::f$0s::Path
202}",
203 r"
204mod std { pub mod fs { pub struct Path; } }
205use std::fs;
206
207fn main() {
208 fs::Path
209}",
210 )
211 }
212
213 #[test]
214 fn test_replace_add_use_no_anchor() {
215 check_assist(
216 replace_qualified_name_with_use,
217 r"
218mod std { pub mod fs { pub struct Path; } }
219std::fs::Path$0
220 ",
221 r"
222use std::fs::Path;
223
224mod std { pub mod fs { pub struct Path; } }
225Path
226 ",
227 );
228 }
229
230 #[test]
231 fn test_replace_add_use_no_anchor_middle_segment() {
232 check_assist(
233 replace_qualified_name_with_use,
234 r"
235mod std { pub mod fs { pub struct Path; } }
236std::fs$0::Path
237 ",
238 r"
239use std::fs;
240
241mod std { pub mod fs { pub struct Path; } }
242fs::Path
243 ",
244 );
245 }
246
247 #[test]
248 fn test_replace_not_applicable_in_use() {
249 cov_mark::check!(not_applicable_in_use);
250 check_assist_not_applicable(replace_qualified_name_with_use, r"use std::fmt$0;");
251 }
252
253 #[test]
254 fn replaces_all_affected_paths() {
255 check_assist(
256 replace_qualified_name_with_use,
257 r"
258mod std { pub mod fmt { pub trait Debug {} } }
259fn main() {
260 std::fmt::Debug$0;
261 let x: std::fmt::Debug = std::fmt::Debug;
262}
263 ",
264 r"
265use std::fmt::Debug;
266
267mod std { pub mod fmt { pub trait Debug {} } }
268fn main() {
269 Debug;
270 let x: Debug = Debug;
271}
272 ",
273 );
274 }
275
276 #[test]
277 fn assist_runs_on_first_segment() {
278 check_assist(
279 replace_qualified_name_with_use,
280 r"
281mod std { pub mod fmt { pub trait Debug {} } }
282fn main() {
283 $0std::fmt::Debug;
284 let x: std::fmt::Debug = std::fmt::Debug;
285}
286 ",
287 r"
288use std::fmt::Debug;
289
290mod std { pub mod fmt { pub trait Debug {} } }
291fn main() {
292 Debug;
293 let x: Debug = Debug;
294}
295 ",
296 );
297 }
298
299 #[test]
300 fn assist_runs_on_first_segment_for_enum() {
301 check_assist(
302 replace_qualified_name_with_use,
303 r"
304mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
305fn main() {
306 $0std::option::Option;
307 let x: std::option::Option<()> = std::option::Option::Some(());
308}
309 ",
310 r"
311use std::option::Option;
312
313mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
314fn main() {
315 Option;
316 let x: Option<()> = Option::Some(());
317}
318 ",
319 );
320
321 check_assist(
322 replace_qualified_name_with_use,
323 r"
324mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
325fn main() {
326 std::option::Option;
327 let x: std::option::Option<()> = $0std::option::Option::Some(());
328}
329 ",
330 r"
331use std::option::Option;
332
333mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
334fn main() {
335 Option;
336 let x: Option<()> = Option::Some(());
337}
338 ",
339 );
340 }
341
342 #[test]
343 fn assist_runs_on_first_segment_for_assoc_type() {
344 check_assist(
345 replace_qualified_name_with_use,
346 r"
347mod foo { pub struct Foo; impl Foo { pub fn foo() {} } }
348fn main() {
349 $0foo::Foo::foo();
350}
351 ",
352 r"
353use foo::Foo;
354
355mod foo { pub struct Foo; impl Foo { pub fn foo() {} } }
356fn main() {
357 Foo::foo();
358}
359 ",
360 );
361 }
362
363 #[test]
364 fn assist_runs_on_enum_variant() {
365 check_assist(
366 replace_qualified_name_with_use,
367 r"
368mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
369fn main() {
370 let x = std::option::Option::Some$0(());
371}
372 ",
373 r"
374use std::option::Option::Some;
375
376mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
377fn main() {
378 let x = Some(());
379}
380 ",
381 );
382
383 check_assist(
384 replace_qualified_name_with_use,
385 r"
386mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
387fn main() {
388 std::option::Option;
389 let x: std::option::Option<()> = $0std::option::Option::Some(());
390}
391 ",
392 r"
393use std::option::Option;
394
395mod std { pub mod option { pub enum Option<T> { Some(T), None } } }
396fn main() {
397 Option;
398 let x: Option<()> = Option::Some(());
399}
400 ",
401 );
402 }
403
404 #[test]
405 fn does_not_replace_in_submodules() {
406 check_assist(
407 replace_qualified_name_with_use,
408 r"
409mod std { pub mod fmt { pub trait Debug {} } }
410fn main() {
411 std::fmt::Debug$0;
412}
413
414mod sub {
415 fn f() {
416 std::fmt::Debug;
417 }
418}
419 ",
420 r"
421use std::fmt::Debug;
422
423mod std { pub mod fmt { pub trait Debug {} } }
424fn main() {
425 Debug;
426}
427
428mod sub {
429 fn f() {
430 std::fmt::Debug;
431 }
432}
433 ",
434 );
435 }
436
437 #[test]
438 fn does_not_replace_in_use() {
439 check_assist(
440 replace_qualified_name_with_use,
441 r"
442mod std { pub mod fmt { pub trait Display {} } }
443use std::fmt::Display;
444
445fn main() {
446 std::fmt$0;
447}
448 ",
449 r"
450mod std { pub mod fmt { pub trait Display {} } }
451use std::fmt::{self, Display};
452
453fn main() {
454 fmt;
455}
456 ",
457 );
458 }
459
460 #[test]
461 fn does_not_replace_assoc_item_path() {
462 check_assist_not_applicable(
463 replace_qualified_name_with_use,
464 r"
465pub struct Foo;
466impl Foo {
467 pub fn foo() {}
468}
469
470fn main() {
471 Foo::foo$0();
472}
473",
474 );
475 }
476
477 #[test]
478 fn replace_reuses_path_qualifier() {
479 check_assist(
480 replace_qualified_name_with_use,
481 r"
482pub mod foo {
483 pub struct Foo;
484}
485
486mod bar {
487 pub use super::foo::Foo as Bar;
488}
489
490fn main() {
491 foo::Foo$0;
492}
493",
494 r"
495use foo::Foo;
496
497pub mod foo {
498 pub struct Foo;
499}
500
501mod bar {
502 pub use super::foo::Foo as Bar;
503}
504
505fn main() {
506 Foo;
507}
508",
509 );
510 }
511
512 #[test]
513 fn replace_does_not_always_try_to_replace_by_full_item_path() {
514 check_assist(
515 replace_qualified_name_with_use,
516 r"
517use std::mem;
518
519mod std {
520 pub mod mem {
521 pub fn drop<T>(_: T) {}
522 }
523}
524
525fn main() {
526 mem::drop$0(0);
527}
528",
529 r"
530use std::mem::{self, drop};
531
532mod std {
533 pub mod mem {
534 pub fn drop<T>(_: T) {}
535 }
536}
537
538fn main() {
539 drop(0);
540}
541",
542 );
543 }
544
545 #[test]
546 fn replace_should_drop_generic_args_in_use() {
547 check_assist(
548 replace_qualified_name_with_use,
549 r"
550mod std {
551 pub mod mem {
552 pub fn drop<T>(_: T) {}
553 }
554}
555
556fn main() {
557 std::mem::drop::<usize>$0(0);
558}
559",
560 r"
561use std::mem::drop;
562
563mod std {
564 pub mod mem {
565 pub fn drop<T>(_: T) {}
566 }
567}
568
569fn main() {
570 drop::<usize>(0);
571}
572",
573 );
574 }
575}