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.rs3
-rw-r--r--crates/assists/src/ast_transform.rs3
-rw-r--r--crates/assists/src/handlers/auto_import.rs5
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs16
-rw-r--r--crates/assists/src/handlers/fill_match_arms.rs9
-rw-r--r--crates/assists/src/handlers/generate_from_impl_for_enum.rs3
-rw-r--r--crates/assists/src/handlers/ignore_test.rs83
-rw-r--r--crates/assists/src/handlers/infer_function_return_type.rs24
-rw-r--r--crates/assists/src/handlers/merge_imports.rs6
-rw-r--r--crates/assists/src/handlers/qualify_path.rs2
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs71
-rw-r--r--crates/assists/src/handlers/replace_derive_with_manual_impl.rs4
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs6
-rw-r--r--crates/assists/src/utils.rs199
-rw-r--r--crates/assists/src/utils/insert_use.rs1203
15 files changed, 185 insertions, 1452 deletions
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs
index b24527ec4..786224cfa 100644
--- a/crates/assists/src/assist_config.rs
+++ b/crates/assists/src/assist_config.rs
@@ -5,8 +5,9 @@
5//! assists if we are allowed to. 5//! assists if we are allowed to.
6 6
7use hir::PrefixKind; 7use hir::PrefixKind;
8use ide_db::helpers::insert_use::MergeBehaviour;
8 9
9use crate::{utils::MergeBehaviour, AssistKind}; 10use crate::AssistKind;
10 11
11#[derive(Clone, Debug, PartialEq, Eq)] 12#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct AssistConfig { 13pub struct AssistConfig {
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
index ac72f3f02..66e4634b1 100644
--- a/crates/assists/src/ast_transform.rs
+++ b/crates/assists/src/ast_transform.rs
@@ -1,5 +1,6 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined. 1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use hir::{HirDisplay, PathResolution, SemanticsScope}; 2use hir::{HirDisplay, PathResolution, SemanticsScope};
3use ide_db::helpers::mod_path_to_ast;
3use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
4use syntax::{ 5use syntax::{
5 algo::SyntaxRewriter, 6 algo::SyntaxRewriter,
@@ -7,8 +8,6 @@ use syntax::{
7 SyntaxNode, 8 SyntaxNode,
8}; 9};
9 10
10use crate::utils::mod_path_to_ast;
11
12pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { 11pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
13 SyntaxRewriter::from_fn(|element| match element { 12 SyntaxRewriter::from_fn(|element| match element {
14 syntax::SyntaxElement::Node(n) => { 13 syntax::SyntaxElement::Node(n) => {
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index d665837a2..bd5bba646 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -1,8 +1,11 @@
1use ide_db::helpers::{
2 insert_use::{insert_use, ImportScope},
3 mod_path_to_ast,
4};
1use syntax::ast; 5use syntax::ast;
2 6
3use crate::{ 7use crate::{
4 utils::import_assets::{ImportAssets, ImportCandidate}, 8 utils::import_assets::{ImportAssets, ImportCandidate},
5 utils::{insert_use, mod_path_to_ast, ImportScope},
6 AssistContext, AssistId, AssistKind, Assists, GroupLabel, 9 AssistContext, AssistId, AssistKind, Assists, GroupLabel,
7}; 10};
8 11
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 cac77c49b..d85767b4e 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -2,6 +2,10 @@ use std::iter;
2 2
3use either::Either; 3use either::Either;
4use hir::{AsName, EnumVariant, Module, ModuleDef, Name}; 4use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
5use ide_db::helpers::{
6 insert_use::{insert_use, ImportScope},
7 mod_path_to_ast,
8};
5use ide_db::{defs::Definition, search::Reference, RootDatabase}; 9use ide_db::{defs::Definition, search::Reference, RootDatabase};
6use rustc_hash::{FxHashMap, FxHashSet}; 10use rustc_hash::{FxHashMap, FxHashSet};
7use syntax::{ 11use syntax::{
@@ -10,10 +14,7 @@ use syntax::{
10 SourceFile, SyntaxElement, SyntaxNode, T, 14 SourceFile, SyntaxElement, SyntaxNode, T,
11}; 15};
12 16
13use crate::{ 17use crate::{AssistContext, AssistId, AssistKind, Assists};
14 utils::{insert_use, mod_path_to_ast, ImportScope},
15 AssistContext, AssistId, AssistKind, Assists,
16};
17 18
18// Assist: extract_struct_from_enum_variant 19// Assist: extract_struct_from_enum_variant
19// 20//
@@ -236,10 +237,9 @@ fn update_reference(
236 237
237#[cfg(test)] 238#[cfg(test)]
238mod tests { 239mod tests {
239 use crate::{ 240 use ide_db::helpers::FamousDefs;
240 tests::{check_assist, check_assist_not_applicable}, 241
241 utils::FamousDefs, 242 use crate::tests::{check_assist, check_assist_not_applicable};
242 };
243 243
244 use super::*; 244 use super::*;
245 245
diff --git a/crates/assists/src/handlers/fill_match_arms.rs b/crates/assists/src/handlers/fill_match_arms.rs
index eda45f5b3..ef12ef0cf 100644
--- a/crates/assists/src/handlers/fill_match_arms.rs
+++ b/crates/assists/src/handlers/fill_match_arms.rs
@@ -1,13 +1,14 @@
1use std::iter; 1use std::iter;
2 2
3use hir::{Adt, HasSource, ModuleDef, Semantics}; 3use hir::{Adt, HasSource, ModuleDef, Semantics};
4use ide_db::helpers::{mod_path_to_ast, FamousDefs};
4use ide_db::RootDatabase; 5use ide_db::RootDatabase;
5use itertools::Itertools; 6use itertools::Itertools;
6use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat}; 7use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
7use test_utils::mark; 8use test_utils::mark;
8 9
9use crate::{ 10use crate::{
10 utils::{mod_path_to_ast, render_snippet, Cursor, FamousDefs}, 11 utils::{render_snippet, Cursor},
11 AssistContext, AssistId, AssistKind, Assists, 12 AssistContext, AssistId, AssistKind, Assists,
12}; 13};
13 14
@@ -212,12 +213,10 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O
212 213
213#[cfg(test)] 214#[cfg(test)]
214mod tests { 215mod tests {
216 use ide_db::helpers::FamousDefs;
215 use test_utils::mark; 217 use test_utils::mark;
216 218
217 use crate::{ 219 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
218 tests::{check_assist, check_assist_not_applicable, check_assist_target},
219 utils::FamousDefs,
220 };
221 220
222 use super::fill_match_arms; 221 use super::fill_match_arms;
223 222
diff --git a/crates/assists/src/handlers/generate_from_impl_for_enum.rs b/crates/assists/src/handlers/generate_from_impl_for_enum.rs
index 674e5a175..3c374e5d9 100644
--- a/crates/assists/src/handlers/generate_from_impl_for_enum.rs
+++ b/crates/assists/src/handlers/generate_from_impl_for_enum.rs
@@ -1,8 +1,9 @@
1use ide_db::helpers::FamousDefs;
1use ide_db::RootDatabase; 2use ide_db::RootDatabase;
2use syntax::ast::{self, AstNode, NameOwner}; 3use syntax::ast::{self, AstNode, NameOwner};
3use test_utils::mark; 4use test_utils::mark;
4 5
5use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
6 7
7// Assist: generate_from_impl_for_enum 8// Assist: generate_from_impl_for_enum
8// 9//
diff --git a/crates/assists/src/handlers/ignore_test.rs b/crates/assists/src/handlers/ignore_test.rs
index d2339184f..5096a0005 100644
--- a/crates/assists/src/handlers/ignore_test.rs
+++ b/crates/assists/src/handlers/ignore_test.rs
@@ -1,4 +1,7 @@
1use syntax::{ast, AstNode}; 1use syntax::{
2 ast::{self, AttrsOwner},
3 AstNode, AstToken,
4};
2 5
3use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind, Assists}; 6use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind, Assists};
4 7
@@ -25,10 +28,76 @@ pub(crate) fn ignore_test(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
25 let func = attr.syntax().parent().and_then(ast::Fn::cast)?; 28 let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
26 let attr = test_related_attribute(&func)?; 29 let attr = test_related_attribute(&func)?;
27 30
28 acc.add( 31 match has_ignore_attribute(&func) {
29 AssistId("ignore_test", AssistKind::None), 32 None => acc.add(
30 "Ignore this test", 33 AssistId("ignore_test", AssistKind::None),
31 attr.syntax().text_range(), 34 "Ignore this test",
32 |builder| builder.insert(attr.syntax().text_range().end(), &format!("\n#[ignore]")), 35 attr.syntax().text_range(),
33 ) 36 |builder| builder.insert(attr.syntax().text_range().end(), &format!("\n#[ignore]")),
37 ),
38 Some(ignore_attr) => acc.add(
39 AssistId("unignore_test", AssistKind::None),
40 "Re-enable this test",
41 ignore_attr.syntax().text_range(),
42 |builder| {
43 builder.delete(ignore_attr.syntax().text_range());
44 let whitespace = ignore_attr
45 .syntax()
46 .next_sibling_or_token()
47 .and_then(|x| x.into_token())
48 .and_then(ast::Whitespace::cast);
49 if let Some(whitespace) = whitespace {
50 builder.delete(whitespace.syntax().text_range());
51 }
52 },
53 ),
54 }
55}
56
57fn has_ignore_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
58 fn_def.attrs().find_map(|attr| {
59 if attr.path()?.syntax().text() == "ignore" {
60 Some(attr)
61 } else {
62 None
63 }
64 })
65}
66
67#[cfg(test)]
68mod tests {
69 use super::ignore_test;
70 use crate::tests::check_assist;
71
72 #[test]
73 fn test_base_case() {
74 check_assist(
75 ignore_test,
76 r#"
77 #[test<|>]
78 fn test() {}
79 "#,
80 r#"
81 #[test]
82 #[ignore]
83 fn test() {}
84 "#,
85 )
86 }
87
88 #[test]
89 fn test_unignore() {
90 check_assist(
91 ignore_test,
92 r#"
93 #[test<|>]
94 #[ignore]
95 fn test() {}
96 "#,
97 r#"
98 #[test]
99 fn test() {}
100 "#,
101 )
102 }
34} 103}
diff --git a/crates/assists/src/handlers/infer_function_return_type.rs b/crates/assists/src/handlers/infer_function_return_type.rs
index 520d07ae0..aa584eb03 100644
--- a/crates/assists/src/handlers/infer_function_return_type.rs
+++ b/crates/assists/src/handlers/infer_function_return_type.rs
@@ -17,7 +17,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
17// fn foo() -> i32 { 42i32 } 17// fn foo() -> i32 { 42i32 }
18// ``` 18// ```
19pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 19pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
20 let (tail_expr, builder_edit_pos, wrap_expr) = extract_tail(ctx)?; 20 let (fn_type, tail_expr, builder_edit_pos) = extract_tail(ctx)?;
21 let module = ctx.sema.scope(tail_expr.syntax()).module()?; 21 let module = ctx.sema.scope(tail_expr.syntax()).module()?;
22 let ty = ctx.sema.type_of_expr(&tail_expr)?; 22 let ty = ctx.sema.type_of_expr(&tail_expr)?;
23 if ty.is_unit() { 23 if ty.is_unit() {
@@ -27,7 +27,10 @@ pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext)
27 27
28 acc.add( 28 acc.add(
29 AssistId("infer_function_return_type", AssistKind::RefactorRewrite), 29 AssistId("infer_function_return_type", AssistKind::RefactorRewrite),
30 "Add this function's return type", 30 match fn_type {
31 FnType::Function => "Add this function's return type",
32 FnType::Closure { .. } => "Add this closure's return type",
33 },
31 tail_expr.syntax().text_range(), 34 tail_expr.syntax().text_range(),
32 |builder| { 35 |builder| {
33 match builder_edit_pos { 36 match builder_edit_pos {
@@ -38,7 +41,7 @@ pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext)
38 builder.replace(text_range, &format!("-> {}", ty)) 41 builder.replace(text_range, &format!("-> {}", ty))
39 } 42 }
40 } 43 }
41 if wrap_expr { 44 if let FnType::Closure { wrap_expr: true } = fn_type {
42 mark::hit!(wrap_closure_non_block_expr); 45 mark::hit!(wrap_closure_non_block_expr);
43 // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block 46 // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
44 builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr)); 47 builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
@@ -72,8 +75,13 @@ fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Optio
72 } 75 }
73} 76}
74 77
75fn extract_tail(ctx: &AssistContext) -> Option<(ast::Expr, InsertOrReplace, bool)> { 78enum FnType {
76 let (tail_expr, return_type_range, action, wrap_expr) = 79 Function,
80 Closure { wrap_expr: bool },
81}
82
83fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrReplace)> {
84 let (fn_type, tail_expr, return_type_range, action) =
77 if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() { 85 if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
78 let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end(); 86 let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
79 let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?; 87 let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
@@ -86,7 +94,7 @@ fn extract_tail(ctx: &AssistContext) -> Option<(ast::Expr, InsertOrReplace, bool
86 }; 94 };
87 95
88 let ret_range = TextRange::new(rpipe_pos, body_start); 96 let ret_range = TextRange::new(rpipe_pos, body_start);
89 (tail_expr, ret_range, action, wrap_expr) 97 (FnType::Closure { wrap_expr }, tail_expr, ret_range, action)
90 } else { 98 } else {
91 let func = ctx.find_node_at_offset::<ast::Fn>()?; 99 let func = ctx.find_node_at_offset::<ast::Fn>()?;
92 let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end(); 100 let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
@@ -97,7 +105,7 @@ fn extract_tail(ctx: &AssistContext) -> Option<(ast::Expr, InsertOrReplace, bool
97 105
98 let ret_range_end = body.l_curly_token()?.text_range().start(); 106 let ret_range_end = body.l_curly_token()?.text_range().start();
99 let ret_range = TextRange::new(rparen_pos, ret_range_end); 107 let ret_range = TextRange::new(rparen_pos, ret_range_end);
100 (tail_expr, ret_range, action, false) 108 (FnType::Function, tail_expr, ret_range, action)
101 }; 109 };
102 let frange = ctx.frange.range; 110 let frange = ctx.frange.range;
103 if return_type_range.contains_range(frange) { 111 if return_type_range.contains_range(frange) {
@@ -109,7 +117,7 @@ fn extract_tail(ctx: &AssistContext) -> Option<(ast::Expr, InsertOrReplace, bool
109 } else { 117 } else {
110 return None; 118 return None;
111 } 119 }
112 Some((tail_expr, action, wrap_expr)) 120 Some((fn_type, tail_expr, action))
113} 121}
114 122
115#[cfg(test)] 123#[cfg(test)]
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs
index fd9c9e03c..b7e853994 100644
--- a/crates/assists/src/handlers/merge_imports.rs
+++ b/crates/assists/src/handlers/merge_imports.rs
@@ -1,3 +1,4 @@
1use ide_db::helpers::insert_use::{try_merge_imports, try_merge_trees, MergeBehaviour};
1use syntax::{ 2use syntax::{
2 algo::{neighbor, SyntaxRewriter}, 3 algo::{neighbor, SyntaxRewriter},
3 ast, AstNode, 4 ast, AstNode,
@@ -5,10 +6,7 @@ use syntax::{
5 6
6use crate::{ 7use crate::{
7 assist_context::{AssistContext, Assists}, 8 assist_context::{AssistContext, Assists},
8 utils::{ 9 utils::next_prev,
9 insert_use::{try_merge_imports, try_merge_trees},
10 next_prev, MergeBehaviour,
11 },
12 AssistId, AssistKind, 10 AssistId, AssistKind,
13}; 11};
14 12
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs
index d5bc4e574..6f9810fe8 100644
--- a/crates/assists/src/handlers/qualify_path.rs
+++ b/crates/assists/src/handlers/qualify_path.rs
@@ -1,6 +1,7 @@
1use std::iter; 1use std::iter;
2 2
3use hir::AsName; 3use hir::AsName;
4use ide_db::helpers::mod_path_to_ast;
4use ide_db::RootDatabase; 5use ide_db::RootDatabase;
5use syntax::{ 6use syntax::{
6 ast, 7 ast,
@@ -12,7 +13,6 @@ use test_utils::mark;
12use crate::{ 13use crate::{
13 assist_context::{AssistContext, Assists}, 14 assist_context::{AssistContext, Assists},
14 utils::import_assets::{ImportAssets, ImportCandidate}, 15 utils::import_assets::{ImportAssets, ImportCandidate},
15 utils::mod_path_to_ast,
16 AssistId, AssistKind, GroupLabel, 16 AssistId, AssistKind, GroupLabel,
17}; 17};
18 18
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index 9731344b8..eae6367c1 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 SyntaxElement, SyntaxKind, TextRange, TextSize, T, 3 match_ast, SyntaxElement, SyntaxKind, TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -49,12 +49,29 @@ fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
49 macro_text_with_brackets.len() - TextSize::of(')'), 49 macro_text_with_brackets.len() - TextSize::of(')'),
50 )); 50 ));
51 51
52 let is_leaf = macro_call.syntax().next_sibling().is_none(); 52 Some(
53 Some(if !is_leaf && needs_parentheses_around_macro_contents(contents) { 53 if !is_leaf_or_control_flow_expr(macro_call)
54 format!("({})", macro_text_in_brackets) 54 && needs_parentheses_around_macro_contents(contents)
55 } else { 55 {
56 macro_text_in_brackets.to_string() 56 format!("({})", macro_text_in_brackets)
57 }) 57 } else {
58 macro_text_in_brackets.to_string()
59 },
60 )
61}
62
63fn is_leaf_or_control_flow_expr(macro_call: &ast::MacroCall) -> bool {
64 macro_call.syntax().next_sibling().is_none()
65 || match macro_call.syntax().parent() {
66 Some(parent) => match_ast! {
67 match parent {
68 ast::Condition(_it) => true,
69 ast::MatchExpr(_it) => true,
70 _ => false,
71 }
72 },
73 None => false,
74 }
58} 75}
59 76
60/// Verifies that the given macro_call actually matches the given name 77/// Verifies that the given macro_call actually matches the given name
@@ -361,4 +378,44 @@ fn main() {
361 r#"let res = (foo..=bar).foo();"#, 378 r#"let res = (foo..=bar).foo();"#,
362 ); 379 );
363 } 380 }
381
382 #[test]
383 fn test_remove_dbg_followed_by_block() {
384 check_assist(
385 remove_dbg,
386 r#"fn foo() {
387 if <|>dbg!(x || y) {}
388}"#,
389 r#"fn foo() {
390 if x || y {}
391}"#,
392 );
393 check_assist(
394 remove_dbg,
395 r#"fn foo() {
396 while let foo = <|>dbg!(&x) {}
397}"#,
398 r#"fn foo() {
399 while let foo = &x {}
400}"#,
401 );
402 check_assist(
403 remove_dbg,
404 r#"fn foo() {
405 if let foo = <|>dbg!(&x) {}
406}"#,
407 r#"fn foo() {
408 if let foo = &x {}
409}"#,
410 );
411 check_assist(
412 remove_dbg,
413 r#"fn foo() {
414 match <|>dbg!(&x) {}
415}"#,
416 r#"fn foo() {
417 match &x {}
418}"#,
419 );
420 }
364} 421}
diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
index 453a6cebf..4d6a1956b 100644
--- a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -1,3 +1,4 @@
1use ide_db::helpers::mod_path_to_ast;
1use ide_db::imports_locator; 2use ide_db::imports_locator;
2use itertools::Itertools; 3use itertools::Itertools;
3use syntax::{ 4use syntax::{
@@ -10,8 +11,7 @@ use syntax::{
10use crate::{ 11use crate::{
11 assist_context::{AssistBuilder, AssistContext, Assists}, 12 assist_context::{AssistBuilder, AssistContext, Assists},
12 utils::{ 13 utils::{
13 add_trait_assoc_items_to_impl, filter_assoc_items, mod_path_to_ast, render_snippet, Cursor, 14 add_trait_assoc_items_to_impl, filter_assoc_items, render_snippet, Cursor, DefaultMethods,
14 DefaultMethods,
15 }, 15 },
16 AssistId, AssistKind, 16 AssistId, AssistKind,
17}; 17};
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 a66db9ae3..8bdf9eea5 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,10 +1,8 @@
1use ide_db::helpers::insert_use::{insert_use, ImportScope};
1use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode}; 2use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode};
2use test_utils::mark; 3use test_utils::mark;
3 4
4use crate::{ 5use crate::{AssistContext, AssistId, AssistKind, Assists};
5 utils::{insert_use, ImportScope},
6 AssistContext, AssistId, AssistKind, Assists,
7};
8 6
9// Assist: replace_qualified_name_with_use 7// Assist: replace_qualified_name_with_use
10// 8//
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 66c0cdd5f..01f5c291f 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -1,10 +1,9 @@
1//! Assorted functions shared by several assists. 1//! Assorted functions shared by several assists.
2pub(crate) mod insert_use;
3pub(crate) mod import_assets; 2pub(crate) mod import_assets;
4 3
5use std::ops; 4use std::ops;
6 5
7use hir::{Crate, Enum, HasSource, Module, ScopeDef, Semantics, Trait}; 6use hir::HasSource;
8use ide_db::RootDatabase; 7use ide_db::RootDatabase;
9use itertools::Itertools; 8use itertools::Itertools;
10use syntax::{ 9use syntax::{
@@ -22,29 +21,6 @@ use crate::{
22 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 21 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
23}; 22};
24 23
25pub use insert_use::{insert_use, ImportScope, MergeBehaviour};
26
27pub fn mod_path_to_ast(path: &hir::ModPath) -> ast::Path {
28 let mut segments = Vec::new();
29 let mut is_abs = false;
30 match path.kind {
31 hir::PathKind::Plain => {}
32 hir::PathKind::Super(0) => segments.push(make::path_segment_self()),
33 hir::PathKind::Super(n) => segments.extend((0..n).map(|_| make::path_segment_super())),
34 hir::PathKind::DollarCrate(_) | hir::PathKind::Crate => {
35 segments.push(make::path_segment_crate())
36 }
37 hir::PathKind::Abs => is_abs = true,
38 }
39
40 segments.extend(
41 path.segments
42 .iter()
43 .map(|segment| make::path_segment(make::name_ref(&segment.to_string()))),
44 );
45 make::path_from_segments(segments, is_abs)
46}
47
48pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { 24pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
49 extract_trivial_expression(&block) 25 extract_trivial_expression(&block)
50 .filter(|expr| !expr.syntax().text().contains_char('\n')) 26 .filter(|expr| !expr.syntax().text().contains_char('\n'))
@@ -259,179 +235,6 @@ fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
259 } 235 }
260} 236}
261 237
262/// Helps with finding well-know things inside the standard library. This is
263/// somewhat similar to the known paths infra inside hir, but it different; We
264/// want to make sure that IDE specific paths don't become interesting inside
265/// the compiler itself as well.
266pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option<Crate>);
267
268#[allow(non_snake_case)]
269impl FamousDefs<'_, '_> {
270 pub const FIXTURE: &'static str = r#"//- /libcore.rs crate:core
271pub mod convert {
272 pub trait From<T> {
273 fn from(t: T) -> Self;
274 }
275}
276
277pub mod default {
278 pub trait Default {
279 fn default() -> Self;
280 }
281}
282
283pub mod iter {
284 pub use self::traits::{collect::IntoIterator, iterator::Iterator};
285 mod traits {
286 pub(crate) mod iterator {
287 use crate::option::Option;
288 pub trait Iterator {
289 type Item;
290 fn next(&mut self) -> Option<Self::Item>;
291 fn by_ref(&mut self) -> &mut Self {
292 self
293 }
294 fn take(self, n: usize) -> crate::iter::Take<Self> {
295 crate::iter::Take { inner: self }
296 }
297 }
298
299 impl<I: Iterator> Iterator for &mut I {
300 type Item = I::Item;
301 fn next(&mut self) -> Option<I::Item> {
302 (**self).next()
303 }
304 }
305 }
306 pub(crate) mod collect {
307 pub trait IntoIterator {
308 type Item;
309 }
310 }
311 }
312
313 pub use self::sources::*;
314 pub(crate) mod sources {
315 use super::Iterator;
316 use crate::option::Option::{self, *};
317 pub struct Repeat<A> {
318 element: A,
319 }
320
321 pub fn repeat<T>(elt: T) -> Repeat<T> {
322 Repeat { element: elt }
323 }
324
325 impl<A> Iterator for Repeat<A> {
326 type Item = A;
327
328 fn next(&mut self) -> Option<A> {
329 None
330 }
331 }
332 }
333
334 pub use self::adapters::*;
335 pub(crate) mod adapters {
336 use super::Iterator;
337 use crate::option::Option::{self, *};
338 pub struct Take<I> { pub(crate) inner: I }
339 impl<I> Iterator for Take<I> where I: Iterator {
340 type Item = <I as Iterator>::Item;
341 fn next(&mut self) -> Option<<I as Iterator>::Item> {
342 None
343 }
344 }
345 }
346}
347
348pub mod option {
349 pub enum Option<T> { None, Some(T)}
350}
351
352pub mod prelude {
353 pub use crate::{convert::From, iter::{IntoIterator, Iterator}, option::Option::{self, *}, default::Default};
354}
355#[prelude_import]
356pub use prelude::*;
357"#;
358
359 pub fn core(&self) -> Option<Crate> {
360 self.find_crate("core")
361 }
362
363 pub(crate) fn core_convert_From(&self) -> Option<Trait> {
364 self.find_trait("core:convert:From")
365 }
366
367 pub(crate) fn core_option_Option(&self) -> Option<Enum> {
368 self.find_enum("core:option:Option")
369 }
370
371 pub fn core_default_Default(&self) -> Option<Trait> {
372 self.find_trait("core:default:Default")
373 }
374
375 pub fn core_iter_Iterator(&self) -> Option<Trait> {
376 self.find_trait("core:iter:traits:iterator:Iterator")
377 }
378
379 pub fn core_iter(&self) -> Option<Module> {
380 self.find_module("core:iter")
381 }
382
383 fn find_trait(&self, path: &str) -> Option<Trait> {
384 match self.find_def(path)? {
385 hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it),
386 _ => None,
387 }
388 }
389
390 fn find_enum(&self, path: &str) -> Option<Enum> {
391 match self.find_def(path)? {
392 hir::ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(it))) => Some(it),
393 _ => None,
394 }
395 }
396
397 fn find_module(&self, path: &str) -> Option<Module> {
398 match self.find_def(path)? {
399 hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(it)) => Some(it),
400 _ => None,
401 }
402 }
403
404 fn find_crate(&self, name: &str) -> Option<Crate> {
405 let krate = self.1?;
406 let db = self.0.db;
407 let res =
408 krate.dependencies(db).into_iter().find(|dep| dep.name.to_string() == name)?.krate;
409 Some(res)
410 }
411
412 fn find_def(&self, path: &str) -> Option<ScopeDef> {
413 let db = self.0.db;
414 let mut path = path.split(':');
415 let trait_ = path.next_back()?;
416 let std_crate = path.next()?;
417 let std_crate = self.find_crate(std_crate)?;
418 let mut module = std_crate.root_module(db);
419 for segment in path {
420 module = module.children(db).find_map(|child| {
421 let name = child.name(db)?;
422 if name.to_string() == segment {
423 Some(child)
424 } else {
425 None
426 }
427 })?;
428 }
429 let def =
430 module.scope(db, None).into_iter().find(|(name, _def)| name.to_string() == trait_)?.1;
431 Some(def)
432 }
433}
434
435pub(crate) fn next_prev() -> impl Iterator<Item = Direction> { 238pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
436 [Direction::Next, Direction::Prev].iter().copied() 239 [Direction::Next, Direction::Prev].iter().copied()
437} 240}
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
deleted file mode 100644
index 423782a0e..000000000
--- a/crates/assists/src/utils/insert_use.rs
+++ /dev/null
@@ -1,1203 +0,0 @@
1//! Handle syntactic aspects of inserting a new `use`.
2use std::{cmp::Ordering, iter::successors};
3
4use hir::Semantics;
5use ide_db::RootDatabase;
6use itertools::{EitherOrBoth, Itertools};
7use syntax::{
8 algo::SyntaxRewriter,
9 ast::{
10 self,
11 edit::{AstNodeEdit, IndentLevel},
12 make, AstNode, PathSegmentKind, VisibilityOwner,
13 },
14 AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
15};
16use test_utils::mark;
17
18#[derive(Debug, Clone)]
19pub enum ImportScope {
20 File(ast::SourceFile),
21 Module(ast::ItemList),
22}
23
24impl ImportScope {
25 pub(crate) fn from(syntax: SyntaxNode) -> Option<Self> {
26 if let Some(module) = ast::Module::cast(syntax.clone()) {
27 module.item_list().map(ImportScope::Module)
28 } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) {
29 this.map(ImportScope::File)
30 } else {
31 ast::ItemList::cast(syntax).map(ImportScope::Module)
32 }
33 }
34
35 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
36 pub fn find_insert_use_container(
37 position: &SyntaxNode,
38 sema: &Semantics<'_, RootDatabase>,
39 ) -> Option<Self> {
40 sema.ancestors_with_macros(position.clone()).find_map(Self::from)
41 }
42
43 pub fn as_syntax_node(&self) -> &SyntaxNode {
44 match self {
45 ImportScope::File(file) => file.syntax(),
46 ImportScope::Module(item_list) => item_list.syntax(),
47 }
48 }
49
50 fn indent_level(&self) -> IndentLevel {
51 match self {
52 ImportScope::File(file) => file.indent_level(),
53 ImportScope::Module(item_list) => item_list.indent_level() + 1,
54 }
55 }
56
57 fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
58 match self {
59 ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
60 // don't insert the imports before the item list's opening curly brace
61 ImportScope::Module(item_list) => item_list
62 .l_curly_token()
63 .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
64 .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)),
65 }
66 }
67
68 fn insert_pos_after_last_inner_element(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
69 self.as_syntax_node()
70 .children_with_tokens()
71 .filter(|child| match child {
72 NodeOrToken::Node(node) => is_inner_attribute(node.clone()),
73 NodeOrToken::Token(token) => is_inner_comment(token.clone()),
74 })
75 .last()
76 .map(|last_inner_element| {
77 (InsertPosition::After(last_inner_element.into()), AddBlankLine::BeforeTwice)
78 })
79 .unwrap_or_else(|| self.first_insert_pos())
80 }
81}
82
83fn is_inner_attribute(node: SyntaxNode) -> bool {
84 ast::Attr::cast(node).map(|attr| attr.kind()) == Some(ast::AttrKind::Inner)
85}
86
87fn is_inner_comment(token: SyntaxToken) -> bool {
88 ast::Comment::cast(token).and_then(|comment| comment.kind().doc)
89 == Some(ast::CommentPlacement::Inner)
90}
91
92/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
93pub fn insert_use<'a>(
94 scope: &ImportScope,
95 path: ast::Path,
96 merge: Option<MergeBehaviour>,
97) -> SyntaxRewriter<'a> {
98 let mut rewriter = SyntaxRewriter::default();
99 let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
100 // merge into existing imports if possible
101 if let Some(mb) = merge {
102 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
103 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
104 rewriter.replace(existing_use.syntax(), merged.syntax());
105 return rewriter;
106 }
107 }
108 }
109
110 // either we weren't allowed to merge or there is no import that fits the merge conditions
111 // so look for the place we have to insert to
112 let (insert_position, add_blank) = find_insert_position(scope, path);
113
114 let indent = if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
115 Some(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into())
116 } else {
117 None
118 };
119
120 let to_insert: Vec<SyntaxElement> = {
121 let mut buf = Vec::new();
122
123 match add_blank {
124 AddBlankLine::Before | AddBlankLine::Around => {
125 buf.push(make::tokens::single_newline().into())
126 }
127 AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
128 _ => (),
129 }
130
131 if add_blank.has_before() {
132 if let Some(indent) = indent.clone() {
133 mark::hit!(insert_use_indent_before);
134 buf.push(indent);
135 }
136 }
137
138 buf.push(use_item.syntax().clone().into());
139
140 match add_blank {
141 AddBlankLine::After | AddBlankLine::Around => {
142 buf.push(make::tokens::single_newline().into())
143 }
144 AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()),
145 _ => (),
146 }
147
148 // only add indentation *after* our stuff if there's another node directly after it
149 if add_blank.has_after() && matches!(insert_position, InsertPosition::Before(_)) {
150 if let Some(indent) = indent {
151 mark::hit!(insert_use_indent_after);
152 buf.push(indent);
153 }
154 } else if add_blank.has_after() && matches!(insert_position, InsertPosition::After(_)) {
155 mark::hit!(insert_use_no_indent_after);
156 }
157
158 buf
159 };
160
161 match insert_position {
162 InsertPosition::First => {
163 rewriter.insert_many_as_first_children(scope.as_syntax_node(), to_insert)
164 }
165 InsertPosition::Last => return rewriter, // actually unreachable
166 InsertPosition::Before(anchor) => rewriter.insert_many_before(&anchor, to_insert),
167 InsertPosition::After(anchor) => rewriter.insert_many_after(&anchor, to_insert),
168 }
169 rewriter
170}
171
172fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
173 match (vis0, vis1) {
174 (None, None) => true,
175 // FIXME: Don't use the string representation to check for equality
176 // spaces inside of the node would break this comparison
177 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
178 _ => false,
179 }
180}
181
182pub(crate) fn try_merge_imports(
183 lhs: &ast::Use,
184 rhs: &ast::Use,
185 merge_behaviour: MergeBehaviour,
186) -> Option<ast::Use> {
187 // don't merge imports with different visibilities
188 if !eq_visibility(lhs.visibility(), rhs.visibility()) {
189 return None;
190 }
191 let lhs_tree = lhs.use_tree()?;
192 let rhs_tree = rhs.use_tree()?;
193 let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behaviour)?;
194 Some(lhs.with_use_tree(merged))
195}
196
197pub(crate) fn try_merge_trees(
198 lhs: &ast::UseTree,
199 rhs: &ast::UseTree,
200 merge: MergeBehaviour,
201) -> Option<ast::UseTree> {
202 let lhs_path = lhs.path()?;
203 let rhs_path = rhs.path()?;
204
205 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
206 let (lhs, rhs) = if is_simple_path(lhs)
207 && is_simple_path(rhs)
208 && lhs_path == lhs_prefix
209 && rhs_path == rhs_prefix
210 {
211 (lhs.clone(), rhs.clone())
212 } else {
213 (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix))
214 };
215 recursive_merge(&lhs, &rhs, merge)
216}
217
218/// Recursively "zips" together lhs and rhs.
219fn recursive_merge(
220 lhs: &ast::UseTree,
221 rhs: &ast::UseTree,
222 merge: MergeBehaviour,
223) -> Option<ast::UseTree> {
224 let mut use_trees = lhs
225 .use_tree_list()
226 .into_iter()
227 .flat_map(|list| list.use_trees())
228 // we use Option here to early return from this function(this is not the same as a `filter` op)
229 .map(|tree| match merge.is_tree_allowed(&tree) {
230 true => Some(tree),
231 false => None,
232 })
233 .collect::<Option<Vec<_>>>()?;
234 use_trees.sort_unstable_by(|a, b| path_cmp_for_sort(a.path(), b.path()));
235 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
236 if !merge.is_tree_allowed(&rhs_t) {
237 return None;
238 }
239 let rhs_path = rhs_t.path();
240 match use_trees.binary_search_by(|lhs_t| {
241 let (lhs_t, rhs_t) = match lhs_t
242 .path()
243 .zip(rhs_path.clone())
244 .and_then(|(lhs, rhs)| common_prefix(&lhs, &rhs))
245 {
246 Some((lhs_p, rhs_p)) => (lhs_t.split_prefix(&lhs_p), rhs_t.split_prefix(&rhs_p)),
247 None => (lhs_t.clone(), rhs_t.clone()),
248 };
249
250 path_cmp_bin_search(lhs_t.path(), rhs_t.path())
251 }) {
252 Ok(idx) => {
253 let lhs_t = &mut use_trees[idx];
254 let lhs_path = lhs_t.path()?;
255 let rhs_path = rhs_path?;
256 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
257 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
258 let tree_is_self = |tree: ast::UseTree| {
259 tree.path().as_ref().map(path_is_self).unwrap_or(false)
260 };
261 // check if only one of the two trees has a tree list, and whether that then contains `self` or not.
262 // 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`
263 let tree_contains_self = |tree: &ast::UseTree| {
264 tree.use_tree_list()
265 .map(|tree_list| tree_list.use_trees().any(tree_is_self))
266 .unwrap_or(false)
267 };
268 match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) {
269 (true, false) => continue,
270 (false, true) => {
271 *lhs_t = rhs_t;
272 continue;
273 }
274 _ => (),
275 }
276
277 // glob imports arent part of the use-tree lists so we need to special handle them here as well
278 // this special handling is only required for when we merge a module import into a glob import of said module
279 // see the `merge_self_glob` or `merge_mod_into_glob` tests
280 if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
281 *lhs_t = make::use_tree(
282 make::path_unqualified(make::path_segment_self()),
283 None,
284 None,
285 false,
286 );
287 use_trees.insert(idx, make::glob_use_tree());
288 continue;
289 }
290
291 if lhs_t.use_tree_list().is_none() && rhs_t.use_tree_list().is_none() {
292 continue;
293 }
294 }
295 let lhs = lhs_t.split_prefix(&lhs_prefix);
296 let rhs = rhs_t.split_prefix(&rhs_prefix);
297 match recursive_merge(&lhs, &rhs, merge) {
298 Some(use_tree) => use_trees[idx] = use_tree,
299 None => return None,
300 }
301 }
302 Err(_)
303 if merge == MergeBehaviour::Last
304 && use_trees.len() > 0
305 && rhs_t.use_tree_list().is_some() =>
306 {
307 return None
308 }
309 Err(idx) => {
310 use_trees.insert(idx, rhs_t);
311 }
312 }
313 }
314 Some(lhs.with_use_tree_list(make::use_tree_list(use_trees)))
315}
316
317/// Traverses both paths until they differ, returning the common prefix of both.
318fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
319 let mut res = None;
320 let mut lhs_curr = first_path(&lhs);
321 let mut rhs_curr = first_path(&rhs);
322 loop {
323 match (lhs_curr.segment(), rhs_curr.segment()) {
324 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
325 _ => break res,
326 }
327 res = Some((lhs_curr.clone(), rhs_curr.clone()));
328
329 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
330 Some((lhs, rhs)) => {
331 lhs_curr = lhs;
332 rhs_curr = rhs;
333 }
334 _ => break res,
335 }
336 }
337}
338
339fn is_simple_path(use_tree: &ast::UseTree) -> bool {
340 use_tree.use_tree_list().is_none() && use_tree.star_token().is_none()
341}
342
343fn path_is_self(path: &ast::Path) -> bool {
344 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
345}
346
347#[inline]
348fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
349 first_path(path).segment()
350}
351
352fn first_path(path: &ast::Path) -> ast::Path {
353 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
354}
355
356fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
357 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
358 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
359}
360
361fn path_len(path: ast::Path) -> usize {
362 segment_iter(&path).count()
363}
364
365/// Orders paths in the following way:
366/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
367// FIXME: rustfmt sorts lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
368// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
369// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
370fn path_cmp_for_sort(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
371 match (a, b) {
372 (None, None) => Ordering::Equal,
373 (None, Some(_)) => Ordering::Less,
374 (Some(_), None) => Ordering::Greater,
375 (Some(ref a), Some(ref b)) => match (path_is_self(a), path_is_self(b)) {
376 (true, true) => Ordering::Equal,
377 (true, false) => Ordering::Less,
378 (false, true) => Ordering::Greater,
379 (false, false) => path_cmp_short(a, b),
380 },
381 }
382}
383
384/// Path comparison func for binary searching for merging.
385fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<ast::Path>) -> Ordering {
386 match (lhs.and_then(|path| path.segment()), rhs.and_then(|path| path.segment())) {
387 (None, None) => Ordering::Equal,
388 (None, Some(_)) => Ordering::Less,
389 (Some(_), None) => Ordering::Greater,
390 (Some(ref a), Some(ref b)) => path_segment_cmp(a, b),
391 }
392}
393
394/// Short circuiting comparison, if both paths are equal until one of them ends they are considered
395/// equal
396fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering {
397 let a = segment_iter(a);
398 let b = segment_iter(b);
399 // cmp_by would be useful for us here but that is currently unstable
400 // cmp doesnt work due the lifetimes on text's return type
401 a.zip(b)
402 .find_map(|(a, b)| match path_segment_cmp(&a, &b) {
403 Ordering::Equal => None,
404 ord => Some(ord),
405 })
406 .unwrap_or(Ordering::Equal)
407}
408
409/// Compares to paths, if one ends earlier than the other the has_tl parameters decide which is
410/// greater as a a path that has a tree list should be greater, while one that just ends without
411/// a tree list should be considered less.
412fn use_tree_path_cmp(a: &ast::Path, a_has_tl: bool, b: &ast::Path, b_has_tl: bool) -> Ordering {
413 let a_segments = segment_iter(a);
414 let b_segments = segment_iter(b);
415 // cmp_by would be useful for us here but that is currently unstable
416 // cmp doesnt work due the lifetimes on text's return type
417 a_segments
418 .zip_longest(b_segments)
419 .find_map(|zipped| match zipped {
420 EitherOrBoth::Both(ref a, ref b) => match path_segment_cmp(a, b) {
421 Ordering::Equal => None,
422 ord => Some(ord),
423 },
424 EitherOrBoth::Left(_) if !b_has_tl => Some(Ordering::Greater),
425 EitherOrBoth::Left(_) => Some(Ordering::Less),
426 EitherOrBoth::Right(_) if !a_has_tl => Some(Ordering::Less),
427 EitherOrBoth::Right(_) => Some(Ordering::Greater),
428 })
429 .unwrap_or(Ordering::Equal)
430}
431
432fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
433 let a = a.name_ref();
434 let b = b.name_ref();
435 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
436}
437
438/// What type of merges are allowed.
439#[derive(Copy, Clone, Debug, PartialEq, Eq)]
440pub enum MergeBehaviour {
441 /// Merge everything together creating deeply nested imports.
442 Full,
443 /// Only merge the last import level, doesn't allow import nesting.
444 Last,
445}
446
447impl MergeBehaviour {
448 #[inline]
449 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
450 match self {
451 MergeBehaviour::Full => true,
452 // only simple single segment paths are allowed
453 MergeBehaviour::Last => {
454 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
455 }
456 }
457 }
458}
459
460#[derive(Eq, PartialEq, PartialOrd, Ord)]
461enum ImportGroup {
462 // the order here defines the order of new group inserts
463 Std,
464 ExternCrate,
465 ThisCrate,
466 ThisModule,
467 SuperModule,
468}
469
470impl ImportGroup {
471 fn new(path: &ast::Path) -> ImportGroup {
472 let default = ImportGroup::ExternCrate;
473
474 let first_segment = match first_segment(path) {
475 Some(it) => it,
476 None => return default,
477 };
478
479 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
480 match kind {
481 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
482 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
483 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
484 PathSegmentKind::Name(name) => match name.text().as_str() {
485 "std" => ImportGroup::Std,
486 "core" => ImportGroup::Std,
487 _ => ImportGroup::ExternCrate,
488 },
489 PathSegmentKind::Type { .. } => unreachable!(),
490 }
491 }
492}
493
494#[derive(PartialEq, Eq)]
495enum AddBlankLine {
496 Before,
497 BeforeTwice,
498 Around,
499 After,
500 AfterTwice,
501}
502
503impl AddBlankLine {
504 fn has_before(&self) -> bool {
505 matches!(self, AddBlankLine::Before | AddBlankLine::BeforeTwice | AddBlankLine::Around)
506 }
507 fn has_after(&self) -> bool {
508 matches!(self, AddBlankLine::After | AddBlankLine::AfterTwice | AddBlankLine::Around)
509 }
510}
511
512fn find_insert_position(
513 scope: &ImportScope,
514 insert_path: ast::Path,
515) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
516 let group = ImportGroup::new(&insert_path);
517 let path_node_iter = scope
518 .as_syntax_node()
519 .children()
520 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
521 .flat_map(|(use_, node)| {
522 let tree = use_.use_tree()?;
523 let path = tree.path()?;
524 let has_tl = tree.use_tree_list().is_some();
525 Some((path, has_tl, node))
526 });
527 // Iterator that discards anything thats not in the required grouping
528 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
529 let group_iter = path_node_iter
530 .clone()
531 .skip_while(|(path, ..)| ImportGroup::new(path) != group)
532 .take_while(|(path, ..)| ImportGroup::new(path) == group);
533
534 // 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
535 let mut last = None;
536 // find the element that would come directly after our new import
537 let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find(
538 |&(ref path, has_tl, _)| {
539 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
540 },
541 );
542 match post_insert {
543 // insert our import before that element
544 Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
545 // there is no element after our new import, so append it to the end of the group
546 None => match last {
547 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
548 // the group we were looking for actually doesnt exist, so insert
549 None => {
550 // similar concept here to the `last` from above
551 let mut last = None;
552 // find the group that comes after where we want to insert
553 let post_group = path_node_iter
554 .inspect(|(.., node)| last = Some(node.clone()))
555 .find(|(p, ..)| ImportGroup::new(p) > group);
556 match post_group {
557 Some((.., node)) => {
558 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
559 }
560 // there is no such group, so append after the last one
561 None => match last {
562 Some(node) => {
563 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
564 }
565 // there are no imports in this file at all
566 None => scope.insert_pos_after_last_inner_element(),
567 },
568 }
569 }
570 },
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577
578 use test_utils::assert_eq_text;
579
580 #[test]
581 fn insert_existing() {
582 check_full("std::fs", "use std::fs;", "use std::fs;")
583 }
584
585 #[test]
586 fn insert_start() {
587 check_none(
588 "std::bar::AA",
589 r"
590use std::bar::B;
591use std::bar::D;
592use std::bar::F;
593use std::bar::G;",
594 r"
595use std::bar::AA;
596use std::bar::B;
597use std::bar::D;
598use std::bar::F;
599use std::bar::G;",
600 )
601 }
602
603 #[test]
604 fn insert_start_indent() {
605 mark::check!(insert_use_indent_after);
606 check_none(
607 "std::bar::AA",
608 r"
609 use std::bar::B;
610 use std::bar::D;",
611 r"
612 use std::bar::AA;
613 use std::bar::B;
614 use std::bar::D;",
615 )
616 }
617
618 #[test]
619 fn insert_middle() {
620 check_none(
621 "std::bar::EE",
622 r"
623use std::bar::A;
624use std::bar::D;
625use std::bar::F;
626use std::bar::G;",
627 r"
628use std::bar::A;
629use std::bar::D;
630use std::bar::EE;
631use std::bar::F;
632use std::bar::G;",
633 )
634 }
635
636 #[test]
637 fn insert_middle_indent() {
638 check_none(
639 "std::bar::EE",
640 r"
641 use std::bar::A;
642 use std::bar::D;
643 use std::bar::F;
644 use std::bar::G;",
645 r"
646 use std::bar::A;
647 use std::bar::D;
648 use std::bar::EE;
649 use std::bar::F;
650 use std::bar::G;",
651 )
652 }
653
654 #[test]
655 fn insert_end() {
656 check_none(
657 "std::bar::ZZ",
658 r"
659use std::bar::A;
660use std::bar::D;
661use std::bar::F;
662use std::bar::G;",
663 r"
664use std::bar::A;
665use std::bar::D;
666use std::bar::F;
667use std::bar::G;
668use std::bar::ZZ;",
669 )
670 }
671
672 #[test]
673 fn insert_end_indent() {
674 mark::check!(insert_use_indent_before);
675 check_none(
676 "std::bar::ZZ",
677 r"
678 use std::bar::A;
679 use std::bar::D;
680 use std::bar::F;
681 use std::bar::G;",
682 r"
683 use std::bar::A;
684 use std::bar::D;
685 use std::bar::F;
686 use std::bar::G;
687 use std::bar::ZZ;",
688 )
689 }
690
691 #[test]
692 fn insert_middle_nested() {
693 check_none(
694 "std::bar::EE",
695 r"
696use std::bar::A;
697use std::bar::{D, Z}; // example of weird imports due to user
698use std::bar::F;
699use std::bar::G;",
700 r"
701use std::bar::A;
702use std::bar::EE;
703use std::bar::{D, Z}; // example of weird imports due to user
704use std::bar::F;
705use std::bar::G;",
706 )
707 }
708
709 #[test]
710 fn insert_middle_groups() {
711 check_none(
712 "foo::bar::GG",
713 r"
714 use std::bar::A;
715 use std::bar::D;
716
717 use foo::bar::F;
718 use foo::bar::H;",
719 r"
720 use std::bar::A;
721 use std::bar::D;
722
723 use foo::bar::F;
724 use foo::bar::GG;
725 use foo::bar::H;",
726 )
727 }
728
729 #[test]
730 fn insert_first_matching_group() {
731 check_none(
732 "foo::bar::GG",
733 r"
734 use foo::bar::A;
735 use foo::bar::D;
736
737 use std;
738
739 use foo::bar::F;
740 use foo::bar::H;",
741 r"
742 use foo::bar::A;
743 use foo::bar::D;
744 use foo::bar::GG;
745
746 use std;
747
748 use foo::bar::F;
749 use foo::bar::H;",
750 )
751 }
752
753 #[test]
754 fn insert_missing_group_std() {
755 check_none(
756 "std::fmt",
757 r"
758 use foo::bar::A;
759 use foo::bar::D;",
760 r"
761 use std::fmt;
762
763 use foo::bar::A;
764 use foo::bar::D;",
765 )
766 }
767
768 #[test]
769 fn insert_missing_group_self() {
770 check_none(
771 "self::fmt",
772 r"
773use foo::bar::A;
774use foo::bar::D;",
775 r"
776use foo::bar::A;
777use foo::bar::D;
778
779use self::fmt;",
780 )
781 }
782
783 #[test]
784 fn insert_no_imports() {
785 check_full(
786 "foo::bar",
787 "fn main() {}",
788 r"use foo::bar;
789
790fn main() {}",
791 )
792 }
793
794 #[test]
795 fn insert_empty_file() {
796 // empty files will get two trailing newlines
797 // this is due to the test case insert_no_imports above
798 check_full(
799 "foo::bar",
800 "",
801 r"use foo::bar;
802
803",
804 )
805 }
806
807 #[test]
808 fn insert_empty_module() {
809 mark::check!(insert_use_no_indent_after);
810 check(
811 "foo::bar",
812 "mod x {}",
813 r"{
814 use foo::bar;
815}",
816 None,
817 true,
818 )
819 }
820
821 #[test]
822 fn insert_after_inner_attr() {
823 check_full(
824 "foo::bar",
825 r"#![allow(unused_imports)]",
826 r"#![allow(unused_imports)]
827
828use foo::bar;",
829 )
830 }
831
832 #[test]
833 fn insert_after_inner_attr2() {
834 check_full(
835 "foo::bar",
836 r"#![allow(unused_imports)]
837
838#![no_std]
839fn main() {}",
840 r"#![allow(unused_imports)]
841
842#![no_std]
843
844use foo::bar;
845fn main() {}",
846 );
847 }
848
849 #[test]
850 fn inserts_after_single_line_inner_comments() {
851 check_none(
852 "foo::bar::Baz",
853 "//! Single line inner comments do not allow any code before them.",
854 r#"//! Single line inner comments do not allow any code before them.
855
856use foo::bar::Baz;"#,
857 );
858 }
859
860 #[test]
861 fn inserts_after_multiline_inner_comments() {
862 check_none(
863 "foo::bar::Baz",
864 r#"/*! Multiline inner comments do not allow any code before them. */
865
866/*! Still an inner comment, cannot place any code before. */
867fn main() {}"#,
868 r#"/*! Multiline inner comments do not allow any code before them. */
869
870/*! Still an inner comment, cannot place any code before. */
871
872use foo::bar::Baz;
873fn main() {}"#,
874 )
875 }
876
877 #[test]
878 fn inserts_after_all_inner_items() {
879 check_none(
880 "foo::bar::Baz",
881 r#"#![allow(unused_imports)]
882/*! Multiline line comment 2 */
883
884
885//! Single line comment 1
886#![no_std]
887//! Single line comment 2
888fn main() {}"#,
889 r#"#![allow(unused_imports)]
890/*! Multiline line comment 2 */
891
892
893//! Single line comment 1
894#![no_std]
895//! Single line comment 2
896
897use foo::bar::Baz;
898fn main() {}"#,
899 )
900 }
901
902 #[test]
903 fn merge_groups() {
904 check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};")
905 }
906
907 #[test]
908 fn merge_groups_last() {
909 check_last(
910 "std::io",
911 r"use std::fmt::{Result, Display};",
912 r"use std::fmt::{Result, Display};
913use std::io;",
914 )
915 }
916
917 #[test]
918 fn merge_last_into_self() {
919 check_last("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};");
920 }
921
922 #[test]
923 fn merge_groups_full() {
924 check_full(
925 "std::io",
926 r"use std::fmt::{Result, Display};",
927 r"use std::{fmt::{Result, Display}, io};",
928 )
929 }
930
931 #[test]
932 fn merge_groups_long_full() {
933 check_full(
934 "std::foo::bar::Baz",
935 r"use std::foo::bar::Qux;",
936 r"use std::foo::bar::{Baz, Qux};",
937 )
938 }
939
940 #[test]
941 fn merge_groups_long_last() {
942 check_last(
943 "std::foo::bar::Baz",
944 r"use std::foo::bar::Qux;",
945 r"use std::foo::bar::{Baz, Qux};",
946 )
947 }
948
949 #[test]
950 fn merge_groups_long_full_list() {
951 check_full(
952 "std::foo::bar::Baz",
953 r"use std::foo::bar::{Qux, Quux};",
954 r"use std::foo::bar::{Baz, Quux, Qux};",
955 )
956 }
957
958 #[test]
959 fn merge_groups_long_last_list() {
960 check_last(
961 "std::foo::bar::Baz",
962 r"use std::foo::bar::{Qux, Quux};",
963 r"use std::foo::bar::{Baz, Quux, Qux};",
964 )
965 }
966
967 #[test]
968 fn merge_groups_long_full_nested() {
969 check_full(
970 "std::foo::bar::Baz",
971 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
972 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};",
973 )
974 }
975
976 #[test]
977 fn merge_groups_long_last_nested() {
978 check_last(
979 "std::foo::bar::Baz",
980 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
981 r"use std::foo::bar::Baz;
982use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
983 )
984 }
985
986 #[test]
987 fn merge_groups_full_nested_deep() {
988 check_full(
989 "std::foo::bar::quux::Baz",
990 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
991 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
992 )
993 }
994
995 #[test]
996 fn merge_groups_full_nested_long() {
997 check_full(
998 "std::foo::bar::Baz",
999 r"use std::{foo::bar::Qux};",
1000 r"use std::{foo::bar::{Baz, Qux}};",
1001 );
1002 }
1003
1004 #[test]
1005 fn merge_groups_last_nested_long() {
1006 check_full(
1007 "std::foo::bar::Baz",
1008 r"use std::{foo::bar::Qux};",
1009 r"use std::{foo::bar::{Baz, Qux}};",
1010 );
1011 }
1012
1013 #[test]
1014 fn merge_groups_skip_pub() {
1015 check_full(
1016 "std::io",
1017 r"pub use std::fmt::{Result, Display};",
1018 r"pub use std::fmt::{Result, Display};
1019use std::io;",
1020 )
1021 }
1022
1023 #[test]
1024 fn merge_groups_skip_pub_crate() {
1025 check_full(
1026 "std::io",
1027 r"pub(crate) use std::fmt::{Result, Display};",
1028 r"pub(crate) use std::fmt::{Result, Display};
1029use std::io;",
1030 )
1031 }
1032
1033 #[test]
1034 #[ignore] // FIXME: Support this
1035 fn split_out_merge() {
1036 check_last(
1037 "std::fmt::Result",
1038 r"use std::{fmt, io};",
1039 r"use std::fmt::{self, Result};
1040use std::io;",
1041 )
1042 }
1043
1044 #[test]
1045 fn merge_into_module_import() {
1046 check_full(
1047 "std::fmt::Result",
1048 r"use std::{fmt, io};",
1049 r"use std::{fmt::{self, Result}, io};",
1050 )
1051 }
1052
1053 #[test]
1054 fn merge_groups_self() {
1055 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
1056 }
1057
1058 #[test]
1059 fn merge_mod_into_glob() {
1060 check_full(
1061 "token::TokenKind",
1062 r"use token::TokenKind::*;",
1063 r"use token::TokenKind::{*, self};",
1064 )
1065 // FIXME: have it emit `use token::TokenKind::{self, *}`?
1066 }
1067
1068 #[test]
1069 fn merge_self_glob() {
1070 check_full("self", r"use self::*;", r"use self::{*, self};")
1071 // FIXME: have it emit `use {self, *}`?
1072 }
1073
1074 #[test]
1075 fn merge_glob_nested() {
1076 check_full(
1077 "foo::bar::quux::Fez",
1078 r"use foo::bar::{Baz, quux::*};",
1079 r"use foo::bar::{Baz, quux::{self::*, Fez}};",
1080 )
1081 }
1082
1083 #[test]
1084 fn skip_merge_last_too_long() {
1085 check_last(
1086 "foo::bar",
1087 r"use foo::bar::baz::Qux;",
1088 r"use foo::bar;
1089use foo::bar::baz::Qux;",
1090 );
1091 }
1092
1093 #[test]
1094 fn skip_merge_last_too_long2() {
1095 check_last(
1096 "foo::bar::baz::Qux",
1097 r"use foo::bar;",
1098 r"use foo::bar;
1099use foo::bar::baz::Qux;",
1100 );
1101 }
1102
1103 #[test]
1104 fn insert_short_before_long() {
1105 check_none(
1106 "foo::bar",
1107 r"use foo::bar::baz::Qux;",
1108 r"use foo::bar;
1109use foo::bar::baz::Qux;",
1110 );
1111 }
1112
1113 #[test]
1114 fn merge_last_fail() {
1115 check_merge_only_fail(
1116 r"use foo::bar::{baz::{Qux, Fez}};",
1117 r"use foo::bar::{baaz::{Quux, Feez}};",
1118 MergeBehaviour::Last,
1119 );
1120 }
1121
1122 #[test]
1123 fn merge_last_fail1() {
1124 check_merge_only_fail(
1125 r"use foo::bar::{baz::{Qux, Fez}};",
1126 r"use foo::bar::baaz::{Quux, Feez};",
1127 MergeBehaviour::Last,
1128 );
1129 }
1130
1131 #[test]
1132 fn merge_last_fail2() {
1133 check_merge_only_fail(
1134 r"use foo::bar::baz::{Qux, Fez};",
1135 r"use foo::bar::{baaz::{Quux, Feez}};",
1136 MergeBehaviour::Last,
1137 );
1138 }
1139
1140 #[test]
1141 fn merge_last_fail3() {
1142 check_merge_only_fail(
1143 r"use foo::bar::baz::{Qux, Fez};",
1144 r"use foo::bar::baaz::{Quux, Feez};",
1145 MergeBehaviour::Last,
1146 );
1147 }
1148
1149 fn check(
1150 path: &str,
1151 ra_fixture_before: &str,
1152 ra_fixture_after: &str,
1153 mb: Option<MergeBehaviour>,
1154 module: bool,
1155 ) {
1156 let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone();
1157 if module {
1158 syntax = syntax.descendants().find_map(ast::Module::cast).unwrap().syntax().clone();
1159 }
1160 let file = super::ImportScope::from(syntax).unwrap();
1161 let path = ast::SourceFile::parse(&format!("use {};", path))
1162 .tree()
1163 .syntax()
1164 .descendants()
1165 .find_map(ast::Path::cast)
1166 .unwrap();
1167
1168 let rewriter = insert_use(&file, path, mb);
1169 let result = rewriter.rewrite(file.as_syntax_node()).to_string();
1170 assert_eq_text!(&result, ra_fixture_after);
1171 }
1172
1173 fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
1174 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full), false)
1175 }
1176
1177 fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
1178 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last), false)
1179 }
1180
1181 fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
1182 check(path, ra_fixture_before, ra_fixture_after, None, false)
1183 }
1184
1185 fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) {
1186 let use0 = ast::SourceFile::parse(ra_fixture0)
1187 .tree()
1188 .syntax()
1189 .descendants()
1190 .find_map(ast::Use::cast)
1191 .unwrap();
1192
1193 let use1 = ast::SourceFile::parse(ra_fixture1)
1194 .tree()
1195 .syntax()
1196 .descendants()
1197 .find_map(ast::Use::cast)
1198 .unwrap();
1199
1200 let result = try_merge_imports(&use0, &use1, mb);
1201 assert_eq!(result.map(|u| u.to_string()), None);
1202 }
1203}