aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src')
-rw-r--r--crates/assists/src/assist_config.rs20
-rw-r--r--crates/assists/src/ast_transform.rs30
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs40
-rw-r--r--crates/assists/src/handlers/auto_import.rs21
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs30
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs31
-rw-r--r--crates/assists/src/handlers/merge_imports.rs175
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs144
-rw-r--r--crates/assists/src/handlers/replace_impl_trait_with_generic.rs168
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs58
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs13
-rw-r--r--crates/assists/src/utils.rs3
-rw-r--r--crates/assists/src/utils/insert_use.rs1347
14 files changed, 1409 insertions, 673 deletions
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs
index cda2abfb9..adf02edab 100644
--- a/crates/assists/src/assist_config.rs
+++ b/crates/assists/src/assist_config.rs
@@ -4,12 +4,13 @@
4//! module, and we use to statically check that we only produce snippet 4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to. 5//! assists if we are allowed to.
6 6
7use crate::AssistKind; 7use crate::{utils::MergeBehaviour, AssistKind};
8 8
9#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct AssistConfig { 10pub struct AssistConfig {
11 pub snippet_cap: Option<SnippetCap>, 11 pub snippet_cap: Option<SnippetCap>,
12 pub allowed: Option<Vec<AssistKind>>, 12 pub allowed: Option<Vec<AssistKind>>,
13 pub insert_use: InsertUseConfig,
13} 14}
14 15
15impl AssistConfig { 16impl AssistConfig {
@@ -25,6 +26,21 @@ pub struct SnippetCap {
25 26
26impl Default for AssistConfig { 27impl Default for AssistConfig {
27 fn default() -> Self { 28 fn default() -> Self {
28 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None } 29 AssistConfig {
30 snippet_cap: Some(SnippetCap { _private: () }),
31 allowed: None,
32 insert_use: InsertUseConfig::default(),
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct InsertUseConfig {
39 pub merge: Option<MergeBehaviour>,
40}
41
42impl Default for InsertUseConfig {
43 fn default() -> Self {
44 InsertUseConfig { merge: Some(MergeBehaviour::Full) }
29 } 45 }
30} 46}
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
index 5216862ba..835da3bb2 100644
--- a/crates/assists/src/ast_transform.rs
+++ b/crates/assists/src/ast_transform.rs
@@ -18,6 +18,34 @@ pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
18 .rewrite_ast(&node) 18 .rewrite_ast(&node)
19} 19}
20 20
21/// `AstTransform` helps with applying bulk transformations to syntax nodes.
22///
23/// This is mostly useful for IDE code generation. If you paste some existing
24/// code into a new context (for example, to add method overrides to an `impl`
25/// block), you generally want to appropriately qualify the names, and sometimes
26/// you might want to substitute generic parameters as well:
27///
28/// ```
29/// mod x {
30/// pub struct A;
31/// pub trait T<U> { fn foo(&self, _: U) -> A; }
32/// }
33///
34/// mod y {
35/// use x::T;
36///
37/// impl T<()> for () {
38/// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`.
39/// // But we want a slightly-modified version of it:
40/// fn foo(&self, _: ()) -> x::A {}
41/// }
42/// }
43/// ```
44///
45/// So, a single `AstTransform` describes such function from `SyntaxNode` to
46/// `SyntaxNode`. Note that the API here is a bit too high-order and high-brow.
47/// We'd want to somehow express this concept simpler, but so far nobody got to
48/// simplifying this!
21pub trait AstTransform<'a> { 49pub trait AstTransform<'a> {
22 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>; 50 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>;
23 51
@@ -166,7 +194,7 @@ impl<'a> QualifyPaths<'a> {
166 .map(|arg_list| apply(self, arg_list)); 194 .map(|arg_list| apply(self, arg_list));
167 if let Some(type_args) = type_args { 195 if let Some(type_args) = type_args {
168 let last_segment = path.segment().unwrap(); 196 let last_segment = path.segment().unwrap();
169 path = path.with_segment(last_segment.with_type_args(type_args)) 197 path = path.with_segment(last_segment.with_generic_args(type_args))
170 } 198 }
171 199
172 Some(path.syntax().clone()) 200 Some(path.syntax().clone())
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index 83a2ada9a..8df1d786b 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -111,8 +111,6 @@ fn add_missing_impl_members_inner(
111) -> Option<()> { 111) -> Option<()> {
112 let _p = profile::span("add_missing_impl_members_inner"); 112 let _p = profile::span("add_missing_impl_members_inner");
113 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?; 113 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
114 let impl_item_list = impl_def.assoc_item_list()?;
115
116 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; 114 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
117 115
118 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { 116 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
@@ -148,11 +146,14 @@ fn add_missing_impl_members_inner(
148 146
149 let target = impl_def.syntax().text_range(); 147 let target = impl_def.syntax().text_range();
150 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { 148 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
149 let impl_item_list = impl_def.assoc_item_list().unwrap_or(make::assoc_item_list());
150
151 let n_existing_items = impl_item_list.assoc_items().count(); 151 let n_existing_items = impl_item_list.assoc_items().count();
152 let source_scope = ctx.sema.scope_for_def(trait_); 152 let source_scope = ctx.sema.scope_for_def(trait_);
153 let target_scope = ctx.sema.scope(impl_item_list.syntax()); 153 let target_scope = ctx.sema.scope(impl_def.syntax());
154 let ast_transform = QualifyPaths::new(&target_scope, &source_scope) 154 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
155 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def)); 155 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
156
156 let items = missing_items 157 let items = missing_items
157 .into_iter() 158 .into_iter()
158 .map(|it| ast_transform::apply(&*ast_transform, it)) 159 .map(|it| ast_transform::apply(&*ast_transform, it))
@@ -162,12 +163,14 @@ fn add_missing_impl_members_inner(
162 _ => it, 163 _ => it,
163 }) 164 })
164 .map(|it| edit::remove_attrs_and_docs(&it)); 165 .map(|it| edit::remove_attrs_and_docs(&it));
166
165 let new_impl_item_list = impl_item_list.append_items(items); 167 let new_impl_item_list = impl_item_list.append_items(items);
166 let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap(); 168 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
169 let first_new_item =
170 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
167 171
168 let original_range = impl_item_list.syntax().text_range();
169 match ctx.config.snippet_cap { 172 match ctx.config.snippet_cap {
170 None => builder.replace(original_range, new_impl_item_list.to_string()), 173 None => builder.replace(target, new_impl_def.to_string()),
171 Some(cap) => { 174 Some(cap) => {
172 let mut cursor = Cursor::Before(first_new_item.syntax()); 175 let mut cursor = Cursor::Before(first_new_item.syntax());
173 let placeholder; 176 let placeholder;
@@ -181,8 +184,8 @@ fn add_missing_impl_members_inner(
181 } 184 }
182 builder.replace_snippet( 185 builder.replace_snippet(
183 cap, 186 cap,
184 original_range, 187 target,
185 render_snippet(cap, new_impl_item_list.syntax(), cursor), 188 render_snippet(cap, new_impl_def.syntax(), cursor),
186 ) 189 )
187 } 190 }
188 }; 191 };
@@ -311,6 +314,25 @@ impl Foo for S {
311 } 314 }
312 315
313 #[test] 316 #[test]
317 fn test_impl_def_without_braces() {
318 check_assist(
319 add_missing_impl_members,
320 r#"
321trait Foo { fn foo(&self); }
322struct S;
323impl Foo for S<|>"#,
324 r#"
325trait Foo { fn foo(&self); }
326struct S;
327impl Foo for S {
328 fn foo(&self) {
329 ${0:todo!()}
330 }
331}"#,
332 );
333 }
334
335 #[test]
314 fn fill_in_type_params_1() { 336 fn fill_in_type_params_1() {
315 check_assist( 337 check_assist(
316 add_missing_impl_members, 338 add_missing_impl_members,
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index c4770f336..b5eb2c722 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -1,20 +1,20 @@
1use std::collections::BTreeSet; 1use std::collections::BTreeSet;
2 2
3use ast::make;
3use either::Either; 4use either::Either;
4use hir::{ 5use hir::{
5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, 6 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6 Type, 7 Type,
7}; 8};
8use ide_db::{imports_locator, RootDatabase}; 9use ide_db::{imports_locator, RootDatabase};
10use insert_use::ImportScope;
9use rustc_hash::FxHashSet; 11use rustc_hash::FxHashSet;
10use syntax::{ 12use syntax::{
11 ast::{self, AstNode}, 13 ast::{self, AstNode},
12 SyntaxNode, 14 SyntaxNode,
13}; 15};
14 16
15use crate::{ 17use crate::{utils::insert_use, AssistContext, AssistId, AssistKind, Assists, GroupLabel};
16 utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel,
17};
18 18
19// Assist: auto_import 19// Assist: auto_import
20// 20//
@@ -44,6 +44,9 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
44 44
45 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; 45 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
46 let group = auto_import_assets.get_import_group_message(); 46 let group = auto_import_assets.get_import_group_message();
47 let scope =
48 ImportScope::find_insert_use_container(&auto_import_assets.syntax_under_caret, ctx)?;
49 let syntax = scope.as_syntax_node();
47 for import in proposed_imports { 50 for import in proposed_imports {
48 acc.add_group( 51 acc.add_group(
49 &group, 52 &group,
@@ -51,12 +54,12 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
51 format!("Import `{}`", &import), 54 format!("Import `{}`", &import),
52 range, 55 range,
53 |builder| { 56 |builder| {
54 insert_use_statement( 57 let new_syntax = insert_use(
55 &auto_import_assets.syntax_under_caret, 58 &scope,
56 &import.to_string(), 59 make::path_from_text(&import.to_string()),
57 ctx, 60 ctx.config.insert_use.merge,
58 builder.text_edit_builder(),
59 ); 61 );
62 builder.replace(syntax.text_range(), new_syntax.to_string())
60 }, 63 },
61 ); 64 );
62 } 65 }
@@ -358,7 +361,7 @@ mod tests {
358 } 361 }
359 ", 362 ",
360 r" 363 r"
361 use PubMod::{PubStruct2, PubStruct1}; 364 use PubMod::{PubStruct1, PubStruct2};
362 365
363 struct Test { 366 struct Test {
364 test: PubStruct2<u8>, 367 test: PubStruct2<u8>,
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index b39d040f6..e14ac7f65 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -4,7 +4,11 @@ use ide_db::{
4 defs::{classify_name_ref, Definition, NameRefClass}, 4 defs::{classify_name_ref, Definition, NameRefClass},
5 search::SearchScope, 5 search::SearchScope,
6}; 6};
7use syntax::{algo, ast, AstNode, Direction, SyntaxNode, SyntaxToken, T}; 7use syntax::{
8 algo,
9 ast::{self, make},
10 AstNode, Direction, SyntaxNode, SyntaxToken, T,
11};
8 12
9use crate::{ 13use crate::{
10 assist_context::{AssistBuilder, AssistContext, Assists}, 14 assist_context::{AssistBuilder, AssistContext, Assists},
@@ -249,7 +253,10 @@ fn replace_ast(
249 253
250 let new_use_trees: Vec<ast::UseTree> = names_to_import 254 let new_use_trees: Vec<ast::UseTree> = names_to_import
251 .iter() 255 .iter()
252 .map(|n| ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false)) 256 .map(|n| {
257 let path = make::path_unqualified(make::path_segment(make::name_ref(&n.to_string())));
258 make::use_tree(path, None, None, false)
259 })
253 .collect(); 260 .collect();
254 261
255 let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat(); 262 let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat();
@@ -257,8 +264,8 @@ fn replace_ast(
257 match use_trees.as_slice() { 264 match use_trees.as_slice() {
258 [name] => { 265 [name] => {
259 if let Some(end_path) = name.path() { 266 if let Some(end_path) = name.path() {
260 let replacement = ast::make::use_tree( 267 let replacement = make::use_tree(
261 ast::make::path_from_text(&format!("{}::{}", path, end_path)), 268 make::path_from_text(&format!("{}::{}", path, end_path)),
262 None, 269 None,
263 None, 270 None,
264 false, 271 false,
@@ -273,15 +280,12 @@ fn replace_ast(
273 } 280 }
274 names => { 281 names => {
275 let replacement = match parent { 282 let replacement = match parent {
276 Either::Left(_) => ast::make::use_tree( 283 Either::Left(_) => {
277 path, 284 make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false)
278 Some(ast::make::use_tree_list(names.to_owned())), 285 .syntax()
279 None, 286 .clone()
280 false, 287 }
281 ) 288 Either::Right(_) => make::use_tree_list(names.to_owned()).syntax().clone(),
282 .syntax()
283 .clone(),
284 Either::Right(_) => ast::make::use_tree_list(names.to_owned()).syntax().clone(),
285 }; 289 };
286 290
287 algo::diff( 291 algo::diff(
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 8ac20210a..3ea50f375 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -10,9 +10,10 @@ use syntax::{
10}; 10};
11 11
12use crate::{ 12use crate::{
13 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, 13 assist_context::AssistBuilder, utils::insert_use, AssistContext, AssistId, AssistKind, Assists,
14 AssistKind, Assists,
15}; 14};
15use ast::make;
16use insert_use::ImportScope;
16 17
17// Assist: extract_struct_from_enum_variant 18// Assist: extract_struct_from_enum_variant
18// 19//
@@ -94,6 +95,7 @@ fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVari
94 .any(|(name, _)| name.to_string() == variant_name.to_string()) 95 .any(|(name, _)| name.to_string() == variant_name.to_string())
95} 96}
96 97
98#[allow(dead_code)]
97fn insert_import( 99fn insert_import(
98 ctx: &AssistContext, 100 ctx: &AssistContext,
99 builder: &mut AssistBuilder, 101 builder: &mut AssistBuilder,
@@ -107,12 +109,16 @@ fn insert_import(
107 if let Some(mut mod_path) = mod_path { 109 if let Some(mut mod_path) = mod_path {
108 mod_path.segments.pop(); 110 mod_path.segments.pop();
109 mod_path.segments.push(variant_hir_name.clone()); 111 mod_path.segments.push(variant_hir_name.clone());
110 insert_use_statement( 112 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
111 path.syntax(), 113 let syntax = scope.as_syntax_node();
112 &mod_path.to_string(), 114
113 ctx, 115 let new_syntax = insert_use(
114 builder.text_edit_builder(), 116 &scope,
117 make::path_from_text(&mod_path.to_string()),
118 ctx.config.insert_use.merge,
115 ); 119 );
120 // FIXME: this will currently panic as multiple imports will have overlapping text ranges
121 builder.replace(syntax.text_range(), new_syntax.to_string())
116 } 122 }
117 Some(()) 123 Some(())
118} 124}
@@ -167,9 +173,9 @@ fn update_reference(
167 builder: &mut AssistBuilder, 173 builder: &mut AssistBuilder,
168 reference: Reference, 174 reference: Reference,
169 source_file: &SourceFile, 175 source_file: &SourceFile,
170 enum_module_def: &ModuleDef, 176 _enum_module_def: &ModuleDef,
171 variant_hir_name: &Name, 177 _variant_hir_name: &Name,
172 visited_modules_set: &mut FxHashSet<Module>, 178 _visited_modules_set: &mut FxHashSet<Module>,
173) -> Option<()> { 179) -> Option<()> {
174 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>( 180 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
175 source_file.syntax(), 181 source_file.syntax(),
@@ -178,13 +184,14 @@ fn update_reference(
178 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 184 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
179 let list = call.arg_list()?; 185 let list = call.arg_list()?;
180 let segment = path_expr.path()?.segment()?; 186 let segment = path_expr.path()?.segment()?;
181 let module = ctx.sema.scope(&path_expr.syntax()).module()?; 187 let _module = ctx.sema.scope(&path_expr.syntax()).module()?;
182 let list_range = list.syntax().text_range(); 188 let list_range = list.syntax().text_range();
183 let inside_list_range = TextRange::new( 189 let inside_list_range = TextRange::new(
184 list_range.start().checked_add(TextSize::from(1))?, 190 list_range.start().checked_add(TextSize::from(1))?,
185 list_range.end().checked_sub(TextSize::from(1))?, 191 list_range.end().checked_sub(TextSize::from(1))?,
186 ); 192 );
187 builder.edit_file(reference.file_range.file_id); 193 builder.edit_file(reference.file_range.file_id);
194 /* FIXME: this most likely requires AST-based editing, see `insert_import`
188 if !visited_modules_set.contains(&module) { 195 if !visited_modules_set.contains(&module) {
189 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name) 196 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
190 .is_some() 197 .is_some()
@@ -192,6 +199,7 @@ fn update_reference(
192 visited_modules_set.insert(module); 199 visited_modules_set.insert(module);
193 } 200 }
194 } 201 }
202 */
195 builder.replace(inside_list_range, format!("{}{}", segment, list)); 203 builder.replace(inside_list_range, format!("{}{}", segment, list));
196 Some(()) 204 Some(())
197} 205}
@@ -250,6 +258,7 @@ pub enum A { One(One) }"#,
250 } 258 }
251 259
252 #[test] 260 #[test]
261 #[ignore] // FIXME: this currently panics if `insert_import` is used
253 fn test_extract_struct_with_complex_imports() { 262 fn test_extract_struct_with_complex_imports() {
254 check_assist( 263 check_assist(
255 extract_struct_from_enum_variant, 264 extract_struct_from_enum_variant,
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs
index 35b884206..fe33cee53 100644
--- a/crates/assists/src/handlers/merge_imports.rs
+++ b/crates/assists/src/handlers/merge_imports.rs
@@ -1,14 +1,14 @@
1use std::iter::successors;
2
3use syntax::{ 1use syntax::{
4 algo::{neighbor, skip_trivia_token, SyntaxRewriter}, 2 algo::{neighbor, SyntaxRewriter},
5 ast::{self, edit::AstNodeEdit, make}, 3 ast, AstNode,
6 AstNode, Direction, InsertPosition, SyntaxElement, T,
7}; 4};
8 5
9use crate::{ 6use crate::{
10 assist_context::{AssistContext, Assists}, 7 assist_context::{AssistContext, Assists},
11 utils::next_prev, 8 utils::{
9 insert_use::{try_merge_imports, try_merge_trees},
10 next_prev, MergeBehaviour,
11 },
12 AssistId, AssistKind, 12 AssistId, AssistKind,
13}; 13};
14 14
@@ -30,23 +30,22 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
30 let mut offset = ctx.offset(); 30 let mut offset = ctx.offset();
31 31
32 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) { 32 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
33 let (merged, to_delete) = next_prev() 33 let (merged, to_delete) =
34 .filter_map(|dir| neighbor(&use_item, dir)) 34 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
35 .filter_map(|it| Some((it.clone(), it.use_tree()?))) 35 try_merge_imports(&use_item, &use_item2, MergeBehaviour::Full).zip(Some(use_item2))
36 .find_map(|(use_item, use_tree)| {
37 Some((try_merge_trees(&tree, &use_tree)?, use_item))
38 })?; 36 })?;
39 37
40 rewriter.replace_ast(&tree, &merged); 38 rewriter.replace_ast(&use_item, &merged);
41 rewriter += to_delete.remove(); 39 rewriter += to_delete.remove();
42 40
43 if to_delete.syntax().text_range().end() < offset { 41 if to_delete.syntax().text_range().end() < offset {
44 offset -= to_delete.syntax().text_range().len(); 42 offset -= to_delete.syntax().text_range().len();
45 } 43 }
46 } else { 44 } else {
47 let (merged, to_delete) = next_prev() 45 let (merged, to_delete) =
48 .filter_map(|dir| neighbor(&tree, dir)) 46 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
49 .find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?; 47 try_merge_trees(&tree, &use_tree, MergeBehaviour::Full).zip(Some(use_tree))
48 })?;
50 49
51 rewriter.replace_ast(&tree, &merged); 50 rewriter.replace_ast(&tree, &merged);
52 rewriter += to_delete.remove(); 51 rewriter += to_delete.remove();
@@ -67,66 +66,6 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
67 ) 66 )
68} 67}
69 68
70fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> {
71 let lhs_path = old.path()?;
72 let rhs_path = new.path()?;
73
74 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
75
76 let lhs = old.split_prefix(&lhs_prefix);
77 let rhs = new.split_prefix(&rhs_prefix);
78
79 let should_insert_comma = lhs
80 .use_tree_list()?
81 .r_curly_token()
82 .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev))
83 .map(|it| it.kind() != T![,])
84 .unwrap_or(true);
85
86 let mut to_insert: Vec<SyntaxElement> = Vec::new();
87 if should_insert_comma {
88 to_insert.push(make::token(T![,]).into());
89 to_insert.push(make::tokens::single_space().into());
90 }
91 to_insert.extend(
92 rhs.use_tree_list()?
93 .syntax()
94 .children_with_tokens()
95 .filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']),
96 );
97 let use_tree_list = lhs.use_tree_list()?;
98 let pos = InsertPosition::Before(use_tree_list.r_curly_token()?.into());
99 let use_tree_list = use_tree_list.insert_children(pos, to_insert);
100 Some(lhs.with_use_tree_list(use_tree_list))
101}
102
103fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
104 let mut res = None;
105 let mut lhs_curr = first_path(&lhs);
106 let mut rhs_curr = first_path(&rhs);
107 loop {
108 match (lhs_curr.segment(), rhs_curr.segment()) {
109 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
110 _ => break,
111 }
112 res = Some((lhs_curr.clone(), rhs_curr.clone()));
113
114 match (lhs_curr.parent_path(), rhs_curr.parent_path()) {
115 (Some(lhs), Some(rhs)) => {
116 lhs_curr = lhs;
117 rhs_curr = rhs;
118 }
119 _ => break,
120 }
121 }
122
123 res
124}
125
126fn first_path(path: &ast::Path) -> ast::Path {
127 successors(Some(path.clone()), |it| it.qualifier()).last().unwrap()
128}
129
130#[cfg(test)] 69#[cfg(test)]
131mod tests { 70mod tests {
132 use crate::tests::{check_assist, check_assist_not_applicable}; 71 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -156,7 +95,7 @@ use std::fmt::Debug;
156use std::fmt<|>::Display; 95use std::fmt<|>::Display;
157", 96",
158 r" 97 r"
159use std::fmt::{Display, Debug}; 98use std::fmt::{Debug, Display};
160", 99",
161 ); 100 );
162 } 101 }
@@ -183,12 +122,84 @@ use std::fmt::{self, Display};
183use std::{fmt, <|>fmt::Display}; 122use std::{fmt, <|>fmt::Display};
184", 123",
185 r" 124 r"
186use std::{fmt::{Display, self}}; 125use std::{fmt::{self, Display}};
187", 126",
188 ); 127 );
189 } 128 }
190 129
191 #[test] 130 #[test]
131 fn skip_pub1() {
132 check_assist_not_applicable(
133 merge_imports,
134 r"
135pub use std::fmt<|>::Debug;
136use std::fmt::Display;
137",
138 );
139 }
140
141 #[test]
142 fn skip_pub_last() {
143 check_assist_not_applicable(
144 merge_imports,
145 r"
146use std::fmt<|>::Debug;
147pub use std::fmt::Display;
148",
149 );
150 }
151
152 #[test]
153 fn skip_pub_crate_pub() {
154 check_assist_not_applicable(
155 merge_imports,
156 r"
157pub(crate) use std::fmt<|>::Debug;
158pub use std::fmt::Display;
159",
160 );
161 }
162
163 #[test]
164 fn skip_pub_pub_crate() {
165 check_assist_not_applicable(
166 merge_imports,
167 r"
168pub use std::fmt<|>::Debug;
169pub(crate) use std::fmt::Display;
170",
171 );
172 }
173
174 #[test]
175 fn merge_pub() {
176 check_assist(
177 merge_imports,
178 r"
179pub use std::fmt<|>::Debug;
180pub use std::fmt::Display;
181",
182 r"
183pub use std::fmt::{Debug, Display};
184",
185 )
186 }
187
188 #[test]
189 fn merge_pub_crate() {
190 check_assist(
191 merge_imports,
192 r"
193pub(crate) use std::fmt<|>::Debug;
194pub(crate) use std::fmt::Display;
195",
196 r"
197pub(crate) use std::fmt::{Debug, Display};
198",
199 )
200 }
201
202 #[test]
192 fn test_merge_nested() { 203 fn test_merge_nested() {
193 check_assist( 204 check_assist(
194 merge_imports, 205 merge_imports,
@@ -199,13 +210,17 @@ use std::{fmt<|>::Debug, fmt::Display};
199use std::{fmt::{Debug, Display}}; 210use std::{fmt::{Debug, Display}};
200", 211",
201 ); 212 );
213 }
214
215 #[test]
216 fn test_merge_nested2() {
202 check_assist( 217 check_assist(
203 merge_imports, 218 merge_imports,
204 r" 219 r"
205use std::{fmt::Debug, fmt<|>::Display}; 220use std::{fmt::Debug, fmt<|>::Display};
206", 221",
207 r" 222 r"
208use std::{fmt::{Display, Debug}}; 223use std::{fmt::{Debug, Display}};
209", 224",
210 ); 225 );
211 } 226 }
@@ -299,9 +314,7 @@ use foo::<|>{
299}; 314};
300", 315",
301 r" 316 r"
302use foo::{ 317use foo::{FooBar, bar::baz};
303 FooBar,
304bar::baz};
305", 318",
306 ) 319 )
307 } 320 }
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index 4e252edf0..a8ab2aecc 100644
--- a/crates/assists/src/handlers/remove_dbg.rs
+++ b/crates/assists/src/handlers/remove_dbg.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 TextRange, TextSize, T, 3 SyntaxElement, TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -22,62 +22,108 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
22// ``` 22// ```
23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; 24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25 let new_contents = adjusted_macro_contents(&macro_call)?;
25 26
26 if !is_valid_macrocall(&macro_call, "dbg")? { 27 let macro_text_range = macro_call.syntax().text_range();
27 return None;
28 }
29
30 let is_leaf = macro_call.syntax().next_sibling().is_none();
31
32 let macro_end = if macro_call.semicolon_token().is_some() { 28 let macro_end = if macro_call.semicolon_token().is_some() {
33 macro_call.syntax().text_range().end() - TextSize::of(';') 29 macro_text_range.end() - TextSize::of(';')
34 } else { 30 } else {
35 macro_call.syntax().text_range().end() 31 macro_text_range.end()
36 }; 32 };
37 33
38 // macro_range determines what will be deleted and replaced with macro_content 34 acc.add(
39 let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end); 35 AssistId("remove_dbg", AssistKind::Refactor),
40 let paste_instead_of_dbg = { 36 "Remove dbg!()",
41 let text = macro_call.token_tree()?.syntax().text(); 37 macro_text_range,
42 38 |builder| {
43 // leafiness determines if we should include the parenthesis or not 39 builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents);
44 let slice_index: TextRange = if is_leaf { 40 },
45 // leaf means - we can extract the contents of the dbg! in text 41 )
46 TextRange::new(TextSize::of('('), text.len() - TextSize::of(')')) 42}
47 } else {
48 // not leaf - means we should keep the parens
49 TextRange::up_to(text.len())
50 };
51 text.slice(slice_index).to_string()
52 };
53 43
54 let target = macro_call.syntax().text_range(); 44fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
55 acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| { 45 let contents = get_valid_macrocall_contents(&macro_call, "dbg")?;
56 builder.replace(macro_range, paste_instead_of_dbg); 46 let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
47 let macro_text_in_brackets = macro_text_with_brackets.slice(TextRange::new(
48 TextSize::of('('),
49 macro_text_with_brackets.len() - TextSize::of(')'),
50 ));
51
52 let is_leaf = macro_call.syntax().next_sibling().is_none();
53 Some(if !is_leaf && needs_parentheses_around_macro_contents(contents) {
54 format!("({})", macro_text_in_brackets)
55 } else {
56 macro_text_in_brackets.to_string()
57 }) 57 })
58} 58}
59 59
60/// Verifies that the given macro_call actually matches the given name 60/// Verifies that the given macro_call actually matches the given name
61/// and contains proper ending tokens 61/// and contains proper ending tokens, then returns the contents between the ending tokens
62fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> { 62fn get_valid_macrocall_contents(
63 macro_call: &ast::MacroCall,
64 macro_name: &str,
65) -> Option<Vec<SyntaxElement>> {
63 let path = macro_call.path()?; 66 let path = macro_call.path()?;
64 let name_ref = path.segment()?.name_ref()?; 67 let name_ref = path.segment()?.name_ref()?;
65 68
66 // Make sure it is actually a dbg-macro call, dbg followed by ! 69 // Make sure it is actually a dbg-macro call, dbg followed by !
67 let excl = path.syntax().next_sibling_or_token()?; 70 let excl = path.syntax().next_sibling_or_token()?;
68
69 if name_ref.text() != macro_name || excl.kind() != T![!] { 71 if name_ref.text() != macro_name || excl.kind() != T![!] {
70 return None; 72 return None;
71 } 73 }
72 74
73 let node = macro_call.token_tree()?.syntax().clone(); 75 let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens();
74 let first_child = node.first_child_or_token()?; 76 let first_child = children_with_tokens.next()?;
75 let last_child = node.last_child_or_token()?; 77 let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
78 let last_child = contents_between_brackets.pop()?;
79
80 if contents_between_brackets.is_empty() {
81 None
82 } else {
83 match (first_child.kind(), last_child.kind()) {
84 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
85 Some(contents_between_brackets)
86 }
87 _ => None,
88 }
89 }
90}
76 91
77 match (first_child.kind(), last_child.kind()) { 92fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool {
78 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), 93 if macro_contents.len() < 2 {
79 _ => Some(false), 94 return false;
80 } 95 }
96 let mut unpaired_brackets_in_contents = Vec::new();
97 for element in macro_contents {
98 match element.kind() {
99 T!['('] | T!['['] | T!['{'] => unpaired_brackets_in_contents.push(element),
100 T![')'] => {
101 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['('])
102 {
103 return true;
104 }
105 }
106 T![']'] => {
107 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['['])
108 {
109 return true;
110 }
111 }
112 T!['}'] => {
113 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{'])
114 {
115 return true;
116 }
117 }
118 symbol_kind => {
119 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty();
120 if symbol_not_in_bracket && symbol_kind.is_punct() {
121 return true;
122 }
123 }
124 }
125 }
126 !unpaired_brackets_in_contents.is_empty()
81} 127}
82 128
83#[cfg(test)] 129#[cfg(test)]
@@ -157,12 +203,38 @@ fn foo(n: usize) {
157 } 203 }
158 204
159 #[test] 205 #[test]
206 fn remove_dbg_from_non_leaf_simple_expression() {
207 check_assist(
208 remove_dbg,
209 "
210fn main() {
211 let mut a = 1;
212 while dbg!<|>(a) < 10000 {
213 a += 1;
214 }
215}
216",
217 "
218fn main() {
219 let mut a = 1;
220 while a < 10000 {
221 a += 1;
222 }
223}
224",
225 );
226 }
227
228 #[test]
160 fn test_remove_dbg_keep_expression() { 229 fn test_remove_dbg_keep_expression() {
161 check_assist( 230 check_assist(
162 remove_dbg, 231 remove_dbg,
163 r#"let res = <|>dbg!(a + b).foo();"#, 232 r#"let res = <|>dbg!(a + b).foo();"#,
164 r#"let res = (a + b).foo();"#, 233 r#"let res = (a + b).foo();"#,
165 ); 234 );
235
236 check_assist(remove_dbg, r#"let res = <|>dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#);
237 check_assist(remove_dbg, r#"let res = <|>dbg![2 + 2] * 5"#, r#"let res = (2 + 2) * 5"#);
166 } 238 }
167 239
168 #[test] 240 #[test]
diff --git a/crates/assists/src/handlers/replace_impl_trait_with_generic.rs b/crates/assists/src/handlers/replace_impl_trait_with_generic.rs
new file mode 100644
index 000000000..6738bc134
--- /dev/null
+++ b/crates/assists/src/handlers/replace_impl_trait_with_generic.rs
@@ -0,0 +1,168 @@
1use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: replace_impl_trait_with_generic
6//
7// Replaces `impl Trait` function argument with the named generic.
8//
9// ```
10// fn foo(bar: <|>impl Bar) {}
11// ```
12// ->
13// ```
14// fn foo<B: Bar>(bar: B) {}
15// ```
16pub(crate) fn replace_impl_trait_with_generic(
17 acc: &mut Assists,
18 ctx: &AssistContext,
19) -> Option<()> {
20 let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
21 let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?;
22 let type_fn = type_param.syntax().ancestors().find_map(ast::Fn::cast)?;
23
24 let impl_trait_ty = type_impl_trait.type_bound_list()?;
25
26 let target = type_fn.syntax().text_range();
27 acc.add(
28 AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
29 "Replace impl trait with generic",
30 target,
31 |edit| {
32 let generic_letter = impl_trait_ty.to_string().chars().next().unwrap().to_string();
33
34 let generic_param_list = type_fn
35 .generic_param_list()
36 .unwrap_or_else(|| make::generic_param_list(None))
37 .append_param(make::generic_param(generic_letter.clone(), Some(impl_trait_ty)));
38
39 let new_type_fn = type_fn
40 .replace_descendant::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter))
41 .with_generic_param_list(generic_param_list);
42
43 edit.replace_ast(type_fn.clone(), new_type_fn);
44 },
45 )
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 use crate::tests::check_assist;
53
54 #[test]
55 fn replace_impl_trait_with_generic_params() {
56 check_assist(
57 replace_impl_trait_with_generic,
58 r#"
59 fn foo<G>(bar: <|>impl Bar) {}
60 "#,
61 r#"
62 fn foo<G, B: Bar>(bar: B) {}
63 "#,
64 );
65 }
66
67 #[test]
68 fn replace_impl_trait_without_generic_params() {
69 check_assist(
70 replace_impl_trait_with_generic,
71 r#"
72 fn foo(bar: <|>impl Bar) {}
73 "#,
74 r#"
75 fn foo<B: Bar>(bar: B) {}
76 "#,
77 );
78 }
79
80 #[test]
81 fn replace_two_impl_trait_with_generic_params() {
82 check_assist(
83 replace_impl_trait_with_generic,
84 r#"
85 fn foo<G>(foo: impl Foo, bar: <|>impl Bar) {}
86 "#,
87 r#"
88 fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}
89 "#,
90 );
91 }
92
93 #[test]
94 fn replace_impl_trait_with_empty_generic_params() {
95 check_assist(
96 replace_impl_trait_with_generic,
97 r#"
98 fn foo<>(bar: <|>impl Bar) {}
99 "#,
100 r#"
101 fn foo<B: Bar>(bar: B) {}
102 "#,
103 );
104 }
105
106 #[test]
107 fn replace_impl_trait_with_empty_multiline_generic_params() {
108 check_assist(
109 replace_impl_trait_with_generic,
110 r#"
111 fn foo<
112 >(bar: <|>impl Bar) {}
113 "#,
114 r#"
115 fn foo<B: Bar
116 >(bar: B) {}
117 "#,
118 );
119 }
120
121 #[test]
122 #[ignore = "This case is very rare but there is no simple solutions to fix it."]
123 fn replace_impl_trait_with_exist_generic_letter() {
124 check_assist(
125 replace_impl_trait_with_generic,
126 r#"
127 fn foo<B>(bar: <|>impl Bar) {}
128 "#,
129 r#"
130 fn foo<B, C: Bar>(bar: C) {}
131 "#,
132 );
133 }
134
135 #[test]
136 fn replace_impl_trait_with_multiline_generic_params() {
137 check_assist(
138 replace_impl_trait_with_generic,
139 r#"
140 fn foo<
141 G: Foo,
142 F,
143 H,
144 >(bar: <|>impl Bar) {}
145 "#,
146 r#"
147 fn foo<
148 G: Foo,
149 F,
150 H, B: Bar
151 >(bar: B) {}
152 "#,
153 );
154 }
155
156 #[test]
157 fn replace_impl_trait_multiple() {
158 check_assist(
159 replace_impl_trait_with_generic,
160 r#"
161 fn foo(bar: <|>impl Foo + Bar) {}
162 "#,
163 r#"
164 fn foo<F: Foo + Bar>(bar: F) {}
165 "#,
166 );
167 }
168}
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
index 470e5f8ff..8ac907707 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -2,9 +2,10 @@ use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode, TextRang
2use test_utils::mark; 2use test_utils::mark;
3 3
4use crate::{ 4use crate::{
5 utils::{find_insert_use_container, insert_use_statement}, 5 utils::{insert_use, ImportScope},
6 AssistContext, AssistId, AssistKind, Assists, 6 AssistContext, AssistId, AssistKind, Assists,
7}; 7};
8use ast::make;
8 9
9// Assist: replace_qualified_name_with_use 10// Assist: replace_qualified_name_with_use
10// 11//
@@ -32,7 +33,7 @@ pub(crate) fn replace_qualified_name_with_use(
32 mark::hit!(dont_import_trivial_paths); 33 mark::hit!(dont_import_trivial_paths);
33 return None; 34 return None;
34 } 35 }
35 let path_to_import = path.to_string().clone(); 36 let path_to_import = path.to_string();
36 let path_to_import = match path.segment()?.generic_arg_list() { 37 let path_to_import = match path.segment()?.generic_arg_list() {
37 Some(generic_args) => { 38 Some(generic_args) => {
38 let generic_args_start = 39 let generic_args_start =
@@ -43,28 +44,26 @@ pub(crate) fn replace_qualified_name_with_use(
43 }; 44 };
44 45
45 let target = path.syntax().text_range(); 46 let target = path.syntax().text_range();
47 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
48 let syntax = scope.as_syntax_node();
46 acc.add( 49 acc.add(
47 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), 50 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
48 "Replace qualified path with use", 51 "Replace qualified path with use",
49 target, 52 target,
50 |builder| { 53 |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 54 // 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). 55 // affected (that is, all paths inside the node we added the `use` to).
64 let mut rewriter = SyntaxRewriter::default(); 56 let mut rewriter = SyntaxRewriter::default();
65 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone()); 57 shorten_paths(&mut rewriter, syntax.clone(), path);
66 shorten_paths(&mut rewriter, syntax, path); 58 let rewritten_syntax = rewriter.rewrite(&syntax);
67 builder.rewrite(rewriter); 59 if let Some(ref import_scope) = ImportScope::from(rewritten_syntax) {
60 let new_syntax = insert_use(
61 import_scope,
62 make::path_from_text(path_to_import),
63 ctx.config.insert_use.merge,
64 );
65 builder.replace(syntax.text_range(), new_syntax.to_string())
66 }
68 }, 67 },
69 ) 68 )
70} 69}
@@ -220,9 +219,10 @@ impl std::fmt::Debug<|> for Foo {
220} 219}
221 ", 220 ",
222 r" 221 r"
223use stdx;
224use std::fmt::Debug; 222use std::fmt::Debug;
225 223
224use stdx;
225
226impl Debug for Foo { 226impl Debug for Foo {
227} 227}
228 ", 228 ",
@@ -274,7 +274,7 @@ impl std::io<|> for Foo {
274} 274}
275 ", 275 ",
276 r" 276 r"
277use std::{io, fmt}; 277use std::{fmt, io};
278 278
279impl io for Foo { 279impl io for Foo {
280} 280}
@@ -293,7 +293,7 @@ impl std::fmt::Debug<|> for Foo {
293} 293}
294 ", 294 ",
295 r" 295 r"
296use std::fmt::{self, Debug, }; 296use std::fmt::{self, Debug};
297 297
298impl Debug for Foo { 298impl Debug for Foo {
299} 299}
@@ -331,7 +331,7 @@ impl std::fmt::nested<|> for Foo {
331} 331}
332", 332",
333 r" 333 r"
334use std::fmt::{Debug, nested::{Display, self}}; 334use std::fmt::{Debug, nested::{self, Display}};
335 335
336impl nested for Foo { 336impl nested for Foo {
337} 337}
@@ -369,7 +369,7 @@ impl std::fmt::nested::Debug<|> for Foo {
369} 369}
370", 370",
371 r" 371 r"
372use std::fmt::{Debug, nested::{Display, Debug}}; 372use std::fmt::{Debug, nested::{Debug, Display}};
373 373
374impl Debug for Foo { 374impl Debug for Foo {
375} 375}
@@ -388,7 +388,7 @@ impl std::fmt::nested::Display<|> for Foo {
388} 388}
389", 389",
390 r" 390 r"
391use std::fmt::{nested::Display, Debug}; 391use std::fmt::{Debug, nested::Display};
392 392
393impl Display for Foo { 393impl Display for Foo {
394} 394}
@@ -428,10 +428,7 @@ use crate::{
428fn foo() { crate::ty::lower<|>::trait_env() } 428fn foo() { crate::ty::lower<|>::trait_env() }
429", 429",
430 r" 430 r"
431use crate::{ 431use crate::{AssocItem, ty::{Substs, Ty, lower}};
432 ty::{Substs, Ty, lower},
433 AssocItem,
434};
435 432
436fn foo() { lower::trait_env() } 433fn foo() { lower::trait_env() }
437", 434",
@@ -451,6 +448,8 @@ impl foo::Debug<|> for Foo {
451 r" 448 r"
452use std::fmt as foo; 449use std::fmt as foo;
453 450
451use foo::Debug;
452
454impl Debug for Foo { 453impl Debug for Foo {
455} 454}
456", 455",
@@ -515,6 +514,7 @@ fn main() {
515 ", 514 ",
516 r" 515 r"
517#![allow(dead_code)] 516#![allow(dead_code)]
517
518use std::fmt::Debug; 518use std::fmt::Debug;
519 519
520fn main() { 520fn main() {
@@ -647,9 +647,8 @@ impl std::io<|> for Foo {
647} 647}
648 ", 648 ",
649 r" 649 r"
650use std::io;
651
652pub use std::fmt; 650pub use std::fmt;
651use std::io;
653 652
654impl io for Foo { 653impl io for Foo {
655} 654}
@@ -668,9 +667,8 @@ impl std::io<|> for Foo {
668} 667}
669 ", 668 ",
670 r" 669 r"
671use std::io;
672
673pub(crate) use std::fmt; 670pub(crate) use std::fmt;
671use std::io;
674 672
675impl io for Foo { 673impl io for Foo {
676} 674}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 2e0d191a6..cbac53e71 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -155,6 +155,7 @@ mod handlers {
155 mod remove_unused_param; 155 mod remove_unused_param;
156 mod reorder_fields; 156 mod reorder_fields;
157 mod replace_if_let_with_match; 157 mod replace_if_let_with_match;
158 mod replace_impl_trait_with_generic;
158 mod replace_let_with_if_let; 159 mod replace_let_with_if_let;
159 mod replace_qualified_name_with_use; 160 mod replace_qualified_name_with_use;
160 mod replace_unwrap_with_match; 161 mod replace_unwrap_with_match;
@@ -202,6 +203,7 @@ mod handlers {
202 remove_unused_param::remove_unused_param, 203 remove_unused_param::remove_unused_param,
203 reorder_fields::reorder_fields, 204 reorder_fields::reorder_fields,
204 replace_if_let_with_match::replace_if_let_with_match, 205 replace_if_let_with_match::replace_if_let_with_match,
206 replace_impl_trait_with_generic::replace_impl_trait_with_generic,
205 replace_let_with_if_let::replace_let_with_if_let, 207 replace_let_with_if_let::replace_let_with_if_let,
206 replace_qualified_name_with_use::replace_qualified_name_with_use, 208 replace_qualified_name_with_use::replace_qualified_name_with_use,
207 replace_unwrap_with_match::replace_unwrap_with_match, 209 replace_unwrap_with_match::replace_unwrap_with_match,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 04c8fd1f9..27d15adb0 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -815,6 +815,19 @@ fn handle(action: Action) {
815} 815}
816 816
817#[test] 817#[test]
818fn doctest_replace_impl_trait_with_generic() {
819 check_doc_test(
820 "replace_impl_trait_with_generic",
821 r#####"
822fn foo(bar: <|>impl Bar) {}
823"#####,
824 r#####"
825fn foo<B: Bar>(bar: B) {}
826"#####,
827 )
828}
829
830#[test]
818fn doctest_replace_let_with_if_let() { 831fn doctest_replace_let_with_if_let() {
819 check_doc_test( 832 check_doc_test(
820 "replace_let_with_if_let", 833 "replace_let_with_if_let",
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index daa7b64f7..b0511ceb6 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -16,7 +16,8 @@ use syntax::{
16 16
17use crate::assist_config::SnippetCap; 17use crate::assist_config::SnippetCap;
18 18
19pub(crate) use insert_use::{find_insert_use_container, insert_use_statement}; 19pub use insert_use::MergeBehaviour;
20pub(crate) use insert_use::{insert_use, ImportScope};
20 21
21pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { 22pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
22 extract_trivial_expression(&block) 23 extract_trivial_expression(&block)
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 49096a67c..09f4a2224 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,546 +1,933 @@
1//! Handle syntactic aspects of inserting a new `use`. 1//! Handle syntactic aspects of inserting a new `use`.
2// FIXME: rewrite according to the plan, outlined in 2use std::{
3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553 3 cmp::Ordering,
4 4 iter::{self, successors},
5use std::iter::successors; 5};
6 6
7use either::Either; 7use ast::{
8 edit::{AstNodeEdit, IndentLevel},
9 PathSegmentKind, VisibilityOwner,
10};
8use syntax::{ 11use syntax::{
9 ast::{self, NameOwner, VisibilityOwner}, 12 algo,
10 AstNode, AstToken, Direction, SmolStr, 13 ast::{self, make, AstNode},
11 SyntaxKind::{PATH, PATH_SEGMENT}, 14 InsertPosition, SyntaxElement, SyntaxNode,
12 SyntaxNode, SyntaxToken, T,
13}; 15};
14use text_edit::TextEditBuilder;
15
16use crate::assist_context::AssistContext;
17
18/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
19pub(crate) fn find_insert_use_container(
20 position: &SyntaxNode,
21 ctx: &AssistContext,
22) -> Option<Either<ast::ItemList, ast::SourceFile>> {
23 ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
24 if let Some(module) = ast::Module::cast(n.clone()) {
25 return module.item_list().map(|it| Either::Left(it));
26 }
27 Some(Either::Right(ast::SourceFile::cast(n)?))
28 })
29}
30 16
31/// Creates and inserts a use statement for the given path to import. 17#[derive(Debug)]
32/// The use statement is inserted in the scope most appropriate to the 18pub enum ImportScope {
33/// the cursor position given, additionally merged with the existing use imports. 19 File(ast::SourceFile),
34pub(crate) fn insert_use_statement( 20 Module(ast::ItemList),
35 // Ideally the position of the cursor, used to
36 position: &SyntaxNode,
37 path_to_import: &str,
38 ctx: &AssistContext,
39 builder: &mut TextEditBuilder,
40) {
41 let target = path_to_import.split("::").map(SmolStr::new).collect::<Vec<_>>();
42 let container = find_insert_use_container(position, ctx);
43
44 if let Some(container) = container {
45 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
46 let action = best_action_for_target(syntax, position.clone(), &target);
47 make_assist(&action, &target, builder);
48 }
49} 21}
50 22
51fn collect_path_segments_raw( 23impl ImportScope {
52 segments: &mut Vec<ast::PathSegment>, 24 pub fn from(syntax: SyntaxNode) -> Option<Self> {
53 mut path: ast::Path, 25 if let Some(module) = ast::Module::cast(syntax.clone()) {
54) -> Option<usize> { 26 module.item_list().map(ImportScope::Module)
55 let oldlen = segments.len(); 27 } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) {
56 loop { 28 this.map(ImportScope::File)
57 let mut children = path.syntax().children_with_tokens(); 29 } else {
58 let (first, second, third) = ( 30 ast::ItemList::cast(syntax).map(ImportScope::Module)
59 children.next().map(|n| (n.clone(), n.kind())),
60 children.next().map(|n| (n.clone(), n.kind())),
61 children.next().map(|n| (n.clone(), n.kind())),
62 );
63 match (first, second, third) {
64 (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => {
65 path = ast::Path::cast(subpath.as_node()?.clone())?;
66 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
67 }
68 (Some((segment, PATH_SEGMENT)), _, _) => {
69 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
70 break;
71 }
72 (_, _, _) => return None,
73 } 31 }
74 } 32 }
75 // We need to reverse only the new added segments
76 let only_new_segments = segments.split_at_mut(oldlen).1;
77 only_new_segments.reverse();
78 Some(segments.len() - oldlen)
79}
80 33
81fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { 34 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
82 let mut iter = segments.iter(); 35 pub(crate) fn find_insert_use_container(
83 if let Some(s) = iter.next() { 36 position: &SyntaxNode,
84 buf.push_str(s); 37 ctx: &crate::assist_context::AssistContext,
85 } 38 ) -> Option<Self> {
86 for s in iter { 39 ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from)
87 buf.push_str("::");
88 buf.push_str(s);
89 } 40 }
90}
91
92/// Returns the number of common segments.
93fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize {
94 left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count()
95}
96 41
97fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { 42 pub(crate) fn as_syntax_node(&self) -> &SyntaxNode {
98 if let Some(kb) = b.kind() { 43 match self {
99 match kb { 44 ImportScope::File(file) => file.syntax(),
100 ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), 45 ImportScope::Module(item_list) => item_list.syntax(),
101 ast::PathSegmentKind::SelfKw => a == "self",
102 ast::PathSegmentKind::SuperKw => a == "super",
103 ast::PathSegmentKind::CrateKw => a == "crate",
104 ast::PathSegmentKind::Type { .. } => false, // not allowed in imports
105 } 46 }
106 } else {
107 false
108 } 47 }
109}
110
111fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool {
112 a == b.text()
113}
114 48
115#[derive(Clone, Debug)] 49 fn indent_level(&self) -> IndentLevel {
116enum ImportAction { 50 match self {
117 Nothing, 51 ImportScope::File(file) => file.indent_level(),
118 // Add a brand new use statement. 52 ImportScope::Module(item_list) => item_list.indent_level() + 1,
119 AddNewUse {
120 anchor: Option<SyntaxNode>, // anchor node
121 add_after_anchor: bool,
122 },
123
124 // To split an existing use statement creating a nested import.
125 AddNestedImport {
126 // how may segments matched with the target path
127 common_segments: usize,
128 path_to_split: ast::Path,
129 // the first segment of path_to_split we want to add into the new nested list
130 first_segment_to_split: Option<ast::PathSegment>,
131 // Wether to add 'self' in addition to the target path
132 add_self: bool,
133 },
134 // To add the target path to an existing nested import tree list.
135 AddInTreeList {
136 common_segments: usize,
137 // The UseTreeList where to add the target path
138 tree_list: ast::UseTreeList,
139 add_self: bool,
140 },
141}
142
143impl ImportAction {
144 fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self {
145 ImportAction::AddNewUse { anchor, add_after_anchor }
146 }
147
148 fn add_nested_import(
149 common_segments: usize,
150 path_to_split: ast::Path,
151 first_segment_to_split: Option<ast::PathSegment>,
152 add_self: bool,
153 ) -> Self {
154 ImportAction::AddNestedImport {
155 common_segments,
156 path_to_split,
157 first_segment_to_split,
158 add_self,
159 } 53 }
160 } 54 }
161 55
162 fn add_in_tree_list( 56 fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
163 common_segments: usize, 57 match self {
164 tree_list: ast::UseTreeList, 58 ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
165 add_self: bool, 59 // don't insert the imports before the item list's opening curly brace
166 ) -> Self { 60 ImportScope::Module(item_list) => item_list
167 ImportAction::AddInTreeList { common_segments, tree_list, add_self } 61 .l_curly_token()
168 } 62 .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
169 63 .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)),
170 fn better(left: ImportAction, right: ImportAction) -> ImportAction {
171 if left.is_better(&right) {
172 left
173 } else {
174 right
175 } 64 }
176 } 65 }
177 66
178 fn is_better(&self, other: &ImportAction) -> bool { 67 fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
179 match (self, other) { 68 // check if the scope has inner attributes, we dont want to insert in front of them
180 (ImportAction::Nothing, _) => true, 69 match self
181 (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, 70 .as_syntax_node()
182 ( 71 .children()
183 ImportAction::AddNestedImport { common_segments: n, .. }, 72 // no flat_map here cause we want to short circuit the iterator
184 ImportAction::AddInTreeList { common_segments: m, .. }, 73 .map(ast::Attr::cast)
185 ) 74 .take_while(|attr| {
186 | ( 75 attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false)
187 ImportAction::AddInTreeList { common_segments: n, .. }, 76 })
188 ImportAction::AddNestedImport { common_segments: m, .. }, 77 .last()
189 ) 78 .flatten()
190 | ( 79 {
191 ImportAction::AddInTreeList { common_segments: n, .. }, 80 Some(attr) => {
192 ImportAction::AddInTreeList { common_segments: m, .. }, 81 (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice)
193 ) 82 }
194 | ( 83 None => self.first_insert_pos(),
195 ImportAction::AddNestedImport { common_segments: n, .. },
196 ImportAction::AddNestedImport { common_segments: m, .. },
197 ) => n > m,
198 (ImportAction::AddInTreeList { .. }, _) => true,
199 (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false,
200 (ImportAction::AddNestedImport { .. }, _) => true,
201 (ImportAction::AddNewUse { .. }, _) => false,
202 } 84 }
203 } 85 }
204} 86}
205 87
206// Find out the best ImportAction to import target path against current_use_tree. 88/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
207// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList. 89pub(crate) fn insert_use(
208fn walk_use_tree_for_best_action( 90 scope: &ImportScope,
209 current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments 91 path: ast::Path,
210 current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import 92 merge: Option<MergeBehaviour>,
211 current_use_tree: ast::UseTree, // the use tree we are currently examinating 93) -> SyntaxNode {
212 target: &[SmolStr], // the path we want to import 94 let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
213) -> ImportAction { 95 // merge into existing imports if possible
214 // We save the number of segments in the buffer so we can restore the correct segments 96 if let Some(mb) = merge {
215 // before returning. Recursive call will add segments so we need to delete them. 97 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
216 let prev_len = current_path_segments.len(); 98 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
217 99 let to_delete: SyntaxElement = existing_use.syntax().clone().into();
218 let tree_list = current_use_tree.use_tree_list(); 100 let to_delete = to_delete.clone()..=to_delete;
219 let alias = current_use_tree.rename(); 101 let to_insert = iter::once(merged.syntax().clone().into());
220 102 return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert);
221 let path = match current_use_tree.path() { 103 }
222 Some(path) => path,
223 None => {
224 // If the use item don't have a path, it means it's broken (syntax error)
225 return ImportAction::add_new_use(
226 current_use_tree
227 .syntax()
228 .ancestors()
229 .find_map(ast::Use::cast)
230 .map(|it| it.syntax().clone()),
231 true,
232 );
233 }
234 };
235
236 // This can happen only if current_use_tree is a direct child of a UseItem
237 if let Some(name) = alias.and_then(|it| it.name()) {
238 if compare_path_segment_with_name(&target[0], &name) {
239 return ImportAction::Nothing;
240 } 104 }
241 } 105 }
242 106
243 collect_path_segments_raw(current_path_segments, path.clone()); 107 // either we weren't allowed to merge or there is no import that fits the merge conditions
244 108 // so look for the place we have to insert to
245 // We compare only the new segments added in the line just above. 109 let (insert_position, add_blank) = find_insert_position(scope, path);
246 // The first prev_len segments were already compared in 'parent' recursive calls. 110
247 let left = target.split_at(prev_len).1; 111 let to_insert: Vec<SyntaxElement> = {
248 let right = current_path_segments.split_at(prev_len).1; 112 let mut buf = Vec::new();
249 let common = compare_path_segments(left, &right); 113
250 let mut action = match common { 114 match add_blank {
251 0 => ImportAction::add_new_use( 115 AddBlankLine::Before | AddBlankLine::Around => {
252 // e.g: target is std::fmt and we can have 116 buf.push(make::tokens::single_newline().into())
253 // use foo::bar
254 // We add a brand new use statement
255 current_use_tree
256 .syntax()
257 .ancestors()
258 .find_map(ast::Use::cast)
259 .map(|it| it.syntax().clone()),
260 true,
261 ),
262 common if common == left.len() && left.len() == right.len() => {
263 // e.g: target is std::fmt and we can have
264 // 1- use std::fmt;
265 // 2- use std::fmt::{ ... }
266 if let Some(list) = tree_list {
267 // In case 2 we need to add self to the nested list
268 // unless it's already there
269 let has_self = list.use_trees().map(|it| it.path()).any(|p| {
270 p.and_then(|it| it.segment())
271 .and_then(|it| it.kind())
272 .filter(|k| *k == ast::PathSegmentKind::SelfKw)
273 .is_some()
274 });
275
276 if has_self {
277 ImportAction::Nothing
278 } else {
279 ImportAction::add_in_tree_list(current_path_segments.len(), list, true)
280 }
281 } else {
282 // Case 1
283 ImportAction::Nothing
284 } 117 }
118 AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
119 _ => (),
285 } 120 }
286 common if common != left.len() && left.len() == right.len() => { 121
287 // e.g: target is std::fmt and we have 122 if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
288 // use std::io; 123 buf.push(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into());
289 // We need to split.
290 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
291 ImportAction::add_nested_import(
292 prev_len + common,
293 path,
294 Some(segments_to_split[0].clone()),
295 false,
296 )
297 } 124 }
298 common if common == right.len() && left.len() > right.len() => { 125 buf.push(use_item.syntax().clone().into());
299 // e.g: target is std::fmt and we can have 126
300 // 1- use std; 127 match add_blank {
301 // 2- use std::{ ... }; 128 AddBlankLine::After | AddBlankLine::Around => {
302 129 buf.push(make::tokens::single_newline().into())
303 // fallback action
304 let mut better_action = ImportAction::add_new_use(
305 current_use_tree
306 .syntax()
307 .ancestors()
308 .find_map(ast::Use::cast)
309 .map(|it| it.syntax().clone()),
310 true,
311 );
312 if let Some(list) = tree_list {
313 // Case 2, check recursively if the path is already imported in the nested list
314 for u in list.use_trees() {
315 let child_action = walk_use_tree_for_best_action(
316 current_path_segments,
317 Some(list.clone()),
318 u,
319 target,
320 );
321 if child_action.is_better(&better_action) {
322 better_action = child_action;
323 if let ImportAction::Nothing = better_action {
324 return better_action;
325 }
326 }
327 }
328 } else {
329 // Case 1, split adding self
330 better_action = ImportAction::add_nested_import(prev_len + common, path, None, true)
331 } 130 }
332 better_action 131 AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()),
132 _ => (),
333 } 133 }
334 common if common == left.len() && left.len() < right.len() => {
335 // e.g: target is std::fmt and we can have
336 // use std::fmt::Debug;
337 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
338 ImportAction::add_nested_import(
339 prev_len + common,
340 path,
341 Some(segments_to_split[0].clone()),
342 true,
343 )
344 }
345 common if common < left.len() && common < right.len() => {
346 // e.g: target is std::fmt::nested::Debug
347 // use std::fmt::Display
348 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
349 ImportAction::add_nested_import(
350 prev_len + common,
351 path,
352 Some(segments_to_split[0].clone()),
353 false,
354 )
355 }
356 _ => unreachable!(),
357 };
358 134
359 // If we are inside a UseTreeList adding a use statement become adding to the existing 135 buf
360 // tree list.
361 action = match (current_parent_use_tree_list, action.clone()) {
362 (Some(use_tree_list), ImportAction::AddNewUse { .. }) => {
363 ImportAction::add_in_tree_list(prev_len, use_tree_list, false)
364 }
365 (_, _) => action,
366 }; 136 };
367 137
368 // We remove the segments added 138 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert)
369 current_path_segments.truncate(prev_len);
370 action
371} 139}
372 140
373fn best_action_for_target( 141fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
374 container: SyntaxNode, 142 match (vis0, vis1) {
375 anchor: SyntaxNode, 143 (None, None) => true,
376 target: &[SmolStr], 144 // FIXME: Don't use the string representation to check for equality
377) -> ImportAction { 145 // spaces inside of the node would break this comparison
378 let mut storage = Vec::with_capacity(16); // this should be the only allocation 146 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
379 let best_action = container 147 _ => false,
380 .children() 148 }
381 .filter_map(ast::Use::cast) 149}
382 .filter(|u| u.visibility().is_none())
383 .filter_map(|it| it.use_tree())
384 .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target))
385 .fold(None, |best, a| match best {
386 Some(best) => Some(ImportAction::better(best, a)),
387 None => Some(a),
388 });
389
390 match best_action {
391 Some(action) => action,
392 None => {
393 // We have no action and no UseItem was found in container so we find
394 // another item and we use it as anchor.
395 // If there are no items above, we choose the target path itself as anchor.
396 // todo: we should include even whitespace blocks as anchor candidates
397 let anchor = container.children().next().or_else(|| Some(anchor));
398 150
399 let add_after_anchor = anchor 151pub(crate) fn try_merge_imports(
400 .clone() 152 lhs: &ast::Use,
401 .and_then(ast::Attr::cast) 153 rhs: &ast::Use,
402 .map(|attr| attr.kind() == ast::AttrKind::Inner) 154 merge_behaviour: MergeBehaviour,
403 .unwrap_or(false); 155) -> Option<ast::Use> {
404 ImportAction::add_new_use(anchor, add_after_anchor) 156 // don't merge imports with different visibilities
405 } 157 if !eq_visibility(lhs.visibility(), rhs.visibility()) {
158 return None;
406 } 159 }
160 let lhs_tree = lhs.use_tree()?;
161 let rhs_tree = rhs.use_tree()?;
162 let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behaviour)?;
163 Some(lhs.with_use_tree(merged))
407} 164}
408 165
409fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { 166pub(crate) fn try_merge_trees(
410 match action { 167 lhs: &ast::UseTree,
411 ImportAction::AddNewUse { anchor, add_after_anchor } => { 168 rhs: &ast::UseTree,
412 make_assist_add_new_use(anchor, *add_after_anchor, target, edit) 169 merge: MergeBehaviour,
413 } 170) -> Option<ast::UseTree> {
414 ImportAction::AddInTreeList { common_segments, tree_list, add_self } => { 171 let lhs_path = lhs.path()?;
415 // We know that the fist n segments already exists in the use statement we want 172 let rhs_path = rhs.path()?;
416 // to modify, so we want to add only the last target.len() - n segments. 173
417 let segments_to_add = target.split_at(*common_segments).1; 174 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
418 make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit) 175 let lhs = lhs.split_prefix(&lhs_prefix);
419 } 176 let rhs = rhs.split_prefix(&rhs_prefix);
420 ImportAction::AddNestedImport { 177 recursive_merge(&lhs, &rhs, merge).map(|(merged, _)| merged)
421 common_segments, 178}
422 path_to_split, 179
423 first_segment_to_split, 180/// Recursively "zips" together lhs and rhs.
424 add_self, 181fn recursive_merge(
425 } => { 182 lhs: &ast::UseTree,
426 let segments_to_add = target.split_at(*common_segments).1; 183 rhs: &ast::UseTree,
427 make_assist_add_nested_import( 184 merge: MergeBehaviour,
428 path_to_split, 185) -> Option<(ast::UseTree, bool)> {
429 first_segment_to_split, 186 let mut use_trees = lhs
430 segments_to_add, 187 .use_tree_list()
431 *add_self, 188 .into_iter()
432 edit, 189 .flat_map(|list| list.use_trees())
433 ) 190 // check if any of the use trees are nested, if they are and the behaviour is `last` we are not allowed to merge this
191 // so early exit the iterator by using Option's Intoiterator impl
192 .map(|tree| match merge == MergeBehaviour::Last && tree.use_tree_list().is_some() {
193 true => None,
194 false => Some(tree),
195 })
196 .collect::<Option<Vec<_>>>()?;
197 use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path()));
198 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
199 let rhs_path = rhs_t.path();
200 match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) {
201 Ok(idx) => {
202 let lhs_t = &mut use_trees[idx];
203 let lhs_path = lhs_t.path()?;
204 let rhs_path = rhs_path?;
205 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
206 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
207 let tree_is_self = |tree: ast::UseTree| {
208 tree.path().as_ref().map(path_is_self).unwrap_or(false)
209 };
210 // check if only one of the two trees has a tree list, and whether that then contains `self` or not.
211 // If this is the case we can skip this iteration since the path without the list is already included in the other one via `self`
212 let tree_contains_self = |tree: &ast::UseTree| {
213 tree.use_tree_list()
214 .map(|tree_list| tree_list.use_trees().any(tree_is_self))
215 .unwrap_or(false)
216 };
217 match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) {
218 (true, false) => continue,
219 (false, true) => {
220 *lhs_t = rhs_t;
221 continue;
222 }
223 _ => (),
224 }
225
226 // glob imports arent part of the use-tree lists so we need to special handle them here as well
227 // this special handling is only required for when we merge a module import into a glob import of said module
228 // see the `merge_self_glob` or `merge_mod_into_glob` tests
229 if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
230 *lhs_t = make::use_tree(
231 make::path_unqualified(make::path_segment_self()),
232 None,
233 None,
234 false,
235 );
236 use_trees.insert(idx, make::glob_use_tree());
237 continue;
238 }
239 }
240 let lhs = lhs_t.split_prefix(&lhs_prefix);
241 let rhs = rhs_t.split_prefix(&rhs_prefix);
242 let this_has_children = use_trees.len() > 0;
243 match recursive_merge(&lhs, &rhs, merge) {
244 Some((_, has_multiple_children))
245 if merge == MergeBehaviour::Last
246 && this_has_children
247 && has_multiple_children =>
248 {
249 return None
250 }
251 Some((use_tree, _)) => use_trees[idx] = use_tree,
252 None => use_trees.insert(idx, rhs_t),
253 }
254 }
255 Err(_)
256 if merge == MergeBehaviour::Last
257 && use_trees.len() > 0
258 && rhs_t.use_tree_list().is_some() =>
259 {
260 return None
261 }
262 Err(idx) => {
263 use_trees.insert(idx, rhs_t);
264 }
434 } 265 }
435 _ => {}
436 } 266 }
267 let has_multiple_children = use_trees.len() > 1;
268 Some((lhs.with_use_tree_list(make::use_tree_list(use_trees)), has_multiple_children))
437} 269}
438 270
439fn make_assist_add_new_use( 271/// Traverses both paths until they differ, returning the common prefix of both.
440 anchor: &Option<SyntaxNode>, 272fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
441 after: bool, 273 let mut res = None;
442 target: &[SmolStr], 274 let mut lhs_curr = first_path(&lhs);
443 edit: &mut TextEditBuilder, 275 let mut rhs_curr = first_path(&rhs);
444) { 276 loop {
445 if let Some(anchor) = anchor { 277 match (lhs_curr.segment(), rhs_curr.segment()) {
446 let indent = leading_indent(anchor); 278 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
447 let mut buf = String::new(); 279 _ => break res,
448 if after {
449 buf.push_str("\n");
450 if let Some(spaces) = &indent {
451 buf.push_str(spaces);
452 }
453 } 280 }
454 buf.push_str("use "); 281 res = Some((lhs_curr.clone(), rhs_curr.clone()));
455 fmt_segments_raw(target, &mut buf); 282
456 buf.push_str(";"); 283 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
457 if !after { 284 Some((lhs, rhs)) => {
458 buf.push_str("\n\n"); 285 lhs_curr = lhs;
459 if let Some(spaces) = &indent { 286 rhs_curr = rhs;
460 buf.push_str(&spaces);
461 } 287 }
288 _ => break res,
462 } 289 }
463 let position = if after { anchor.text_range().end() } else { anchor.text_range().start() };
464 edit.insert(position, buf);
465 } 290 }
466} 291}
467 292
468fn make_assist_add_in_tree_list( 293fn path_is_self(path: &ast::Path) -> bool {
469 tree_list: &ast::UseTreeList, 294 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
470 target: &[SmolStr], 295}
471 add_self: bool, 296
472 edit: &mut TextEditBuilder, 297#[inline]
473) { 298fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
474 let last = tree_list.use_trees().last(); 299 first_path(path).segment()
475 if let Some(last) = last { 300}
476 let mut buf = String::new(); 301
477 let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]); 302fn first_path(path: &ast::Path) -> ast::Path {
478 let offset = if let Some(comma) = comma { 303 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
479 comma.text_range().end() 304}
480 } else { 305
481 buf.push_str(","); 306fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
482 last.syntax().text_range().end() 307 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
483 }; 308 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
484 if add_self { 309}
485 buf.push_str(" self") 310
486 } else { 311/// Orders paths in the following way:
487 buf.push_str(" "); 312/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
313// FIXME: rustfmt sort lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
314// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
315// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
316fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering {
317 match (path_is_self(a), path_is_self(b)) {
318 (true, true) => Ordering::Equal,
319 (true, false) => Ordering::Less,
320 (false, true) => Ordering::Greater,
321 (false, false) => {
322 let a = segment_iter(a);
323 let b = segment_iter(b);
324 // cmp_by would be useful for us here but that is currently unstable
325 // cmp doesnt work due the lifetimes on text's return type
326 a.zip(b)
327 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
328 .find_map(|(a, b)| match a.text().cmp(b.text()) {
329 ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord),
330 Ordering::Equal => None,
331 })
332 .unwrap_or(Ordering::Equal)
488 } 333 }
489 fmt_segments_raw(target, &mut buf);
490 edit.insert(offset, buf);
491 } else {
492 } 334 }
493} 335}
494 336
495fn make_assist_add_nested_import( 337fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
496 path: &ast::Path, 338 match (a, b) {
497 first_segment_to_split: &Option<ast::PathSegment>, 339 (None, None) => Ordering::Equal,
498 target: &[SmolStr], 340 (None, Some(_)) => Ordering::Less,
499 add_self: bool, 341 (Some(_), None) => Ordering::Greater,
500 edit: &mut TextEditBuilder, 342 (Some(a), Some(b)) => path_cmp(&a, &b),
501) { 343 }
502 let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); 344}
503 if let Some(use_tree) = use_tree { 345
504 let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split 346/// What type of merges are allowed.
505 { 347#[derive(Copy, Clone, Debug, PartialEq, Eq)]
506 (first_segment_to_split.syntax().text_range().start(), false) 348pub enum MergeBehaviour {
507 } else { 349 /// Merge everything together creating deeply nested imports.
508 (use_tree.syntax().text_range().end(), true) 350 Full,
351 /// Only merge the last import level, doesn't allow import nesting.
352 Last,
353}
354
355#[derive(Eq, PartialEq, PartialOrd, Ord)]
356enum ImportGroup {
357 // the order here defines the order of new group inserts
358 Std,
359 ExternCrate,
360 ThisCrate,
361 ThisModule,
362 SuperModule,
363}
364
365impl ImportGroup {
366 fn new(path: &ast::Path) -> ImportGroup {
367 let default = ImportGroup::ExternCrate;
368
369 let first_segment = match first_segment(path) {
370 Some(it) => it,
371 None => return default,
509 }; 372 };
510 let end = use_tree.syntax().text_range().end();
511 373
512 let mut buf = String::new(); 374 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
513 if add_colon_colon { 375 match kind {
514 buf.push_str("::"); 376 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
377 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
378 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
379 PathSegmentKind::Name(name) => match name.text().as_str() {
380 "std" => ImportGroup::Std,
381 "core" => ImportGroup::Std,
382 // FIXME: can be ThisModule as well
383 _ => ImportGroup::ExternCrate,
384 },
385 PathSegmentKind::Type { .. } => unreachable!(),
515 } 386 }
516 buf.push_str("{");
517 if add_self {
518 buf.push_str("self, ");
519 }
520 fmt_segments_raw(target, &mut buf);
521 if !target.is_empty() {
522 buf.push_str(", ");
523 }
524 edit.insert(start, buf);
525 edit.insert(end, "}".to_string());
526 } 387 }
527} 388}
528 389
529/// If the node is on the beginning of the line, calculate indent. 390#[derive(PartialEq, Eq)]
530fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> { 391enum AddBlankLine {
531 for token in prev_tokens(node.first_token()?) { 392 Before,
532 if let Some(ws) = ast::Whitespace::cast(token.clone()) { 393 BeforeTwice,
533 let ws_text = ws.text(); 394 Around,
534 if let Some(pos) = ws_text.rfind('\n') { 395 After,
535 return Some(ws_text[pos + 1..].into()); 396 AfterTwice,
397}
398
399fn find_insert_position(
400 scope: &ImportScope,
401 insert_path: ast::Path,
402) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
403 let group = ImportGroup::new(&insert_path);
404 let path_node_iter = scope
405 .as_syntax_node()
406 .children()
407 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
408 .flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node)));
409 // Iterator that discards anything thats not in the required grouping
410 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
411 let group_iter = path_node_iter
412 .clone()
413 .skip_while(|(path, _)| ImportGroup::new(path) != group)
414 .take_while(|(path, _)| ImportGroup::new(path) == group);
415
416 let segments = segment_iter(&insert_path);
417 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
418 let mut last = None;
419 // find the element that would come directly after our new import
420 let post_insert =
421 group_iter.inspect(|(_, node)| last = Some(node.clone())).find(|(path, _)| {
422 let check_segments = segment_iter(&path);
423 segments
424 .clone()
425 .zip(check_segments)
426 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
427 .all(|(l, r)| l.text() <= r.text())
428 });
429 match post_insert {
430 // insert our import before that element
431 Some((_, node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
432 // there is no element after our new import, so append it to the end of the group
433 None => match last {
434 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
435 // the group we were looking for actually doesnt exist, so insert
436 None => {
437 // similar concept here to the `last` from above
438 let mut last = None;
439 // find the group that comes after where we want to insert
440 let post_group = path_node_iter
441 .inspect(|(_, node)| last = Some(node.clone()))
442 .find(|(p, _)| ImportGroup::new(p) > group);
443 match post_group {
444 Some((_, node)) => {
445 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
446 }
447 // there is no such group, so append after the last one
448 None => match last {
449 Some(node) => {
450 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
451 }
452 // there are no imports in this file at all
453 None => scope.insert_pos_after_inner_attribute(),
454 },
455 }
536 } 456 }
537 } 457 },
538 if token.text().contains('\n') {
539 break;
540 }
541 } 458 }
542 return None; 459}
543 fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { 460
544 successors(token.prev_token(), |token| token.prev_token()) 461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 use test_utils::assert_eq_text;
466
467 #[test]
468 fn insert_start() {
469 check_none(
470 "std::bar::AA",
471 r"
472use std::bar::B;
473use std::bar::D;
474use std::bar::F;
475use std::bar::G;",
476 r"
477use std::bar::AA;
478use std::bar::B;
479use std::bar::D;
480use std::bar::F;
481use std::bar::G;",
482 )
483 }
484
485 #[test]
486 fn insert_middle() {
487 check_none(
488 "std::bar::EE",
489 r"
490use std::bar::A;
491use std::bar::D;
492use std::bar::F;
493use std::bar::G;",
494 r"
495use std::bar::A;
496use std::bar::D;
497use std::bar::EE;
498use std::bar::F;
499use std::bar::G;",
500 )
501 }
502
503 #[test]
504 fn insert_end() {
505 check_none(
506 "std::bar::ZZ",
507 r"
508use std::bar::A;
509use std::bar::D;
510use std::bar::F;
511use std::bar::G;",
512 r"
513use std::bar::A;
514use std::bar::D;
515use std::bar::F;
516use std::bar::G;
517use std::bar::ZZ;",
518 )
519 }
520
521 #[test]
522 fn insert_middle_nested() {
523 check_none(
524 "std::bar::EE",
525 r"
526use std::bar::A;
527use std::bar::{D, Z}; // example of weird imports due to user
528use std::bar::F;
529use std::bar::G;",
530 r"
531use std::bar::A;
532use std::bar::EE;
533use std::bar::{D, Z}; // example of weird imports due to user
534use std::bar::F;
535use std::bar::G;",
536 )
537 }
538
539 #[test]
540 fn insert_middle_groups() {
541 check_none(
542 "foo::bar::GG",
543 r"
544use std::bar::A;
545use std::bar::D;
546
547use foo::bar::F;
548use foo::bar::H;",
549 r"
550use std::bar::A;
551use std::bar::D;
552
553use foo::bar::F;
554use foo::bar::GG;
555use foo::bar::H;",
556 )
557 }
558
559 #[test]
560 fn insert_first_matching_group() {
561 check_none(
562 "foo::bar::GG",
563 r"
564use foo::bar::A;
565use foo::bar::D;
566
567use std;
568
569use foo::bar::F;
570use foo::bar::H;",
571 r"
572use foo::bar::A;
573use foo::bar::D;
574use foo::bar::GG;
575
576use std;
577
578use foo::bar::F;
579use foo::bar::H;",
580 )
581 }
582
583 #[test]
584 fn insert_missing_group_std() {
585 check_none(
586 "std::fmt",
587 r"
588use foo::bar::A;
589use foo::bar::D;",
590 r"
591use std::fmt;
592
593use foo::bar::A;
594use foo::bar::D;",
595 )
596 }
597
598 #[test]
599 fn insert_missing_group_self() {
600 check_none(
601 "self::fmt",
602 r"
603use foo::bar::A;
604use foo::bar::D;",
605 r"
606use foo::bar::A;
607use foo::bar::D;
608
609use self::fmt;",
610 )
611 }
612
613 #[test]
614 fn insert_no_imports() {
615 check_full(
616 "foo::bar",
617 "fn main() {}",
618 r"use foo::bar;
619
620fn main() {}",
621 )
622 }
623
624 #[test]
625 fn insert_empty_file() {
626 // empty files will get two trailing newlines
627 // this is due to the test case insert_no_imports above
628 check_full(
629 "foo::bar",
630 "",
631 r"use foo::bar;
632
633",
634 )
635 }
636
637 #[test]
638 fn insert_after_inner_attr() {
639 check_full(
640 "foo::bar",
641 r"#![allow(unused_imports)]",
642 r"#![allow(unused_imports)]
643
644use foo::bar;",
645 )
646 }
647
648 #[test]
649 fn insert_after_inner_attr2() {
650 check_full(
651 "foo::bar",
652 r"#![allow(unused_imports)]
653
654fn main() {}",
655 r"#![allow(unused_imports)]
656
657use foo::bar;
658
659fn main() {}",
660 )
661 }
662
663 #[test]
664 fn merge_groups() {
665 check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};")
666 }
667
668 #[test]
669 fn merge_groups_last() {
670 check_last(
671 "std::io",
672 r"use std::fmt::{Result, Display};",
673 r"use std::fmt::{Result, Display};
674use std::io;",
675 )
676 }
677
678 #[test]
679 fn merge_groups_full() {
680 check_full(
681 "std::io",
682 r"use std::fmt::{Result, Display};",
683 r"use std::{fmt::{Result, Display}, io};",
684 )
685 }
686
687 #[test]
688 fn merge_groups_long_full() {
689 check_full(
690 "std::foo::bar::Baz",
691 r"use std::foo::bar::Qux;",
692 r"use std::foo::bar::{Baz, Qux};",
693 )
694 }
695
696 #[test]
697 fn merge_groups_long_last() {
698 check_last(
699 "std::foo::bar::Baz",
700 r"use std::foo::bar::Qux;",
701 r"use std::foo::bar::{Baz, Qux};",
702 )
703 }
704
705 #[test]
706 fn merge_groups_long_full_list() {
707 check_full(
708 "std::foo::bar::Baz",
709 r"use std::foo::bar::{Qux, Quux};",
710 r"use std::foo::bar::{Baz, Quux, Qux};",
711 )
712 }
713
714 #[test]
715 fn merge_groups_long_last_list() {
716 check_last(
717 "std::foo::bar::Baz",
718 r"use std::foo::bar::{Qux, Quux};",
719 r"use std::foo::bar::{Baz, Quux, Qux};",
720 )
721 }
722
723 #[test]
724 fn merge_groups_long_full_nested() {
725 check_full(
726 "std::foo::bar::Baz",
727 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
728 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};",
729 )
730 }
731
732 #[test]
733 fn merge_groups_long_last_nested() {
734 check_last(
735 "std::foo::bar::Baz",
736 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
737 r"use std::foo::bar::Baz;
738use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
739 )
740 }
741
742 #[test]
743 fn merge_groups_full_nested_deep() {
744 check_full(
745 "std::foo::bar::quux::Baz",
746 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
747 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
748 )
749 }
750
751 #[test]
752 fn merge_groups_skip_pub() {
753 check_full(
754 "std::io",
755 r"pub use std::fmt::{Result, Display};",
756 r"pub use std::fmt::{Result, Display};
757use std::io;",
758 )
759 }
760
761 #[test]
762 fn merge_groups_skip_pub_crate() {
763 check_full(
764 "std::io",
765 r"pub(crate) use std::fmt::{Result, Display};",
766 r"pub(crate) use std::fmt::{Result, Display};
767use std::io;",
768 )
769 }
770
771 #[test]
772 #[ignore] // FIXME: Support this
773 fn split_out_merge() {
774 check_last(
775 "std::fmt::Result",
776 r"use std::{fmt, io};",
777 r"use std::fmt::{self, Result};
778use std::io;",
779 )
780 }
781
782 #[test]
783 fn merge_into_module_import() {
784 check_full(
785 "std::fmt::Result",
786 r"use std::{fmt, io};",
787 r"use std::{fmt::{self, Result}, io};",
788 )
789 }
790
791 #[test]
792 fn merge_groups_self() {
793 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
794 }
795
796 #[test]
797 fn merge_mod_into_glob() {
798 check_full(
799 "token::TokenKind",
800 r"use token::TokenKind::*;",
801 r"use token::TokenKind::{*, self};",
802 )
803 // FIXME: have it emit `use token::TokenKind::{self, *}`?
804 }
805
806 #[test]
807 fn merge_self_glob() {
808 check_full("self", r"use self::*;", r"use self::{*, self};")
809 // FIXME: have it emit `use {self, *}`?
810 }
811
812 #[test]
813 #[ignore] // FIXME: Support this
814 fn merge_partial_path() {
815 check_full(
816 "ast::Foo",
817 r"use syntax::{ast, algo};",
818 r"use syntax::{ast::{self, Foo}, algo};",
819 )
820 }
821
822 #[test]
823 fn merge_glob_nested() {
824 check_full(
825 "foo::bar::quux::Fez",
826 r"use foo::bar::{Baz, quux::*};",
827 r"use foo::bar::{Baz, quux::{self::*, Fez}};",
828 )
829 }
830
831 #[test]
832 fn merge_last_too_long() {
833 check_last("foo::bar", r"use foo::bar::baz::Qux;", r"use foo::bar::{self, baz::Qux};");
834 }
835
836 #[test]
837 fn insert_short_before_long() {
838 check_none(
839 "foo::bar",
840 r"use foo::bar::baz::Qux;",
841 r"use foo::bar;
842use foo::bar::baz::Qux;",
843 );
844 }
845
846 #[test]
847 fn merge_last_fail() {
848 check_merge_only_fail(
849 r"use foo::bar::{baz::{Qux, Fez}};",
850 r"use foo::bar::{baaz::{Quux, Feez}};",
851 MergeBehaviour::Last,
852 );
853 }
854
855 #[test]
856 fn merge_last_fail1() {
857 check_merge_only_fail(
858 r"use foo::bar::{baz::{Qux, Fez}};",
859 r"use foo::bar::baaz::{Quux, Feez};",
860 MergeBehaviour::Last,
861 );
862 }
863
864 #[test]
865 fn merge_last_fail2() {
866 check_merge_only_fail(
867 r"use foo::bar::baz::{Qux, Fez};",
868 r"use foo::bar::{baaz::{Quux, Feez}};",
869 MergeBehaviour::Last,
870 );
871 }
872
873 #[test]
874 fn merge_last_fail3() {
875 check_merge_only_fail(
876 r"use foo::bar::baz::{Qux, Fez};",
877 r"use foo::bar::baaz::{Quux, Feez};",
878 MergeBehaviour::Last,
879 );
880 }
881
882 fn check(
883 path: &str,
884 ra_fixture_before: &str,
885 ra_fixture_after: &str,
886 mb: Option<MergeBehaviour>,
887 ) {
888 let file = super::ImportScope::from(
889 ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(),
890 )
891 .unwrap();
892 let path = ast::SourceFile::parse(&format!("use {};", path))
893 .tree()
894 .syntax()
895 .descendants()
896 .find_map(ast::Path::cast)
897 .unwrap();
898
899 let result = insert_use(&file, path, mb).to_string();
900 assert_eq_text!(&result, ra_fixture_after);
901 }
902
903 fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
904 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full))
905 }
906
907 fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
908 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last))
909 }
910
911 fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
912 check(path, ra_fixture_before, ra_fixture_after, None)
913 }
914
915 fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) {
916 let use0 = ast::SourceFile::parse(ra_fixture0)
917 .tree()
918 .syntax()
919 .descendants()
920 .find_map(ast::Use::cast)
921 .unwrap();
922
923 let use1 = ast::SourceFile::parse(ra_fixture1)
924 .tree()
925 .syntax()
926 .descendants()
927 .find_map(ast::Use::cast)
928 .unwrap();
929
930 let result = try_merge_imports(&use0, &use1, mb);
931 assert_eq!(result.map(|u| u.to_string()), None);
545 } 932 }
546} 933}