aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-02-07 16:28:33 +0000
committerGitHub <[email protected]>2020-02-07 16:28:33 +0000
commit5397f05bfe7f3b18229a65040c6685e762b2f9a3 (patch)
treea3c4aab400ffe1c84bd33e094a047798e7136d2d /crates/ra_assists/src/handlers
parent1996762b1f2b9cb196cc879f0ce26d28a3c450c8 (diff)
parentd00add1f1fec59494c3c1a99c27937ae3891458d (diff)
Merge #3049
3049: Introduce assists utils r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_assists/src/handlers')
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs209
-rw-r--r--crates/ra_assists/src/handlers/add_derive.rs120
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs178
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs94
-rw-r--r--crates/ra_assists/src/handlers/add_import.rs967
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs608
-rw-r--r--crates/ra_assists/src/handlers/add_new.rs430
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs89
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs258
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs167
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs505
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs290
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs142
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs80
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs116
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs662
-rw-r--r--crates/ra_assists/src/handlers/introduce_variable.rs529
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs91
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs264
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs137
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs308
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs499
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs150
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs148
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs69
25 files changed, 7110 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
new file mode 100644
index 000000000..7fdd816bf
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -0,0 +1,209 @@
1//! FIXME: write short doc here
2
3use crate::{Assist, AssistCtx, AssistId};
4
5use join_to_string::join;
6use ra_syntax::{
7 ast::{self, AstNode},
8 Direction, SmolStr,
9 SyntaxKind::{IDENT, WHITESPACE},
10 TextRange, TextUnit,
11};
12
13const DERIVE_TRAIT: &str = "derive";
14
15// Assist: add_custom_impl
16//
17// Adds impl block for derived trait.
18//
19// ```
20// #[derive(Deb<|>ug, Display)]
21// struct S;
22// ```
23// ->
24// ```
25// #[derive(Display)]
26// struct S;
27//
28// impl Debug for S {
29//
30// }
31// ```
32pub(crate) fn add_custom_impl(ctx: AssistCtx) -> Option<Assist> {
33 let input = ctx.find_node_at_offset::<ast::AttrInput>()?;
34 let attr = input.syntax().parent().and_then(ast::Attr::cast)?;
35
36 let attr_name = attr
37 .syntax()
38 .descendants_with_tokens()
39 .filter(|t| t.kind() == IDENT)
40 .find_map(|i| i.into_token())
41 .filter(|t| *t.text() == DERIVE_TRAIT)?
42 .text()
43 .clone();
44
45 let trait_token =
46 ctx.token_at_offset().filter(|t| t.kind() == IDENT && *t.text() != attr_name).next()?;
47
48 let annotated = attr.syntax().siblings(Direction::Next).find_map(|s| ast::Name::cast(s))?;
49 let annotated_name = annotated.syntax().text().to_string();
50 let start_offset = annotated.syntax().parent()?.text_range().end();
51
52 let label =
53 format!("Add custom impl '{}' for '{}'", trait_token.text().as_str(), annotated_name);
54
55 ctx.add_assist(AssistId("add_custom_impl"), label, |edit| {
56 edit.target(attr.syntax().text_range());
57
58 let new_attr_input = input
59 .syntax()
60 .descendants_with_tokens()
61 .filter(|t| t.kind() == IDENT)
62 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
63 .filter(|t| t != trait_token.text())
64 .collect::<Vec<SmolStr>>();
65 let has_more_derives = new_attr_input.len() > 0;
66 let new_attr_input =
67 join(new_attr_input.iter()).separator(", ").surround_with("(", ")").to_string();
68 let new_attr_input_len = new_attr_input.len();
69
70 let mut buf = String::new();
71 buf.push_str("\n\nimpl ");
72 buf.push_str(trait_token.text().as_str());
73 buf.push_str(" for ");
74 buf.push_str(annotated_name.as_str());
75 buf.push_str(" {\n");
76
77 let cursor_delta = if has_more_derives {
78 edit.replace(input.syntax().text_range(), new_attr_input);
79 input.syntax().text_range().len() - TextUnit::from_usize(new_attr_input_len)
80 } else {
81 let attr_range = attr.syntax().text_range();
82 edit.delete(attr_range);
83
84 let line_break_range = attr
85 .syntax()
86 .next_sibling_or_token()
87 .filter(|t| t.kind() == WHITESPACE)
88 .map(|t| t.text_range())
89 .unwrap_or(TextRange::from_to(TextUnit::from(0), TextUnit::from(0)));
90 edit.delete(line_break_range);
91
92 attr_range.len() + line_break_range.len()
93 };
94
95 edit.set_cursor(start_offset + TextUnit::of_str(&buf) - cursor_delta);
96 buf.push_str("\n}");
97 edit.insert(start_offset, buf);
98 })
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::helpers::{check_assist, check_assist_not_applicable};
105
106 #[test]
107 fn add_custom_impl_for_unique_input() {
108 check_assist(
109 add_custom_impl,
110 "
111#[derive(Debu<|>g)]
112struct Foo {
113 bar: String,
114}
115 ",
116 "
117struct Foo {
118 bar: String,
119}
120
121impl Debug for Foo {
122<|>
123}
124 ",
125 )
126 }
127
128 #[test]
129 fn add_custom_impl_for_with_visibility_modifier() {
130 check_assist(
131 add_custom_impl,
132 "
133#[derive(Debug<|>)]
134pub struct Foo {
135 bar: String,
136}
137 ",
138 "
139pub struct Foo {
140 bar: String,
141}
142
143impl Debug for Foo {
144<|>
145}
146 ",
147 )
148 }
149
150 #[test]
151 fn add_custom_impl_when_multiple_inputs() {
152 check_assist(
153 add_custom_impl,
154 "
155#[derive(Display, Debug<|>, Serialize)]
156struct Foo {}
157 ",
158 "
159#[derive(Display, Serialize)]
160struct Foo {}
161
162impl Debug for Foo {
163<|>
164}
165 ",
166 )
167 }
168
169 #[test]
170 fn test_ignore_derive_macro_without_input() {
171 check_assist_not_applicable(
172 add_custom_impl,
173 "
174#[derive(<|>)]
175struct Foo {}
176 ",
177 )
178 }
179
180 #[test]
181 fn test_ignore_if_cursor_on_param() {
182 check_assist_not_applicable(
183 add_custom_impl,
184 "
185#[derive<|>(Debug)]
186struct Foo {}
187 ",
188 );
189
190 check_assist_not_applicable(
191 add_custom_impl,
192 "
193#[derive(Debug)<|>]
194struct Foo {}
195 ",
196 )
197 }
198
199 #[test]
200 fn test_ignore_if_not_derive() {
201 check_assist_not_applicable(
202 add_custom_impl,
203 "
204#[allow(non_camel_<|>case_types)]
205struct Foo {}
206 ",
207 )
208 }
209}
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/add_derive.rs
new file mode 100644
index 000000000..b0d1a0a80
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_derive.rs
@@ -0,0 +1,120 @@
1use ra_syntax::{
2 ast::{self, AstNode, AttrsOwner},
3 SyntaxKind::{COMMENT, WHITESPACE},
4 TextUnit,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8
9// Assist: add_derive
10//
11// Adds a new `#[derive()]` clause to a struct or enum.
12//
13// ```
14// struct Point {
15// x: u32,
16// y: u32,<|>
17// }
18// ```
19// ->
20// ```
21// #[derive()]
22// struct Point {
23// x: u32,
24// y: u32,
25// }
26// ```
27pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
28 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
29 let node_start = derive_insertion_offset(&nominal)?;
30 ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", |edit| {
31 let derive_attr = nominal
32 .attrs()
33 .filter_map(|x| x.as_simple_call())
34 .filter(|(name, _arg)| name == "derive")
35 .map(|(_name, arg)| arg)
36 .next();
37 let offset = match derive_attr {
38 None => {
39 edit.insert(node_start, "#[derive()]\n");
40 node_start + TextUnit::of_str("#[derive(")
41 }
42 Some(tt) => tt.syntax().text_range().end() - TextUnit::of_char(')'),
43 };
44 edit.target(nominal.syntax().text_range());
45 edit.set_cursor(offset)
46 })
47}
48
49// Insert `derive` after doc comments.
50fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextUnit> {
51 let non_ws_child = nominal
52 .syntax()
53 .children_with_tokens()
54 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
55 Some(non_ws_child.text_range().start())
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use crate::helpers::{check_assist, check_assist_target};
62
63 #[test]
64 fn add_derive_new() {
65 check_assist(
66 add_derive,
67 "struct Foo { a: i32, <|>}",
68 "#[derive(<|>)]\nstruct Foo { a: i32, }",
69 );
70 check_assist(
71 add_derive,
72 "struct Foo { <|> a: i32, }",
73 "#[derive(<|>)]\nstruct Foo { a: i32, }",
74 );
75 }
76
77 #[test]
78 fn add_derive_existing() {
79 check_assist(
80 add_derive,
81 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
82 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
83 );
84 }
85
86 #[test]
87 fn add_derive_new_with_doc_comment() {
88 check_assist(
89 add_derive,
90 "
91/// `Foo` is a pretty important struct.
92/// It does stuff.
93struct Foo { a: i32<|>, }
94 ",
95 "
96/// `Foo` is a pretty important struct.
97/// It does stuff.
98#[derive(<|>)]
99struct Foo { a: i32, }
100 ",
101 );
102 }
103
104 #[test]
105 fn add_derive_target() {
106 check_assist_target(
107 add_derive,
108 "
109struct SomeThingIrrelevant;
110/// `Foo` is a pretty important struct.
111/// It does stuff.
112struct Foo { a: i32<|>, }
113struct EvenMoreIrrelevant;
114 ",
115 "/// `Foo` is a pretty important struct.
116/// It does stuff.
117struct Foo { a: i32, }",
118 );
119 }
120}
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
new file mode 100644
index 000000000..2cb9d2f48
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -0,0 +1,178 @@
1use hir::HirDisplay;
2use ra_syntax::{
3 ast::{self, AstNode, LetStmt, NameOwner, TypeAscriptionOwner},
4 TextRange,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8
9// Assist: add_explicit_type
10//
11// Specify type for a let binding.
12//
13// ```
14// fn main() {
15// let x<|> = 92;
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// let x: i32 = 92;
22// }
23// ```
24pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option<Assist> {
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let expr = stmt.initializer()?;
27 let pat = stmt.pat()?;
28 // Must be a binding
29 let pat = match pat {
30 ast::Pat::BindPat(bind_pat) => bind_pat,
31 _ => return None,
32 };
33 let pat_range = pat.syntax().text_range();
34 // The binding must have a name
35 let name = pat.name()?;
36 let name_range = name.syntax().text_range();
37 let stmt_range = stmt.syntax().text_range();
38 let eq_range = stmt.eq_token()?.text_range();
39 // Assist should only be applicable if cursor is between 'let' and '='
40 let let_range = TextRange::from_to(stmt_range.start(), eq_range.start());
41 let cursor_in_range = ctx.frange.range.is_subrange(&let_range);
42 if !cursor_in_range {
43 return None;
44 }
45 // Assist not applicable if the type has already been specified
46 // and it has no placeholders
47 let ascribed_ty = stmt.ascribed_type();
48 if let Some(ref ty) = ascribed_ty {
49 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() {
50 return None;
51 }
52 }
53 // Infer type
54 let db = ctx.db;
55 let analyzer = ctx.source_analyzer(stmt.syntax(), None);
56 let ty = analyzer.type_of(db, &expr)?;
57 // Assist not applicable if the type is unknown
58 if ty.contains_unknown() {
59 return None;
60 }
61
62 ctx.add_assist(
63 AssistId("add_explicit_type"),
64 format!("Insert explicit type '{}'", ty.display(db)),
65 |edit| {
66 edit.target(pat_range);
67 if let Some(ascribed_ty) = ascribed_ty {
68 edit.replace(ascribed_ty.syntax().text_range(), format!("{}", ty.display(db)));
69 } else {
70 edit.insert(name_range.end(), format!(": {}", ty.display(db)));
71 }
72 },
73 )
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
81
82 #[test]
83 fn add_explicit_type_target() {
84 check_assist_target(add_explicit_type, "fn f() { let a<|> = 1; }", "a");
85 }
86
87 #[test]
88 fn add_explicit_type_works_for_simple_expr() {
89 check_assist(
90 add_explicit_type,
91 "fn f() { let a<|> = 1; }",
92 "fn f() { let a<|>: i32 = 1; }",
93 );
94 }
95
96 #[test]
97 fn add_explicit_type_works_for_underscore() {
98 check_assist(
99 add_explicit_type,
100 "fn f() { let a<|>: _ = 1; }",
101 "fn f() { let a<|>: i32 = 1; }",
102 );
103 }
104
105 #[test]
106 fn add_explicit_type_works_for_nested_underscore() {
107 check_assist(
108 add_explicit_type,
109 r#"
110 enum Option<T> {
111 Some(T),
112 None
113 }
114
115 fn f() {
116 let a<|>: Option<_> = Option::Some(1);
117 }"#,
118 r#"
119 enum Option<T> {
120 Some(T),
121 None
122 }
123
124 fn f() {
125 let a<|>: Option<i32> = Option::Some(1);
126 }"#,
127 );
128 }
129
130 #[test]
131 fn add_explicit_type_works_for_macro_call() {
132 check_assist(
133 add_explicit_type,
134 "macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }",
135 "macro_rules! v { () => {0u64} } fn f() { let a<|>: u64 = v!(); }",
136 );
137 }
138
139 #[test]
140 fn add_explicit_type_works_for_macro_call_recursive() {
141 check_assist(
142 add_explicit_type,
143 "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }",
144 "macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|>: u64 = v!(); }",
145 );
146 }
147
148 #[test]
149 fn add_explicit_type_not_applicable_if_ty_not_inferred() {
150 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }");
151 }
152
153 #[test]
154 fn add_explicit_type_not_applicable_if_ty_already_specified() {
155 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }");
156 }
157
158 #[test]
159 fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() {
160 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }");
161 }
162
163 #[test]
164 fn add_explicit_type_not_applicable_if_cursor_after_equals() {
165 check_assist_not_applicable(
166 add_explicit_type,
167 "fn f() {let a =<|> match 1 {2 => 3, 3 => 5};}",
168 )
169 }
170
171 #[test]
172 fn add_explicit_type_not_applicable_if_cursor_before_let() {
173 check_assist_not_applicable(
174 add_explicit_type,
175 "fn f() <|>{let a = match 1 {2 => 3, 3 => 5};}",
176 )
177 }
178}
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
new file mode 100644
index 000000000..241b085fd
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_impl.rs
@@ -0,0 +1,94 @@
1use format_buf::format;
2
3use join_to_string::join;
4use ra_syntax::{
5 ast::{self, AstNode, NameOwner, TypeParamsOwner},
6 TextUnit,
7};
8
9use crate::{Assist, AssistCtx, AssistId};
10
11// Assist: add_impl
12//
13// Adds a new inherent impl for a type.
14//
15// ```
16// struct Ctx<T: Clone> {
17// data: T,<|>
18// }
19// ```
20// ->
21// ```
22// struct Ctx<T: Clone> {
23// data: T,
24// }
25//
26// impl<T: Clone> Ctx<T> {
27//
28// }
29// ```
30pub(crate) fn add_impl(ctx: AssistCtx) -> Option<Assist> {
31 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
32 let name = nominal.name()?;
33 ctx.add_assist(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), |edit| {
34 edit.target(nominal.syntax().text_range());
35 let type_params = nominal.type_param_list();
36 let start_offset = nominal.syntax().text_range().end();
37 let mut buf = String::new();
38 buf.push_str("\n\nimpl");
39 if let Some(type_params) = &type_params {
40 format!(buf, "{}", type_params.syntax());
41 }
42 buf.push_str(" ");
43 buf.push_str(name.text().as_str());
44 if let Some(type_params) = type_params {
45 let lifetime_params = type_params
46 .lifetime_params()
47 .filter_map(|it| it.lifetime_token())
48 .map(|it| it.text().clone());
49 let type_params =
50 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
51 join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf);
52 }
53 buf.push_str(" {\n");
54 edit.set_cursor(start_offset + TextUnit::of_str(&buf));
55 buf.push_str("\n}");
56 edit.insert(start_offset, buf);
57 })
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use crate::helpers::{check_assist, check_assist_target};
64
65 #[test]
66 fn test_add_impl() {
67 check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n");
68 check_assist(
69 add_impl,
70 "struct Foo<T: Clone> {<|>}",
71 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
72 );
73 check_assist(
74 add_impl,
75 "struct Foo<'a, T: Foo<'a>> {<|>}",
76 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}",
77 );
78 }
79
80 #[test]
81 fn add_impl_target() {
82 check_assist_target(
83 add_impl,
84 "
85struct SomeThingIrrelevant;
86/// Has a lifetime parameter
87struct Foo<'a, T: Foo<'a>> {<|>}
88struct EvenMoreIrrelevant;
89",
90 "/// Has a lifetime parameter
91struct Foo<'a, T: Foo<'a>> {}",
92 );
93 }
94}
diff --git a/crates/ra_assists/src/handlers/add_import.rs b/crates/ra_assists/src/handlers/add_import.rs
new file mode 100644
index 000000000..f03dddac8
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_import.rs
@@ -0,0 +1,967 @@
1use hir::{self, ModPath};
2use ra_syntax::{
3 ast::{self, NameOwner},
4 AstNode, Direction, SmolStr,
5 SyntaxKind::{PATH, PATH_SEGMENT},
6 SyntaxNode, TextRange, T,
7};
8use ra_text_edit::TextEditBuilder;
9
10use crate::{
11 assist_ctx::{Assist, AssistCtx},
12 AssistId,
13};
14
15/// This function produces sequence of text edits into edit
16/// to import the target path in the most appropriate scope given
17/// the cursor position
18pub fn auto_import_text_edit(
19 // Ideally the position of the cursor, used to
20 position: &SyntaxNode,
21 // The statement to use as anchor (last resort)
22 anchor: &SyntaxNode,
23 path_to_import: &ModPath,
24 edit: &mut TextEditBuilder,
25) {
26 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
27 let container = position.ancestors().find_map(|n| {
28 if let Some(module) = ast::Module::cast(n.clone()) {
29 return module.item_list().map(|it| it.syntax().clone());
30 }
31 ast::SourceFile::cast(n).map(|it| it.syntax().clone())
32 });
33
34 if let Some(container) = container {
35 let action = best_action_for_target(container, anchor.clone(), &target);
36 make_assist(&action, &target, edit);
37 }
38}
39
40// Assist: add_import
41//
42// Adds a use statement for a given fully-qualified path.
43//
44// ```
45// fn process(map: std::collections::<|>HashMap<String, String>) {}
46// ```
47// ->
48// ```
49// use std::collections::HashMap;
50//
51// fn process(map: HashMap<String, String>) {}
52// ```
53pub(crate) fn add_import(ctx: AssistCtx) -> Option<Assist> {
54 let path: ast::Path = ctx.find_node_at_offset()?;
55 // We don't want to mess with use statements
56 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
57 return None;
58 }
59
60 let hir_path = hir::Path::from_ast(path.clone())?;
61 let segments = collect_hir_path_segments(&hir_path)?;
62 if segments.len() < 2 {
63 return None;
64 }
65
66 let module = path.syntax().ancestors().find_map(ast::Module::cast);
67 let position = match module.and_then(|it| it.item_list()) {
68 Some(item_list) => item_list.syntax().clone(),
69 None => {
70 let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?;
71 current_file.syntax().clone()
72 }
73 };
74
75 ctx.add_assist(AssistId("add_import"), format!("Import {}", fmt_segments(&segments)), |edit| {
76 apply_auto_import(&position, &path, &segments, edit.text_edit_builder());
77 })
78}
79
80fn collect_path_segments_raw(
81 segments: &mut Vec<ast::PathSegment>,
82 mut path: ast::Path,
83) -> Option<usize> {
84 let oldlen = segments.len();
85 loop {
86 let mut children = path.syntax().children_with_tokens();
87 let (first, second, third) = (
88 children.next().map(|n| (n.clone(), n.kind())),
89 children.next().map(|n| (n.clone(), n.kind())),
90 children.next().map(|n| (n.clone(), n.kind())),
91 );
92 match (first, second, third) {
93 (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => {
94 path = ast::Path::cast(subpath.as_node()?.clone())?;
95 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
96 }
97 (Some((segment, PATH_SEGMENT)), _, _) => {
98 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
99 break;
100 }
101 (_, _, _) => return None,
102 }
103 }
104 // We need to reverse only the new added segments
105 let only_new_segments = segments.split_at_mut(oldlen).1;
106 only_new_segments.reverse();
107 Some(segments.len() - oldlen)
108}
109
110fn fmt_segments(segments: &[SmolStr]) -> String {
111 let mut buf = String::new();
112 fmt_segments_raw(segments, &mut buf);
113 buf
114}
115
116fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) {
117 let mut iter = segments.iter();
118 if let Some(s) = iter.next() {
119 buf.push_str(s);
120 }
121 for s in iter {
122 buf.push_str("::");
123 buf.push_str(s);
124 }
125}
126
127/// Returns the number of common segments.
128fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize {
129 left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count()
130}
131
132fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool {
133 if let Some(kb) = b.kind() {
134 match kb {
135 ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(),
136 ast::PathSegmentKind::SelfKw => a == "self",
137 ast::PathSegmentKind::SuperKw => a == "super",
138 ast::PathSegmentKind::CrateKw => a == "crate",
139 ast::PathSegmentKind::Type { .. } => false, // not allowed in imports
140 }
141 } else {
142 false
143 }
144}
145
146fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool {
147 a == b.text()
148}
149
150#[derive(Clone, Debug)]
151enum ImportAction {
152 Nothing,
153 // Add a brand new use statement.
154 AddNewUse {
155 anchor: Option<SyntaxNode>, // anchor node
156 add_after_anchor: bool,
157 },
158
159 // To split an existing use statement creating a nested import.
160 AddNestedImport {
161 // how may segments matched with the target path
162 common_segments: usize,
163 path_to_split: ast::Path,
164 // the first segment of path_to_split we want to add into the new nested list
165 first_segment_to_split: Option<ast::PathSegment>,
166 // Wether to add 'self' in addition to the target path
167 add_self: bool,
168 },
169 // To add the target path to an existing nested import tree list.
170 AddInTreeList {
171 common_segments: usize,
172 // The UseTreeList where to add the target path
173 tree_list: ast::UseTreeList,
174 add_self: bool,
175 },
176}
177
178impl ImportAction {
179 fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self {
180 ImportAction::AddNewUse { anchor, add_after_anchor }
181 }
182
183 fn add_nested_import(
184 common_segments: usize,
185 path_to_split: ast::Path,
186 first_segment_to_split: Option<ast::PathSegment>,
187 add_self: bool,
188 ) -> Self {
189 ImportAction::AddNestedImport {
190 common_segments,
191 path_to_split,
192 first_segment_to_split,
193 add_self,
194 }
195 }
196
197 fn add_in_tree_list(
198 common_segments: usize,
199 tree_list: ast::UseTreeList,
200 add_self: bool,
201 ) -> Self {
202 ImportAction::AddInTreeList { common_segments, tree_list, add_self }
203 }
204
205 fn better(left: ImportAction, right: ImportAction) -> ImportAction {
206 if left.is_better(&right) {
207 left
208 } else {
209 right
210 }
211 }
212
213 fn is_better(&self, other: &ImportAction) -> bool {
214 match (self, other) {
215 (ImportAction::Nothing, _) => true,
216 (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false,
217 (
218 ImportAction::AddNestedImport { common_segments: n, .. },
219 ImportAction::AddInTreeList { common_segments: m, .. },
220 )
221 | (
222 ImportAction::AddInTreeList { common_segments: n, .. },
223 ImportAction::AddNestedImport { common_segments: m, .. },
224 )
225 | (
226 ImportAction::AddInTreeList { common_segments: n, .. },
227 ImportAction::AddInTreeList { common_segments: m, .. },
228 )
229 | (
230 ImportAction::AddNestedImport { common_segments: n, .. },
231 ImportAction::AddNestedImport { common_segments: m, .. },
232 ) => n > m,
233 (ImportAction::AddInTreeList { .. }, _) => true,
234 (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false,
235 (ImportAction::AddNestedImport { .. }, _) => true,
236 (ImportAction::AddNewUse { .. }, _) => false,
237 }
238 }
239}
240
241// Find out the best ImportAction to import target path against current_use_tree.
242// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList.
243fn walk_use_tree_for_best_action(
244 current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments
245 current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import
246 current_use_tree: ast::UseTree, // the use tree we are currently examinating
247 target: &[SmolStr], // the path we want to import
248) -> ImportAction {
249 // We save the number of segments in the buffer so we can restore the correct segments
250 // before returning. Recursive call will add segments so we need to delete them.
251 let prev_len = current_path_segments.len();
252
253 let tree_list = current_use_tree.use_tree_list();
254 let alias = current_use_tree.alias();
255
256 let path = match current_use_tree.path() {
257 Some(path) => path,
258 None => {
259 // If the use item don't have a path, it means it's broken (syntax error)
260 return ImportAction::add_new_use(
261 current_use_tree
262 .syntax()
263 .ancestors()
264 .find_map(ast::UseItem::cast)
265 .map(|it| it.syntax().clone()),
266 true,
267 );
268 }
269 };
270
271 // This can happen only if current_use_tree is a direct child of a UseItem
272 if let Some(name) = alias.and_then(|it| it.name()) {
273 if compare_path_segment_with_name(&target[0], &name) {
274 return ImportAction::Nothing;
275 }
276 }
277
278 collect_path_segments_raw(current_path_segments, path.clone());
279
280 // We compare only the new segments added in the line just above.
281 // The first prev_len segments were already compared in 'parent' recursive calls.
282 let left = target.split_at(prev_len).1;
283 let right = current_path_segments.split_at(prev_len).1;
284 let common = compare_path_segments(left, &right);
285 let mut action = match common {
286 0 => ImportAction::add_new_use(
287 // e.g: target is std::fmt and we can have
288 // use foo::bar
289 // We add a brand new use statement
290 current_use_tree
291 .syntax()
292 .ancestors()
293 .find_map(ast::UseItem::cast)
294 .map(|it| it.syntax().clone()),
295 true,
296 ),
297 common if common == left.len() && left.len() == right.len() => {
298 // e.g: target is std::fmt and we can have
299 // 1- use std::fmt;
300 // 2- use std::fmt::{ ... }
301 if let Some(list) = tree_list {
302 // In case 2 we need to add self to the nested list
303 // unless it's already there
304 let has_self = list.use_trees().map(|it| it.path()).any(|p| {
305 p.and_then(|it| it.segment())
306 .and_then(|it| it.kind())
307 .filter(|k| *k == ast::PathSegmentKind::SelfKw)
308 .is_some()
309 });
310
311 if has_self {
312 ImportAction::Nothing
313 } else {
314 ImportAction::add_in_tree_list(current_path_segments.len(), list, true)
315 }
316 } else {
317 // Case 1
318 ImportAction::Nothing
319 }
320 }
321 common if common != left.len() && left.len() == right.len() => {
322 // e.g: target is std::fmt and we have
323 // use std::io;
324 // We need to split.
325 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
326 ImportAction::add_nested_import(
327 prev_len + common,
328 path,
329 Some(segments_to_split[0].clone()),
330 false,
331 )
332 }
333 common if common == right.len() && left.len() > right.len() => {
334 // e.g: target is std::fmt and we can have
335 // 1- use std;
336 // 2- use std::{ ... };
337
338 // fallback action
339 let mut better_action = ImportAction::add_new_use(
340 current_use_tree
341 .syntax()
342 .ancestors()
343 .find_map(ast::UseItem::cast)
344 .map(|it| it.syntax().clone()),
345 true,
346 );
347 if let Some(list) = tree_list {
348 // Case 2, check recursively if the path is already imported in the nested list
349 for u in list.use_trees() {
350 let child_action = walk_use_tree_for_best_action(
351 current_path_segments,
352 Some(list.clone()),
353 u,
354 target,
355 );
356 if child_action.is_better(&better_action) {
357 better_action = child_action;
358 if let ImportAction::Nothing = better_action {
359 return better_action;
360 }
361 }
362 }
363 } else {
364 // Case 1, split adding self
365 better_action = ImportAction::add_nested_import(prev_len + common, path, None, true)
366 }
367 better_action
368 }
369 common if common == left.len() && left.len() < right.len() => {
370 // e.g: target is std::fmt and we can have
371 // use std::fmt::Debug;
372 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
373 ImportAction::add_nested_import(
374 prev_len + common,
375 path,
376 Some(segments_to_split[0].clone()),
377 true,
378 )
379 }
380 common if common < left.len() && common < right.len() => {
381 // e.g: target is std::fmt::nested::Debug
382 // use std::fmt::Display
383 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
384 ImportAction::add_nested_import(
385 prev_len + common,
386 path,
387 Some(segments_to_split[0].clone()),
388 false,
389 )
390 }
391 _ => unreachable!(),
392 };
393
394 // If we are inside a UseTreeList adding a use statement become adding to the existing
395 // tree list.
396 action = match (current_parent_use_tree_list, action.clone()) {
397 (Some(use_tree_list), ImportAction::AddNewUse { .. }) => {
398 ImportAction::add_in_tree_list(prev_len, use_tree_list, false)
399 }
400 (_, _) => action,
401 };
402
403 // We remove the segments added
404 current_path_segments.truncate(prev_len);
405 action
406}
407
408fn best_action_for_target(
409 container: SyntaxNode,
410 anchor: SyntaxNode,
411 target: &[SmolStr],
412) -> ImportAction {
413 let mut storage = Vec::with_capacity(16); // this should be the only allocation
414 let best_action = container
415 .children()
416 .filter_map(ast::UseItem::cast)
417 .filter_map(|it| it.use_tree())
418 .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target))
419 .fold(None, |best, a| match best {
420 Some(best) => Some(ImportAction::better(best, a)),
421 None => Some(a),
422 });
423
424 match best_action {
425 Some(action) => action,
426 None => {
427 // We have no action and no UseItem was found in container so we find
428 // another item and we use it as anchor.
429 // If there are no items above, we choose the target path itself as anchor.
430 // todo: we should include even whitespace blocks as anchor candidates
431 let anchor = container
432 .children()
433 .find(|n| n.text_range().start() < anchor.text_range().start())
434 .or_else(|| Some(anchor));
435
436 ImportAction::add_new_use(anchor, false)
437 }
438 }
439}
440
441fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) {
442 match action {
443 ImportAction::AddNewUse { anchor, add_after_anchor } => {
444 make_assist_add_new_use(anchor, *add_after_anchor, target, edit)
445 }
446 ImportAction::AddInTreeList { common_segments, tree_list, add_self } => {
447 // We know that the fist n segments already exists in the use statement we want
448 // to modify, so we want to add only the last target.len() - n segments.
449 let segments_to_add = target.split_at(*common_segments).1;
450 make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit)
451 }
452 ImportAction::AddNestedImport {
453 common_segments,
454 path_to_split,
455 first_segment_to_split,
456 add_self,
457 } => {
458 let segments_to_add = target.split_at(*common_segments).1;
459 make_assist_add_nested_import(
460 path_to_split,
461 first_segment_to_split,
462 segments_to_add,
463 *add_self,
464 edit,
465 )
466 }
467 _ => {}
468 }
469}
470
471fn make_assist_add_new_use(
472 anchor: &Option<SyntaxNode>,
473 after: bool,
474 target: &[SmolStr],
475 edit: &mut TextEditBuilder,
476) {
477 if let Some(anchor) = anchor {
478 let indent = ra_fmt::leading_indent(anchor);
479 let mut buf = String::new();
480 if after {
481 buf.push_str("\n");
482 if let Some(spaces) = &indent {
483 buf.push_str(spaces);
484 }
485 }
486 buf.push_str("use ");
487 fmt_segments_raw(target, &mut buf);
488 buf.push_str(";");
489 if !after {
490 buf.push_str("\n\n");
491 if let Some(spaces) = &indent {
492 buf.push_str(&spaces);
493 }
494 }
495 let position = if after { anchor.text_range().end() } else { anchor.text_range().start() };
496 edit.insert(position, buf);
497 }
498}
499
500fn make_assist_add_in_tree_list(
501 tree_list: &ast::UseTreeList,
502 target: &[SmolStr],
503 add_self: bool,
504 edit: &mut TextEditBuilder,
505) {
506 let last = tree_list.use_trees().last();
507 if let Some(last) = last {
508 let mut buf = String::new();
509 let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]);
510 let offset = if let Some(comma) = comma {
511 comma.text_range().end()
512 } else {
513 buf.push_str(",");
514 last.syntax().text_range().end()
515 };
516 if add_self {
517 buf.push_str(" self")
518 } else {
519 buf.push_str(" ");
520 }
521 fmt_segments_raw(target, &mut buf);
522 edit.insert(offset, buf);
523 } else {
524 }
525}
526
527fn make_assist_add_nested_import(
528 path: &ast::Path,
529 first_segment_to_split: &Option<ast::PathSegment>,
530 target: &[SmolStr],
531 add_self: bool,
532 edit: &mut TextEditBuilder,
533) {
534 let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast);
535 if let Some(use_tree) = use_tree {
536 let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split
537 {
538 (first_segment_to_split.syntax().text_range().start(), false)
539 } else {
540 (use_tree.syntax().text_range().end(), true)
541 };
542 let end = use_tree.syntax().text_range().end();
543
544 let mut buf = String::new();
545 if add_colon_colon {
546 buf.push_str("::");
547 }
548 buf.push_str("{");
549 if add_self {
550 buf.push_str("self, ");
551 }
552 fmt_segments_raw(target, &mut buf);
553 if !target.is_empty() {
554 buf.push_str(", ");
555 }
556 edit.insert(start, buf);
557 edit.insert(end, "}".to_string());
558 }
559}
560
561fn apply_auto_import(
562 container: &SyntaxNode,
563 path: &ast::Path,
564 target: &[SmolStr],
565 edit: &mut TextEditBuilder,
566) {
567 let action = best_action_for_target(container.clone(), path.syntax().clone(), target);
568 make_assist(&action, target, edit);
569 if let Some(last) = path.segment() {
570 // Here we are assuming the assist will provide a correct use statement
571 // so we can delete the path qualifier
572 edit.delete(TextRange::from_to(
573 path.syntax().text_range().start(),
574 last.syntax().text_range().start(),
575 ));
576 }
577}
578
579fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
580 let mut ps = Vec::<SmolStr>::with_capacity(10);
581 match path.kind() {
582 hir::PathKind::Abs => ps.push("".into()),
583 hir::PathKind::Crate => ps.push("crate".into()),
584 hir::PathKind::Plain => {}
585 hir::PathKind::Super(0) => ps.push("self".into()),
586 hir::PathKind::Super(lvl) => {
587 let mut chain = "super".to_string();
588 for _ in 0..*lvl {
589 chain += "::super";
590 }
591 ps.push(chain.into());
592 }
593 hir::PathKind::DollarCrate(_) => return None,
594 }
595 ps.extend(path.segments().iter().map(|it| it.name.to_string().into()));
596 Some(ps)
597}
598
599#[cfg(test)]
600mod tests {
601 use crate::helpers::{check_assist, check_assist_not_applicable};
602
603 use super::*;
604
605 #[test]
606 fn test_auto_import_add_use_no_anchor() {
607 check_assist(
608 add_import,
609 "
610std::fmt::Debug<|>
611 ",
612 "
613use std::fmt::Debug;
614
615Debug<|>
616 ",
617 );
618 }
619 #[test]
620 fn test_auto_import_add_use_no_anchor_with_item_below() {
621 check_assist(
622 add_import,
623 "
624std::fmt::Debug<|>
625
626fn main() {
627}
628 ",
629 "
630use std::fmt::Debug;
631
632Debug<|>
633
634fn main() {
635}
636 ",
637 );
638 }
639
640 #[test]
641 fn test_auto_import_add_use_no_anchor_with_item_above() {
642 check_assist(
643 add_import,
644 "
645fn main() {
646}
647
648std::fmt::Debug<|>
649 ",
650 "
651use std::fmt::Debug;
652
653fn main() {
654}
655
656Debug<|>
657 ",
658 );
659 }
660
661 #[test]
662 fn test_auto_import_add_use_no_anchor_2seg() {
663 check_assist(
664 add_import,
665 "
666std::fmt<|>::Debug
667 ",
668 "
669use std::fmt;
670
671fmt<|>::Debug
672 ",
673 );
674 }
675
676 #[test]
677 fn test_auto_import_add_use() {
678 check_assist(
679 add_import,
680 "
681use stdx;
682
683impl std::fmt::Debug<|> for Foo {
684}
685 ",
686 "
687use stdx;
688use std::fmt::Debug;
689
690impl Debug<|> for Foo {
691}
692 ",
693 );
694 }
695
696 #[test]
697 fn test_auto_import_file_use_other_anchor() {
698 check_assist(
699 add_import,
700 "
701impl std::fmt::Debug<|> for Foo {
702}
703 ",
704 "
705use std::fmt::Debug;
706
707impl Debug<|> for Foo {
708}
709 ",
710 );
711 }
712
713 #[test]
714 fn test_auto_import_add_use_other_anchor_indent() {
715 check_assist(
716 add_import,
717 "
718 impl std::fmt::Debug<|> for Foo {
719 }
720 ",
721 "
722 use std::fmt::Debug;
723
724 impl Debug<|> for Foo {
725 }
726 ",
727 );
728 }
729
730 #[test]
731 fn test_auto_import_split_different() {
732 check_assist(
733 add_import,
734 "
735use std::fmt;
736
737impl std::io<|> for Foo {
738}
739 ",
740 "
741use std::{io, fmt};
742
743impl io<|> for Foo {
744}
745 ",
746 );
747 }
748
749 #[test]
750 fn test_auto_import_split_self_for_use() {
751 check_assist(
752 add_import,
753 "
754use std::fmt;
755
756impl std::fmt::Debug<|> for Foo {
757}
758 ",
759 "
760use std::fmt::{self, Debug, };
761
762impl Debug<|> for Foo {
763}
764 ",
765 );
766 }
767
768 #[test]
769 fn test_auto_import_split_self_for_target() {
770 check_assist(
771 add_import,
772 "
773use std::fmt::Debug;
774
775impl std::fmt<|> for Foo {
776}
777 ",
778 "
779use std::fmt::{self, Debug};
780
781impl fmt<|> for Foo {
782}
783 ",
784 );
785 }
786
787 #[test]
788 fn test_auto_import_add_to_nested_self_nested() {
789 check_assist(
790 add_import,
791 "
792use std::fmt::{Debug, nested::{Display}};
793
794impl std::fmt::nested<|> for Foo {
795}
796",
797 "
798use std::fmt::{Debug, nested::{Display, self}};
799
800impl nested<|> for Foo {
801}
802",
803 );
804 }
805
806 #[test]
807 fn test_auto_import_add_to_nested_self_already_included() {
808 check_assist(
809 add_import,
810 "
811use std::fmt::{Debug, nested::{self, Display}};
812
813impl std::fmt::nested<|> for Foo {
814}
815",
816 "
817use std::fmt::{Debug, nested::{self, Display}};
818
819impl nested<|> for Foo {
820}
821",
822 );
823 }
824
825 #[test]
826 fn test_auto_import_add_to_nested_nested() {
827 check_assist(
828 add_import,
829 "
830use std::fmt::{Debug, nested::{Display}};
831
832impl std::fmt::nested::Debug<|> for Foo {
833}
834",
835 "
836use std::fmt::{Debug, nested::{Display, Debug}};
837
838impl Debug<|> for Foo {
839}
840",
841 );
842 }
843
844 #[test]
845 fn test_auto_import_split_common_target_longer() {
846 check_assist(
847 add_import,
848 "
849use std::fmt::Debug;
850
851impl std::fmt::nested::Display<|> for Foo {
852}
853",
854 "
855use std::fmt::{nested::Display, Debug};
856
857impl Display<|> for Foo {
858}
859",
860 );
861 }
862
863 #[test]
864 fn test_auto_import_split_common_use_longer() {
865 check_assist(
866 add_import,
867 "
868use std::fmt::nested::Debug;
869
870impl std::fmt::Display<|> for Foo {
871}
872",
873 "
874use std::fmt::{Display, nested::Debug};
875
876impl Display<|> for Foo {
877}
878",
879 );
880 }
881
882 #[test]
883 fn test_auto_import_use_nested_import() {
884 check_assist(
885 add_import,
886 "
887use crate::{
888 ty::{Substs, Ty},
889 AssocItem,
890};
891
892fn foo() { crate::ty::lower<|>::trait_env() }
893",
894 "
895use crate::{
896 ty::{Substs, Ty, lower},
897 AssocItem,
898};
899
900fn foo() { lower<|>::trait_env() }
901",
902 );
903 }
904
905 #[test]
906 fn test_auto_import_alias() {
907 check_assist(
908 add_import,
909 "
910use std::fmt as foo;
911
912impl foo::Debug<|> for Foo {
913}
914",
915 "
916use std::fmt as foo;
917
918impl Debug<|> for Foo {
919}
920",
921 );
922 }
923
924 #[test]
925 fn test_auto_import_not_applicable_one_segment() {
926 check_assist_not_applicable(
927 add_import,
928 "
929impl foo<|> for Foo {
930}
931",
932 );
933 }
934
935 #[test]
936 fn test_auto_import_not_applicable_in_use() {
937 check_assist_not_applicable(
938 add_import,
939 "
940use std::fmt<|>;
941",
942 );
943 }
944
945 #[test]
946 fn test_auto_import_add_use_no_anchor_in_mod_mod() {
947 check_assist(
948 add_import,
949 "
950mod foo {
951 mod bar {
952 std::fmt::Debug<|>
953 }
954}
955 ",
956 "
957mod foo {
958 mod bar {
959 use std::fmt::Debug;
960
961 Debug<|>
962 }
963}
964 ",
965 );
966 }
967}
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
new file mode 100644
index 000000000..448697d31
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -0,0 +1,608 @@
1use hir::{db::HirDatabase, HasSource, InFile};
2use ra_syntax::{
3 ast::{self, edit, make, AstNode, NameOwner},
4 SmolStr,
5};
6
7use crate::{
8 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
9 Assist, AssistCtx, AssistId,
10};
11
12#[derive(PartialEq)]
13enum AddMissingImplMembersMode {
14 DefaultMethodsOnly,
15 NoDefaultMethods,
16}
17
18// Assist: add_impl_missing_members
19//
20// Adds scaffold for required impl members.
21//
22// ```
23// trait Trait<T> {
24// Type X;
25// fn foo(&self) -> T;
26// fn bar(&self) {}
27// }
28//
29// impl Trait<u32> for () {<|>
30//
31// }
32// ```
33// ->
34// ```
35// trait Trait<T> {
36// Type X;
37// fn foo(&self) -> T;
38// fn bar(&self) {}
39// }
40//
41// impl Trait<u32> for () {
42// fn foo(&self) -> u32 { unimplemented!() }
43//
44// }
45// ```
46pub(crate) fn add_missing_impl_members(ctx: AssistCtx) -> Option<Assist> {
47 add_missing_impl_members_inner(
48 ctx,
49 AddMissingImplMembersMode::NoDefaultMethods,
50 "add_impl_missing_members",
51 "Implement missing members",
52 )
53}
54
55// Assist: add_impl_default_members
56//
57// Adds scaffold for overriding default impl members.
58//
59// ```
60// trait Trait {
61// Type X;
62// fn foo(&self);
63// fn bar(&self) {}
64// }
65//
66// impl Trait for () {
67// Type X = ();
68// fn foo(&self) {}<|>
69//
70// }
71// ```
72// ->
73// ```
74// trait Trait {
75// Type X;
76// fn foo(&self);
77// fn bar(&self) {}
78// }
79//
80// impl Trait for () {
81// Type X = ();
82// fn foo(&self) {}
83// fn bar(&self) {}
84//
85// }
86// ```
87pub(crate) fn add_missing_default_members(ctx: AssistCtx) -> Option<Assist> {
88 add_missing_impl_members_inner(
89 ctx,
90 AddMissingImplMembersMode::DefaultMethodsOnly,
91 "add_impl_default_members",
92 "Implement default members",
93 )
94}
95
96fn add_missing_impl_members_inner(
97 ctx: AssistCtx,
98 mode: AddMissingImplMembersMode,
99 assist_id: &'static str,
100 label: &'static str,
101) -> Option<Assist> {
102 let _p = ra_prof::profile("add_missing_impl_members_inner");
103 let impl_node = ctx.find_node_at_offset::<ast::ImplBlock>()?;
104 let impl_item_list = impl_node.item_list()?;
105
106 let (trait_, trait_def) = {
107 let analyzer = ctx.source_analyzer(impl_node.syntax(), None);
108
109 resolve_target_trait_def(ctx.db, &analyzer, &impl_node)?
110 };
111
112 let def_name = |item: &ast::ImplItem| -> Option<SmolStr> {
113 match item {
114 ast::ImplItem::FnDef(def) => def.name(),
115 ast::ImplItem::TypeAliasDef(def) => def.name(),
116 ast::ImplItem::ConstDef(def) => def.name(),
117 }
118 .map(|it| it.text().clone())
119 };
120
121 let trait_items = trait_def.item_list()?.impl_items();
122 let impl_items = impl_item_list.impl_items().collect::<Vec<_>>();
123
124 let missing_items: Vec<_> = trait_items
125 .filter(|t| def_name(t).is_some())
126 .filter(|t| match t {
127 ast::ImplItem::FnDef(def) => match mode {
128 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
129 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
130 },
131 _ => mode == AddMissingImplMembersMode::NoDefaultMethods,
132 })
133 .filter(|t| impl_items.iter().all(|i| def_name(i) != def_name(t)))
134 .collect();
135 if missing_items.is_empty() {
136 return None;
137 }
138
139 let db = ctx.db;
140 let file_id = ctx.frange.file_id;
141 let trait_file_id = trait_.source(db).file_id;
142
143 ctx.add_assist(AssistId(assist_id), label, |edit| {
144 let n_existing_items = impl_item_list.impl_items().count();
145 let module = hir::SourceAnalyzer::new(
146 db,
147 hir::InFile::new(file_id.into(), impl_node.syntax()),
148 None,
149 )
150 .module();
151 let ast_transform = QualifyPaths::new(db, module)
152 .or(SubstituteTypeParams::for_trait_impl(db, trait_, impl_node));
153 let items = missing_items
154 .into_iter()
155 .map(|it| ast_transform::apply(&*ast_transform, InFile::new(trait_file_id, it)))
156 .map(|it| match it {
157 ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)),
158 _ => it,
159 })
160 .map(|it| edit::strip_attrs_and_docs(&it));
161 let new_impl_item_list = impl_item_list.append_items(items);
162 let cursor_position = {
163 let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap();
164 first_new_item.syntax().text_range().start()
165 };
166
167 edit.replace_ast(impl_item_list, new_impl_item_list);
168 edit.set_cursor(cursor_position);
169 })
170}
171
172fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
173 if fn_def.body().is_none() {
174 fn_def.with_body(make::block_from_expr(make::expr_unimplemented()))
175 } else {
176 fn_def
177 }
178}
179
180/// Given an `ast::ImplBlock`, resolves the target trait (the one being
181/// implemented) to a `ast::TraitDef`.
182fn resolve_target_trait_def(
183 db: &impl HirDatabase,
184 analyzer: &hir::SourceAnalyzer,
185 impl_block: &ast::ImplBlock,
186) -> Option<(hir::Trait, ast::TraitDef)> {
187 let ast_path = impl_block
188 .target_trait()
189 .map(|it| it.syntax().clone())
190 .and_then(ast::PathType::cast)?
191 .path()?;
192
193 match analyzer.resolve_path(db, &ast_path) {
194 Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => {
195 Some((def, def.source(db).value))
196 }
197 _ => None,
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::helpers::{check_assist, check_assist_not_applicable};
205
206 #[test]
207 fn test_add_missing_impl_members() {
208 check_assist(
209 add_missing_impl_members,
210 "
211trait Foo {
212 type Output;
213
214 const CONST: usize = 42;
215
216 fn foo(&self);
217 fn bar(&self);
218 fn baz(&self);
219}
220
221struct S;
222
223impl Foo for S {
224 fn bar(&self) {}
225<|>
226}",
227 "
228trait Foo {
229 type Output;
230
231 const CONST: usize = 42;
232
233 fn foo(&self);
234 fn bar(&self);
235 fn baz(&self);
236}
237
238struct S;
239
240impl Foo for S {
241 fn bar(&self) {}
242 <|>type Output;
243 const CONST: usize = 42;
244 fn foo(&self) { unimplemented!() }
245 fn baz(&self) { unimplemented!() }
246
247}",
248 );
249 }
250
251 #[test]
252 fn test_copied_overriden_members() {
253 check_assist(
254 add_missing_impl_members,
255 "
256trait Foo {
257 fn foo(&self);
258 fn bar(&self) -> bool { true }
259 fn baz(&self) -> u32 { 42 }
260}
261
262struct S;
263
264impl Foo for S {
265 fn bar(&self) {}
266<|>
267}",
268 "
269trait Foo {
270 fn foo(&self);
271 fn bar(&self) -> bool { true }
272 fn baz(&self) -> u32 { 42 }
273}
274
275struct S;
276
277impl Foo for S {
278 fn bar(&self) {}
279 <|>fn foo(&self) { unimplemented!() }
280
281}",
282 );
283 }
284
285 #[test]
286 fn test_empty_impl_block() {
287 check_assist(
288 add_missing_impl_members,
289 "
290trait Foo { fn foo(&self); }
291struct S;
292impl Foo for S { <|> }",
293 "
294trait Foo { fn foo(&self); }
295struct S;
296impl Foo for S {
297 <|>fn foo(&self) { unimplemented!() }
298}",
299 );
300 }
301
302 #[test]
303 fn fill_in_type_params_1() {
304 check_assist(
305 add_missing_impl_members,
306 "
307trait Foo<T> { fn foo(&self, t: T) -> &T; }
308struct S;
309impl Foo<u32> for S { <|> }",
310 "
311trait Foo<T> { fn foo(&self, t: T) -> &T; }
312struct S;
313impl Foo<u32> for S {
314 <|>fn foo(&self, t: u32) -> &u32 { unimplemented!() }
315}",
316 );
317 }
318
319 #[test]
320 fn fill_in_type_params_2() {
321 check_assist(
322 add_missing_impl_members,
323 "
324trait Foo<T> { fn foo(&self, t: T) -> &T; }
325struct S;
326impl<U> Foo<U> for S { <|> }",
327 "
328trait Foo<T> { fn foo(&self, t: T) -> &T; }
329struct S;
330impl<U> Foo<U> for S {
331 <|>fn foo(&self, t: U) -> &U { unimplemented!() }
332}",
333 );
334 }
335
336 #[test]
337 fn test_cursor_after_empty_impl_block() {
338 check_assist(
339 add_missing_impl_members,
340 "
341trait Foo { fn foo(&self); }
342struct S;
343impl Foo for S {}<|>",
344 "
345trait Foo { fn foo(&self); }
346struct S;
347impl Foo for S {
348 <|>fn foo(&self) { unimplemented!() }
349}",
350 )
351 }
352
353 #[test]
354 fn test_qualify_path_1() {
355 check_assist(
356 add_missing_impl_members,
357 "
358mod foo {
359 pub struct Bar;
360 trait Foo { fn foo(&self, bar: Bar); }
361}
362struct S;
363impl foo::Foo for S { <|> }",
364 "
365mod foo {
366 pub struct Bar;
367 trait Foo { fn foo(&self, bar: Bar); }
368}
369struct S;
370impl foo::Foo for S {
371 <|>fn foo(&self, bar: foo::Bar) { unimplemented!() }
372}",
373 );
374 }
375
376 #[test]
377 fn test_qualify_path_generic() {
378 check_assist(
379 add_missing_impl_members,
380 "
381mod foo {
382 pub struct Bar<T>;
383 trait Foo { fn foo(&self, bar: Bar<u32>); }
384}
385struct S;
386impl foo::Foo for S { <|> }",
387 "
388mod foo {
389 pub struct Bar<T>;
390 trait Foo { fn foo(&self, bar: Bar<u32>); }
391}
392struct S;
393impl foo::Foo for S {
394 <|>fn foo(&self, bar: foo::Bar<u32>) { unimplemented!() }
395}",
396 );
397 }
398
399 #[test]
400 fn test_qualify_path_and_substitute_param() {
401 check_assist(
402 add_missing_impl_members,
403 "
404mod foo {
405 pub struct Bar<T>;
406 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
407}
408struct S;
409impl foo::Foo<u32> for S { <|> }",
410 "
411mod foo {
412 pub struct Bar<T>;
413 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
414}
415struct S;
416impl foo::Foo<u32> for S {
417 <|>fn foo(&self, bar: foo::Bar<u32>) { unimplemented!() }
418}",
419 );
420 }
421
422 #[test]
423 fn test_substitute_param_no_qualify() {
424 // when substituting params, the substituted param should not be qualified!
425 check_assist(
426 add_missing_impl_members,
427 "
428mod foo {
429 trait Foo<T> { fn foo(&self, bar: T); }
430 pub struct Param;
431}
432struct Param;
433struct S;
434impl foo::Foo<Param> for S { <|> }",
435 "
436mod foo {
437 trait Foo<T> { fn foo(&self, bar: T); }
438 pub struct Param;
439}
440struct Param;
441struct S;
442impl foo::Foo<Param> for S {
443 <|>fn foo(&self, bar: Param) { unimplemented!() }
444}",
445 );
446 }
447
448 #[test]
449 fn test_qualify_path_associated_item() {
450 check_assist(
451 add_missing_impl_members,
452 "
453mod foo {
454 pub struct Bar<T>;
455 impl Bar<T> { type Assoc = u32; }
456 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
457}
458struct S;
459impl foo::Foo for S { <|> }",
460 "
461mod foo {
462 pub struct Bar<T>;
463 impl Bar<T> { type Assoc = u32; }
464 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
465}
466struct S;
467impl foo::Foo for S {
468 <|>fn foo(&self, bar: foo::Bar<u32>::Assoc) { unimplemented!() }
469}",
470 );
471 }
472
473 #[test]
474 fn test_qualify_path_nested() {
475 check_assist(
476 add_missing_impl_members,
477 "
478mod foo {
479 pub struct Bar<T>;
480 pub struct Baz;
481 trait Foo { fn foo(&self, bar: Bar<Baz>); }
482}
483struct S;
484impl foo::Foo for S { <|> }",
485 "
486mod foo {
487 pub struct Bar<T>;
488 pub struct Baz;
489 trait Foo { fn foo(&self, bar: Bar<Baz>); }
490}
491struct S;
492impl foo::Foo for S {
493 <|>fn foo(&self, bar: foo::Bar<foo::Baz>) { unimplemented!() }
494}",
495 );
496 }
497
498 #[test]
499 fn test_qualify_path_fn_trait_notation() {
500 check_assist(
501 add_missing_impl_members,
502 "
503mod foo {
504 pub trait Fn<Args> { type Output; }
505 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
506}
507struct S;
508impl foo::Foo for S { <|> }",
509 "
510mod foo {
511 pub trait Fn<Args> { type Output; }
512 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
513}
514struct S;
515impl foo::Foo for S {
516 <|>fn foo(&self, bar: dyn Fn(u32) -> i32) { unimplemented!() }
517}",
518 );
519 }
520
521 #[test]
522 fn test_empty_trait() {
523 check_assist_not_applicable(
524 add_missing_impl_members,
525 "
526trait Foo;
527struct S;
528impl Foo for S { <|> }",
529 )
530 }
531
532 #[test]
533 fn test_ignore_unnamed_trait_members_and_default_methods() {
534 check_assist_not_applicable(
535 add_missing_impl_members,
536 "
537trait Foo {
538 fn (arg: u32);
539 fn valid(some: u32) -> bool { false }
540}
541struct S;
542impl Foo for S { <|> }",
543 )
544 }
545
546 #[test]
547 fn test_with_docstring_and_attrs() {
548 check_assist(
549 add_missing_impl_members,
550 r#"
551#[doc(alias = "test alias")]
552trait Foo {
553 /// doc string
554 type Output;
555
556 #[must_use]
557 fn foo(&self);
558}
559struct S;
560impl Foo for S {}<|>"#,
561 r#"
562#[doc(alias = "test alias")]
563trait Foo {
564 /// doc string
565 type Output;
566
567 #[must_use]
568 fn foo(&self);
569}
570struct S;
571impl Foo for S {
572 <|>type Output;
573 fn foo(&self) { unimplemented!() }
574}"#,
575 )
576 }
577
578 #[test]
579 fn test_default_methods() {
580 check_assist(
581 add_missing_default_members,
582 "
583trait Foo {
584 type Output;
585
586 const CONST: usize = 42;
587
588 fn valid(some: u32) -> bool { false }
589 fn foo(some: u32) -> bool;
590}
591struct S;
592impl Foo for S { <|> }",
593 "
594trait Foo {
595 type Output;
596
597 const CONST: usize = 42;
598
599 fn valid(some: u32) -> bool { false }
600 fn foo(some: u32) -> bool;
601}
602struct S;
603impl Foo for S {
604 <|>fn valid(some: u32) -> bool { false }
605}",
606 )
607 }
608}
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs
new file mode 100644
index 000000000..a08639311
--- /dev/null
+++ b/crates/ra_assists/src/handlers/add_new.rs
@@ -0,0 +1,430 @@
1use format_buf::format;
2use hir::InFile;
3use join_to_string::join;
4use ra_syntax::{
5 ast::{
6 self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
7 },
8 TextUnit, T,
9};
10use std::fmt::Write;
11
12use crate::{Assist, AssistCtx, AssistId};
13
14// Assist: add_new
15//
16// Adds a new inherent impl for a type.
17//
18// ```
19// struct Ctx<T: Clone> {
20// data: T,<|>
21// }
22// ```
23// ->
24// ```
25// struct Ctx<T: Clone> {
26// data: T,
27// }
28//
29// impl<T: Clone> Ctx<T> {
30// fn new(data: T) -> Self { Self { data } }
31// }
32//
33// ```
34pub(crate) fn add_new(ctx: AssistCtx) -> Option<Assist> {
35 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
36
37 // We want to only apply this to non-union structs with named fields
38 let field_list = match strukt.kind() {
39 StructKind::Record(named) => named,
40 _ => return None,
41 };
42
43 // Return early if we've found an existing new fn
44 let impl_block = find_struct_impl(&ctx, &strukt)?;
45
46 ctx.add_assist(AssistId("add_new"), "Add default constructor", |edit| {
47 edit.target(strukt.syntax().text_range());
48
49 let mut buf = String::with_capacity(512);
50
51 if impl_block.is_some() {
52 buf.push('\n');
53 }
54
55 let vis = strukt.visibility().map(|v| format!("{} ", v.syntax()));
56 let vis = vis.as_ref().map(String::as_str).unwrap_or("");
57 write!(&mut buf, " {}fn new(", vis).unwrap();
58
59 join(field_list.fields().filter_map(|f| {
60 Some(format!("{}: {}", f.name()?.syntax().text(), f.ascribed_type()?.syntax().text()))
61 }))
62 .separator(", ")
63 .to_buf(&mut buf);
64
65 buf.push_str(") -> Self { Self {");
66
67 join(field_list.fields().filter_map(|f| Some(f.name()?.syntax().text())))
68 .separator(", ")
69 .surround_with(" ", " ")
70 .to_buf(&mut buf);
71
72 buf.push_str("} }");
73
74 let (start_offset, end_offset) = impl_block
75 .and_then(|impl_block| {
76 buf.push('\n');
77 let start = impl_block
78 .syntax()
79 .descendants_with_tokens()
80 .find(|t| t.kind() == T!['{'])?
81 .text_range()
82 .end();
83
84 Some((start, TextUnit::from_usize(1)))
85 })
86 .unwrap_or_else(|| {
87 buf = generate_impl_text(&strukt, &buf);
88 let start = strukt.syntax().text_range().end();
89
90 (start, TextUnit::from_usize(3))
91 });
92
93 edit.set_cursor(start_offset + TextUnit::of_str(&buf) - end_offset);
94 edit.insert(start_offset, buf);
95 })
96}
97
98// Generates the surrounding `impl Type { <code> }` including type and lifetime
99// parameters
100fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
101 let type_params = strukt.type_param_list();
102 let mut buf = String::with_capacity(code.len());
103 buf.push_str("\n\nimpl");
104 if let Some(type_params) = &type_params {
105 format!(buf, "{}", type_params.syntax());
106 }
107 buf.push_str(" ");
108 buf.push_str(strukt.name().unwrap().text().as_str());
109 if let Some(type_params) = type_params {
110 let lifetime_params = type_params
111 .lifetime_params()
112 .filter_map(|it| it.lifetime_token())
113 .map(|it| it.text().clone());
114 let type_params =
115 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
116 join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf);
117 }
118
119 format!(&mut buf, " {{\n{}\n}}\n", code);
120
121 buf
122}
123
124// Uses a syntax-driven approach to find any impl blocks for the struct that
125// exist within the module/file
126//
127// Returns `None` if we've found an existing `new` fn
128//
129// FIXME: change the new fn checking to a more semantic approach when that's more
130// viable (e.g. we process proc macros, etc)
131fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<ast::ImplBlock>> {
132 let db = ctx.db;
133 let module = strukt.syntax().ancestors().find(|node| {
134 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
135 })?;
136 let mut sb = ctx.source_binder();
137
138 let struct_ty = {
139 let src = InFile { file_id: ctx.frange.file_id.into(), value: strukt.clone() };
140 sb.to_def(src)?.ty(db)
141 };
142
143 let block = module.descendants().filter_map(ast::ImplBlock::cast).find_map(|impl_blk| {
144 let src = InFile { file_id: ctx.frange.file_id.into(), value: impl_blk.clone() };
145 let blk = sb.to_def(src)?;
146
147 let same_ty = blk.target_ty(db) == struct_ty;
148 let not_trait_impl = blk.target_trait(db).is_none();
149
150 if !(same_ty && not_trait_impl) {
151 None
152 } else {
153 Some(impl_blk)
154 }
155 });
156
157 if let Some(ref impl_blk) = block {
158 if has_new_fn(impl_blk) {
159 return None;
160 }
161 }
162
163 Some(block)
164}
165
166fn has_new_fn(imp: &ast::ImplBlock) -> bool {
167 if let Some(il) = imp.item_list() {
168 for item in il.impl_items() {
169 if let ast::ImplItem::FnDef(f) = item {
170 if let Some(name) = f.name() {
171 if name.text().eq_ignore_ascii_case("new") {
172 return true;
173 }
174 }
175 }
176 }
177 }
178
179 false
180}
181
182#[cfg(test)]
183mod tests {
184 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
185
186 use super::*;
187
188 #[test]
189 #[rustfmt::skip]
190 fn test_add_new() {
191 // Check output of generation
192 check_assist(
193 add_new,
194"struct Foo {<|>}",
195"struct Foo {}
196
197impl Foo {
198 fn new() -> Self { Self { } }<|>
199}
200",
201 );
202 check_assist(
203 add_new,
204"struct Foo<T: Clone> {<|>}",
205"struct Foo<T: Clone> {}
206
207impl<T: Clone> Foo<T> {
208 fn new() -> Self { Self { } }<|>
209}
210",
211 );
212 check_assist(
213 add_new,
214"struct Foo<'a, T: Foo<'a>> {<|>}",
215"struct Foo<'a, T: Foo<'a>> {}
216
217impl<'a, T: Foo<'a>> Foo<'a, T> {
218 fn new() -> Self { Self { } }<|>
219}
220",
221 );
222 check_assist(
223 add_new,
224"struct Foo { baz: String <|>}",
225"struct Foo { baz: String }
226
227impl Foo {
228 fn new(baz: String) -> Self { Self { baz } }<|>
229}
230",
231 );
232 check_assist(
233 add_new,
234"struct Foo { baz: String, qux: Vec<i32> <|>}",
235"struct Foo { baz: String, qux: Vec<i32> }
236
237impl Foo {
238 fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|>
239}
240",
241 );
242
243 // Check that visibility modifiers don't get brought in for fields
244 check_assist(
245 add_new,
246"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
247"struct Foo { pub baz: String, pub qux: Vec<i32> }
248
249impl Foo {
250 fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|>
251}
252",
253 );
254
255 // Check that it reuses existing impls
256 check_assist(
257 add_new,
258"struct Foo {<|>}
259
260impl Foo {}
261",
262"struct Foo {}
263
264impl Foo {
265 fn new() -> Self { Self { } }<|>
266}
267",
268 );
269 check_assist(
270 add_new,
271"struct Foo {<|>}
272
273impl Foo {
274 fn qux(&self) {}
275}
276",
277"struct Foo {}
278
279impl Foo {
280 fn new() -> Self { Self { } }<|>
281
282 fn qux(&self) {}
283}
284",
285 );
286
287 check_assist(
288 add_new,
289"struct Foo {<|>}
290
291impl Foo {
292 fn qux(&self) {}
293 fn baz() -> i32 {
294 5
295 }
296}
297",
298"struct Foo {}
299
300impl Foo {
301 fn new() -> Self { Self { } }<|>
302
303 fn qux(&self) {}
304 fn baz() -> i32 {
305 5
306 }
307}
308",
309 );
310
311 // Check visibility of new fn based on struct
312 check_assist(
313 add_new,
314"pub struct Foo {<|>}",
315"pub struct Foo {}
316
317impl Foo {
318 pub fn new() -> Self { Self { } }<|>
319}
320",
321 );
322 check_assist(
323 add_new,
324"pub(crate) struct Foo {<|>}",
325"pub(crate) struct Foo {}
326
327impl Foo {
328 pub(crate) fn new() -> Self { Self { } }<|>
329}
330",
331 );
332 }
333
334 #[test]
335 fn add_new_not_applicable_if_fn_exists() {
336 check_assist_not_applicable(
337 add_new,
338 "
339struct Foo {<|>}
340
341impl Foo {
342 fn new() -> Self {
343 Self
344 }
345}",
346 );
347
348 check_assist_not_applicable(
349 add_new,
350 "
351struct Foo {<|>}
352
353impl Foo {
354 fn New() -> Self {
355 Self
356 }
357}",
358 );
359 }
360
361 #[test]
362 fn add_new_target() {
363 check_assist_target(
364 add_new,
365 "
366struct SomeThingIrrelevant;
367/// Has a lifetime parameter
368struct Foo<'a, T: Foo<'a>> {<|>}
369struct EvenMoreIrrelevant;
370",
371 "/// Has a lifetime parameter
372struct Foo<'a, T: Foo<'a>> {}",
373 );
374 }
375
376 #[test]
377 fn test_unrelated_new() {
378 check_assist(
379 add_new,
380 r##"
381pub struct AstId<N: AstNode> {
382 file_id: HirFileId,
383 file_ast_id: FileAstId<N>,
384}
385
386impl<N: AstNode> AstId<N> {
387 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
388 AstId { file_id, file_ast_id }
389 }
390}
391
392pub struct Source<T> {
393 pub file_id: HirFileId,<|>
394 pub ast: T,
395}
396
397impl<T> Source<T> {
398 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
399 Source { file_id: self.file_id, ast: f(self.ast) }
400 }
401}
402"##,
403 r##"
404pub struct AstId<N: AstNode> {
405 file_id: HirFileId,
406 file_ast_id: FileAstId<N>,
407}
408
409impl<N: AstNode> AstId<N> {
410 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
411 AstId { file_id, file_ast_id }
412 }
413}
414
415pub struct Source<T> {
416 pub file_id: HirFileId,
417 pub ast: T,
418}
419
420impl<T> Source<T> {
421 pub fn new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }<|>
422
423 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
424 Source { file_id: self.file_id, ast: f(self.ast) }
425 }
426}
427"##,
428 );
429 }
430}
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
new file mode 100644
index 000000000..239807e24
--- /dev/null
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -0,0 +1,89 @@
1use ra_syntax::ast::{self, AstNode};
2
3use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
4
5// Assist: apply_demorgan
6//
7// Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws).
8// This transforms expressions of the form `!l || !r` into `!(l && r)`.
9// This also works with `&&`. This assist can only be applied with the cursor
10// on either `||` or `&&`, with both operands being a negation of some kind.
11// This means something of the form `!x` or `x != y`.
12//
13// ```
14// fn main() {
15// if x != 4 ||<|> !y {}
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// if !(x == 4 && y) {}
22// }
23// ```
24pub(crate) fn apply_demorgan(ctx: AssistCtx) -> Option<Assist> {
25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
26 let op = expr.op_kind()?;
27 let op_range = expr.op_token()?.text_range();
28 let opposite_op = opposite_logic_op(op)?;
29 let cursor_in_range = ctx.frange.range.is_subrange(&op_range);
30 if !cursor_in_range {
31 return None;
32 }
33
34 let lhs = expr.lhs()?;
35 let lhs_range = lhs.syntax().text_range();
36 let not_lhs = invert_boolean_expression(lhs);
37
38 let rhs = expr.rhs()?;
39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs);
41
42 ctx.add_assist(AssistId("apply_demorgan"), "Apply De Morgan's law", |edit| {
43 edit.target(op_range);
44 edit.replace(op_range, opposite_op);
45 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
46 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
47 })
48}
49
50// Return the opposite text for a given logical operator, if it makes sense
51fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
52 match kind {
53 ast::BinOp::BooleanOr => Some("&&"),
54 ast::BinOp::BooleanAnd => Some("||"),
55 _ => None,
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 use crate::helpers::{check_assist, check_assist_not_applicable};
64
65 #[test]
66 fn demorgan_turns_and_into_or() {
67 check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x ||<|> x) }")
68 }
69
70 #[test]
71 fn demorgan_turns_or_into_and() {
72 check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x &&<|> x) }")
73 }
74
75 #[test]
76 fn demorgan_removes_inequality() {
77 check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x &&<|> x) }")
78 }
79
80 #[test]
81 fn demorgan_general_case() {
82 check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x &&<|> !x) }")
83 }
84
85 #[test]
86 fn demorgan_doesnt_apply_with_cursor_not_on_op() {
87 check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }")
88 }
89}
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
new file mode 100644
index 000000000..84b5474f9
--- /dev/null
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -0,0 +1,258 @@
1use hir::ModPath;
2use ra_ide_db::imports_locator::ImportsLocator;
3use ra_syntax::{
4 ast::{self, AstNode},
5 SyntaxNode,
6};
7
8use crate::{
9 assist_ctx::{ActionBuilder, Assist, AssistCtx},
10 auto_import_text_edit, AssistId,
11};
12use std::collections::BTreeSet;
13
14// Assist: auto_import
15//
16// If the name is unresolved, provides all possible imports for it.
17//
18// ```
19// fn main() {
20// let map = HashMap<|>::new();
21// }
22// # pub mod std { pub mod collections { pub struct HashMap { } } }
23// ```
24// ->
25// ```
26// use std::collections::HashMap;
27//
28// fn main() {
29// let map = HashMap::new();
30// }
31// # pub mod std { pub mod collections { pub struct HashMap { } } }
32// ```
33pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
34 let path_to_import: ast::Path = ctx.find_node_at_offset()?;
35 let path_to_import_syntax = path_to_import.syntax();
36 if path_to_import_syntax.ancestors().find_map(ast::UseItem::cast).is_some() {
37 return None;
38 }
39 let name_to_import =
40 path_to_import_syntax.descendants().find_map(ast::NameRef::cast)?.syntax().to_string();
41
42 let module = path_to_import_syntax.ancestors().find_map(ast::Module::cast);
43 let position = match module.and_then(|it| it.item_list()) {
44 Some(item_list) => item_list.syntax().clone(),
45 None => {
46 let current_file = path_to_import_syntax.ancestors().find_map(ast::SourceFile::cast)?;
47 current_file.syntax().clone()
48 }
49 };
50 let source_analyzer = ctx.source_analyzer(&position, None);
51 let module_with_name_to_import = source_analyzer.module()?;
52 if source_analyzer.resolve_path(ctx.db, &path_to_import).is_some() {
53 return None;
54 }
55
56 let mut imports_locator = ImportsLocator::new(ctx.db);
57
58 let proposed_imports = imports_locator
59 .find_imports(&name_to_import)
60 .into_iter()
61 .filter_map(|module_def| module_with_name_to_import.find_use_path(ctx.db, module_def))
62 .filter(|use_path| !use_path.segments.is_empty())
63 .take(20)
64 .collect::<BTreeSet<_>>();
65
66 if proposed_imports.is_empty() {
67 return None;
68 }
69
70 ctx.add_assist_group(AssistId("auto_import"), format!("Import {}", name_to_import), || {
71 proposed_imports
72 .into_iter()
73 .map(|import| import_to_action(import, &position, &path_to_import_syntax))
74 .collect()
75 })
76}
77
78fn import_to_action(import: ModPath, position: &SyntaxNode, anchor: &SyntaxNode) -> ActionBuilder {
79 let mut action_builder = ActionBuilder::default();
80 action_builder.label(format!("Import `{}`", &import));
81 auto_import_text_edit(position, anchor, &import, action_builder.text_edit_builder());
82 action_builder
83}
84
85#[cfg(test)]
86mod tests {
87 use crate::helpers::{check_assist, check_assist_not_applicable};
88
89 use super::*;
90
91 #[test]
92 fn applicable_when_found_an_import() {
93 check_assist(
94 auto_import,
95 r"
96 <|>PubStruct
97
98 pub mod PubMod {
99 pub struct PubStruct;
100 }
101 ",
102 r"
103 <|>use PubMod::PubStruct;
104
105 PubStruct
106
107 pub mod PubMod {
108 pub struct PubStruct;
109 }
110 ",
111 );
112 }
113
114 #[test]
115 fn auto_imports_are_merged() {
116 check_assist(
117 auto_import,
118 r"
119 use PubMod::PubStruct1;
120
121 struct Test {
122 test: Pub<|>Struct2<u8>,
123 }
124
125 pub mod PubMod {
126 pub struct PubStruct1;
127 pub struct PubStruct2<T> {
128 _t: T,
129 }
130 }
131 ",
132 r"
133 use PubMod::{PubStruct2, PubStruct1};
134
135 struct Test {
136 test: Pub<|>Struct2<u8>,
137 }
138
139 pub mod PubMod {
140 pub struct PubStruct1;
141 pub struct PubStruct2<T> {
142 _t: T,
143 }
144 }
145 ",
146 );
147 }
148
149 #[test]
150 fn applicable_when_found_multiple_imports() {
151 check_assist(
152 auto_import,
153 r"
154 PubSt<|>ruct
155
156 pub mod PubMod1 {
157 pub struct PubStruct;
158 }
159 pub mod PubMod2 {
160 pub struct PubStruct;
161 }
162 pub mod PubMod3 {
163 pub struct PubStruct;
164 }
165 ",
166 r"
167 use PubMod1::PubStruct;
168
169 PubSt<|>ruct
170
171 pub mod PubMod1 {
172 pub struct PubStruct;
173 }
174 pub mod PubMod2 {
175 pub struct PubStruct;
176 }
177 pub mod PubMod3 {
178 pub struct PubStruct;
179 }
180 ",
181 );
182 }
183
184 #[test]
185 fn not_applicable_for_already_imported_types() {
186 check_assist_not_applicable(
187 auto_import,
188 r"
189 use PubMod::PubStruct;
190
191 PubStruct<|>
192
193 pub mod PubMod {
194 pub struct PubStruct;
195 }
196 ",
197 );
198 }
199
200 #[test]
201 fn not_applicable_for_types_with_private_paths() {
202 check_assist_not_applicable(
203 auto_import,
204 r"
205 PrivateStruct<|>
206
207 pub mod PubMod {
208 struct PrivateStruct;
209 }
210 ",
211 );
212 }
213
214 #[test]
215 fn not_applicable_when_no_imports_found() {
216 check_assist_not_applicable(
217 auto_import,
218 "
219 PubStruct<|>",
220 );
221 }
222
223 #[test]
224 fn not_applicable_in_import_statements() {
225 check_assist_not_applicable(
226 auto_import,
227 r"
228 use PubStruct<|>;
229
230 pub mod PubMod {
231 pub struct PubStruct;
232 }",
233 );
234 }
235
236 #[test]
237 fn function_import() {
238 check_assist(
239 auto_import,
240 r"
241 test_function<|>
242
243 pub mod PubMod {
244 pub fn test_function() {};
245 }
246 ",
247 r"
248 use PubMod::test_function;
249
250 test_function<|>
251
252 pub mod PubMod {
253 pub fn test_function() {};
254 }
255 ",
256 );
257 }
258}
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
new file mode 100644
index 000000000..f325b6f92
--- /dev/null
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -0,0 +1,167 @@
1use ra_syntax::{
2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode,
4 SyntaxKind::{
5 ATTR, COMMENT, ENUM_DEF, FN_DEF, IDENT, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY,
6 WHITESPACE,
7 },
8 SyntaxNode, TextUnit, T,
9};
10
11use crate::{Assist, AssistCtx, AssistId};
12
13// Assist: change_visibility
14//
15// Adds or changes existing visibility specifier.
16//
17// ```
18// <|>fn frobnicate() {}
19// ```
20// ->
21// ```
22// pub(crate) fn frobnicate() {}
23// ```
24pub(crate) fn change_visibility(ctx: AssistCtx) -> Option<Assist> {
25 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
26 return change_vis(ctx, vis);
27 }
28 add_vis(ctx)
29}
30
31fn add_vis(ctx: AssistCtx) -> Option<Assist> {
32 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() {
33 T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true,
34 _ => false,
35 });
36
37 let (offset, target) = if let Some(keyword) = item_keyword {
38 let parent = keyword.parent();
39 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
40 // Parent is not a definition, can't add visibility
41 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
42 return None;
43 }
44 // Already have visibility, do nothing
45 if parent.children().any(|child| child.kind() == VISIBILITY) {
46 return None;
47 }
48 (vis_offset(&parent), keyword.text_range())
49 } else {
50 let ident = ctx.token_at_offset().find(|leaf| leaf.kind() == IDENT)?;
51 let field = ident.parent().ancestors().find_map(ast::RecordFieldDef::cast)?;
52 if field.name()?.syntax().text_range() != ident.text_range() && field.visibility().is_some()
53 {
54 return None;
55 }
56 (vis_offset(field.syntax()), ident.text_range())
57 };
58
59 ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| {
60 edit.target(target);
61 edit.insert(offset, "pub(crate) ");
62 edit.set_cursor(offset);
63 })
64}
65
66fn vis_offset(node: &SyntaxNode) -> TextUnit {
67 node.children_with_tokens()
68 .skip_while(|it| match it.kind() {
69 WHITESPACE | COMMENT | ATTR => true,
70 _ => false,
71 })
72 .next()
73 .map(|it| it.text_range().start())
74 .unwrap_or_else(|| node.text_range().start())
75}
76
77fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
78 if vis.syntax().text() == "pub" {
79 return ctx.add_assist(
80 AssistId("change_visibility"),
81 "Change Visibility to pub(crate)",
82 |edit| {
83 edit.target(vis.syntax().text_range());
84 edit.replace(vis.syntax().text_range(), "pub(crate)");
85 edit.set_cursor(vis.syntax().text_range().start())
86 },
87 );
88 }
89 if vis.syntax().text() == "pub(crate)" {
90 return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| {
91 edit.target(vis.syntax().text_range());
92 edit.replace(vis.syntax().text_range(), "pub");
93 edit.set_cursor(vis.syntax().text_range().start());
94 });
95 }
96 None
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::helpers::{check_assist, check_assist_target};
103
104 #[test]
105 fn change_visibility_adds_pub_crate_to_items() {
106 check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}");
107 check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}");
108 check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}");
109 check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}");
110 check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}");
111 check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}");
112 check_assist(
113 change_visibility,
114 "unsafe f<|>n foo() {}",
115 "<|>pub(crate) unsafe fn foo() {}",
116 );
117 }
118
119 #[test]
120 fn change_visibility_works_with_struct_fields() {
121 check_assist(
122 change_visibility,
123 "struct S { <|>field: u32 }",
124 "struct S { <|>pub(crate) field: u32 }",
125 )
126 }
127
128 #[test]
129 fn change_visibility_pub_to_pub_crate() {
130 check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}")
131 }
132
133 #[test]
134 fn change_visibility_pub_crate_to_pub() {
135 check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}")
136 }
137
138 #[test]
139 fn change_visibility_handles_comment_attrs() {
140 check_assist(
141 change_visibility,
142 "
143 /// docs
144
145 // comments
146
147 #[derive(Debug)]
148 <|>struct Foo;
149 ",
150 "
151 /// docs
152
153 // comments
154
155 #[derive(Debug)]
156 <|>pub(crate) struct Foo;
157 ",
158 )
159 }
160
161 #[test]
162 fn change_visibility_target() {
163 check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
164 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");
165 check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field");
166 }
167}
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
new file mode 100644
index 000000000..22f88884f
--- /dev/null
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -0,0 +1,505 @@
1use std::{iter::once, ops::RangeInclusive};
2
3use ra_syntax::{
4 algo::replace_children,
5 ast::{self, edit::IndentLevel, make, Block, Pat::TupleStructPat},
6 AstNode,
7 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
8 SyntaxNode,
9};
10
11use crate::{
12 assist_ctx::{Assist, AssistCtx},
13 utils::invert_boolean_expression,
14 AssistId,
15};
16
17// Assist: convert_to_guarded_return
18//
19// Replace a large conditional with a guarded return.
20//
21// ```
22// fn main() {
23// <|>if cond {
24// foo();
25// bar();
26// }
27// }
28// ```
29// ->
30// ```
31// fn main() {
32// if !cond {
33// return;
34// }
35// foo();
36// bar();
37// }
38// ```
39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx) -> Option<Assist> {
40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
41 if if_expr.else_branch().is_some() {
42 return None;
43 }
44
45 let cond = if_expr.condition()?;
46
47 // Check if there is an IfLet that we can handle.
48 let if_let_pat = match cond.pat() {
49 None => None, // No IfLet, supported.
50 Some(TupleStructPat(pat)) if pat.args().count() == 1 => {
51 let path = pat.path()?;
52 match path.qualifier() {
53 None => {
54 let bound_ident = pat.args().next().unwrap();
55 Some((path, bound_ident))
56 }
57 Some(_) => return None,
58 }
59 }
60 Some(_) => return None, // Unsupported IfLet.
61 };
62
63 let cond_expr = cond.expr()?;
64 let then_block = if_expr.then_branch()?.block()?;
65
66 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::Block::cast)?;
67
68 if parent_block.expr()? != if_expr.clone().into() {
69 return None;
70 }
71
72 // check for early return and continue
73 let first_in_then_block = then_block.syntax().first_child()?;
74 if ast::ReturnExpr::can_cast(first_in_then_block.kind())
75 || ast::ContinueExpr::can_cast(first_in_then_block.kind())
76 || first_in_then_block
77 .children()
78 .any(|x| ast::ReturnExpr::can_cast(x.kind()) || ast::ContinueExpr::can_cast(x.kind()))
79 {
80 return None;
81 }
82
83 let parent_container = parent_block.syntax().parent()?.parent()?;
84
85 let early_expression: ast::Expr = match parent_container.kind() {
86 WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
87 FN_DEF => make::expr_return(),
88 _ => return None,
89 };
90
91 if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() {
92 return None;
93 }
94
95 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
96 let cursor_position = ctx.frange.range.start();
97
98 ctx.add_assist(AssistId("convert_to_guarded_return"), "Convert to guarded return", |edit| {
99 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
100 let new_block = match if_let_pat {
101 None => {
102 // If.
103 let new_expr = {
104 let then_branch =
105 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
106 let cond = invert_boolean_expression(cond_expr);
107 let e = make::expr_if(cond, then_branch);
108 if_indent_level.increase_indent(e)
109 };
110 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
111 }
112 Some((path, bound_ident)) => {
113 // If-let.
114 let match_expr = {
115 let happy_arm = make::match_arm(
116 once(
117 make::tuple_struct_pat(
118 path,
119 once(make::bind_pat(make::name("it")).into()),
120 )
121 .into(),
122 ),
123 make::expr_path(make::path_from_name_ref(make::name_ref("it"))),
124 );
125
126 let sad_arm = make::match_arm(
127 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
128 once(make::placeholder_pat().into()),
129 early_expression,
130 );
131
132 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
133 };
134
135 let let_stmt = make::let_stmt(
136 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
137 Some(match_expr),
138 );
139 let let_stmt = if_indent_level.increase_indent(let_stmt);
140 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
141 }
142 };
143 edit.target(if_expr.syntax().text_range());
144 edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap());
145 edit.set_cursor(cursor_position);
146
147 fn replace(
148 new_expr: &SyntaxNode,
149 then_block: &Block,
150 parent_block: &Block,
151 if_expr: &ast::IfExpr,
152 ) -> SyntaxNode {
153 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
154 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
155 let end_of_then =
156 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
157 end_of_then.prev_sibling_or_token().unwrap()
158 } else {
159 end_of_then
160 };
161 let mut then_statements = new_expr.children_with_tokens().chain(
162 then_block_items
163 .syntax()
164 .children_with_tokens()
165 .skip(1)
166 .take_while(|i| *i != end_of_then),
167 );
168 replace_children(
169 &parent_block.syntax(),
170 RangeInclusive::new(
171 if_expr.clone().syntax().clone().into(),
172 if_expr.syntax().clone().into(),
173 ),
174 &mut then_statements,
175 )
176 }
177 })
178}
179
180#[cfg(test)]
181mod tests {
182 use crate::helpers::{check_assist, check_assist_not_applicable};
183
184 use super::*;
185
186 #[test]
187 fn convert_inside_fn() {
188 check_assist(
189 convert_to_guarded_return,
190 r#"
191 fn main() {
192 bar();
193 if<|> true {
194 foo();
195
196 //comment
197 bar();
198 }
199 }
200 "#,
201 r#"
202 fn main() {
203 bar();
204 if<|> !true {
205 return;
206 }
207 foo();
208
209 //comment
210 bar();
211 }
212 "#,
213 );
214 }
215
216 #[test]
217 fn convert_let_inside_fn() {
218 check_assist(
219 convert_to_guarded_return,
220 r#"
221 fn main(n: Option<String>) {
222 bar();
223 if<|> let Some(n) = n {
224 foo(n);
225
226 //comment
227 bar();
228 }
229 }
230 "#,
231 r#"
232 fn main(n: Option<String>) {
233 bar();
234 le<|>t n = match n {
235 Some(it) => it,
236 _ => return,
237 };
238 foo(n);
239
240 //comment
241 bar();
242 }
243 "#,
244 );
245 }
246
247 #[test]
248 fn convert_if_let_result() {
249 check_assist(
250 convert_to_guarded_return,
251 r#"
252 fn main() {
253 if<|> let Ok(x) = Err(92) {
254 foo(x);
255 }
256 }
257 "#,
258 r#"
259 fn main() {
260 le<|>t x = match Err(92) {
261 Ok(it) => it,
262 _ => return,
263 };
264 foo(x);
265 }
266 "#,
267 );
268 }
269
270 #[test]
271 fn convert_let_ok_inside_fn() {
272 check_assist(
273 convert_to_guarded_return,
274 r#"
275 fn main(n: Option<String>) {
276 bar();
277 if<|> let Ok(n) = n {
278 foo(n);
279
280 //comment
281 bar();
282 }
283 }
284 "#,
285 r#"
286 fn main(n: Option<String>) {
287 bar();
288 le<|>t n = match n {
289 Ok(it) => it,
290 _ => return,
291 };
292 foo(n);
293
294 //comment
295 bar();
296 }
297 "#,
298 );
299 }
300
301 #[test]
302 fn convert_inside_while() {
303 check_assist(
304 convert_to_guarded_return,
305 r#"
306 fn main() {
307 while true {
308 if<|> true {
309 foo();
310 bar();
311 }
312 }
313 }
314 "#,
315 r#"
316 fn main() {
317 while true {
318 if<|> !true {
319 continue;
320 }
321 foo();
322 bar();
323 }
324 }
325 "#,
326 );
327 }
328
329 #[test]
330 fn convert_let_inside_while() {
331 check_assist(
332 convert_to_guarded_return,
333 r#"
334 fn main() {
335 while true {
336 if<|> let Some(n) = n {
337 foo(n);
338 bar();
339 }
340 }
341 }
342 "#,
343 r#"
344 fn main() {
345 while true {
346 le<|>t n = match n {
347 Some(it) => it,
348 _ => continue,
349 };
350 foo(n);
351 bar();
352 }
353 }
354 "#,
355 );
356 }
357
358 #[test]
359 fn convert_inside_loop() {
360 check_assist(
361 convert_to_guarded_return,
362 r#"
363 fn main() {
364 loop {
365 if<|> true {
366 foo();
367 bar();
368 }
369 }
370 }
371 "#,
372 r#"
373 fn main() {
374 loop {
375 if<|> !true {
376 continue;
377 }
378 foo();
379 bar();
380 }
381 }
382 "#,
383 );
384 }
385
386 #[test]
387 fn convert_let_inside_loop() {
388 check_assist(
389 convert_to_guarded_return,
390 r#"
391 fn main() {
392 loop {
393 if<|> let Some(n) = n {
394 foo(n);
395 bar();
396 }
397 }
398 }
399 "#,
400 r#"
401 fn main() {
402 loop {
403 le<|>t n = match n {
404 Some(it) => it,
405 _ => continue,
406 };
407 foo(n);
408 bar();
409 }
410 }
411 "#,
412 );
413 }
414
415 #[test]
416 fn ignore_already_converted_if() {
417 check_assist_not_applicable(
418 convert_to_guarded_return,
419 r#"
420 fn main() {
421 if<|> true {
422 return;
423 }
424 }
425 "#,
426 );
427 }
428
429 #[test]
430 fn ignore_already_converted_loop() {
431 check_assist_not_applicable(
432 convert_to_guarded_return,
433 r#"
434 fn main() {
435 loop {
436 if<|> true {
437 continue;
438 }
439 }
440 }
441 "#,
442 );
443 }
444
445 #[test]
446 fn ignore_return() {
447 check_assist_not_applicable(
448 convert_to_guarded_return,
449 r#"
450 fn main() {
451 if<|> true {
452 return
453 }
454 }
455 "#,
456 );
457 }
458
459 #[test]
460 fn ignore_else_branch() {
461 check_assist_not_applicable(
462 convert_to_guarded_return,
463 r#"
464 fn main() {
465 if<|> true {
466 foo();
467 } else {
468 bar()
469 }
470 }
471 "#,
472 );
473 }
474
475 #[test]
476 fn ignore_statements_aftert_if() {
477 check_assist_not_applicable(
478 convert_to_guarded_return,
479 r#"
480 fn main() {
481 if<|> true {
482 foo();
483 }
484 bar();
485 }
486 "#,
487 );
488 }
489
490 #[test]
491 fn ignore_statements_inside_if() {
492 check_assist_not_applicable(
493 convert_to_guarded_return,
494 r#"
495 fn main() {
496 if false {
497 if<|> true {
498 foo();
499 }
500 }
501 }
502 "#,
503 );
504 }
505}
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
new file mode 100644
index 000000000..0908fc246
--- /dev/null
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -0,0 +1,290 @@
1//! FIXME: write short doc here
2
3use std::iter;
4
5use hir::{db::HirDatabase, Adt, HasSource};
6use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
7
8use crate::{Assist, AssistCtx, AssistId};
9
10// Assist: fill_match_arms
11//
12// Adds missing clauses to a `match` expression.
13//
14// ```
15// enum Action { Move { distance: u32 }, Stop }
16//
17// fn handle(action: Action) {
18// match action {
19// <|>
20// }
21// }
22// ```
23// ->
24// ```
25// enum Action { Move { distance: u32 }, Stop }
26//
27// fn handle(action: Action) {
28// match action {
29// Action::Move { distance } => (),
30// Action::Stop => (),
31// }
32// }
33// ```
34pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
35 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
36 let match_arm_list = match_expr.match_arm_list()?;
37
38 // We already have some match arms, so we don't provide any assists.
39 // Unless if there is only one trivial match arm possibly created
40 // by match postfix complete. Trivial match arm is the catch all arm.
41 let mut existing_arms = match_arm_list.arms();
42 if let Some(arm) = existing_arms.next() {
43 if !is_trivial(&arm) || existing_arms.next().is_some() {
44 return None;
45 }
46 };
47
48 let expr = match_expr.expr()?;
49 let (enum_def, module) = {
50 let analyzer = ctx.source_analyzer(expr.syntax(), None);
51 (resolve_enum_def(ctx.db, &analyzer, &expr)?, analyzer.module()?)
52 };
53 let variants = enum_def.variants(ctx.db);
54 if variants.is_empty() {
55 return None;
56 }
57
58 let db = ctx.db;
59
60 ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| {
61 let indent_level = IndentLevel::from_node(match_arm_list.syntax());
62
63 let new_arm_list = {
64 let arms = variants
65 .into_iter()
66 .filter_map(|variant| build_pat(db, module, variant))
67 .map(|pat| make::match_arm(iter::once(pat), make::expr_unit()));
68 indent_level.increase_indent(make::match_arm_list(arms))
69 };
70
71 edit.target(match_expr.syntax().text_range());
72 edit.set_cursor(expr.syntax().text_range().start());
73 edit.replace_ast(match_arm_list, new_arm_list);
74 })
75}
76
77fn is_trivial(arm: &ast::MatchArm) -> bool {
78 arm.pats().any(|pat| match pat {
79 ast::Pat::PlaceholderPat(..) => true,
80 _ => false,
81 })
82}
83
84fn resolve_enum_def(
85 db: &impl HirDatabase,
86 analyzer: &hir::SourceAnalyzer,
87 expr: &ast::Expr,
88) -> Option<hir::Enum> {
89 let expr_ty = analyzer.type_of(db, &expr)?;
90
91 let result = expr_ty.autoderef(db).find_map(|ty| match ty.as_adt() {
92 Some(Adt::Enum(e)) => Some(e),
93 _ => None,
94 });
95 result
96}
97
98fn build_pat(
99 db: &impl HirDatabase,
100 module: hir::Module,
101 var: hir::EnumVariant,
102) -> Option<ast::Pat> {
103 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?);
104
105 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
106 let pat: ast::Pat = match var.source(db).value.kind() {
107 ast::StructKind::Tuple(field_list) => {
108 let pats =
109 iter::repeat(make::placeholder_pat().into()).take(field_list.fields().count());
110 make::tuple_struct_pat(path, pats).into()
111 }
112 ast::StructKind::Record(field_list) => {
113 let pats = field_list.fields().map(|f| make::bind_pat(f.name().unwrap()).into());
114 make::record_pat(path, pats).into()
115 }
116 ast::StructKind::Unit => make::path_pat(path),
117 };
118
119 Some(pat)
120}
121
122#[cfg(test)]
123mod tests {
124 use crate::helpers::{check_assist, check_assist_target};
125
126 use super::fill_match_arms;
127
128 #[test]
129 fn fill_match_arms_empty_body() {
130 check_assist(
131 fill_match_arms,
132 r#"
133 enum A {
134 As,
135 Bs,
136 Cs(String),
137 Ds(String, String),
138 Es{ x: usize, y: usize }
139 }
140
141 fn main() {
142 let a = A::As;
143 match a<|> {}
144 }
145 "#,
146 r#"
147 enum A {
148 As,
149 Bs,
150 Cs(String),
151 Ds(String, String),
152 Es{ x: usize, y: usize }
153 }
154
155 fn main() {
156 let a = A::As;
157 match <|>a {
158 A::As => (),
159 A::Bs => (),
160 A::Cs(_) => (),
161 A::Ds(_, _) => (),
162 A::Es { x, y } => (),
163 }
164 }
165 "#,
166 );
167 }
168
169 #[test]
170 fn test_fill_match_arm_refs() {
171 check_assist(
172 fill_match_arms,
173 r#"
174 enum A {
175 As,
176 }
177
178 fn foo(a: &A) {
179 match a<|> {
180 }
181 }
182 "#,
183 r#"
184 enum A {
185 As,
186 }
187
188 fn foo(a: &A) {
189 match <|>a {
190 A::As => (),
191 }
192 }
193 "#,
194 );
195
196 check_assist(
197 fill_match_arms,
198 r#"
199 enum A {
200 Es{ x: usize, y: usize }
201 }
202
203 fn foo(a: &mut A) {
204 match a<|> {
205 }
206 }
207 "#,
208 r#"
209 enum A {
210 Es{ x: usize, y: usize }
211 }
212
213 fn foo(a: &mut A) {
214 match <|>a {
215 A::Es { x, y } => (),
216 }
217 }
218 "#,
219 );
220 }
221
222 #[test]
223 fn fill_match_arms_target() {
224 check_assist_target(
225 fill_match_arms,
226 r#"
227 enum E { X, Y }
228
229 fn main() {
230 match E::X<|> {}
231 }
232 "#,
233 "match E::X {}",
234 );
235 }
236
237 #[test]
238 fn fill_match_arms_trivial_arm() {
239 check_assist(
240 fill_match_arms,
241 r#"
242 enum E { X, Y }
243
244 fn main() {
245 match E::X {
246 <|>_ => {},
247 }
248 }
249 "#,
250 r#"
251 enum E { X, Y }
252
253 fn main() {
254 match <|>E::X {
255 E::X => (),
256 E::Y => (),
257 }
258 }
259 "#,
260 );
261 }
262
263 #[test]
264 fn fill_match_arms_qualifies_path() {
265 check_assist(
266 fill_match_arms,
267 r#"
268 mod foo { pub enum E { X, Y } }
269 use foo::E::X;
270
271 fn main() {
272 match X {
273 <|>
274 }
275 }
276 "#,
277 r#"
278 mod foo { pub enum E { X, Y } }
279 use foo::E::X;
280
281 fn main() {
282 match <|>X {
283 X => (),
284 foo::E::Y => (),
285 }
286 }
287 "#,
288 );
289 }
290}
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
new file mode 100644
index 000000000..bfcc09e90
--- /dev/null
+++ b/crates/ra_assists/src/handlers/flip_binexpr.rs
@@ -0,0 +1,142 @@
1use ra_syntax::ast::{AstNode, BinExpr, BinOp};
2
3use crate::{Assist, AssistCtx, AssistId};
4
5// Assist: flip_binexpr
6//
7// Flips operands of a binary expression.
8//
9// ```
10// fn main() {
11// let _ = 90 +<|> 2;
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// let _ = 2 + 90;
18// }
19// ```
20pub(crate) fn flip_binexpr(ctx: AssistCtx) -> Option<Assist> {
21 let expr = ctx.find_node_at_offset::<BinExpr>()?;
22 let lhs = expr.lhs()?.syntax().clone();
23 let rhs = expr.rhs()?.syntax().clone();
24 let op_range = expr.op_token()?.text_range();
25 // The assist should be applied only if the cursor is on the operator
26 let cursor_in_range = ctx.frange.range.is_subrange(&op_range);
27 if !cursor_in_range {
28 return None;
29 }
30 let action: FlipAction = expr.op_kind()?.into();
31 // The assist should not be applied for certain operators
32 if let FlipAction::DontFlip = action {
33 return None;
34 }
35
36 ctx.add_assist(AssistId("flip_binexpr"), "Flip binary expression", |edit| {
37 edit.target(op_range);
38 if let FlipAction::FlipAndReplaceOp(new_op) = action {
39 edit.replace(op_range, new_op);
40 }
41 edit.replace(lhs.text_range(), rhs.text());
42 edit.replace(rhs.text_range(), lhs.text());
43 })
44}
45
46enum FlipAction {
47 // Flip the expression
48 Flip,
49 // Flip the expression and replace the operator with this string
50 FlipAndReplaceOp(&'static str),
51 // Do not flip the expression
52 DontFlip,
53}
54
55impl From<BinOp> for FlipAction {
56 fn from(op_kind: BinOp) -> Self {
57 match op_kind {
58 kind if kind.is_assignment() => FlipAction::DontFlip,
59 BinOp::GreaterTest => FlipAction::FlipAndReplaceOp("<"),
60 BinOp::GreaterEqualTest => FlipAction::FlipAndReplaceOp("<="),
61 BinOp::LesserTest => FlipAction::FlipAndReplaceOp(">"),
62 BinOp::LesserEqualTest => FlipAction::FlipAndReplaceOp(">="),
63 _ => FlipAction::Flip,
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
73
74 #[test]
75 fn flip_binexpr_target_is_the_op() {
76 check_assist_target(flip_binexpr, "fn f() { let res = 1 ==<|> 2; }", "==")
77 }
78
79 #[test]
80 fn flip_binexpr_not_applicable_for_assignment() {
81 check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=<|> 2 }")
82 }
83
84 #[test]
85 fn flip_binexpr_works_for_eq() {
86 check_assist(
87 flip_binexpr,
88 "fn f() { let res = 1 ==<|> 2; }",
89 "fn f() { let res = 2 ==<|> 1; }",
90 )
91 }
92
93 #[test]
94 fn flip_binexpr_works_for_gt() {
95 check_assist(
96 flip_binexpr,
97 "fn f() { let res = 1 ><|> 2; }",
98 "fn f() { let res = 2 <<|> 1; }",
99 )
100 }
101
102 #[test]
103 fn flip_binexpr_works_for_lteq() {
104 check_assist(
105 flip_binexpr,
106 "fn f() { let res = 1 <=<|> 2; }",
107 "fn f() { let res = 2 >=<|> 1; }",
108 )
109 }
110
111 #[test]
112 fn flip_binexpr_works_for_complex_expr() {
113 check_assist(
114 flip_binexpr,
115 "fn f() { let res = (1 + 1) ==<|> (2 + 2); }",
116 "fn f() { let res = (2 + 2) ==<|> (1 + 1); }",
117 )
118 }
119
120 #[test]
121 fn flip_binexpr_works_inside_match() {
122 check_assist(
123 flip_binexpr,
124 r#"
125 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
126 match other.downcast_ref::<Self>() {
127 None => false,
128 Some(it) => it ==<|> self,
129 }
130 }
131 "#,
132 r#"
133 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
134 match other.downcast_ref::<Self>() {
135 None => false,
136 Some(it) => self ==<|> it,
137 }
138 }
139 "#,
140 )
141 }
142}
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
new file mode 100644
index 000000000..1dacf29f8
--- /dev/null
+++ b/crates/ra_assists/src/handlers/flip_comma.rs
@@ -0,0 +1,80 @@
1use ra_syntax::{algo::non_trivia_sibling, Direction, T};
2
3use crate::{Assist, AssistCtx, AssistId};
4
5// Assist: flip_comma
6//
7// Flips two comma-separated items.
8//
9// ```
10// fn main() {
11// ((1, 2),<|> (3, 4));
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// ((3, 4), (1, 2));
18// }
19// ```
20pub(crate) fn flip_comma(ctx: AssistCtx) -> Option<Assist> {
21 let comma = ctx.find_token_at_offset(T![,])?;
22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
24
25 // Don't apply a "flip" in case of a last comma
26 // that typically comes before punctuation
27 if next.kind().is_punct() {
28 return None;
29 }
30
31 ctx.add_assist(AssistId("flip_comma"), "Flip comma", |edit| {
32 edit.target(comma.text_range());
33 edit.replace(prev.text_range(), next.to_string());
34 edit.replace(next.text_range(), prev.to_string());
35 })
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 use crate::helpers::{check_assist, check_assist_target};
43
44 #[test]
45 fn flip_comma_works_for_function_parameters() {
46 check_assist(
47 flip_comma,
48 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
49 "fn foo(y: Result<(), ()>,<|> x: i32) {}",
50 )
51 }
52
53 #[test]
54 fn flip_comma_target() {
55 check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",")
56 }
57
58 #[test]
59 #[should_panic]
60 fn flip_comma_before_punct() {
61 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619
62 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
63 // declaration body.
64 check_assist_target(
65 flip_comma,
66 "pub enum Test { \
67 A,<|> \
68 }",
69 ",",
70 );
71
72 check_assist_target(
73 flip_comma,
74 "pub struct Test { \
75 foo: usize,<|> \
76 }",
77 ",",
78 );
79 }
80}
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
new file mode 100644
index 000000000..f56769624
--- /dev/null
+++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs
@@ -0,0 +1,116 @@
1use ra_syntax::{
2 algo::non_trivia_sibling,
3 ast::{self, AstNode},
4 Direction, T,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8
9// Assist: flip_trait_bound
10//
11// Flips two trait bounds.
12//
13// ```
14// fn foo<T: Clone +<|> Copy>() { }
15// ```
16// ->
17// ```
18// fn foo<T: Copy + Clone>() { }
19// ```
20pub(crate) fn flip_trait_bound(ctx: AssistCtx) -> Option<Assist> {
21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
22 // the assist when the cursor is on a `+`
23 let plus = ctx.find_token_at_offset(T![+])?;
24
25 // Make sure we're in a `TypeBoundList`
26 if ast::TypeBoundList::cast(plus.parent()).is_none() {
27 return None;
28 }
29
30 let (before, after) = (
31 non_trivia_sibling(plus.clone().into(), Direction::Prev)?,
32 non_trivia_sibling(plus.clone().into(), Direction::Next)?,
33 );
34
35 ctx.add_assist(AssistId("flip_trait_bound"), "Flip trait bounds", |edit| {
36 edit.target(plus.text_range());
37 edit.replace(before.text_range(), after.to_string());
38 edit.replace(after.text_range(), before.to_string());
39 })
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
47
48 #[test]
49 fn flip_trait_bound_assist_available() {
50 check_assist_target(flip_trait_bound, "struct S<T> where T: A <|>+ B + C { }", "+")
51 }
52
53 #[test]
54 fn flip_trait_bound_not_applicable_for_single_trait_bound() {
55 check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: <|>A { }")
56 }
57
58 #[test]
59 fn flip_trait_bound_works_for_struct() {
60 check_assist(
61 flip_trait_bound,
62 "struct S<T> where T: A <|>+ B { }",
63 "struct S<T> where T: B <|>+ A { }",
64 )
65 }
66
67 #[test]
68 fn flip_trait_bound_works_for_trait_impl() {
69 check_assist(
70 flip_trait_bound,
71 "impl X for S<T> where T: A +<|> B { }",
72 "impl X for S<T> where T: B +<|> A { }",
73 )
74 }
75
76 #[test]
77 fn flip_trait_bound_works_for_fn() {
78 check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }")
79 }
80
81 #[test]
82 fn flip_trait_bound_works_for_fn_where_clause() {
83 check_assist(
84 flip_trait_bound,
85 "fn f<T>(t: T) where T: A +<|> B { }",
86 "fn f<T>(t: T) where T: B +<|> A { }",
87 )
88 }
89
90 #[test]
91 fn flip_trait_bound_works_for_lifetime() {
92 check_assist(
93 flip_trait_bound,
94 "fn f<T>(t: T) where T: A <|>+ 'static { }",
95 "fn f<T>(t: T) where T: 'static <|>+ A { }",
96 )
97 }
98
99 #[test]
100 fn flip_trait_bound_works_for_complex_bounds() {
101 check_assist(
102 flip_trait_bound,
103 "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }",
104 "struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }",
105 )
106 }
107
108 #[test]
109 fn flip_trait_bound_works_for_long_bounds() {
110 check_assist(
111 flip_trait_bound,
112 "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }",
113 "struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }",
114 )
115 }
116}
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
new file mode 100644
index 000000000..91b588243
--- /dev/null
+++ b/crates/ra_assists/src/handlers/inline_local_variable.rs
@@ -0,0 +1,662 @@
1use ra_syntax::{
2 ast::{self, AstNode, AstToken},
3 TextRange,
4};
5
6use crate::assist_ctx::ActionBuilder;
7use crate::{Assist, AssistCtx, AssistId};
8
9// Assist: inline_local_variable
10//
11// Inlines local variable.
12//
13// ```
14// fn main() {
15// let x<|> = 1 + 2;
16// x * 4;
17// }
18// ```
19// ->
20// ```
21// fn main() {
22// (1 + 2) * 4;
23// }
24// ```
25pub(crate) fn inline_local_variable(ctx: AssistCtx) -> Option<Assist> {
26 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
27 let bind_pat = match let_stmt.pat()? {
28 ast::Pat::BindPat(pat) => pat,
29 _ => return None,
30 };
31 if bind_pat.is_mutable() {
32 return None;
33 }
34 let initializer_expr = let_stmt.initializer()?;
35 let delete_range = if let Some(whitespace) = let_stmt
36 .syntax()
37 .next_sibling_or_token()
38 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone()))
39 {
40 TextRange::from_to(
41 let_stmt.syntax().text_range().start(),
42 whitespace.syntax().text_range().end(),
43 )
44 } else {
45 let_stmt.syntax().text_range()
46 };
47 let analyzer = ctx.source_analyzer(bind_pat.syntax(), None);
48 let refs = analyzer.find_all_refs(&bind_pat);
49 if refs.is_empty() {
50 return None;
51 };
52
53 let mut wrap_in_parens = vec![true; refs.len()];
54
55 for (i, desc) in refs.iter().enumerate() {
56 let usage_node =
57 ctx.covering_node_for_range(desc.range).ancestors().find_map(ast::PathExpr::cast)?;
58 let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast);
59 let usage_parent = match usage_parent_option {
60 Some(u) => u,
61 None => {
62 wrap_in_parens[i] = false;
63 continue;
64 }
65 };
66
67 wrap_in_parens[i] = match (&initializer_expr, usage_parent) {
68 (ast::Expr::CallExpr(_), _)
69 | (ast::Expr::IndexExpr(_), _)
70 | (ast::Expr::MethodCallExpr(_), _)
71 | (ast::Expr::FieldExpr(_), _)
72 | (ast::Expr::TryExpr(_), _)
73 | (ast::Expr::RefExpr(_), _)
74 | (ast::Expr::Literal(_), _)
75 | (ast::Expr::TupleExpr(_), _)
76 | (ast::Expr::ArrayExpr(_), _)
77 | (ast::Expr::ParenExpr(_), _)
78 | (ast::Expr::PathExpr(_), _)
79 | (ast::Expr::BlockExpr(_), _)
80 | (_, ast::Expr::CallExpr(_))
81 | (_, ast::Expr::TupleExpr(_))
82 | (_, ast::Expr::ArrayExpr(_))
83 | (_, ast::Expr::ParenExpr(_))
84 | (_, ast::Expr::ForExpr(_))
85 | (_, ast::Expr::WhileExpr(_))
86 | (_, ast::Expr::BreakExpr(_))
87 | (_, ast::Expr::ReturnExpr(_))
88 | (_, ast::Expr::MatchExpr(_)) => false,
89 _ => true,
90 };
91 }
92
93 let init_str = initializer_expr.syntax().text().to_string();
94 let init_in_paren = format!("({})", &init_str);
95
96 ctx.add_assist(
97 AssistId("inline_local_variable"),
98 "Inline variable",
99 move |edit: &mut ActionBuilder| {
100 edit.delete(delete_range);
101 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
102 if should_wrap {
103 edit.replace(desc.range, init_in_paren.clone())
104 } else {
105 edit.replace(desc.range, init_str.clone())
106 }
107 }
108 edit.set_cursor(delete_range.start())
109 },
110 )
111}
112
113#[cfg(test)]
114mod tests {
115 use crate::helpers::{check_assist, check_assist_not_applicable};
116
117 use super::*;
118
119 #[test]
120 fn test_inline_let_bind_literal_expr() {
121 check_assist(
122 inline_local_variable,
123 "
124fn bar(a: usize) {}
125fn foo() {
126 let a<|> = 1;
127 a + 1;
128 if a > 10 {
129 }
130
131 while a > 10 {
132
133 }
134 let b = a * 10;
135 bar(a);
136}",
137 "
138fn bar(a: usize) {}
139fn foo() {
140 <|>1 + 1;
141 if 1 > 10 {
142 }
143
144 while 1 > 10 {
145
146 }
147 let b = 1 * 10;
148 bar(1);
149}",
150 );
151 }
152
153 #[test]
154 fn test_inline_let_bind_bin_expr() {
155 check_assist(
156 inline_local_variable,
157 "
158fn bar(a: usize) {}
159fn foo() {
160 let a<|> = 1 + 1;
161 a + 1;
162 if a > 10 {
163 }
164
165 while a > 10 {
166
167 }
168 let b = a * 10;
169 bar(a);
170}",
171 "
172fn bar(a: usize) {}
173fn foo() {
174 <|>(1 + 1) + 1;
175 if (1 + 1) > 10 {
176 }
177
178 while (1 + 1) > 10 {
179
180 }
181 let b = (1 + 1) * 10;
182 bar(1 + 1);
183}",
184 );
185 }
186
187 #[test]
188 fn test_inline_let_bind_function_call_expr() {
189 check_assist(
190 inline_local_variable,
191 "
192fn bar(a: usize) {}
193fn foo() {
194 let a<|> = bar(1);
195 a + 1;
196 if a > 10 {
197 }
198
199 while a > 10 {
200
201 }
202 let b = a * 10;
203 bar(a);
204}",
205 "
206fn bar(a: usize) {}
207fn foo() {
208 <|>bar(1) + 1;
209 if bar(1) > 10 {
210 }
211
212 while bar(1) > 10 {
213
214 }
215 let b = bar(1) * 10;
216 bar(bar(1));
217}",
218 );
219 }
220
221 #[test]
222 fn test_inline_let_bind_cast_expr() {
223 check_assist(
224 inline_local_variable,
225 "
226fn bar(a: usize): usize { a }
227fn foo() {
228 let a<|> = bar(1) as u64;
229 a + 1;
230 if a > 10 {
231 }
232
233 while a > 10 {
234
235 }
236 let b = a * 10;
237 bar(a);
238}",
239 "
240fn bar(a: usize): usize { a }
241fn foo() {
242 <|>(bar(1) as u64) + 1;
243 if (bar(1) as u64) > 10 {
244 }
245
246 while (bar(1) as u64) > 10 {
247
248 }
249 let b = (bar(1) as u64) * 10;
250 bar(bar(1) as u64);
251}",
252 );
253 }
254
255 #[test]
256 fn test_inline_let_bind_block_expr() {
257 check_assist(
258 inline_local_variable,
259 "
260fn foo() {
261 let a<|> = { 10 + 1 };
262 a + 1;
263 if a > 10 {
264 }
265
266 while a > 10 {
267
268 }
269 let b = a * 10;
270 bar(a);
271}",
272 "
273fn foo() {
274 <|>{ 10 + 1 } + 1;
275 if { 10 + 1 } > 10 {
276 }
277
278 while { 10 + 1 } > 10 {
279
280 }
281 let b = { 10 + 1 } * 10;
282 bar({ 10 + 1 });
283}",
284 );
285 }
286
287 #[test]
288 fn test_inline_let_bind_paren_expr() {
289 check_assist(
290 inline_local_variable,
291 "
292fn foo() {
293 let a<|> = ( 10 + 1 );
294 a + 1;
295 if a > 10 {
296 }
297
298 while a > 10 {
299
300 }
301 let b = a * 10;
302 bar(a);
303}",
304 "
305fn foo() {
306 <|>( 10 + 1 ) + 1;
307 if ( 10 + 1 ) > 10 {
308 }
309
310 while ( 10 + 1 ) > 10 {
311
312 }
313 let b = ( 10 + 1 ) * 10;
314 bar(( 10 + 1 ));
315}",
316 );
317 }
318
319 #[test]
320 fn test_not_inline_mut_variable() {
321 check_assist_not_applicable(
322 inline_local_variable,
323 "
324fn foo() {
325 let mut a<|> = 1 + 1;
326 a + 1;
327}",
328 );
329 }
330
331 #[test]
332 fn test_call_expr() {
333 check_assist(
334 inline_local_variable,
335 "
336fn foo() {
337 let a<|> = bar(10 + 1);
338 let b = a * 10;
339 let c = a as usize;
340}",
341 "
342fn foo() {
343 <|>let b = bar(10 + 1) * 10;
344 let c = bar(10 + 1) as usize;
345}",
346 );
347 }
348
349 #[test]
350 fn test_index_expr() {
351 check_assist(
352 inline_local_variable,
353 "
354fn foo() {
355 let x = vec![1, 2, 3];
356 let a<|> = x[0];
357 let b = a * 10;
358 let c = a as usize;
359}",
360 "
361fn foo() {
362 let x = vec![1, 2, 3];
363 <|>let b = x[0] * 10;
364 let c = x[0] as usize;
365}",
366 );
367 }
368
369 #[test]
370 fn test_method_call_expr() {
371 check_assist(
372 inline_local_variable,
373 "
374fn foo() {
375 let bar = vec![1];
376 let a<|> = bar.len();
377 let b = a * 10;
378 let c = a as usize;
379}",
380 "
381fn foo() {
382 let bar = vec![1];
383 <|>let b = bar.len() * 10;
384 let c = bar.len() as usize;
385}",
386 );
387 }
388
389 #[test]
390 fn test_field_expr() {
391 check_assist(
392 inline_local_variable,
393 "
394struct Bar {
395 foo: usize
396}
397
398fn foo() {
399 let bar = Bar { foo: 1 };
400 let a<|> = bar.foo;
401 let b = a * 10;
402 let c = a as usize;
403}",
404 "
405struct Bar {
406 foo: usize
407}
408
409fn foo() {
410 let bar = Bar { foo: 1 };
411 <|>let b = bar.foo * 10;
412 let c = bar.foo as usize;
413}",
414 );
415 }
416
417 #[test]
418 fn test_try_expr() {
419 check_assist(
420 inline_local_variable,
421 "
422fn foo() -> Option<usize> {
423 let bar = Some(1);
424 let a<|> = bar?;
425 let b = a * 10;
426 let c = a as usize;
427 None
428}",
429 "
430fn foo() -> Option<usize> {
431 let bar = Some(1);
432 <|>let b = bar? * 10;
433 let c = bar? as usize;
434 None
435}",
436 );
437 }
438
439 #[test]
440 fn test_ref_expr() {
441 check_assist(
442 inline_local_variable,
443 "
444fn foo() {
445 let bar = 10;
446 let a<|> = &bar;
447 let b = a * 10;
448}",
449 "
450fn foo() {
451 let bar = 10;
452 <|>let b = &bar * 10;
453}",
454 );
455 }
456
457 #[test]
458 fn test_tuple_expr() {
459 check_assist(
460 inline_local_variable,
461 "
462fn foo() {
463 let a<|> = (10, 20);
464 let b = a[0];
465}",
466 "
467fn foo() {
468 <|>let b = (10, 20)[0];
469}",
470 );
471 }
472
473 #[test]
474 fn test_array_expr() {
475 check_assist(
476 inline_local_variable,
477 "
478fn foo() {
479 let a<|> = [1, 2, 3];
480 let b = a.len();
481}",
482 "
483fn foo() {
484 <|>let b = [1, 2, 3].len();
485}",
486 );
487 }
488
489 #[test]
490 fn test_paren() {
491 check_assist(
492 inline_local_variable,
493 "
494fn foo() {
495 let a<|> = (10 + 20);
496 let b = a * 10;
497 let c = a as usize;
498}",
499 "
500fn foo() {
501 <|>let b = (10 + 20) * 10;
502 let c = (10 + 20) as usize;
503}",
504 );
505 }
506
507 #[test]
508 fn test_path_expr() {
509 check_assist(
510 inline_local_variable,
511 "
512fn foo() {
513 let d = 10;
514 let a<|> = d;
515 let b = a * 10;
516 let c = a as usize;
517}",
518 "
519fn foo() {
520 let d = 10;
521 <|>let b = d * 10;
522 let c = d as usize;
523}",
524 );
525 }
526
527 #[test]
528 fn test_block_expr() {
529 check_assist(
530 inline_local_variable,
531 "
532fn foo() {
533 let a<|> = { 10 };
534 let b = a * 10;
535 let c = a as usize;
536}",
537 "
538fn foo() {
539 <|>let b = { 10 } * 10;
540 let c = { 10 } as usize;
541}",
542 );
543 }
544
545 #[test]
546 fn test_used_in_different_expr1() {
547 check_assist(
548 inline_local_variable,
549 "
550fn foo() {
551 let a<|> = 10 + 20;
552 let b = a * 10;
553 let c = (a, 20);
554 let d = [a, 10];
555 let e = (a);
556}",
557 "
558fn foo() {
559 <|>let b = (10 + 20) * 10;
560 let c = (10 + 20, 20);
561 let d = [10 + 20, 10];
562 let e = (10 + 20);
563}",
564 );
565 }
566
567 #[test]
568 fn test_used_in_for_expr() {
569 check_assist(
570 inline_local_variable,
571 "
572fn foo() {
573 let a<|> = vec![10, 20];
574 for i in a {}
575}",
576 "
577fn foo() {
578 <|>for i in vec![10, 20] {}
579}",
580 );
581 }
582
583 #[test]
584 fn test_used_in_while_expr() {
585 check_assist(
586 inline_local_variable,
587 "
588fn foo() {
589 let a<|> = 1 > 0;
590 while a {}
591}",
592 "
593fn foo() {
594 <|>while 1 > 0 {}
595}",
596 );
597 }
598
599 #[test]
600 fn test_used_in_break_expr() {
601 check_assist(
602 inline_local_variable,
603 "
604fn foo() {
605 let a<|> = 1 + 1;
606 loop {
607 break a;
608 }
609}",
610 "
611fn foo() {
612 <|>loop {
613 break 1 + 1;
614 }
615}",
616 );
617 }
618
619 #[test]
620 fn test_used_in_return_expr() {
621 check_assist(
622 inline_local_variable,
623 "
624fn foo() {
625 let a<|> = 1 > 0;
626 return a;
627}",
628 "
629fn foo() {
630 <|>return 1 > 0;
631}",
632 );
633 }
634
635 #[test]
636 fn test_used_in_match_expr() {
637 check_assist(
638 inline_local_variable,
639 "
640fn foo() {
641 let a<|> = 1 > 0;
642 match a {}
643}",
644 "
645fn foo() {
646 <|>match 1 > 0 {}
647}",
648 );
649 }
650
651 #[test]
652 fn test_not_applicable_if_variable_unused() {
653 check_assist_not_applicable(
654 inline_local_variable,
655 "
656fn foo() {
657 let <|>a = 0;
658}
659 ",
660 )
661 }
662}
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/introduce_variable.rs
new file mode 100644
index 000000000..7312ce687
--- /dev/null
+++ b/crates/ra_assists/src/handlers/introduce_variable.rs
@@ -0,0 +1,529 @@
1use format_buf::format;
2use ra_syntax::{
3 ast::{self, AstNode},
4 SyntaxKind::{
5 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
6 WHITESPACE,
7 },
8 SyntaxNode, TextUnit,
9};
10use test_utils::tested_by;
11
12use crate::{Assist, AssistCtx, AssistId};
13
14// Assist: introduce_variable
15//
16// Extracts subexpression into a variable.
17//
18// ```
19// fn main() {
20// <|>(1 + 2)<|> * 4;
21// }
22// ```
23// ->
24// ```
25// fn main() {
26// let var_name = (1 + 2);
27// var_name * 4;
28// }
29// ```
30pub(crate) fn introduce_variable(ctx: AssistCtx) -> Option<Assist> {
31 if ctx.frange.range.is_empty() {
32 return None;
33 }
34 let node = ctx.covering_element();
35 if node.kind() == COMMENT {
36 tested_by!(introduce_var_in_comment_is_not_applicable);
37 return None;
38 }
39 let expr = node.ancestors().find_map(valid_target_expr)?;
40 let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?;
41 let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone();
42 if indent.kind() != WHITESPACE {
43 return None;
44 }
45 ctx.add_assist(AssistId("introduce_variable"), "Extract into variable", move |edit| {
46 let mut buf = String::new();
47
48 let cursor_offset = if wrap_in_block {
49 buf.push_str("{ let var_name = ");
50 TextUnit::of_str("{ let ")
51 } else {
52 buf.push_str("let var_name = ");
53 TextUnit::of_str("let ")
54 };
55 format!(buf, "{}", expr.syntax());
56 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
57 let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
58 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
59 } else {
60 false
61 };
62 if is_full_stmt {
63 tested_by!(test_introduce_var_expr_stmt);
64 if !full_stmt.unwrap().has_semi() {
65 buf.push_str(";");
66 }
67 edit.replace(expr.syntax().text_range(), buf);
68 } else {
69 buf.push_str(";");
70
71 // We want to maintain the indent level,
72 // but we do not want to duplicate possible
73 // extra newlines in the indent block
74 let text = indent.text();
75 if text.starts_with('\n') {
76 buf.push_str("\n");
77 buf.push_str(text.trim_start_matches('\n'));
78 } else {
79 buf.push_str(text);
80 }
81
82 edit.target(expr.syntax().text_range());
83 edit.replace(expr.syntax().text_range(), "var_name".to_string());
84 edit.insert(anchor_stmt.text_range().start(), buf);
85 if wrap_in_block {
86 edit.insert(anchor_stmt.text_range().end(), " }");
87 }
88 }
89 edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset);
90 })
91}
92
93/// Check whether the node is a valid expression which can be extracted to a variable.
94/// In general that's true for any expression, but in some cases that would produce invalid code.
95fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
96 match node.kind() {
97 PATH_EXPR | LOOP_EXPR => None,
98 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
99 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
100 BLOCK_EXPR => {
101 ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
102 }
103 _ => ast::Expr::cast(node),
104 }
105}
106
107/// Returns the syntax node which will follow the freshly introduced var
108/// and a boolean indicating whether we have to wrap it within a { } block
109/// to produce correct code.
110/// It can be a statement, the last in a block expression or a wanna be block
111/// expression like a lambda or match arm.
112fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
113 expr.syntax().ancestors().find_map(|node| {
114 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) {
115 if expr.syntax() == &node {
116 tested_by!(test_introduce_var_last_expr);
117 return Some((node, false));
118 }
119 }
120
121 if let Some(parent) = node.parent() {
122 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
123 return Some((node, true));
124 }
125 }
126
127 if ast::Stmt::cast(node.clone()).is_some() {
128 return Some((node, false));
129 }
130
131 None
132 })
133}
134
135#[cfg(test)]
136mod tests {
137 use test_utils::covers;
138
139 use crate::helpers::{
140 check_assist_range, check_assist_range_not_applicable, check_assist_range_target,
141 };
142
143 use super::*;
144
145 #[test]
146 fn test_introduce_var_simple() {
147 check_assist_range(
148 introduce_variable,
149 "
150fn foo() {
151 foo(<|>1 + 1<|>);
152}",
153 "
154fn foo() {
155 let <|>var_name = 1 + 1;
156 foo(var_name);
157}",
158 );
159 }
160
161 #[test]
162 fn introduce_var_in_comment_is_not_applicable() {
163 covers!(introduce_var_in_comment_is_not_applicable);
164 check_assist_range_not_applicable(
165 introduce_variable,
166 "fn main() { 1 + /* <|>comment<|> */ 1; }",
167 );
168 }
169
170 #[test]
171 fn test_introduce_var_expr_stmt() {
172 covers!(test_introduce_var_expr_stmt);
173 check_assist_range(
174 introduce_variable,
175 "
176fn foo() {
177 <|>1 + 1<|>;
178}",
179 "
180fn foo() {
181 let <|>var_name = 1 + 1;
182}",
183 );
184 check_assist_range(
185 introduce_variable,
186 "
187fn foo() {
188 <|>{ let x = 0; x }<|>
189 something_else();
190}",
191 "
192fn foo() {
193 let <|>var_name = { let x = 0; x };
194 something_else();
195}",
196 );
197 }
198
199 #[test]
200 fn test_introduce_var_part_of_expr_stmt() {
201 check_assist_range(
202 introduce_variable,
203 "
204fn foo() {
205 <|>1<|> + 1;
206}",
207 "
208fn foo() {
209 let <|>var_name = 1;
210 var_name + 1;
211}",
212 );
213 }
214
215 #[test]
216 fn test_introduce_var_last_expr() {
217 covers!(test_introduce_var_last_expr);
218 check_assist_range(
219 introduce_variable,
220 "
221fn foo() {
222 bar(<|>1 + 1<|>)
223}",
224 "
225fn foo() {
226 let <|>var_name = 1 + 1;
227 bar(var_name)
228}",
229 );
230 check_assist_range(
231 introduce_variable,
232 "
233fn foo() {
234 <|>bar(1 + 1)<|>
235}",
236 "
237fn foo() {
238 let <|>var_name = bar(1 + 1);
239 var_name
240}",
241 )
242 }
243
244 #[test]
245 fn test_introduce_var_in_match_arm_no_block() {
246 check_assist_range(
247 introduce_variable,
248 "
249fn main() {
250 let x = true;
251 let tuple = match x {
252 true => (<|>2 + 2<|>, true)
253 _ => (0, false)
254 };
255}
256",
257 "
258fn main() {
259 let x = true;
260 let tuple = match x {
261 true => { let <|>var_name = 2 + 2; (var_name, true) }
262 _ => (0, false)
263 };
264}
265",
266 );
267 }
268
269 #[test]
270 fn test_introduce_var_in_match_arm_with_block() {
271 check_assist_range(
272 introduce_variable,
273 "
274fn main() {
275 let x = true;
276 let tuple = match x {
277 true => {
278 let y = 1;
279 (<|>2 + y<|>, true)
280 }
281 _ => (0, false)
282 };
283}
284",
285 "
286fn main() {
287 let x = true;
288 let tuple = match x {
289 true => {
290 let y = 1;
291 let <|>var_name = 2 + y;
292 (var_name, true)
293 }
294 _ => (0, false)
295 };
296}
297",
298 );
299 }
300
301 #[test]
302 fn test_introduce_var_in_closure_no_block() {
303 check_assist_range(
304 introduce_variable,
305 "
306fn main() {
307 let lambda = |x: u32| <|>x * 2<|>;
308}
309",
310 "
311fn main() {
312 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
313}
314",
315 );
316 }
317
318 #[test]
319 fn test_introduce_var_in_closure_with_block() {
320 check_assist_range(
321 introduce_variable,
322 "
323fn main() {
324 let lambda = |x: u32| { <|>x * 2<|> };
325}
326",
327 "
328fn main() {
329 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
330}
331",
332 );
333 }
334
335 #[test]
336 fn test_introduce_var_path_simple() {
337 check_assist_range(
338 introduce_variable,
339 "
340fn main() {
341 let o = <|>Some(true)<|>;
342}
343",
344 "
345fn main() {
346 let <|>var_name = Some(true);
347 let o = var_name;
348}
349",
350 );
351 }
352
353 #[test]
354 fn test_introduce_var_path_method() {
355 check_assist_range(
356 introduce_variable,
357 "
358fn main() {
359 let v = <|>bar.foo()<|>;
360}
361",
362 "
363fn main() {
364 let <|>var_name = bar.foo();
365 let v = var_name;
366}
367",
368 );
369 }
370
371 #[test]
372 fn test_introduce_var_return() {
373 check_assist_range(
374 introduce_variable,
375 "
376fn foo() -> u32 {
377 <|>return 2 + 2<|>;
378}
379",
380 "
381fn foo() -> u32 {
382 let <|>var_name = 2 + 2;
383 return var_name;
384}
385",
386 );
387 }
388
389 #[test]
390 fn test_introduce_var_does_not_add_extra_whitespace() {
391 check_assist_range(
392 introduce_variable,
393 "
394fn foo() -> u32 {
395
396
397 <|>return 2 + 2<|>;
398}
399",
400 "
401fn foo() -> u32 {
402
403
404 let <|>var_name = 2 + 2;
405 return var_name;
406}
407",
408 );
409
410 check_assist_range(
411 introduce_variable,
412 "
413fn foo() -> u32 {
414
415 <|>return 2 + 2<|>;
416}
417",
418 "
419fn foo() -> u32 {
420
421 let <|>var_name = 2 + 2;
422 return var_name;
423}
424",
425 );
426
427 check_assist_range(
428 introduce_variable,
429 "
430fn foo() -> u32 {
431 let foo = 1;
432
433 // bar
434
435
436 <|>return 2 + 2<|>;
437}
438",
439 "
440fn foo() -> u32 {
441 let foo = 1;
442
443 // bar
444
445
446 let <|>var_name = 2 + 2;
447 return var_name;
448}
449",
450 );
451 }
452
453 #[test]
454 fn test_introduce_var_break() {
455 check_assist_range(
456 introduce_variable,
457 "
458fn main() {
459 let result = loop {
460 <|>break 2 + 2<|>;
461 };
462}
463",
464 "
465fn main() {
466 let result = loop {
467 let <|>var_name = 2 + 2;
468 break var_name;
469 };
470}
471",
472 );
473 }
474
475 #[test]
476 fn test_introduce_var_for_cast() {
477 check_assist_range(
478 introduce_variable,
479 "
480fn main() {
481 let v = <|>0f32 as u32<|>;
482}
483",
484 "
485fn main() {
486 let <|>var_name = 0f32 as u32;
487 let v = var_name;
488}
489",
490 );
491 }
492
493 #[test]
494 fn test_introduce_var_for_return_not_applicable() {
495 check_assist_range_not_applicable(introduce_variable, "fn foo() { <|>return<|>; } ");
496 }
497
498 #[test]
499 fn test_introduce_var_for_break_not_applicable() {
500 check_assist_range_not_applicable(
501 introduce_variable,
502 "fn main() { loop { <|>break<|>; }; }",
503 );
504 }
505
506 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
507 #[test]
508 fn introduce_var_target() {
509 check_assist_range_target(
510 introduce_variable,
511 "fn foo() -> u32 { <|>return 2 + 2<|>; }",
512 "2 + 2",
513 );
514
515 check_assist_range_target(
516 introduce_variable,
517 "
518fn main() {
519 let x = true;
520 let tuple = match x {
521 true => (<|>2 + 2<|>, true)
522 _ => (0, false)
523 };
524}
525",
526 "2 + 2",
527 );
528 }
529}
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
new file mode 100644
index 000000000..a594e7e0c
--- /dev/null
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -0,0 +1,91 @@
1use ra_syntax::ast::{self, AstNode};
2use ra_syntax::T;
3
4use crate::{utils::invert_boolean_expression, Assist, AssistCtx, AssistId};
5
6// Assist: invert_if
7//
8// Apply invert_if
9// This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
10// This also works with `!=`. This assist can only be applied with the cursor
11// on `if`.
12//
13// ```
14// fn main() {
15// if<|> !y { A } else { B }
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// if y { B } else { A }
22// }
23// ```
24
25pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
26 let if_keyword = ctx.find_token_at_offset(T![if])?;
27 let expr = ast::IfExpr::cast(if_keyword.parent())?;
28 let if_range = if_keyword.text_range();
29 let cursor_in_range = ctx.frange.range.is_subrange(&if_range);
30 if !cursor_in_range {
31 return None;
32 }
33
34 let cond = expr.condition()?.expr()?;
35 let then_node = expr.then_branch()?.syntax().clone();
36
37 if let ast::ElseBranch::Block(else_block) = expr.else_branch()? {
38 let cond_range = cond.syntax().text_range();
39 let flip_cond = invert_boolean_expression(cond);
40 let else_node = else_block.syntax();
41 let else_range = else_node.text_range();
42 let then_range = then_node.text_range();
43 return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| {
44 edit.target(if_range);
45 edit.replace(cond_range, flip_cond.syntax().text());
46 edit.replace(else_range, then_node.text());
47 edit.replace(then_range, else_node.text());
48 });
49 }
50
51 None
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 use crate::helpers::{check_assist, check_assist_not_applicable};
59
60 #[test]
61 fn invert_if_remove_inequality() {
62 check_assist(
63 invert_if,
64 "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }",
65 "fn f() { i<|>f x == 3 { 3 + 2 } else { 1 } }",
66 )
67 }
68
69 #[test]
70 fn invert_if_remove_not() {
71 check_assist(
72 invert_if,
73 "fn f() { <|>if !cond { 3 * 2 } else { 1 } }",
74 "fn f() { <|>if cond { 1 } else { 3 * 2 } }",
75 )
76 }
77
78 #[test]
79 fn invert_if_general_case() {
80 check_assist(
81 invert_if,
82 "fn f() { i<|>f cond { 3 * 2 } else { 1 } }",
83 "fn f() { i<|>f !cond { 1 } else { 3 * 2 } }",
84 )
85 }
86
87 #[test]
88 fn invert_if_doesnt_apply_with_cursor_not_on_if() {
89 check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }")
90 }
91}
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
new file mode 100644
index 000000000..670614dd8
--- /dev/null
+++ b/crates/ra_assists/src/handlers/merge_match_arms.rs
@@ -0,0 +1,264 @@
1use std::iter::successors;
2
3use ra_syntax::{
4 ast::{self, AstNode},
5 Direction, TextUnit,
6};
7
8use crate::{Assist, AssistCtx, AssistId, TextRange};
9
10// Assist: merge_match_arms
11//
12// Merges identical match arms.
13//
14// ```
15// enum Action { Move { distance: u32 }, Stop }
16//
17// fn handle(action: Action) {
18// match action {
19// <|>Action::Move(..) => foo(),
20// Action::Stop => foo(),
21// }
22// }
23// ```
24// ->
25// ```
26// enum Action { Move { distance: u32 }, Stop }
27//
28// fn handle(action: Action) {
29// match action {
30// Action::Move(..) | Action::Stop => foo(),
31// }
32// }
33// ```
34pub(crate) fn merge_match_arms(ctx: AssistCtx) -> Option<Assist> {
35 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
36 // Don't try to handle arms with guards for now - can add support for this later
37 if current_arm.guard().is_some() {
38 return None;
39 }
40 let current_expr = current_arm.expr()?;
41 let current_text_range = current_arm.syntax().text_range();
42
43 enum CursorPos {
44 InExpr(TextUnit),
45 InPat(TextUnit),
46 }
47 let cursor_pos = ctx.frange.range.start();
48 let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) {
49 CursorPos::InExpr(current_text_range.end() - cursor_pos)
50 } else {
51 CursorPos::InPat(cursor_pos)
52 };
53
54 // We check if the following match arms match this one. We could, but don't,
55 // compare to the previous match arm as well.
56 let arms_to_merge = successors(Some(current_arm), next_arm)
57 .take_while(|arm| {
58 if arm.guard().is_some() {
59 return false;
60 }
61 match arm.expr() {
62 Some(expr) => expr.syntax().text() == current_expr.syntax().text(),
63 None => false,
64 }
65 })
66 .collect::<Vec<_>>();
67
68 if arms_to_merge.len() <= 1 {
69 return None;
70 }
71
72 ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| {
73 let pats = if arms_to_merge.iter().any(contains_placeholder) {
74 "_".into()
75 } else {
76 arms_to_merge
77 .iter()
78 .flat_map(ast::MatchArm::pats)
79 .map(|x| x.syntax().to_string())
80 .collect::<Vec<String>>()
81 .join(" | ")
82 };
83
84 let arm = format!("{} => {}", pats, current_expr.syntax().text());
85
86 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
87 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
88
89 edit.target(current_text_range);
90 edit.set_cursor(match cursor_pos {
91 CursorPos::InExpr(back_offset) => start + TextUnit::from_usize(arm.len()) - back_offset,
92 CursorPos::InPat(offset) => offset,
93 });
94 edit.replace(TextRange::from_to(start, end), arm);
95 })
96}
97
98fn contains_placeholder(a: &ast::MatchArm) -> bool {
99 a.pats().any(|x| match x {
100 ra_syntax::ast::Pat::PlaceholderPat(..) => true,
101 _ => false,
102 })
103}
104
105fn next_arm(arm: &ast::MatchArm) -> Option<ast::MatchArm> {
106 arm.syntax().siblings(Direction::Next).skip(1).find_map(ast::MatchArm::cast)
107}
108
109#[cfg(test)]
110mod tests {
111 use super::merge_match_arms;
112 use crate::helpers::{check_assist, check_assist_not_applicable};
113
114 #[test]
115 fn merge_match_arms_single_patterns() {
116 check_assist(
117 merge_match_arms,
118 r#"
119 #[derive(Debug)]
120 enum X { A, B, C }
121
122 fn main() {
123 let x = X::A;
124 let y = match x {
125 X::A => { 1i32<|> }
126 X::B => { 1i32 }
127 X::C => { 2i32 }
128 }
129 }
130 "#,
131 r#"
132 #[derive(Debug)]
133 enum X { A, B, C }
134
135 fn main() {
136 let x = X::A;
137 let y = match x {
138 X::A | X::B => { 1i32<|> }
139 X::C => { 2i32 }
140 }
141 }
142 "#,
143 );
144 }
145
146 #[test]
147 fn merge_match_arms_multiple_patterns() {
148 check_assist(
149 merge_match_arms,
150 r#"
151 #[derive(Debug)]
152 enum X { A, B, C, D, E }
153
154 fn main() {
155 let x = X::A;
156 let y = match x {
157 X::A | X::B => {<|> 1i32 },
158 X::C | X::D => { 1i32 },
159 X::E => { 2i32 },
160 }
161 }
162 "#,
163 r#"
164 #[derive(Debug)]
165 enum X { A, B, C, D, E }
166
167 fn main() {
168 let x = X::A;
169 let y = match x {
170 X::A | X::B | X::C | X::D => {<|> 1i32 },
171 X::E => { 2i32 },
172 }
173 }
174 "#,
175 );
176 }
177
178 #[test]
179 fn merge_match_arms_placeholder_pattern() {
180 check_assist(
181 merge_match_arms,
182 r#"
183 #[derive(Debug)]
184 enum X { A, B, C, D, E }
185
186 fn main() {
187 let x = X::A;
188 let y = match x {
189 X::A => { 1i32 },
190 X::B => { 2i<|>32 },
191 _ => { 2i32 }
192 }
193 }
194 "#,
195 r#"
196 #[derive(Debug)]
197 enum X { A, B, C, D, E }
198
199 fn main() {
200 let x = X::A;
201 let y = match x {
202 X::A => { 1i32 },
203 _ => { 2i<|>32 }
204 }
205 }
206 "#,
207 );
208 }
209
210 #[test]
211 fn merges_all_subsequent_arms() {
212 check_assist(
213 merge_match_arms,
214 r#"
215 enum X { A, B, C, D, E }
216
217 fn main() {
218 match X::A {
219 X::A<|> => 92,
220 X::B => 92,
221 X::C => 92,
222 X::D => 62,
223 _ => panic!(),
224 }
225 }
226 "#,
227 r#"
228 enum X { A, B, C, D, E }
229
230 fn main() {
231 match X::A {
232 X::A<|> | X::B | X::C => 92,
233 X::D => 62,
234 _ => panic!(),
235 }
236 }
237 "#,
238 )
239 }
240
241 #[test]
242 fn merge_match_arms_rejects_guards() {
243 check_assist_not_applicable(
244 merge_match_arms,
245 r#"
246 #[derive(Debug)]
247 enum X {
248 A(i32),
249 B,
250 C
251 }
252
253 fn main() {
254 let x = X::A;
255 let y = match x {
256 X::A(a) if a > 5 => { <|>1i32 },
257 X::B => { 1i32 },
258 X::C => { 2i32 }
259 }
260 }
261 "#,
262 );
263 }
264}
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
new file mode 100644
index 000000000..90793b5fc
--- /dev/null
+++ b/crates/ra_assists/src/handlers/move_bounds.rs
@@ -0,0 +1,137 @@
1use ra_syntax::{
2 ast::{self, edit, make, AstNode, NameOwner, TypeBoundsOwner},
3 SyntaxElement,
4 SyntaxKind::*,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8
9// Assist: move_bounds_to_where_clause
10//
11// Moves inline type bounds to a where clause.
12//
13// ```
14// fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U {
15// f(x)
16// }
17// ```
18// ->
19// ```
20// fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
21// f(x)
22// }
23// ```
24pub(crate) fn move_bounds_to_where_clause(ctx: AssistCtx) -> Option<Assist> {
25 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?;
26
27 let mut type_params = type_param_list.type_params();
28 if type_params.all(|p| p.type_bound_list().is_none()) {
29 return None;
30 }
31
32 let parent = type_param_list.syntax().parent()?;
33 if parent.children_with_tokens().any(|it| it.kind() == WHERE_CLAUSE) {
34 return None;
35 }
36
37 let anchor: SyntaxElement = match parent.kind() {
38 FN_DEF => ast::FnDef::cast(parent)?.body()?.syntax().clone().into(),
39 TRAIT_DEF => ast::TraitDef::cast(parent)?.item_list()?.syntax().clone().into(),
40 IMPL_BLOCK => ast::ImplBlock::cast(parent)?.item_list()?.syntax().clone().into(),
41 ENUM_DEF => ast::EnumDef::cast(parent)?.variant_list()?.syntax().clone().into(),
42 STRUCT_DEF => parent
43 .children_with_tokens()
44 .find(|it| it.kind() == RECORD_FIELD_DEF_LIST || it.kind() == SEMI)?,
45 _ => return None,
46 };
47
48 ctx.add_assist(AssistId("move_bounds_to_where_clause"), "Move to where clause", |edit| {
49 let new_params = type_param_list
50 .type_params()
51 .filter(|it| it.type_bound_list().is_some())
52 .map(|type_param| {
53 let without_bounds = type_param.remove_bounds();
54 (type_param, without_bounds)
55 });
56
57 let new_type_param_list = edit::replace_descendants(&type_param_list, new_params);
58 edit.replace_ast(type_param_list.clone(), new_type_param_list);
59
60 let where_clause = {
61 let predicates = type_param_list.type_params().filter_map(build_predicate);
62 make::where_clause(predicates)
63 };
64
65 let to_insert = match anchor.prev_sibling_or_token() {
66 Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()),
67 _ => format!(" {}", where_clause.syntax()),
68 };
69 edit.insert(anchor.text_range().start(), to_insert);
70 edit.target(type_param_list.syntax().text_range());
71 })
72}
73
74fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
75 let path = make::path_from_name_ref(make::name_ref(&param.name()?.syntax().to_string()));
76 let predicate = make::where_pred(path, param.type_bound_list()?.bounds());
77 Some(predicate)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 use crate::helpers::check_assist;
85
86 #[test]
87 fn move_bounds_to_where_clause_fn() {
88 check_assist(
89 move_bounds_to_where_clause,
90 r#"
91 fn foo<T: u32, <|>F: FnOnce(T) -> T>() {}
92 "#,
93 r#"
94 fn foo<T, <|>F>() where T: u32, F: FnOnce(T) -> T {}
95 "#,
96 );
97 }
98
99 #[test]
100 fn move_bounds_to_where_clause_impl() {
101 check_assist(
102 move_bounds_to_where_clause,
103 r#"
104 impl<U: u32, <|>T> A<U, T> {}
105 "#,
106 r#"
107 impl<U, <|>T> A<U, T> where U: u32 {}
108 "#,
109 );
110 }
111
112 #[test]
113 fn move_bounds_to_where_clause_struct() {
114 check_assist(
115 move_bounds_to_where_clause,
116 r#"
117 struct A<<|>T: Iterator<Item = u32>> {}
118 "#,
119 r#"
120 struct A<<|>T> where T: Iterator<Item = u32> {}
121 "#,
122 );
123 }
124
125 #[test]
126 fn move_bounds_to_where_clause_tuple_struct() {
127 check_assist(
128 move_bounds_to_where_clause,
129 r#"
130 struct Pair<<|>T: u32>(T, T);
131 "#,
132 r#"
133 struct Pair<<|>T>(T, T) where T: u32;
134 "#,
135 );
136 }
137}
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs
new file mode 100644
index 000000000..2b91ce7c4
--- /dev/null
+++ b/crates/ra_assists/src/handlers/move_guard.rs
@@ -0,0 +1,308 @@
1use ra_syntax::{
2 ast,
3 ast::{AstNode, AstToken, IfExpr, MatchArm},
4 TextUnit,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8
9// Assist: move_guard_to_arm_body
10//
11// Moves match guard into match arm body.
12//
13// ```
14// enum Action { Move { distance: u32 }, Stop }
15//
16// fn handle(action: Action) {
17// match action {
18// Action::Move { distance } <|>if distance > 10 => foo(),
19// _ => (),
20// }
21// }
22// ```
23// ->
24// ```
25// enum Action { Move { distance: u32 }, Stop }
26//
27// fn handle(action: Action) {
28// match action {
29// Action::Move { distance } => if distance > 10 { foo() },
30// _ => (),
31// }
32// }
33// ```
34pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> {
35 let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
36 let guard = match_arm.guard()?;
37 let space_before_guard = guard.syntax().prev_sibling_or_token();
38
39 let guard_conditions = guard.expr()?;
40 let arm_expr = match_arm.expr()?;
41 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
42
43 ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", |edit| {
44 edit.target(guard.syntax().text_range());
45 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) {
46 Some(tok) => {
47 if let Some(_) = ast::Whitespace::cast(tok.clone()) {
48 let ele = tok.text_range();
49 edit.delete(ele);
50 ele.len()
51 } else {
52 TextUnit::from(0)
53 }
54 }
55 _ => TextUnit::from(0),
56 };
57
58 edit.delete(guard.syntax().text_range());
59 edit.replace_node_and_indent(arm_expr.syntax(), buf);
60 edit.set_cursor(
61 arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount,
62 );
63 })
64}
65
66// Assist: move_arm_cond_to_match_guard
67//
68// Moves if expression from match arm body into a guard.
69//
70// ```
71// enum Action { Move { distance: u32 }, Stop }
72//
73// fn handle(action: Action) {
74// match action {
75// Action::Move { distance } => <|>if distance > 10 { foo() },
76// _ => (),
77// }
78// }
79// ```
80// ->
81// ```
82// enum Action { Move { distance: u32 }, Stop }
83//
84// fn handle(action: Action) {
85// match action {
86// Action::Move { distance } if distance > 10 => foo(),
87// _ => (),
88// }
89// }
90// ```
91pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> {
92 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
93 let last_match_pat = match_arm.pats().last()?;
94
95 let arm_body = match_arm.expr()?;
96 let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?;
97 let cond = if_expr.condition()?;
98 let then_block = if_expr.then_branch()?;
99
100 // Not support if with else branch
101 if let Some(_) = if_expr.else_branch() {
102 return None;
103 }
104 // Not support moving if let to arm guard
105 if let Some(_) = cond.pat() {
106 return None;
107 }
108
109 let buf = format!(" if {}", cond.syntax().text());
110
111 ctx.add_assist(
112 AssistId("move_arm_cond_to_match_guard"),
113 "Move condition to match guard",
114 |edit| {
115 edit.target(if_expr.syntax().text_range());
116 let then_only_expr = then_block.block().and_then(|it| it.statements().next()).is_none();
117
118 match &then_block.block().and_then(|it| it.expr()) {
119 Some(then_expr) if then_only_expr => {
120 edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text())
121 }
122 _ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()),
123 }
124
125 edit.insert(last_match_pat.syntax().text_range().end(), buf);
126 edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1));
127 },
128 )
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
136
137 #[test]
138 fn move_guard_to_arm_body_target() {
139 check_assist_target(
140 move_guard_to_arm_body,
141 r#"
142 fn f() {
143 let t = 'a';
144 let chars = "abcd";
145 match t {
146 '\r' <|>if chars.clone().next() == Some('\n') => false,
147 _ => true
148 }
149 }
150 "#,
151 r#"if chars.clone().next() == Some('\n')"#,
152 );
153 }
154
155 #[test]
156 fn move_guard_to_arm_body_works() {
157 check_assist(
158 move_guard_to_arm_body,
159 r#"
160 fn f() {
161 let t = 'a';
162 let chars = "abcd";
163 match t {
164 '\r' <|>if chars.clone().next() == Some('\n') => false,
165 _ => true
166 }
167 }
168 "#,
169 r#"
170 fn f() {
171 let t = 'a';
172 let chars = "abcd";
173 match t {
174 '\r' => if chars.clone().next() == Some('\n') { <|>false },
175 _ => true
176 }
177 }
178 "#,
179 );
180 }
181
182 #[test]
183 fn move_guard_to_arm_body_works_complex_match() {
184 check_assist(
185 move_guard_to_arm_body,
186 r#"
187 fn f() {
188 match x {
189 <|>y @ 4 | y @ 5 if y > 5 => true,
190 _ => false
191 }
192 }
193 "#,
194 r#"
195 fn f() {
196 match x {
197 y @ 4 | y @ 5 => if y > 5 { <|>true },
198 _ => false
199 }
200 }
201 "#,
202 );
203 }
204
205 #[test]
206 fn move_arm_cond_to_match_guard_works() {
207 check_assist(
208 move_arm_cond_to_match_guard,
209 r#"
210 fn f() {
211 let t = 'a';
212 let chars = "abcd";
213 match t {
214 '\r' => if chars.clone().next() == Some('\n') { <|>false },
215 _ => true
216 }
217 }
218 "#,
219 r#"
220 fn f() {
221 let t = 'a';
222 let chars = "abcd";
223 match t {
224 '\r' <|>if chars.clone().next() == Some('\n') => false,
225 _ => true
226 }
227 }
228 "#,
229 );
230 }
231
232 #[test]
233 fn move_arm_cond_to_match_guard_if_let_not_works() {
234 check_assist_not_applicable(
235 move_arm_cond_to_match_guard,
236 r#"
237 fn f() {
238 let t = 'a';
239 let chars = "abcd";
240 match t {
241 '\r' => if let Some(_) = chars.clone().next() { <|>false },
242 _ => true
243 }
244 }
245 "#,
246 );
247 }
248
249 #[test]
250 fn move_arm_cond_to_match_guard_if_empty_body_works() {
251 check_assist(
252 move_arm_cond_to_match_guard,
253 r#"
254 fn f() {
255 let t = 'a';
256 let chars = "abcd";
257 match t {
258 '\r' => if chars.clone().next().is_some() { <|> },
259 _ => true
260 }
261 }
262 "#,
263 r#"
264 fn f() {
265 let t = 'a';
266 let chars = "abcd";
267 match t {
268 '\r' <|>if chars.clone().next().is_some() => { },
269 _ => true
270 }
271 }
272 "#,
273 );
274 }
275
276 #[test]
277 fn move_arm_cond_to_match_guard_if_multiline_body_works() {
278 check_assist(
279 move_arm_cond_to_match_guard,
280 r#"
281 fn f() {
282 let mut t = 'a';
283 let chars = "abcd";
284 match t {
285 '\r' => if chars.clone().next().is_some() {
286 t = 'e';<|>
287 false
288 },
289 _ => true
290 }
291 }
292 "#,
293 r#"
294 fn f() {
295 let mut t = 'a';
296 let chars = "abcd";
297 match t {
298 '\r' <|>if chars.clone().next().is_some() => {
299 t = 'e';
300 false
301 },
302 _ => true
303 }
304 }
305 "#,
306 );
307 }
308}
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
new file mode 100644
index 000000000..2c0a1e126
--- /dev/null
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -0,0 +1,499 @@
1use ra_syntax::{
2 ast, AstToken,
3 SyntaxKind::{RAW_STRING, STRING},
4 TextUnit,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8
9// Assist: make_raw_string
10//
11// Adds `r#` to a plain string literal.
12//
13// ```
14// fn main() {
15// "Hello,<|> World!";
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// r#"Hello, World!"#;
22// }
23// ```
24pub(crate) fn make_raw_string(ctx: AssistCtx) -> Option<Assist> {
25 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
26 let value = token.value()?;
27 ctx.add_assist(AssistId("make_raw_string"), "Rewrite as raw string", |edit| {
28 edit.target(token.syntax().text_range());
29 let max_hash_streak = count_hashes(&value);
30 let mut hashes = String::with_capacity(max_hash_streak + 1);
31 for _ in 0..hashes.capacity() {
32 hashes.push('#');
33 }
34 edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes));
35 })
36}
37
38// Assist: make_usual_string
39//
40// Turns a raw string into a plain string.
41//
42// ```
43// fn main() {
44// r#"Hello,<|> "World!""#;
45// }
46// ```
47// ->
48// ```
49// fn main() {
50// "Hello, \"World!\"";
51// }
52// ```
53pub(crate) fn make_usual_string(ctx: AssistCtx) -> Option<Assist> {
54 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
55 let value = token.value()?;
56 ctx.add_assist(AssistId("make_usual_string"), "Rewrite as regular string", |edit| {
57 edit.target(token.syntax().text_range());
58 // parse inside string to escape `"`
59 let escaped = value.escape_default().to_string();
60 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
61 })
62}
63
64// Assist: add_hash
65//
66// Adds a hash to a raw string literal.
67//
68// ```
69// fn main() {
70// r#"Hello,<|> World!"#;
71// }
72// ```
73// ->
74// ```
75// fn main() {
76// r##"Hello, World!"##;
77// }
78// ```
79pub(crate) fn add_hash(ctx: AssistCtx) -> Option<Assist> {
80 let token = ctx.find_token_at_offset(RAW_STRING)?;
81 ctx.add_assist(AssistId("add_hash"), "Add # to raw string", |edit| {
82 edit.target(token.text_range());
83 edit.insert(token.text_range().start() + TextUnit::of_char('r'), "#");
84 edit.insert(token.text_range().end(), "#");
85 })
86}
87
88// Assist: remove_hash
89//
90// Removes a hash from a raw string literal.
91//
92// ```
93// fn main() {
94// r#"Hello,<|> World!"#;
95// }
96// ```
97// ->
98// ```
99// fn main() {
100// r"Hello, World!";
101// }
102// ```
103pub(crate) fn remove_hash(ctx: AssistCtx) -> Option<Assist> {
104 let token = ctx.find_token_at_offset(RAW_STRING)?;
105 let text = token.text().as_str();
106 if text.starts_with("r\"") {
107 // no hash to remove
108 return None;
109 }
110 ctx.add_assist(AssistId("remove_hash"), "Remove hash from raw string", |edit| {
111 edit.target(token.text_range());
112 let result = &text[2..text.len() - 1];
113 let result = if result.starts_with('\"') {
114 // FIXME: this logic is wrong, not only the last has has to handled specially
115 // no more hash, escape
116 let internal_str = &result[1..result.len() - 1];
117 format!("\"{}\"", internal_str.escape_default().to_string())
118 } else {
119 result.to_owned()
120 };
121 edit.replace(token.text_range(), format!("r{}", result));
122 })
123}
124
125fn count_hashes(s: &str) -> usize {
126 let mut max_hash_streak = 0usize;
127 for idx in s.match_indices("\"#").map(|(i, _)| i) {
128 let (_, sub) = s.split_at(idx + 1);
129 let nb_hash = sub.chars().take_while(|c| *c == '#').count();
130 if nb_hash > max_hash_streak {
131 max_hash_streak = nb_hash;
132 }
133 }
134 max_hash_streak
135}
136
137#[cfg(test)]
138mod test {
139 use super::*;
140 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
141
142 #[test]
143 fn make_raw_string_target() {
144 check_assist_target(
145 make_raw_string,
146 r#"
147 fn f() {
148 let s = <|>"random\nstring";
149 }
150 "#,
151 r#""random\nstring""#,
152 );
153 }
154
155 #[test]
156 fn make_raw_string_works() {
157 check_assist(
158 make_raw_string,
159 r#"
160 fn f() {
161 let s = <|>"random\nstring";
162 }
163 "#,
164 r##"
165 fn f() {
166 let s = <|>r#"random
167string"#;
168 }
169 "##,
170 )
171 }
172
173 #[test]
174 fn make_raw_string_works_inside_macros() {
175 check_assist(
176 make_raw_string,
177 r#"
178 fn f() {
179 format!(<|>"x = {}", 92)
180 }
181 "#,
182 r##"
183 fn f() {
184 format!(<|>r#"x = {}"#, 92)
185 }
186 "##,
187 )
188 }
189
190 #[test]
191 fn make_raw_string_hashes_inside_works() {
192 check_assist(
193 make_raw_string,
194 r###"
195 fn f() {
196 let s = <|>"#random##\nstring";
197 }
198 "###,
199 r####"
200 fn f() {
201 let s = <|>r#"#random##
202string"#;
203 }
204 "####,
205 )
206 }
207
208 #[test]
209 fn make_raw_string_closing_hashes_inside_works() {
210 check_assist(
211 make_raw_string,
212 r###"
213 fn f() {
214 let s = <|>"#random\"##\nstring";
215 }
216 "###,
217 r####"
218 fn f() {
219 let s = <|>r###"#random"##
220string"###;
221 }
222 "####,
223 )
224 }
225
226 #[test]
227 fn make_raw_string_nothing_to_unescape_works() {
228 check_assist(
229 make_raw_string,
230 r#"
231 fn f() {
232 let s = <|>"random string";
233 }
234 "#,
235 r##"
236 fn f() {
237 let s = <|>r#"random string"#;
238 }
239 "##,
240 )
241 }
242
243 #[test]
244 fn make_raw_string_not_works_on_partial_string() {
245 check_assist_not_applicable(
246 make_raw_string,
247 r#"
248 fn f() {
249 let s = "foo<|>
250 }
251 "#,
252 )
253 }
254
255 #[test]
256 fn make_usual_string_not_works_on_partial_string() {
257 check_assist_not_applicable(
258 make_usual_string,
259 r#"
260 fn main() {
261 let s = r#"bar<|>
262 }
263 "#,
264 )
265 }
266
267 #[test]
268 fn add_hash_target() {
269 check_assist_target(
270 add_hash,
271 r#"
272 fn f() {
273 let s = <|>r"random string";
274 }
275 "#,
276 r#"r"random string""#,
277 );
278 }
279
280 #[test]
281 fn add_hash_works() {
282 check_assist(
283 add_hash,
284 r#"
285 fn f() {
286 let s = <|>r"random string";
287 }
288 "#,
289 r##"
290 fn f() {
291 let s = <|>r#"random string"#;
292 }
293 "##,
294 )
295 }
296
297 #[test]
298 fn add_more_hash_works() {
299 check_assist(
300 add_hash,
301 r##"
302 fn f() {
303 let s = <|>r#"random"string"#;
304 }
305 "##,
306 r###"
307 fn f() {
308 let s = <|>r##"random"string"##;
309 }
310 "###,
311 )
312 }
313
314 #[test]
315 fn add_hash_not_works() {
316 check_assist_not_applicable(
317 add_hash,
318 r#"
319 fn f() {
320 let s = <|>"random string";
321 }
322 "#,
323 );
324 }
325
326 #[test]
327 fn remove_hash_target() {
328 check_assist_target(
329 remove_hash,
330 r##"
331 fn f() {
332 let s = <|>r#"random string"#;
333 }
334 "##,
335 r##"r#"random string"#"##,
336 );
337 }
338
339 #[test]
340 fn remove_hash_works() {
341 check_assist(
342 remove_hash,
343 r##"
344 fn f() {
345 let s = <|>r#"random string"#;
346 }
347 "##,
348 r#"
349 fn f() {
350 let s = <|>r"random string";
351 }
352 "#,
353 )
354 }
355
356 #[test]
357 fn remove_hash_with_quote_works() {
358 check_assist(
359 remove_hash,
360 r##"
361 fn f() {
362 let s = <|>r#"random"str"ing"#;
363 }
364 "##,
365 r#"
366 fn f() {
367 let s = <|>r"random\"str\"ing";
368 }
369 "#,
370 )
371 }
372
373 #[test]
374 fn remove_more_hash_works() {
375 check_assist(
376 remove_hash,
377 r###"
378 fn f() {
379 let s = <|>r##"random string"##;
380 }
381 "###,
382 r##"
383 fn f() {
384 let s = <|>r#"random string"#;
385 }
386 "##,
387 )
388 }
389
390 #[test]
391 fn remove_hash_not_works() {
392 check_assist_not_applicable(
393 remove_hash,
394 r#"
395 fn f() {
396 let s = <|>"random string";
397 }
398 "#,
399 );
400 }
401
402 #[test]
403 fn remove_hash_no_hash_not_works() {
404 check_assist_not_applicable(
405 remove_hash,
406 r#"
407 fn f() {
408 let s = <|>r"random string";
409 }
410 "#,
411 );
412 }
413
414 #[test]
415 fn make_usual_string_target() {
416 check_assist_target(
417 make_usual_string,
418 r##"
419 fn f() {
420 let s = <|>r#"random string"#;
421 }
422 "##,
423 r##"r#"random string"#"##,
424 );
425 }
426
427 #[test]
428 fn make_usual_string_works() {
429 check_assist(
430 make_usual_string,
431 r##"
432 fn f() {
433 let s = <|>r#"random string"#;
434 }
435 "##,
436 r#"
437 fn f() {
438 let s = <|>"random string";
439 }
440 "#,
441 )
442 }
443
444 #[test]
445 fn make_usual_string_with_quote_works() {
446 check_assist(
447 make_usual_string,
448 r##"
449 fn f() {
450 let s = <|>r#"random"str"ing"#;
451 }
452 "##,
453 r#"
454 fn f() {
455 let s = <|>"random\"str\"ing";
456 }
457 "#,
458 )
459 }
460
461 #[test]
462 fn make_usual_string_more_hash_works() {
463 check_assist(
464 make_usual_string,
465 r###"
466 fn f() {
467 let s = <|>r##"random string"##;
468 }
469 "###,
470 r##"
471 fn f() {
472 let s = <|>"random string";
473 }
474 "##,
475 )
476 }
477
478 #[test]
479 fn make_usual_string_not_works() {
480 check_assist_not_applicable(
481 make_usual_string,
482 r#"
483 fn f() {
484 let s = <|>"random string";
485 }
486 "#,
487 );
488 }
489
490 #[test]
491 fn count_hashes_test() {
492 assert_eq!(0, count_hashes("abc"));
493 assert_eq!(0, count_hashes("###"));
494 assert_eq!(1, count_hashes("\"#abc"));
495 assert_eq!(0, count_hashes("#abc"));
496 assert_eq!(2, count_hashes("#ab\"##c"));
497 assert_eq!(4, count_hashes("#ab\"##\"####c"));
498 }
499}
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
new file mode 100644
index 000000000..5085649b4
--- /dev/null
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -0,0 +1,150 @@
1use ra_syntax::{
2 ast::{self, AstNode},
3 TextUnit, T,
4};
5
6use crate::{Assist, AssistCtx, AssistId};
7
8// Assist: remove_dbg
9//
10// Removes `dbg!()` macro call.
11//
12// ```
13// fn main() {
14// <|>dbg!(92);
15// }
16// ```
17// ->
18// ```
19// fn main() {
20// 92;
21// }
22// ```
23pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25
26 if !is_valid_macrocall(&macro_call, "dbg")? {
27 return None;
28 }
29
30 let macro_range = macro_call.syntax().text_range();
31
32 // If the cursor is inside the macro call, we'll try to maintain the cursor
33 // position by subtracting the length of dbg!( from the start of the file
34 // range, otherwise we'll default to using the start of the macro call
35 let cursor_pos = {
36 let file_range = ctx.frange.range;
37
38 let offset_start = file_range
39 .start()
40 .checked_sub(macro_range.start())
41 .unwrap_or_else(|| TextUnit::from(0));
42
43 let dbg_size = TextUnit::of_str("dbg!(");
44
45 if offset_start > dbg_size {
46 file_range.start() - dbg_size
47 } else {
48 macro_range.start()
49 }
50 };
51
52 let macro_content = {
53 let macro_args = macro_call.token_tree()?.syntax().clone();
54
55 let text = macro_args.text();
56 let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')');
57 text.slice(without_parens).to_string()
58 };
59
60 ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| {
61 edit.target(macro_call.syntax().text_range());
62 edit.replace(macro_range, macro_content);
63 edit.set_cursor(cursor_pos);
64 })
65}
66
67/// Verifies that the given macro_call actually matches the given name
68/// and contains proper ending tokens
69fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> {
70 let path = macro_call.path()?;
71 let name_ref = path.segment()?.name_ref()?;
72
73 // Make sure it is actually a dbg-macro call, dbg followed by !
74 let excl = path.syntax().next_sibling_or_token()?;
75
76 if name_ref.text() != macro_name || excl.kind() != T![!] {
77 return None;
78 }
79
80 let node = macro_call.token_tree()?.syntax().clone();
81 let first_child = node.first_child_or_token()?;
82 let last_child = node.last_child_or_token()?;
83
84 match (first_child.kind(), last_child.kind()) {
85 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true),
86 _ => Some(false),
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
94
95 #[test]
96 fn test_remove_dbg() {
97 check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1");
98
99 check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)");
100
101 check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1");
102
103 check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1");
104
105 check_assist(
106 remove_dbg,
107 "
108fn foo(n: usize) {
109 if let Some(_) = dbg!(n.<|>checked_sub(4)) {
110 // ...
111 }
112}
113",
114 "
115fn foo(n: usize) {
116 if let Some(_) = n.<|>checked_sub(4) {
117 // ...
118 }
119}
120",
121 );
122 }
123 #[test]
124 fn test_remove_dbg_with_brackets_and_braces() {
125 check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1");
126 check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1");
127 }
128
129 #[test]
130 fn test_remove_dbg_not_applicable() {
131 check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]");
132 check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)");
133 check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7");
134 }
135
136 #[test]
137 fn remove_dbg_target() {
138 check_assist_target(
139 remove_dbg,
140 "
141fn foo(n: usize) {
142 if let Some(_) = dbg!(n.<|>checked_sub(4)) {
143 // ...
144 }
145}
146",
147 "dbg!(n.checked_sub(4))",
148 );
149 }
150}
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
new file mode 100644
index 000000000..e6cd50bc1
--- /dev/null
+++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
@@ -0,0 +1,148 @@
1use ra_fmt::unwrap_trivial_block;
2use ra_syntax::{
3 ast::{self, make},
4 AstNode,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8use ast::edit::IndentLevel;
9
10// Assist: replace_if_let_with_match
11//
12// Replaces `if let` with an else branch with a `match` expression.
13//
14// ```
15// enum Action { Move { distance: u32 }, Stop }
16//
17// fn handle(action: Action) {
18// <|>if let Action::Move { distance } = action {
19// foo(distance)
20// } else {
21// bar()
22// }
23// }
24// ```
25// ->
26// ```
27// enum Action { Move { distance: u32 }, Stop }
28//
29// fn handle(action: Action) {
30// match action {
31// Action::Move { distance } => foo(distance),
32// _ => bar(),
33// }
34// }
35// ```
36pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
37 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
38 let cond = if_expr.condition()?;
39 let pat = cond.pat()?;
40 let expr = cond.expr()?;
41 let then_block = if_expr.then_branch()?;
42 let else_block = match if_expr.else_branch()? {
43 ast::ElseBranch::Block(it) => it,
44 ast::ElseBranch::IfExpr(_) => return None,
45 };
46
47 ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| {
48 let match_expr = {
49 let then_arm = {
50 let then_expr = unwrap_trivial_block(then_block);
51 make::match_arm(vec![pat], then_expr)
52 };
53 let else_arm = {
54 let else_expr = unwrap_trivial_block(else_block);
55 make::match_arm(vec![make::placeholder_pat().into()], else_expr)
56 };
57 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
58 };
59
60 let match_expr = IndentLevel::from_node(if_expr.syntax()).increase_indent(match_expr);
61
62 edit.target(if_expr.syntax().text_range());
63 edit.set_cursor(if_expr.syntax().text_range().start());
64 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr.into());
65 })
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::helpers::{check_assist, check_assist_target};
72
73 #[test]
74 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
75 check_assist(
76 replace_if_let_with_match,
77 "
78impl VariantData {
79 pub fn is_struct(&self) -> bool {
80 if <|>let VariantData::Struct(..) = *self {
81 true
82 } else {
83 false
84 }
85 }
86} ",
87 "
88impl VariantData {
89 pub fn is_struct(&self) -> bool {
90 <|>match *self {
91 VariantData::Struct(..) => true,
92 _ => false,
93 }
94 }
95} ",
96 )
97 }
98
99 #[test]
100 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
101 check_assist(
102 replace_if_let_with_match,
103 "
104fn foo() {
105 if <|>let VariantData::Struct(..) = a {
106 bar(
107 123
108 )
109 } else {
110 false
111 }
112} ",
113 "
114fn foo() {
115 <|>match a {
116 VariantData::Struct(..) => {
117 bar(
118 123
119 )
120 }
121 _ => false,
122 }
123} ",
124 )
125 }
126
127 #[test]
128 fn replace_if_let_with_match_target() {
129 check_assist_target(
130 replace_if_let_with_match,
131 "
132impl VariantData {
133 pub fn is_struct(&self) -> bool {
134 if <|>let VariantData::Struct(..) = *self {
135 true
136 } else {
137 false
138 }
139 }
140} ",
141 "if let VariantData::Struct(..) = *self {
142 true
143 } else {
144 false
145 }",
146 );
147 }
148}
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
new file mode 100644
index 000000000..2c3f07a79
--- /dev/null
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -0,0 +1,69 @@
1use std::iter::successors;
2
3use ra_syntax::{ast, AstNode, TextUnit, T};
4
5use crate::{Assist, AssistCtx, AssistId};
6
7// Assist: split_import
8//
9// Wraps the tail of import into braces.
10//
11// ```
12// use std::<|>collections::HashMap;
13// ```
14// ->
15// ```
16// use std::{collections::HashMap};
17// ```
18pub(crate) fn split_import(ctx: AssistCtx) -> Option<Assist> {
19 let colon_colon = ctx.find_token_at_offset(T![::])?;
20 let path = ast::Path::cast(colon_colon.parent())?;
21 let top_path = successors(Some(path), |it| it.parent_path()).last()?;
22
23 let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast);
24 if use_tree.is_none() {
25 return None;
26 }
27
28 let l_curly = colon_colon.text_range().end();
29 let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) {
30 Some(tree) => tree.syntax().text_range().end(),
31 None => top_path.syntax().text_range().end(),
32 };
33
34 ctx.add_assist(AssistId("split_import"), "Split import", |edit| {
35 edit.target(colon_colon.text_range());
36 edit.insert(l_curly, "{");
37 edit.insert(r_curly, "}");
38 edit.set_cursor(l_curly + TextUnit::of_str("{"));
39 })
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::helpers::{check_assist, check_assist_target};
46
47 #[test]
48 fn test_split_import() {
49 check_assist(
50 split_import,
51 "use crate::<|>db::RootDatabase;",
52 "use crate::{<|>db::RootDatabase};",
53 )
54 }
55
56 #[test]
57 fn split_import_works_with_trees() {
58 check_assist(
59 split_import,
60 "use crate:<|>:db::{RootDatabase, FileSymbol}",
61 "use crate::{<|>db::{RootDatabase, FileSymbol}}",
62 )
63 }
64
65 #[test]
66 fn split_import_target() {
67 check_assist_target(split_import, "use crate::<|>db::{RootDatabase, FileSymbol}", "::");
68 }
69}