diff options
Diffstat (limited to 'crates/ra_assists/src/assists')
18 files changed, 4541 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/assists/add_derive.rs new file mode 100644 index 000000000..9c88644df --- /dev/null +++ b/crates/ra_assists/src/assists/add_derive.rs | |||
@@ -0,0 +1,105 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, AttrsOwner}, | ||
4 | SyntaxKind::{COMMENT, WHITESPACE}, | ||
5 | TextUnit, | ||
6 | }; | ||
7 | |||
8 | use crate::{Assist, AssistCtx, AssistId}; | ||
9 | |||
10 | pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
11 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | ||
12 | let node_start = derive_insertion_offset(&nominal)?; | ||
13 | ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| { | ||
14 | let derive_attr = nominal | ||
15 | .attrs() | ||
16 | .filter_map(|x| x.as_call()) | ||
17 | .filter(|(name, _arg)| name == "derive") | ||
18 | .map(|(_name, arg)| arg) | ||
19 | .next(); | ||
20 | let offset = match derive_attr { | ||
21 | None => { | ||
22 | edit.insert(node_start, "#[derive()]\n"); | ||
23 | node_start + TextUnit::of_str("#[derive(") | ||
24 | } | ||
25 | Some(tt) => tt.syntax().text_range().end() - TextUnit::of_char(')'), | ||
26 | }; | ||
27 | edit.target(nominal.syntax().text_range()); | ||
28 | edit.set_cursor(offset) | ||
29 | }); | ||
30 | |||
31 | ctx.build() | ||
32 | } | ||
33 | |||
34 | // Insert `derive` after doc comments. | ||
35 | fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextUnit> { | ||
36 | let non_ws_child = nominal | ||
37 | .syntax() | ||
38 | .children_with_tokens() | ||
39 | .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?; | ||
40 | Some(non_ws_child.text_range().start()) | ||
41 | } | ||
42 | |||
43 | #[cfg(test)] | ||
44 | mod tests { | ||
45 | use super::*; | ||
46 | use crate::helpers::{check_assist, check_assist_target}; | ||
47 | |||
48 | #[test] | ||
49 | fn add_derive_new() { | ||
50 | check_assist( | ||
51 | add_derive, | ||
52 | "struct Foo { a: i32, <|>}", | ||
53 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
54 | ); | ||
55 | check_assist( | ||
56 | add_derive, | ||
57 | "struct Foo { <|> a: i32, }", | ||
58 | "#[derive(<|>)]\nstruct Foo { a: i32, }", | ||
59 | ); | ||
60 | } | ||
61 | |||
62 | #[test] | ||
63 | fn add_derive_existing() { | ||
64 | check_assist( | ||
65 | add_derive, | ||
66 | "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", | ||
67 | "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", | ||
68 | ); | ||
69 | } | ||
70 | |||
71 | #[test] | ||
72 | fn add_derive_new_with_doc_comment() { | ||
73 | check_assist( | ||
74 | add_derive, | ||
75 | " | ||
76 | /// `Foo` is a pretty important struct. | ||
77 | /// It does stuff. | ||
78 | struct Foo { a: i32<|>, } | ||
79 | ", | ||
80 | " | ||
81 | /// `Foo` is a pretty important struct. | ||
82 | /// It does stuff. | ||
83 | #[derive(<|>)] | ||
84 | struct Foo { a: i32, } | ||
85 | ", | ||
86 | ); | ||
87 | } | ||
88 | |||
89 | #[test] | ||
90 | fn add_derive_target() { | ||
91 | check_assist_target( | ||
92 | add_derive, | ||
93 | " | ||
94 | struct SomeThingIrrelevant; | ||
95 | /// `Foo` is a pretty important struct. | ||
96 | /// It does stuff. | ||
97 | struct Foo { a: i32<|>, } | ||
98 | struct EvenMoreIrrelevant; | ||
99 | ", | ||
100 | "/// `Foo` is a pretty important struct. | ||
101 | /// It does stuff. | ||
102 | struct Foo { a: i32, }", | ||
103 | ); | ||
104 | } | ||
105 | } | ||
diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs new file mode 100644 index 000000000..78f0f7f28 --- /dev/null +++ b/crates/ra_assists/src/assists/add_explicit_type.rs | |||
@@ -0,0 +1,86 @@ | |||
1 | use hir::{db::HirDatabase, HirDisplay, Ty}; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, LetStmt, NameOwner}, | ||
4 | T, | ||
5 | }; | ||
6 | |||
7 | use crate::{Assist, AssistCtx, AssistId}; | ||
8 | |||
9 | /// Add explicit type assist. | ||
10 | pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
11 | let stmt = ctx.node_at_offset::<LetStmt>()?; | ||
12 | let expr = stmt.initializer()?; | ||
13 | let pat = stmt.pat()?; | ||
14 | // Must be a binding | ||
15 | let pat = match pat { | ||
16 | ast::Pat::BindPat(bind_pat) => bind_pat, | ||
17 | _ => return None, | ||
18 | }; | ||
19 | let pat_range = pat.syntax().text_range(); | ||
20 | // The binding must have a name | ||
21 | let name = pat.name()?; | ||
22 | let name_range = name.syntax().text_range(); | ||
23 | // Assist not applicable if the type has already been specified | ||
24 | if stmt.syntax().children_with_tokens().any(|child| child.kind() == T![:]) { | ||
25 | return None; | ||
26 | } | ||
27 | // Infer type | ||
28 | let db = ctx.db; | ||
29 | let analyzer = hir::SourceAnalyzer::new(db, ctx.frange.file_id, stmt.syntax(), None); | ||
30 | let ty = analyzer.type_of(db, &expr)?; | ||
31 | // Assist not applicable if the type is unknown | ||
32 | if is_unknown(&ty) { | ||
33 | return None; | ||
34 | } | ||
35 | |||
36 | ctx.add_action(AssistId("add_explicit_type"), "add explicit type", |edit| { | ||
37 | edit.target(pat_range); | ||
38 | edit.insert(name_range.end(), format!(": {}", ty.display(db))); | ||
39 | }); | ||
40 | ctx.build() | ||
41 | } | ||
42 | |||
43 | /// Returns true if any type parameter is unknown | ||
44 | fn is_unknown(ty: &Ty) -> bool { | ||
45 | match ty { | ||
46 | Ty::Unknown => true, | ||
47 | Ty::Apply(a_ty) => a_ty.parameters.iter().any(is_unknown), | ||
48 | _ => false, | ||
49 | } | ||
50 | } | ||
51 | |||
52 | #[cfg(test)] | ||
53 | mod tests { | ||
54 | use super::*; | ||
55 | |||
56 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
57 | |||
58 | #[test] | ||
59 | fn add_explicit_type_target() { | ||
60 | check_assist_target(add_explicit_type, "fn f() { let a<|> = 1; }", "a"); | ||
61 | } | ||
62 | |||
63 | #[test] | ||
64 | fn add_explicit_type_works_for_simple_expr() { | ||
65 | check_assist( | ||
66 | add_explicit_type, | ||
67 | "fn f() { let a<|> = 1; }", | ||
68 | "fn f() { let a<|>: i32 = 1; }", | ||
69 | ); | ||
70 | } | ||
71 | |||
72 | #[test] | ||
73 | fn add_explicit_type_not_applicable_if_ty_not_inferred() { | ||
74 | check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }"); | ||
75 | } | ||
76 | |||
77 | #[test] | ||
78 | fn add_explicit_type_not_applicable_if_ty_already_specified() { | ||
79 | check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }"); | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() { | ||
84 | check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }"); | ||
85 | } | ||
86 | } | ||
diff --git a/crates/ra_assists/src/assists/add_impl.rs b/crates/ra_assists/src/assists/add_impl.rs new file mode 100644 index 000000000..4b61f4031 --- /dev/null +++ b/crates/ra_assists/src/assists/add_impl.rs | |||
@@ -0,0 +1,77 @@ | |||
1 | use format_buf::format; | ||
2 | use hir::db::HirDatabase; | ||
3 | use join_to_string::join; | ||
4 | use ra_syntax::{ | ||
5 | ast::{self, AstNode, NameOwner, TypeParamsOwner}, | ||
6 | TextUnit, | ||
7 | }; | ||
8 | |||
9 | use crate::{Assist, AssistCtx, AssistId}; | ||
10 | |||
11 | pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
12 | let nominal = ctx.node_at_offset::<ast::NominalDef>()?; | ||
13 | let name = nominal.name()?; | ||
14 | ctx.add_action(AssistId("add_impl"), "add impl", |edit| { | ||
15 | edit.target(nominal.syntax().text_range()); | ||
16 | let type_params = nominal.type_param_list(); | ||
17 | let start_offset = nominal.syntax().text_range().end(); | ||
18 | let mut buf = String::new(); | ||
19 | buf.push_str("\n\nimpl"); | ||
20 | if let Some(type_params) = &type_params { | ||
21 | format!(buf, "{}", type_params.syntax()); | ||
22 | } | ||
23 | buf.push_str(" "); | ||
24 | buf.push_str(name.text().as_str()); | ||
25 | if let Some(type_params) = type_params { | ||
26 | let lifetime_params = type_params | ||
27 | .lifetime_params() | ||
28 | .filter_map(|it| it.lifetime_token()) | ||
29 | .map(|it| it.text().clone()); | ||
30 | let type_params = | ||
31 | type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); | ||
32 | join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf); | ||
33 | } | ||
34 | buf.push_str(" {\n"); | ||
35 | edit.set_cursor(start_offset + TextUnit::of_str(&buf)); | ||
36 | buf.push_str("\n}"); | ||
37 | edit.insert(start_offset, buf); | ||
38 | }); | ||
39 | |||
40 | ctx.build() | ||
41 | } | ||
42 | |||
43 | #[cfg(test)] | ||
44 | mod tests { | ||
45 | use super::*; | ||
46 | use crate::helpers::{check_assist, check_assist_target}; | ||
47 | |||
48 | #[test] | ||
49 | fn test_add_impl() { | ||
50 | check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n"); | ||
51 | check_assist( | ||
52 | add_impl, | ||
53 | "struct Foo<T: Clone> {<|>}", | ||
54 | "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}", | ||
55 | ); | ||
56 | check_assist( | ||
57 | add_impl, | ||
58 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
59 | "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}", | ||
60 | ); | ||
61 | } | ||
62 | |||
63 | #[test] | ||
64 | fn add_impl_target() { | ||
65 | check_assist_target( | ||
66 | add_impl, | ||
67 | " | ||
68 | struct SomeThingIrrelevant; | ||
69 | /// Has a lifetime parameter | ||
70 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
71 | struct EvenMoreIrrelevant; | ||
72 | ", | ||
73 | "/// Has a lifetime parameter | ||
74 | struct Foo<'a, T: Foo<'a>> {}", | ||
75 | ); | ||
76 | } | ||
77 | } | ||
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs new file mode 100644 index 000000000..22d20909d --- /dev/null +++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs | |||
@@ -0,0 +1,335 @@ | |||
1 | use hir::{db::HirDatabase, HasSource}; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, NameOwner}, | ||
4 | SmolStr, | ||
5 | }; | ||
6 | |||
7 | use crate::{ast_builder::Make, ast_editor::AstEditor, Assist, AssistCtx, AssistId}; | ||
8 | |||
9 | #[derive(PartialEq)] | ||
10 | enum AddMissingImplMembersMode { | ||
11 | DefaultMethodsOnly, | ||
12 | NoDefaultMethods, | ||
13 | } | ||
14 | |||
15 | pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
16 | add_missing_impl_members_inner( | ||
17 | ctx, | ||
18 | AddMissingImplMembersMode::NoDefaultMethods, | ||
19 | "add_impl_missing_members", | ||
20 | "add missing impl members", | ||
21 | ) | ||
22 | } | ||
23 | |||
24 | pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
25 | add_missing_impl_members_inner( | ||
26 | ctx, | ||
27 | AddMissingImplMembersMode::DefaultMethodsOnly, | ||
28 | "add_impl_default_members", | ||
29 | "add impl default members", | ||
30 | ) | ||
31 | } | ||
32 | |||
33 | fn add_missing_impl_members_inner( | ||
34 | mut ctx: AssistCtx<impl HirDatabase>, | ||
35 | mode: AddMissingImplMembersMode, | ||
36 | assist_id: &'static str, | ||
37 | label: &'static str, | ||
38 | ) -> Option<Assist> { | ||
39 | let impl_node = ctx.node_at_offset::<ast::ImplBlock>()?; | ||
40 | let impl_item_list = impl_node.item_list()?; | ||
41 | |||
42 | let trait_def = { | ||
43 | let file_id = ctx.frange.file_id; | ||
44 | let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, impl_node.syntax(), None); | ||
45 | |||
46 | resolve_target_trait_def(ctx.db, &analyzer, &impl_node)? | ||
47 | }; | ||
48 | |||
49 | let def_name = |item: &ast::ImplItem| -> Option<SmolStr> { | ||
50 | match item { | ||
51 | ast::ImplItem::FnDef(def) => def.name(), | ||
52 | ast::ImplItem::TypeAliasDef(def) => def.name(), | ||
53 | ast::ImplItem::ConstDef(def) => def.name(), | ||
54 | } | ||
55 | .map(|it| it.text().clone()) | ||
56 | }; | ||
57 | |||
58 | let trait_items = trait_def.item_list()?.impl_items(); | ||
59 | let impl_items = impl_item_list.impl_items().collect::<Vec<_>>(); | ||
60 | |||
61 | let missing_items: Vec<_> = trait_items | ||
62 | .filter(|t| def_name(t).is_some()) | ||
63 | .filter(|t| match t { | ||
64 | ast::ImplItem::FnDef(def) => match mode { | ||
65 | AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), | ||
66 | AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), | ||
67 | }, | ||
68 | _ => mode == AddMissingImplMembersMode::NoDefaultMethods, | ||
69 | }) | ||
70 | .filter(|t| impl_items.iter().all(|i| def_name(i) != def_name(t))) | ||
71 | .collect(); | ||
72 | if missing_items.is_empty() { | ||
73 | return None; | ||
74 | } | ||
75 | |||
76 | ctx.add_action(AssistId(assist_id), label, |edit| { | ||
77 | let n_existing_items = impl_item_list.impl_items().count(); | ||
78 | let items = missing_items.into_iter().map(|it| match it { | ||
79 | ast::ImplItem::FnDef(def) => strip_docstring(add_body(def).into()), | ||
80 | _ => strip_docstring(it), | ||
81 | }); | ||
82 | let mut ast_editor = AstEditor::new(impl_item_list); | ||
83 | |||
84 | ast_editor.append_items(items); | ||
85 | |||
86 | let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); | ||
87 | let cursor_position = first_new_item.syntax().text_range().start(); | ||
88 | ast_editor.into_text_edit(edit.text_edit_builder()); | ||
89 | |||
90 | edit.set_cursor(cursor_position); | ||
91 | }); | ||
92 | |||
93 | ctx.build() | ||
94 | } | ||
95 | |||
96 | fn strip_docstring(item: ast::ImplItem) -> ast::ImplItem { | ||
97 | let mut ast_editor = AstEditor::new(item); | ||
98 | ast_editor.strip_attrs_and_docs(); | ||
99 | ast_editor.ast().to_owned() | ||
100 | } | ||
101 | |||
102 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { | ||
103 | let mut ast_editor = AstEditor::new(fn_def.clone()); | ||
104 | if fn_def.body().is_none() { | ||
105 | ast_editor.set_body(&Make::<ast::Block>::single_expr(Make::<ast::Expr>::unimplemented())); | ||
106 | } | ||
107 | ast_editor.ast().to_owned() | ||
108 | } | ||
109 | |||
110 | /// Given an `ast::ImplBlock`, resolves the target trait (the one being | ||
111 | /// implemented) to a `ast::TraitDef`. | ||
112 | fn resolve_target_trait_def( | ||
113 | db: &impl HirDatabase, | ||
114 | analyzer: &hir::SourceAnalyzer, | ||
115 | impl_block: &ast::ImplBlock, | ||
116 | ) -> Option<ast::TraitDef> { | ||
117 | let ast_path = impl_block | ||
118 | .target_trait() | ||
119 | .map(|it| it.syntax().clone()) | ||
120 | .and_then(ast::PathType::cast)? | ||
121 | .path()?; | ||
122 | |||
123 | match analyzer.resolve_path(db, &ast_path) { | ||
124 | Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).ast), | ||
125 | _ => None, | ||
126 | } | ||
127 | } | ||
128 | |||
129 | #[cfg(test)] | ||
130 | mod tests { | ||
131 | use super::*; | ||
132 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
133 | |||
134 | #[test] | ||
135 | fn test_add_missing_impl_members() { | ||
136 | check_assist( | ||
137 | add_missing_impl_members, | ||
138 | " | ||
139 | trait Foo { | ||
140 | type Output; | ||
141 | |||
142 | const CONST: usize = 42; | ||
143 | |||
144 | fn foo(&self); | ||
145 | fn bar(&self); | ||
146 | fn baz(&self); | ||
147 | } | ||
148 | |||
149 | struct S; | ||
150 | |||
151 | impl Foo for S { | ||
152 | fn bar(&self) {} | ||
153 | <|> | ||
154 | }", | ||
155 | " | ||
156 | trait Foo { | ||
157 | type Output; | ||
158 | |||
159 | const CONST: usize = 42; | ||
160 | |||
161 | fn foo(&self); | ||
162 | fn bar(&self); | ||
163 | fn baz(&self); | ||
164 | } | ||
165 | |||
166 | struct S; | ||
167 | |||
168 | impl Foo for S { | ||
169 | fn bar(&self) {} | ||
170 | <|>type Output; | ||
171 | const CONST: usize = 42; | ||
172 | fn foo(&self) { unimplemented!() } | ||
173 | fn baz(&self) { unimplemented!() } | ||
174 | |||
175 | }", | ||
176 | ); | ||
177 | } | ||
178 | |||
179 | #[test] | ||
180 | fn test_copied_overriden_members() { | ||
181 | check_assist( | ||
182 | add_missing_impl_members, | ||
183 | " | ||
184 | trait Foo { | ||
185 | fn foo(&self); | ||
186 | fn bar(&self) -> bool { true } | ||
187 | fn baz(&self) -> u32 { 42 } | ||
188 | } | ||
189 | |||
190 | struct S; | ||
191 | |||
192 | impl Foo for S { | ||
193 | fn bar(&self) {} | ||
194 | <|> | ||
195 | }", | ||
196 | " | ||
197 | trait Foo { | ||
198 | fn foo(&self); | ||
199 | fn bar(&self) -> bool { true } | ||
200 | fn baz(&self) -> u32 { 42 } | ||
201 | } | ||
202 | |||
203 | struct S; | ||
204 | |||
205 | impl Foo for S { | ||
206 | fn bar(&self) {} | ||
207 | <|>fn foo(&self) { unimplemented!() } | ||
208 | |||
209 | }", | ||
210 | ); | ||
211 | } | ||
212 | |||
213 | #[test] | ||
214 | fn test_empty_impl_block() { | ||
215 | check_assist( | ||
216 | add_missing_impl_members, | ||
217 | " | ||
218 | trait Foo { fn foo(&self); } | ||
219 | struct S; | ||
220 | impl Foo for S { <|> }", | ||
221 | " | ||
222 | trait Foo { fn foo(&self); } | ||
223 | struct S; | ||
224 | impl Foo for S { | ||
225 | <|>fn foo(&self) { unimplemented!() } | ||
226 | }", | ||
227 | ); | ||
228 | } | ||
229 | |||
230 | #[test] | ||
231 | fn test_cursor_after_empty_impl_block() { | ||
232 | check_assist( | ||
233 | add_missing_impl_members, | ||
234 | " | ||
235 | trait Foo { fn foo(&self); } | ||
236 | struct S; | ||
237 | impl Foo for S {}<|>", | ||
238 | " | ||
239 | trait Foo { fn foo(&self); } | ||
240 | struct S; | ||
241 | impl Foo for S { | ||
242 | <|>fn foo(&self) { unimplemented!() } | ||
243 | }", | ||
244 | ) | ||
245 | } | ||
246 | |||
247 | #[test] | ||
248 | fn test_empty_trait() { | ||
249 | check_assist_not_applicable( | ||
250 | add_missing_impl_members, | ||
251 | " | ||
252 | trait Foo; | ||
253 | struct S; | ||
254 | impl Foo for S { <|> }", | ||
255 | ) | ||
256 | } | ||
257 | |||
258 | #[test] | ||
259 | fn test_ignore_unnamed_trait_members_and_default_methods() { | ||
260 | check_assist_not_applicable( | ||
261 | add_missing_impl_members, | ||
262 | " | ||
263 | trait Foo { | ||
264 | fn (arg: u32); | ||
265 | fn valid(some: u32) -> bool { false } | ||
266 | } | ||
267 | struct S; | ||
268 | impl Foo for S { <|> }", | ||
269 | ) | ||
270 | } | ||
271 | |||
272 | #[test] | ||
273 | fn test_with_docstring_and_attrs() { | ||
274 | check_assist( | ||
275 | add_missing_impl_members, | ||
276 | r#" | ||
277 | #[doc(alias = "test alias")] | ||
278 | trait Foo { | ||
279 | /// doc string | ||
280 | type Output; | ||
281 | |||
282 | #[must_use] | ||
283 | fn foo(&self); | ||
284 | } | ||
285 | struct S; | ||
286 | impl Foo for S {}<|>"#, | ||
287 | r#" | ||
288 | #[doc(alias = "test alias")] | ||
289 | trait Foo { | ||
290 | /// doc string | ||
291 | type Output; | ||
292 | |||
293 | #[must_use] | ||
294 | fn foo(&self); | ||
295 | } | ||
296 | struct S; | ||
297 | impl Foo for S { | ||
298 | <|>type Output; | ||
299 | fn foo(&self) { unimplemented!() } | ||
300 | }"#, | ||
301 | ) | ||
302 | } | ||
303 | |||
304 | #[test] | ||
305 | fn test_default_methods() { | ||
306 | check_assist( | ||
307 | add_missing_default_members, | ||
308 | " | ||
309 | trait Foo { | ||
310 | type Output; | ||
311 | |||
312 | const CONST: usize = 42; | ||
313 | |||
314 | fn valid(some: u32) -> bool { false } | ||
315 | fn foo(some: u32) -> bool; | ||
316 | } | ||
317 | struct S; | ||
318 | impl Foo for S { <|> }", | ||
319 | " | ||
320 | trait Foo { | ||
321 | type Output; | ||
322 | |||
323 | const CONST: usize = 42; | ||
324 | |||
325 | fn valid(some: u32) -> bool { false } | ||
326 | fn foo(some: u32) -> bool; | ||
327 | } | ||
328 | struct S; | ||
329 | impl Foo for S { | ||
330 | <|>fn valid(some: u32) -> bool { false } | ||
331 | }", | ||
332 | ) | ||
333 | } | ||
334 | |||
335 | } | ||
diff --git a/crates/ra_assists/src/assists/auto_import.rs b/crates/ra_assists/src/assists/auto_import.rs new file mode 100644 index 000000000..5aae98546 --- /dev/null +++ b/crates/ra_assists/src/assists/auto_import.rs | |||
@@ -0,0 +1,939 @@ | |||
1 | use hir::{self, db::HirDatabase}; | ||
2 | use ra_text_edit::TextEditBuilder; | ||
3 | |||
4 | use crate::{ | ||
5 | assist_ctx::{Assist, AssistCtx}, | ||
6 | AssistId, | ||
7 | }; | ||
8 | use ra_syntax::{ | ||
9 | ast::{self, NameOwner}, | ||
10 | AstNode, Direction, SmolStr, | ||
11 | SyntaxKind::{PATH, PATH_SEGMENT}, | ||
12 | SyntaxNode, TextRange, T, | ||
13 | }; | ||
14 | |||
15 | fn collect_path_segments_raw( | ||
16 | segments: &mut Vec<ast::PathSegment>, | ||
17 | mut path: ast::Path, | ||
18 | ) -> Option<usize> { | ||
19 | let oldlen = segments.len(); | ||
20 | loop { | ||
21 | let mut children = path.syntax().children_with_tokens(); | ||
22 | let (first, second, third) = ( | ||
23 | children.next().map(|n| (n.clone(), n.kind())), | ||
24 | children.next().map(|n| (n.clone(), n.kind())), | ||
25 | children.next().map(|n| (n.clone(), n.kind())), | ||
26 | ); | ||
27 | match (first, second, third) { | ||
28 | (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => { | ||
29 | path = ast::Path::cast(subpath.as_node()?.clone())?; | ||
30 | segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?); | ||
31 | } | ||
32 | (Some((segment, PATH_SEGMENT)), _, _) => { | ||
33 | segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?); | ||
34 | break; | ||
35 | } | ||
36 | (_, _, _) => return None, | ||
37 | } | ||
38 | } | ||
39 | // We need to reverse only the new added segments | ||
40 | let only_new_segments = segments.split_at_mut(oldlen).1; | ||
41 | only_new_segments.reverse(); | ||
42 | Some(segments.len() - oldlen) | ||
43 | } | ||
44 | |||
45 | fn fmt_segments(segments: &[SmolStr]) -> String { | ||
46 | let mut buf = String::new(); | ||
47 | fmt_segments_raw(segments, &mut buf); | ||
48 | buf | ||
49 | } | ||
50 | |||
51 | fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { | ||
52 | let mut iter = segments.iter(); | ||
53 | if let Some(s) = iter.next() { | ||
54 | buf.push_str(s); | ||
55 | } | ||
56 | for s in iter { | ||
57 | buf.push_str("::"); | ||
58 | buf.push_str(s); | ||
59 | } | ||
60 | } | ||
61 | |||
62 | // Returns the numeber of common segments. | ||
63 | fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize { | ||
64 | left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count() | ||
65 | } | ||
66 | |||
67 | fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { | ||
68 | if let Some(kb) = b.kind() { | ||
69 | match kb { | ||
70 | ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), | ||
71 | ast::PathSegmentKind::SelfKw => a == "self", | ||
72 | ast::PathSegmentKind::SuperKw => a == "super", | ||
73 | ast::PathSegmentKind::CrateKw => a == "crate", | ||
74 | ast::PathSegmentKind::Type { .. } => false, // not allowed in imports | ||
75 | } | ||
76 | } else { | ||
77 | false | ||
78 | } | ||
79 | } | ||
80 | |||
81 | fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool { | ||
82 | a == b.text() | ||
83 | } | ||
84 | |||
85 | #[derive(Clone)] | ||
86 | enum ImportAction { | ||
87 | Nothing, | ||
88 | // Add a brand new use statement. | ||
89 | AddNewUse { | ||
90 | anchor: Option<SyntaxNode>, // anchor node | ||
91 | add_after_anchor: bool, | ||
92 | }, | ||
93 | |||
94 | // To split an existing use statement creating a nested import. | ||
95 | AddNestedImport { | ||
96 | // how may segments matched with the target path | ||
97 | common_segments: usize, | ||
98 | path_to_split: ast::Path, | ||
99 | // the first segment of path_to_split we want to add into the new nested list | ||
100 | first_segment_to_split: Option<ast::PathSegment>, | ||
101 | // Wether to add 'self' in addition to the target path | ||
102 | add_self: bool, | ||
103 | }, | ||
104 | // To add the target path to an existing nested import tree list. | ||
105 | AddInTreeList { | ||
106 | common_segments: usize, | ||
107 | // The UseTreeList where to add the target path | ||
108 | tree_list: ast::UseTreeList, | ||
109 | add_self: bool, | ||
110 | }, | ||
111 | } | ||
112 | |||
113 | impl ImportAction { | ||
114 | fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self { | ||
115 | ImportAction::AddNewUse { anchor, add_after_anchor } | ||
116 | } | ||
117 | |||
118 | fn add_nested_import( | ||
119 | common_segments: usize, | ||
120 | path_to_split: ast::Path, | ||
121 | first_segment_to_split: Option<ast::PathSegment>, | ||
122 | add_self: bool, | ||
123 | ) -> Self { | ||
124 | ImportAction::AddNestedImport { | ||
125 | common_segments, | ||
126 | path_to_split, | ||
127 | first_segment_to_split, | ||
128 | add_self, | ||
129 | } | ||
130 | } | ||
131 | |||
132 | fn add_in_tree_list( | ||
133 | common_segments: usize, | ||
134 | tree_list: ast::UseTreeList, | ||
135 | add_self: bool, | ||
136 | ) -> Self { | ||
137 | ImportAction::AddInTreeList { common_segments, tree_list, add_self } | ||
138 | } | ||
139 | |||
140 | fn better(left: ImportAction, right: ImportAction) -> ImportAction { | ||
141 | if left.is_better(&right) { | ||
142 | left | ||
143 | } else { | ||
144 | right | ||
145 | } | ||
146 | } | ||
147 | |||
148 | fn is_better(&self, other: &ImportAction) -> bool { | ||
149 | match (self, other) { | ||
150 | (ImportAction::Nothing, _) => true, | ||
151 | (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, | ||
152 | ( | ||
153 | ImportAction::AddNestedImport { common_segments: n, .. }, | ||
154 | ImportAction::AddInTreeList { common_segments: m, .. }, | ||
155 | ) => n > m, | ||
156 | ( | ||
157 | ImportAction::AddInTreeList { common_segments: n, .. }, | ||
158 | ImportAction::AddNestedImport { common_segments: m, .. }, | ||
159 | ) => n > m, | ||
160 | (ImportAction::AddInTreeList { .. }, _) => true, | ||
161 | (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false, | ||
162 | (ImportAction::AddNestedImport { .. }, _) => true, | ||
163 | (ImportAction::AddNewUse { .. }, _) => false, | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | |||
168 | // Find out the best ImportAction to import target path against current_use_tree. | ||
169 | // If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList. | ||
170 | fn walk_use_tree_for_best_action( | ||
171 | current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments | ||
172 | current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import | ||
173 | current_use_tree: ast::UseTree, // the use tree we are currently examinating | ||
174 | target: &[SmolStr], // the path we want to import | ||
175 | ) -> ImportAction { | ||
176 | // We save the number of segments in the buffer so we can restore the correct segments | ||
177 | // before returning. Recursive call will add segments so we need to delete them. | ||
178 | let prev_len = current_path_segments.len(); | ||
179 | |||
180 | let tree_list = current_use_tree.use_tree_list(); | ||
181 | let alias = current_use_tree.alias(); | ||
182 | |||
183 | let path = match current_use_tree.path() { | ||
184 | Some(path) => path, | ||
185 | None => { | ||
186 | // If the use item don't have a path, it means it's broken (syntax error) | ||
187 | return ImportAction::add_new_use( | ||
188 | current_use_tree | ||
189 | .syntax() | ||
190 | .ancestors() | ||
191 | .find_map(ast::UseItem::cast) | ||
192 | .map(|it| it.syntax().clone()), | ||
193 | true, | ||
194 | ); | ||
195 | } | ||
196 | }; | ||
197 | |||
198 | // This can happen only if current_use_tree is a direct child of a UseItem | ||
199 | if let Some(name) = alias.and_then(|it| it.name()) { | ||
200 | if compare_path_segment_with_name(&target[0], &name) { | ||
201 | return ImportAction::Nothing; | ||
202 | } | ||
203 | } | ||
204 | |||
205 | collect_path_segments_raw(current_path_segments, path.clone()); | ||
206 | |||
207 | // We compare only the new segments added in the line just above. | ||
208 | // The first prev_len segments were already compared in 'parent' recursive calls. | ||
209 | let left = target.split_at(prev_len).1; | ||
210 | let right = current_path_segments.split_at(prev_len).1; | ||
211 | let common = compare_path_segments(left, &right); | ||
212 | let mut action = match common { | ||
213 | 0 => ImportAction::add_new_use( | ||
214 | // e.g: target is std::fmt and we can have | ||
215 | // use foo::bar | ||
216 | // We add a brand new use statement | ||
217 | current_use_tree | ||
218 | .syntax() | ||
219 | .ancestors() | ||
220 | .find_map(ast::UseItem::cast) | ||
221 | .map(|it| it.syntax().clone()), | ||
222 | true, | ||
223 | ), | ||
224 | common if common == left.len() && left.len() == right.len() => { | ||
225 | // e.g: target is std::fmt and we can have | ||
226 | // 1- use std::fmt; | ||
227 | // 2- use std::fmt:{ ... } | ||
228 | if let Some(list) = tree_list { | ||
229 | // In case 2 we need to add self to the nested list | ||
230 | // unless it's already there | ||
231 | let has_self = list.use_trees().map(|it| it.path()).any(|p| { | ||
232 | p.and_then(|it| it.segment()) | ||
233 | .and_then(|it| it.kind()) | ||
234 | .filter(|k| *k == ast::PathSegmentKind::SelfKw) | ||
235 | .is_some() | ||
236 | }); | ||
237 | |||
238 | if has_self { | ||
239 | ImportAction::Nothing | ||
240 | } else { | ||
241 | ImportAction::add_in_tree_list(current_path_segments.len(), list, true) | ||
242 | } | ||
243 | } else { | ||
244 | // Case 1 | ||
245 | ImportAction::Nothing | ||
246 | } | ||
247 | } | ||
248 | common if common != left.len() && left.len() == right.len() => { | ||
249 | // e.g: target is std::fmt and we have | ||
250 | // use std::io; | ||
251 | // We need to split. | ||
252 | let segments_to_split = current_path_segments.split_at(prev_len + common).1; | ||
253 | ImportAction::add_nested_import( | ||
254 | prev_len + common, | ||
255 | path, | ||
256 | Some(segments_to_split[0].clone()), | ||
257 | false, | ||
258 | ) | ||
259 | } | ||
260 | common if common == right.len() && left.len() > right.len() => { | ||
261 | // e.g: target is std::fmt and we can have | ||
262 | // 1- use std; | ||
263 | // 2- use std::{ ... }; | ||
264 | |||
265 | // fallback action | ||
266 | let mut better_action = ImportAction::add_new_use( | ||
267 | current_use_tree | ||
268 | .syntax() | ||
269 | .ancestors() | ||
270 | .find_map(ast::UseItem::cast) | ||
271 | .map(|it| it.syntax().clone()), | ||
272 | true, | ||
273 | ); | ||
274 | if let Some(list) = tree_list { | ||
275 | // Case 2, check recursively if the path is already imported in the nested list | ||
276 | for u in list.use_trees() { | ||
277 | let child_action = walk_use_tree_for_best_action( | ||
278 | current_path_segments, | ||
279 | Some(list.clone()), | ||
280 | u, | ||
281 | target, | ||
282 | ); | ||
283 | if child_action.is_better(&better_action) { | ||
284 | better_action = child_action; | ||
285 | if let ImportAction::Nothing = better_action { | ||
286 | return better_action; | ||
287 | } | ||
288 | } | ||
289 | } | ||
290 | } else { | ||
291 | // Case 1, split adding self | ||
292 | better_action = ImportAction::add_nested_import(prev_len + common, path, None, true) | ||
293 | } | ||
294 | better_action | ||
295 | } | ||
296 | common if common == left.len() && left.len() < right.len() => { | ||
297 | // e.g: target is std::fmt and we can have | ||
298 | // use std::fmt::Debug; | ||
299 | let segments_to_split = current_path_segments.split_at(prev_len + common).1; | ||
300 | ImportAction::add_nested_import( | ||
301 | prev_len + common, | ||
302 | path, | ||
303 | Some(segments_to_split[0].clone()), | ||
304 | true, | ||
305 | ) | ||
306 | } | ||
307 | common if common < left.len() && common < right.len() => { | ||
308 | // e.g: target is std::fmt::nested::Debug | ||
309 | // use std::fmt::Display | ||
310 | let segments_to_split = current_path_segments.split_at(prev_len + common).1; | ||
311 | ImportAction::add_nested_import( | ||
312 | prev_len + common, | ||
313 | path, | ||
314 | Some(segments_to_split[0].clone()), | ||
315 | false, | ||
316 | ) | ||
317 | } | ||
318 | _ => unreachable!(), | ||
319 | }; | ||
320 | |||
321 | // If we are inside a UseTreeList adding a use statement become adding to the existing | ||
322 | // tree list. | ||
323 | action = match (current_parent_use_tree_list, action.clone()) { | ||
324 | (Some(use_tree_list), ImportAction::AddNewUse { .. }) => { | ||
325 | ImportAction::add_in_tree_list(prev_len, use_tree_list, false) | ||
326 | } | ||
327 | (_, _) => action, | ||
328 | }; | ||
329 | |||
330 | // We remove the segments added | ||
331 | current_path_segments.truncate(prev_len); | ||
332 | action | ||
333 | } | ||
334 | |||
335 | fn best_action_for_target( | ||
336 | container: SyntaxNode, | ||
337 | anchor: SyntaxNode, | ||
338 | target: &[SmolStr], | ||
339 | ) -> ImportAction { | ||
340 | let mut storage = Vec::with_capacity(16); // this should be the only allocation | ||
341 | let best_action = container | ||
342 | .children() | ||
343 | .filter_map(ast::UseItem::cast) | ||
344 | .filter_map(|it| it.use_tree()) | ||
345 | .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) | ||
346 | .fold(None, |best, a| match best { | ||
347 | Some(best) => Some(ImportAction::better(best, a)), | ||
348 | None => Some(a), | ||
349 | }); | ||
350 | |||
351 | match best_action { | ||
352 | Some(action) => action, | ||
353 | None => { | ||
354 | // We have no action and no UseItem was found in container so we find | ||
355 | // another item and we use it as anchor. | ||
356 | // If there are no items above, we choose the target path itself as anchor. | ||
357 | // todo: we should include even whitespace blocks as anchor candidates | ||
358 | let anchor = container | ||
359 | .children() | ||
360 | .find(|n| n.text_range().start() < anchor.text_range().start()) | ||
361 | .or_else(|| Some(anchor)); | ||
362 | |||
363 | ImportAction::add_new_use(anchor, false) | ||
364 | } | ||
365 | } | ||
366 | } | ||
367 | |||
368 | fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { | ||
369 | match action { | ||
370 | ImportAction::AddNewUse { anchor, add_after_anchor } => { | ||
371 | make_assist_add_new_use(anchor, *add_after_anchor, target, edit) | ||
372 | } | ||
373 | ImportAction::AddInTreeList { common_segments, tree_list, add_self } => { | ||
374 | // We know that the fist n segments already exists in the use statement we want | ||
375 | // to modify, so we want to add only the last target.len() - n segments. | ||
376 | let segments_to_add = target.split_at(*common_segments).1; | ||
377 | make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit) | ||
378 | } | ||
379 | ImportAction::AddNestedImport { | ||
380 | common_segments, | ||
381 | path_to_split, | ||
382 | first_segment_to_split, | ||
383 | add_self, | ||
384 | } => { | ||
385 | let segments_to_add = target.split_at(*common_segments).1; | ||
386 | make_assist_add_nested_import( | ||
387 | path_to_split, | ||
388 | first_segment_to_split, | ||
389 | segments_to_add, | ||
390 | *add_self, | ||
391 | edit, | ||
392 | ) | ||
393 | } | ||
394 | _ => {} | ||
395 | } | ||
396 | } | ||
397 | |||
398 | fn make_assist_add_new_use( | ||
399 | anchor: &Option<SyntaxNode>, | ||
400 | after: bool, | ||
401 | target: &[SmolStr], | ||
402 | edit: &mut TextEditBuilder, | ||
403 | ) { | ||
404 | if let Some(anchor) = anchor { | ||
405 | let indent = ra_fmt::leading_indent(anchor); | ||
406 | let mut buf = String::new(); | ||
407 | if after { | ||
408 | buf.push_str("\n"); | ||
409 | if let Some(spaces) = &indent { | ||
410 | buf.push_str(spaces); | ||
411 | } | ||
412 | } | ||
413 | buf.push_str("use "); | ||
414 | fmt_segments_raw(target, &mut buf); | ||
415 | buf.push_str(";"); | ||
416 | if !after { | ||
417 | buf.push_str("\n\n"); | ||
418 | if let Some(spaces) = &indent { | ||
419 | buf.push_str(&spaces); | ||
420 | } | ||
421 | } | ||
422 | let position = if after { anchor.text_range().end() } else { anchor.text_range().start() }; | ||
423 | edit.insert(position, buf); | ||
424 | } | ||
425 | } | ||
426 | |||
427 | fn make_assist_add_in_tree_list( | ||
428 | tree_list: &ast::UseTreeList, | ||
429 | target: &[SmolStr], | ||
430 | add_self: bool, | ||
431 | edit: &mut TextEditBuilder, | ||
432 | ) { | ||
433 | let last = tree_list.use_trees().last(); | ||
434 | if let Some(last) = last { | ||
435 | let mut buf = String::new(); | ||
436 | let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]); | ||
437 | let offset = if let Some(comma) = comma { | ||
438 | comma.text_range().end() | ||
439 | } else { | ||
440 | buf.push_str(","); | ||
441 | last.syntax().text_range().end() | ||
442 | }; | ||
443 | if add_self { | ||
444 | buf.push_str(" self") | ||
445 | } else { | ||
446 | buf.push_str(" "); | ||
447 | } | ||
448 | fmt_segments_raw(target, &mut buf); | ||
449 | edit.insert(offset, buf); | ||
450 | } else { | ||
451 | |||
452 | } | ||
453 | } | ||
454 | |||
455 | fn make_assist_add_nested_import( | ||
456 | path: &ast::Path, | ||
457 | first_segment_to_split: &Option<ast::PathSegment>, | ||
458 | target: &[SmolStr], | ||
459 | add_self: bool, | ||
460 | edit: &mut TextEditBuilder, | ||
461 | ) { | ||
462 | let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); | ||
463 | if let Some(use_tree) = use_tree { | ||
464 | let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split | ||
465 | { | ||
466 | (first_segment_to_split.syntax().text_range().start(), false) | ||
467 | } else { | ||
468 | (use_tree.syntax().text_range().end(), true) | ||
469 | }; | ||
470 | let end = use_tree.syntax().text_range().end(); | ||
471 | |||
472 | let mut buf = String::new(); | ||
473 | if add_colon_colon { | ||
474 | buf.push_str("::"); | ||
475 | } | ||
476 | buf.push_str("{ "); | ||
477 | if add_self { | ||
478 | buf.push_str("self, "); | ||
479 | } | ||
480 | fmt_segments_raw(target, &mut buf); | ||
481 | if !target.is_empty() { | ||
482 | buf.push_str(", "); | ||
483 | } | ||
484 | edit.insert(start, buf); | ||
485 | edit.insert(end, "}".to_string()); | ||
486 | } | ||
487 | } | ||
488 | |||
489 | fn apply_auto_import( | ||
490 | container: &SyntaxNode, | ||
491 | path: &ast::Path, | ||
492 | target: &[SmolStr], | ||
493 | edit: &mut TextEditBuilder, | ||
494 | ) { | ||
495 | let action = best_action_for_target(container.clone(), path.syntax().clone(), target); | ||
496 | make_assist(&action, target, edit); | ||
497 | if let Some(last) = path.segment() { | ||
498 | // Here we are assuming the assist will provide a correct use statement | ||
499 | // so we can delete the path qualifier | ||
500 | edit.delete(TextRange::from_to( | ||
501 | path.syntax().text_range().start(), | ||
502 | last.syntax().text_range().start(), | ||
503 | )); | ||
504 | } | ||
505 | } | ||
506 | |||
507 | pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { | ||
508 | let mut ps = Vec::<SmolStr>::with_capacity(10); | ||
509 | match path.kind { | ||
510 | hir::PathKind::Abs => ps.push("".into()), | ||
511 | hir::PathKind::Crate => ps.push("crate".into()), | ||
512 | hir::PathKind::Plain => {} | ||
513 | hir::PathKind::Self_ => ps.push("self".into()), | ||
514 | hir::PathKind::Super => ps.push("super".into()), | ||
515 | hir::PathKind::Type(_) => return None, | ||
516 | } | ||
517 | for s in path.segments.iter() { | ||
518 | ps.push(s.name.to_string().into()); | ||
519 | } | ||
520 | Some(ps) | ||
521 | } | ||
522 | |||
523 | // This function produces sequence of text edits into edit | ||
524 | // to import the target path in the most appropriate scope given | ||
525 | // the cursor position | ||
526 | pub fn auto_import_text_edit( | ||
527 | // Ideally the position of the cursor, used to | ||
528 | position: &SyntaxNode, | ||
529 | // The statement to use as anchor (last resort) | ||
530 | anchor: &SyntaxNode, | ||
531 | // The path to import as a sequence of strings | ||
532 | target: &[SmolStr], | ||
533 | edit: &mut TextEditBuilder, | ||
534 | ) { | ||
535 | let container = position.ancestors().find_map(|n| { | ||
536 | if let Some(module) = ast::Module::cast(n.clone()) { | ||
537 | return module.item_list().map(|it| it.syntax().clone()); | ||
538 | } | ||
539 | ast::SourceFile::cast(n).map(|it| it.syntax().clone()) | ||
540 | }); | ||
541 | |||
542 | if let Some(container) = container { | ||
543 | let action = best_action_for_target(container, anchor.clone(), target); | ||
544 | make_assist(&action, target, edit); | ||
545 | } | ||
546 | } | ||
547 | |||
548 | pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
549 | let path: ast::Path = ctx.node_at_offset()?; | ||
550 | // We don't want to mess with use statements | ||
551 | if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { | ||
552 | return None; | ||
553 | } | ||
554 | |||
555 | let hir_path = hir::Path::from_ast(path.clone())?; | ||
556 | let segments = collect_hir_path_segments(&hir_path)?; | ||
557 | if segments.len() < 2 { | ||
558 | return None; | ||
559 | } | ||
560 | |||
561 | if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) { | ||
562 | if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) { | ||
563 | ctx.add_action( | ||
564 | AssistId("auto_import"), | ||
565 | format!("import {} in mod {}", fmt_segments(&segments), name.text()), | ||
566 | |edit| { | ||
567 | apply_auto_import( | ||
568 | item_list.syntax(), | ||
569 | &path, | ||
570 | &segments, | ||
571 | edit.text_edit_builder(), | ||
572 | ); | ||
573 | }, | ||
574 | ); | ||
575 | } | ||
576 | } else { | ||
577 | let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; | ||
578 | ctx.add_action( | ||
579 | AssistId("auto_import"), | ||
580 | format!("import {} in the current file", fmt_segments(&segments)), | ||
581 | |edit| { | ||
582 | apply_auto_import( | ||
583 | current_file.syntax(), | ||
584 | &path, | ||
585 | &segments, | ||
586 | edit.text_edit_builder(), | ||
587 | ); | ||
588 | }, | ||
589 | ); | ||
590 | } | ||
591 | |||
592 | ctx.build() | ||
593 | } | ||
594 | |||
595 | #[cfg(test)] | ||
596 | mod tests { | ||
597 | use super::*; | ||
598 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
599 | |||
600 | #[test] | ||
601 | fn test_auto_import_add_use_no_anchor() { | ||
602 | check_assist( | ||
603 | auto_import, | ||
604 | " | ||
605 | std::fmt::Debug<|> | ||
606 | ", | ||
607 | " | ||
608 | use std::fmt::Debug; | ||
609 | |||
610 | Debug<|> | ||
611 | ", | ||
612 | ); | ||
613 | } | ||
614 | #[test] | ||
615 | fn test_auto_import_add_use_no_anchor_with_item_below() { | ||
616 | check_assist( | ||
617 | auto_import, | ||
618 | " | ||
619 | std::fmt::Debug<|> | ||
620 | |||
621 | fn main() { | ||
622 | } | ||
623 | ", | ||
624 | " | ||
625 | use std::fmt::Debug; | ||
626 | |||
627 | Debug<|> | ||
628 | |||
629 | fn main() { | ||
630 | } | ||
631 | ", | ||
632 | ); | ||
633 | } | ||
634 | |||
635 | #[test] | ||
636 | fn test_auto_import_add_use_no_anchor_with_item_above() { | ||
637 | check_assist( | ||
638 | auto_import, | ||
639 | " | ||
640 | fn main() { | ||
641 | } | ||
642 | |||
643 | std::fmt::Debug<|> | ||
644 | ", | ||
645 | " | ||
646 | use std::fmt::Debug; | ||
647 | |||
648 | fn main() { | ||
649 | } | ||
650 | |||
651 | Debug<|> | ||
652 | ", | ||
653 | ); | ||
654 | } | ||
655 | |||
656 | #[test] | ||
657 | fn test_auto_import_add_use_no_anchor_2seg() { | ||
658 | check_assist( | ||
659 | auto_import, | ||
660 | " | ||
661 | std::fmt<|>::Debug | ||
662 | ", | ||
663 | " | ||
664 | use std::fmt; | ||
665 | |||
666 | fmt<|>::Debug | ||
667 | ", | ||
668 | ); | ||
669 | } | ||
670 | |||
671 | #[test] | ||
672 | fn test_auto_import_add_use() { | ||
673 | check_assist( | ||
674 | auto_import, | ||
675 | " | ||
676 | use stdx; | ||
677 | |||
678 | impl std::fmt::Debug<|> for Foo { | ||
679 | } | ||
680 | ", | ||
681 | " | ||
682 | use stdx; | ||
683 | use std::fmt::Debug; | ||
684 | |||
685 | impl Debug<|> for Foo { | ||
686 | } | ||
687 | ", | ||
688 | ); | ||
689 | } | ||
690 | |||
691 | #[test] | ||
692 | fn test_auto_import_file_use_other_anchor() { | ||
693 | check_assist( | ||
694 | auto_import, | ||
695 | " | ||
696 | impl std::fmt::Debug<|> for Foo { | ||
697 | } | ||
698 | ", | ||
699 | " | ||
700 | use std::fmt::Debug; | ||
701 | |||
702 | impl Debug<|> for Foo { | ||
703 | } | ||
704 | ", | ||
705 | ); | ||
706 | } | ||
707 | |||
708 | #[test] | ||
709 | fn test_auto_import_add_use_other_anchor_indent() { | ||
710 | check_assist( | ||
711 | auto_import, | ||
712 | " | ||
713 | impl std::fmt::Debug<|> for Foo { | ||
714 | } | ||
715 | ", | ||
716 | " | ||
717 | use std::fmt::Debug; | ||
718 | |||
719 | impl Debug<|> for Foo { | ||
720 | } | ||
721 | ", | ||
722 | ); | ||
723 | } | ||
724 | |||
725 | #[test] | ||
726 | fn test_auto_import_split_different() { | ||
727 | check_assist( | ||
728 | auto_import, | ||
729 | " | ||
730 | use std::fmt; | ||
731 | |||
732 | impl std::io<|> for Foo { | ||
733 | } | ||
734 | ", | ||
735 | " | ||
736 | use std::{ io, fmt}; | ||
737 | |||
738 | impl io<|> for Foo { | ||
739 | } | ||
740 | ", | ||
741 | ); | ||
742 | } | ||
743 | |||
744 | #[test] | ||
745 | fn test_auto_import_split_self_for_use() { | ||
746 | check_assist( | ||
747 | auto_import, | ||
748 | " | ||
749 | use std::fmt; | ||
750 | |||
751 | impl std::fmt::Debug<|> for Foo { | ||
752 | } | ||
753 | ", | ||
754 | " | ||
755 | use std::fmt::{ self, Debug, }; | ||
756 | |||
757 | impl Debug<|> for Foo { | ||
758 | } | ||
759 | ", | ||
760 | ); | ||
761 | } | ||
762 | |||
763 | #[test] | ||
764 | fn test_auto_import_split_self_for_target() { | ||
765 | check_assist( | ||
766 | auto_import, | ||
767 | " | ||
768 | use std::fmt::Debug; | ||
769 | |||
770 | impl std::fmt<|> for Foo { | ||
771 | } | ||
772 | ", | ||
773 | " | ||
774 | use std::fmt::{ self, Debug}; | ||
775 | |||
776 | impl fmt<|> for Foo { | ||
777 | } | ||
778 | ", | ||
779 | ); | ||
780 | } | ||
781 | |||
782 | #[test] | ||
783 | fn test_auto_import_add_to_nested_self_nested() { | ||
784 | check_assist( | ||
785 | auto_import, | ||
786 | " | ||
787 | use std::fmt::{Debug, nested::{Display}}; | ||
788 | |||
789 | impl std::fmt::nested<|> for Foo { | ||
790 | } | ||
791 | ", | ||
792 | " | ||
793 | use std::fmt::{Debug, nested::{Display, self}}; | ||
794 | |||
795 | impl nested<|> for Foo { | ||
796 | } | ||
797 | ", | ||
798 | ); | ||
799 | } | ||
800 | |||
801 | #[test] | ||
802 | fn test_auto_import_add_to_nested_self_already_included() { | ||
803 | check_assist( | ||
804 | auto_import, | ||
805 | " | ||
806 | use std::fmt::{Debug, nested::{self, Display}}; | ||
807 | |||
808 | impl std::fmt::nested<|> for Foo { | ||
809 | } | ||
810 | ", | ||
811 | " | ||
812 | use std::fmt::{Debug, nested::{self, Display}}; | ||
813 | |||
814 | impl nested<|> for Foo { | ||
815 | } | ||
816 | ", | ||
817 | ); | ||
818 | } | ||
819 | |||
820 | #[test] | ||
821 | fn test_auto_import_add_to_nested_nested() { | ||
822 | check_assist( | ||
823 | auto_import, | ||
824 | " | ||
825 | use std::fmt::{Debug, nested::{Display}}; | ||
826 | |||
827 | impl std::fmt::nested::Debug<|> for Foo { | ||
828 | } | ||
829 | ", | ||
830 | " | ||
831 | use std::fmt::{Debug, nested::{Display, Debug}}; | ||
832 | |||
833 | impl Debug<|> for Foo { | ||
834 | } | ||
835 | ", | ||
836 | ); | ||
837 | } | ||
838 | |||
839 | #[test] | ||
840 | fn test_auto_import_split_common_target_longer() { | ||
841 | check_assist( | ||
842 | auto_import, | ||
843 | " | ||
844 | use std::fmt::Debug; | ||
845 | |||
846 | impl std::fmt::nested::Display<|> for Foo { | ||
847 | } | ||
848 | ", | ||
849 | " | ||
850 | use std::fmt::{ nested::Display, Debug}; | ||
851 | |||
852 | impl Display<|> for Foo { | ||
853 | } | ||
854 | ", | ||
855 | ); | ||
856 | } | ||
857 | |||
858 | #[test] | ||
859 | fn test_auto_import_split_common_use_longer() { | ||
860 | check_assist( | ||
861 | auto_import, | ||
862 | " | ||
863 | use std::fmt::nested::Debug; | ||
864 | |||
865 | impl std::fmt::Display<|> for Foo { | ||
866 | } | ||
867 | ", | ||
868 | " | ||
869 | use std::fmt::{ Display, nested::Debug}; | ||
870 | |||
871 | impl Display<|> for Foo { | ||
872 | } | ||
873 | ", | ||
874 | ); | ||
875 | } | ||
876 | |||
877 | #[test] | ||
878 | fn test_auto_import_alias() { | ||
879 | check_assist( | ||
880 | auto_import, | ||
881 | " | ||
882 | use std::fmt as foo; | ||
883 | |||
884 | impl foo::Debug<|> for Foo { | ||
885 | } | ||
886 | ", | ||
887 | " | ||
888 | use std::fmt as foo; | ||
889 | |||
890 | impl Debug<|> for Foo { | ||
891 | } | ||
892 | ", | ||
893 | ); | ||
894 | } | ||
895 | |||
896 | #[test] | ||
897 | fn test_auto_import_not_applicable_one_segment() { | ||
898 | check_assist_not_applicable( | ||
899 | auto_import, | ||
900 | " | ||
901 | impl foo<|> for Foo { | ||
902 | } | ||
903 | ", | ||
904 | ); | ||
905 | } | ||
906 | |||
907 | #[test] | ||
908 | fn test_auto_import_not_applicable_in_use() { | ||
909 | check_assist_not_applicable( | ||
910 | auto_import, | ||
911 | " | ||
912 | use std::fmt<|>; | ||
913 | ", | ||
914 | ); | ||
915 | } | ||
916 | |||
917 | #[test] | ||
918 | fn test_auto_import_add_use_no_anchor_in_mod_mod() { | ||
919 | check_assist( | ||
920 | auto_import, | ||
921 | " | ||
922 | mod foo { | ||
923 | mod bar { | ||
924 | std::fmt::Debug<|> | ||
925 | } | ||
926 | } | ||
927 | ", | ||
928 | " | ||
929 | mod foo { | ||
930 | mod bar { | ||
931 | use std::fmt::Debug; | ||
932 | |||
933 | Debug<|> | ||
934 | } | ||
935 | } | ||
936 | ", | ||
937 | ); | ||
938 | } | ||
939 | } | ||
diff --git a/crates/ra_assists/src/assists/change_visibility.rs b/crates/ra_assists/src/assists/change_visibility.rs new file mode 100644 index 000000000..60c74debc --- /dev/null +++ b/crates/ra_assists/src/assists/change_visibility.rs | |||
@@ -0,0 +1,159 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, NameOwner, VisibilityOwner}, | ||
4 | AstNode, | ||
5 | SyntaxKind::{ | ||
6 | ATTR, COMMENT, ENUM_DEF, FN_DEF, IDENT, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, | ||
7 | WHITESPACE, | ||
8 | }, | ||
9 | SyntaxNode, TextUnit, T, | ||
10 | }; | ||
11 | |||
12 | use crate::{Assist, AssistCtx, AssistId}; | ||
13 | |||
14 | pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
15 | if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() { | ||
16 | return change_vis(ctx, vis); | ||
17 | } | ||
18 | add_vis(ctx) | ||
19 | } | ||
20 | |||
21 | fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
22 | let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { | ||
23 | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, | ||
24 | _ => false, | ||
25 | }); | ||
26 | |||
27 | let (offset, target) = if let Some(keyword) = item_keyword { | ||
28 | let parent = keyword.parent(); | ||
29 | let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; | ||
30 | // Parent is not a definition, can't add visibility | ||
31 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { | ||
32 | return None; | ||
33 | } | ||
34 | // Already have visibility, do nothing | ||
35 | if parent.children().any(|child| child.kind() == VISIBILITY) { | ||
36 | return None; | ||
37 | } | ||
38 | (vis_offset(&parent), keyword.text_range()) | ||
39 | } else { | ||
40 | let ident = ctx.token_at_offset().find(|leaf| leaf.kind() == IDENT)?; | ||
41 | let field = ident.parent().ancestors().find_map(ast::RecordFieldDef::cast)?; | ||
42 | if field.name()?.syntax().text_range() != ident.text_range() && field.visibility().is_some() | ||
43 | { | ||
44 | return None; | ||
45 | } | ||
46 | (vis_offset(field.syntax()), ident.text_range()) | ||
47 | }; | ||
48 | |||
49 | ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| { | ||
50 | edit.target(target); | ||
51 | edit.insert(offset, "pub(crate) "); | ||
52 | edit.set_cursor(offset); | ||
53 | }); | ||
54 | |||
55 | ctx.build() | ||
56 | } | ||
57 | |||
58 | fn vis_offset(node: &SyntaxNode) -> TextUnit { | ||
59 | node.children_with_tokens() | ||
60 | .skip_while(|it| match it.kind() { | ||
61 | WHITESPACE | COMMENT | ATTR => true, | ||
62 | _ => false, | ||
63 | }) | ||
64 | .next() | ||
65 | .map(|it| it.text_range().start()) | ||
66 | .unwrap_or_else(|| node.text_range().start()) | ||
67 | } | ||
68 | |||
69 | fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> { | ||
70 | if vis.syntax().text() == "pub" { | ||
71 | ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| { | ||
72 | edit.target(vis.syntax().text_range()); | ||
73 | edit.replace(vis.syntax().text_range(), "pub(crate)"); | ||
74 | edit.set_cursor(vis.syntax().text_range().start()) | ||
75 | }); | ||
76 | |||
77 | return ctx.build(); | ||
78 | } | ||
79 | if vis.syntax().text() == "pub(crate)" { | ||
80 | ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| { | ||
81 | edit.target(vis.syntax().text_range()); | ||
82 | edit.replace(vis.syntax().text_range(), "pub"); | ||
83 | edit.set_cursor(vis.syntax().text_range().start()); | ||
84 | }); | ||
85 | |||
86 | return ctx.build(); | ||
87 | } | ||
88 | None | ||
89 | } | ||
90 | |||
91 | #[cfg(test)] | ||
92 | mod tests { | ||
93 | use super::*; | ||
94 | use crate::helpers::{check_assist, check_assist_target}; | ||
95 | |||
96 | #[test] | ||
97 | fn change_visibility_adds_pub_crate_to_items() { | ||
98 | check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}"); | ||
99 | check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}"); | ||
100 | check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}"); | ||
101 | check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}"); | ||
102 | check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}"); | ||
103 | check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}"); | ||
104 | check_assist( | ||
105 | change_visibility, | ||
106 | "unsafe f<|>n foo() {}", | ||
107 | "<|>pub(crate) unsafe fn foo() {}", | ||
108 | ); | ||
109 | } | ||
110 | |||
111 | #[test] | ||
112 | fn change_visibility_works_with_struct_fields() { | ||
113 | check_assist( | ||
114 | change_visibility, | ||
115 | "struct S { <|>field: u32 }", | ||
116 | "struct S { <|>pub(crate) field: u32 }", | ||
117 | ) | ||
118 | } | ||
119 | |||
120 | #[test] | ||
121 | fn change_visibility_pub_to_pub_crate() { | ||
122 | check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}") | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn change_visibility_pub_crate_to_pub() { | ||
127 | check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}") | ||
128 | } | ||
129 | |||
130 | #[test] | ||
131 | fn change_visibility_handles_comment_attrs() { | ||
132 | check_assist( | ||
133 | change_visibility, | ||
134 | " | ||
135 | /// docs | ||
136 | |||
137 | // comments | ||
138 | |||
139 | #[derive(Debug)] | ||
140 | <|>struct Foo; | ||
141 | ", | ||
142 | " | ||
143 | /// docs | ||
144 | |||
145 | // comments | ||
146 | |||
147 | #[derive(Debug)] | ||
148 | <|>pub(crate) struct Foo; | ||
149 | ", | ||
150 | ) | ||
151 | } | ||
152 | |||
153 | #[test] | ||
154 | fn change_visibility_target() { | ||
155 | check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); | ||
156 | check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); | ||
157 | check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field"); | ||
158 | } | ||
159 | } | ||
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs new file mode 100644 index 000000000..817433526 --- /dev/null +++ b/crates/ra_assists/src/assists/fill_match_arms.rs | |||
@@ -0,0 +1,226 @@ | |||
1 | use std::iter; | ||
2 | |||
3 | use hir::{db::HirDatabase, Adt, HasSource}; | ||
4 | use ra_syntax::ast::{self, AstNode, NameOwner}; | ||
5 | |||
6 | use crate::{ast_builder::Make, Assist, AssistCtx, AssistId}; | ||
7 | |||
8 | pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
9 | let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?; | ||
10 | let match_arm_list = match_expr.match_arm_list()?; | ||
11 | |||
12 | // We already have some match arms, so we don't provide any assists. | ||
13 | // Unless if there is only one trivial match arm possibly created | ||
14 | // by match postfix complete. Trivial match arm is the catch all arm. | ||
15 | let mut existing_arms = match_arm_list.arms(); | ||
16 | if let Some(arm) = existing_arms.next() { | ||
17 | if !is_trivial(&arm) || existing_arms.next().is_some() { | ||
18 | return None; | ||
19 | } | ||
20 | }; | ||
21 | |||
22 | let expr = match_expr.expr()?; | ||
23 | let enum_def = { | ||
24 | let file_id = ctx.frange.file_id; | ||
25 | let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, expr.syntax(), None); | ||
26 | resolve_enum_def(ctx.db, &analyzer, &expr)? | ||
27 | }; | ||
28 | let variant_list = enum_def.variant_list()?; | ||
29 | |||
30 | ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { | ||
31 | let variants = variant_list.variants(); | ||
32 | let arms = variants | ||
33 | .filter_map(build_pat) | ||
34 | .map(|pat| Make::<ast::MatchArm>::from(iter::once(pat), Make::<ast::Expr>::unit())); | ||
35 | let new_arm_list = Make::<ast::MatchArmList>::from_arms(arms); | ||
36 | |||
37 | edit.target(match_expr.syntax().text_range()); | ||
38 | edit.set_cursor(expr.syntax().text_range().start()); | ||
39 | edit.replace_node_and_indent(match_arm_list.syntax(), new_arm_list.syntax().text()); | ||
40 | }); | ||
41 | |||
42 | ctx.build() | ||
43 | } | ||
44 | |||
45 | fn is_trivial(arm: &ast::MatchArm) -> bool { | ||
46 | arm.pats().any(|pat| match pat { | ||
47 | ast::Pat::PlaceholderPat(..) => true, | ||
48 | _ => false, | ||
49 | }) | ||
50 | } | ||
51 | |||
52 | fn resolve_enum_def( | ||
53 | db: &impl HirDatabase, | ||
54 | analyzer: &hir::SourceAnalyzer, | ||
55 | expr: &ast::Expr, | ||
56 | ) -> Option<ast::EnumDef> { | ||
57 | let expr_ty = analyzer.type_of(db, &expr)?; | ||
58 | |||
59 | analyzer.autoderef(db, expr_ty).find_map(|ty| match ty.as_adt() { | ||
60 | Some((Adt::Enum(e), _)) => Some(e.source(db).ast), | ||
61 | _ => None, | ||
62 | }) | ||
63 | } | ||
64 | |||
65 | fn build_pat(var: ast::EnumVariant) -> Option<ast::Pat> { | ||
66 | let path = Make::<ast::Path>::from(var.parent_enum().name()?, var.name()?); | ||
67 | |||
68 | let pat: ast::Pat = match var.kind() { | ||
69 | ast::StructKind::Tuple(field_list) => { | ||
70 | let pats = iter::repeat(Make::<ast::PlaceholderPat>::placeholder().into()) | ||
71 | .take(field_list.fields().count()); | ||
72 | Make::<ast::TupleStructPat>::from(path, pats).into() | ||
73 | } | ||
74 | ast::StructKind::Named(field_list) => { | ||
75 | let pats = field_list | ||
76 | .fields() | ||
77 | .map(|f| Make::<ast::BindPat>::from_name(f.name().unwrap()).into()); | ||
78 | Make::<ast::RecordPat>::from(path, pats).into() | ||
79 | } | ||
80 | ast::StructKind::Unit => Make::<ast::PathPat>::from_path(path).into(), | ||
81 | }; | ||
82 | |||
83 | Some(pat) | ||
84 | } | ||
85 | |||
86 | #[cfg(test)] | ||
87 | mod tests { | ||
88 | use crate::helpers::{check_assist, check_assist_target}; | ||
89 | |||
90 | use super::fill_match_arms; | ||
91 | |||
92 | #[test] | ||
93 | fn fill_match_arms_empty_body() { | ||
94 | check_assist( | ||
95 | fill_match_arms, | ||
96 | r#" | ||
97 | enum A { | ||
98 | As, | ||
99 | Bs, | ||
100 | Cs(String), | ||
101 | Ds(String, String), | ||
102 | Es{ x: usize, y: usize } | ||
103 | } | ||
104 | |||
105 | fn main() { | ||
106 | let a = A::As; | ||
107 | match a<|> {} | ||
108 | } | ||
109 | "#, | ||
110 | r#" | ||
111 | enum A { | ||
112 | As, | ||
113 | Bs, | ||
114 | Cs(String), | ||
115 | Ds(String, String), | ||
116 | Es{ x: usize, y: usize } | ||
117 | } | ||
118 | |||
119 | fn main() { | ||
120 | let a = A::As; | ||
121 | match <|>a { | ||
122 | A::As => (), | ||
123 | A::Bs => (), | ||
124 | A::Cs(_) => (), | ||
125 | A::Ds(_, _) => (), | ||
126 | A::Es{ x, y } => (), | ||
127 | } | ||
128 | } | ||
129 | "#, | ||
130 | ); | ||
131 | } | ||
132 | |||
133 | #[test] | ||
134 | fn test_fill_match_arm_refs() { | ||
135 | check_assist( | ||
136 | fill_match_arms, | ||
137 | r#" | ||
138 | enum A { | ||
139 | As, | ||
140 | } | ||
141 | |||
142 | fn foo(a: &A) { | ||
143 | match a<|> { | ||
144 | } | ||
145 | } | ||
146 | "#, | ||
147 | r#" | ||
148 | enum A { | ||
149 | As, | ||
150 | } | ||
151 | |||
152 | fn foo(a: &A) { | ||
153 | match <|>a { | ||
154 | A::As => (), | ||
155 | } | ||
156 | } | ||
157 | "#, | ||
158 | ); | ||
159 | |||
160 | check_assist( | ||
161 | fill_match_arms, | ||
162 | r#" | ||
163 | enum A { | ||
164 | Es{ x: usize, y: usize } | ||
165 | } | ||
166 | |||
167 | fn foo(a: &mut A) { | ||
168 | match a<|> { | ||
169 | } | ||
170 | } | ||
171 | "#, | ||
172 | r#" | ||
173 | enum A { | ||
174 | Es{ x: usize, y: usize } | ||
175 | } | ||
176 | |||
177 | fn foo(a: &mut A) { | ||
178 | match <|>a { | ||
179 | A::Es{ x, y } => (), | ||
180 | } | ||
181 | } | ||
182 | "#, | ||
183 | ); | ||
184 | } | ||
185 | |||
186 | #[test] | ||
187 | fn fill_match_arms_target() { | ||
188 | check_assist_target( | ||
189 | fill_match_arms, | ||
190 | r#" | ||
191 | enum E { X, Y } | ||
192 | |||
193 | fn main() { | ||
194 | match E::X<|> {} | ||
195 | } | ||
196 | "#, | ||
197 | "match E::X {}", | ||
198 | ); | ||
199 | } | ||
200 | |||
201 | #[test] | ||
202 | fn fill_match_arms_trivial_arm() { | ||
203 | check_assist( | ||
204 | fill_match_arms, | ||
205 | r#" | ||
206 | enum E { X, Y } | ||
207 | |||
208 | fn main() { | ||
209 | match E::X { | ||
210 | <|>_ => {}, | ||
211 | } | ||
212 | } | ||
213 | "#, | ||
214 | r#" | ||
215 | enum E { X, Y } | ||
216 | |||
217 | fn main() { | ||
218 | match <|>E::X { | ||
219 | E::X => (), | ||
220 | E::Y => (), | ||
221 | } | ||
222 | } | ||
223 | "#, | ||
224 | ); | ||
225 | } | ||
226 | } | ||
diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/assists/flip_binexpr.rs new file mode 100644 index 000000000..b55b36a8e --- /dev/null +++ b/crates/ra_assists/src/assists/flip_binexpr.rs | |||
@@ -0,0 +1,141 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; | ||
3 | |||
4 | use crate::{Assist, AssistCtx, AssistId}; | ||
5 | |||
6 | /// Flip binary expression assist. | ||
7 | pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
8 | let expr = ctx.node_at_offset::<BinExpr>()?; | ||
9 | let lhs = expr.lhs()?.syntax().clone(); | ||
10 | let rhs = expr.rhs()?.syntax().clone(); | ||
11 | let op_range = expr.op_token()?.text_range(); | ||
12 | // The assist should be applied only if the cursor is on the operator | ||
13 | let cursor_in_range = ctx.frange.range.is_subrange(&op_range); | ||
14 | if !cursor_in_range { | ||
15 | return None; | ||
16 | } | ||
17 | let action: FlipAction = expr.op_kind()?.into(); | ||
18 | // The assist should not be applied for certain operators | ||
19 | if let FlipAction::DontFlip = action { | ||
20 | return None; | ||
21 | } | ||
22 | |||
23 | ctx.add_action(AssistId("flip_binexpr"), "flip binary expression", |edit| { | ||
24 | edit.target(op_range); | ||
25 | if let FlipAction::FlipAndReplaceOp(new_op) = action { | ||
26 | edit.replace(op_range, new_op); | ||
27 | } | ||
28 | edit.replace(lhs.text_range(), rhs.text()); | ||
29 | edit.replace(rhs.text_range(), lhs.text()); | ||
30 | }); | ||
31 | |||
32 | ctx.build() | ||
33 | } | ||
34 | |||
35 | enum FlipAction { | ||
36 | // Flip the expression | ||
37 | Flip, | ||
38 | // Flip the expression and replace the operator with this string | ||
39 | FlipAndReplaceOp(&'static str), | ||
40 | // Do not flip the expression | ||
41 | DontFlip, | ||
42 | } | ||
43 | |||
44 | impl From<BinOp> for FlipAction { | ||
45 | fn from(op_kind: BinOp) -> Self { | ||
46 | match op_kind { | ||
47 | BinOp::Assignment => FlipAction::DontFlip, | ||
48 | BinOp::AddAssign => FlipAction::DontFlip, | ||
49 | BinOp::DivAssign => FlipAction::DontFlip, | ||
50 | BinOp::MulAssign => FlipAction::DontFlip, | ||
51 | BinOp::RemAssign => FlipAction::DontFlip, | ||
52 | BinOp::ShrAssign => FlipAction::DontFlip, | ||
53 | BinOp::ShlAssign => FlipAction::DontFlip, | ||
54 | BinOp::SubAssign => FlipAction::DontFlip, | ||
55 | BinOp::BitOrAssign => FlipAction::DontFlip, | ||
56 | BinOp::BitAndAssign => FlipAction::DontFlip, | ||
57 | BinOp::BitXorAssign => FlipAction::DontFlip, | ||
58 | BinOp::GreaterTest => FlipAction::FlipAndReplaceOp("<"), | ||
59 | BinOp::GreaterEqualTest => FlipAction::FlipAndReplaceOp("<="), | ||
60 | BinOp::LesserTest => FlipAction::FlipAndReplaceOp(">"), | ||
61 | BinOp::LesserEqualTest => FlipAction::FlipAndReplaceOp(">="), | ||
62 | _ => FlipAction::Flip, | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | |||
67 | #[cfg(test)] | ||
68 | mod tests { | ||
69 | use super::*; | ||
70 | |||
71 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
72 | |||
73 | #[test] | ||
74 | fn flip_binexpr_target_is_the_op() { | ||
75 | check_assist_target(flip_binexpr, "fn f() { let res = 1 ==<|> 2; }", "==") | ||
76 | } | ||
77 | |||
78 | #[test] | ||
79 | fn flip_binexpr_not_applicable_for_assignment() { | ||
80 | check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=<|> 2 }") | ||
81 | } | ||
82 | |||
83 | #[test] | ||
84 | fn flip_binexpr_works_for_eq() { | ||
85 | check_assist( | ||
86 | flip_binexpr, | ||
87 | "fn f() { let res = 1 ==<|> 2; }", | ||
88 | "fn f() { let res = 2 ==<|> 1; }", | ||
89 | ) | ||
90 | } | ||
91 | |||
92 | #[test] | ||
93 | fn flip_binexpr_works_for_gt() { | ||
94 | check_assist( | ||
95 | flip_binexpr, | ||
96 | "fn f() { let res = 1 ><|> 2; }", | ||
97 | "fn f() { let res = 2 <<|> 1; }", | ||
98 | ) | ||
99 | } | ||
100 | |||
101 | #[test] | ||
102 | fn flip_binexpr_works_for_lteq() { | ||
103 | check_assist( | ||
104 | flip_binexpr, | ||
105 | "fn f() { let res = 1 <=<|> 2; }", | ||
106 | "fn f() { let res = 2 >=<|> 1; }", | ||
107 | ) | ||
108 | } | ||
109 | |||
110 | #[test] | ||
111 | fn flip_binexpr_works_for_complex_expr() { | ||
112 | check_assist( | ||
113 | flip_binexpr, | ||
114 | "fn f() { let res = (1 + 1) ==<|> (2 + 2); }", | ||
115 | "fn f() { let res = (2 + 2) ==<|> (1 + 1); }", | ||
116 | ) | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn flip_binexpr_works_inside_match() { | ||
121 | check_assist( | ||
122 | flip_binexpr, | ||
123 | r#" | ||
124 | fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { | ||
125 | match other.downcast_ref::<Self>() { | ||
126 | None => false, | ||
127 | Some(it) => it ==<|> self, | ||
128 | } | ||
129 | } | ||
130 | "#, | ||
131 | r#" | ||
132 | fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { | ||
133 | match other.downcast_ref::<Self>() { | ||
134 | None => false, | ||
135 | Some(it) => self ==<|> it, | ||
136 | } | ||
137 | } | ||
138 | "#, | ||
139 | ) | ||
140 | } | ||
141 | } | ||
diff --git a/crates/ra_assists/src/assists/flip_comma.rs b/crates/ra_assists/src/assists/flip_comma.rs new file mode 100644 index 000000000..5ee7561bc --- /dev/null +++ b/crates/ra_assists/src/assists/flip_comma.rs | |||
@@ -0,0 +1,68 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{algo::non_trivia_sibling, Direction, T}; | ||
3 | |||
4 | use crate::{Assist, AssistCtx, AssistId}; | ||
5 | |||
6 | pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
7 | let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?; | ||
8 | let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?; | ||
9 | let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?; | ||
10 | |||
11 | // Don't apply a "flip" in case of a last comma | ||
12 | // that typically comes before punctuation | ||
13 | if next.kind().is_punct() { | ||
14 | return None; | ||
15 | } | ||
16 | |||
17 | ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| { | ||
18 | edit.target(comma.text_range()); | ||
19 | edit.replace(prev.text_range(), next.to_string()); | ||
20 | edit.replace(next.text_range(), prev.to_string()); | ||
21 | }); | ||
22 | |||
23 | ctx.build() | ||
24 | } | ||
25 | |||
26 | #[cfg(test)] | ||
27 | mod tests { | ||
28 | use super::*; | ||
29 | |||
30 | use crate::helpers::{check_assist, check_assist_target}; | ||
31 | |||
32 | #[test] | ||
33 | fn flip_comma_works_for_function_parameters() { | ||
34 | check_assist( | ||
35 | flip_comma, | ||
36 | "fn foo(x: i32,<|> y: Result<(), ()>) {}", | ||
37 | "fn foo(y: Result<(), ()>,<|> x: i32) {}", | ||
38 | ) | ||
39 | } | ||
40 | |||
41 | #[test] | ||
42 | fn flip_comma_target() { | ||
43 | check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",") | ||
44 | } | ||
45 | |||
46 | #[test] | ||
47 | #[should_panic] | ||
48 | fn flip_comma_before_punct() { | ||
49 | // See https://github.com/rust-analyzer/rust-analyzer/issues/1619 | ||
50 | // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct | ||
51 | // declaration body. | ||
52 | check_assist_target( | ||
53 | flip_comma, | ||
54 | "pub enum Test { \ | ||
55 | A,<|> \ | ||
56 | }", | ||
57 | ",", | ||
58 | ); | ||
59 | |||
60 | check_assist_target( | ||
61 | flip_comma, | ||
62 | "pub struct Test { \ | ||
63 | foo: usize,<|> \ | ||
64 | }", | ||
65 | ",", | ||
66 | ); | ||
67 | } | ||
68 | } | ||
diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs new file mode 100644 index 000000000..eedb29199 --- /dev/null +++ b/crates/ra_assists/src/assists/inline_local_variable.rs | |||
@@ -0,0 +1,636 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, AstToken}, | ||
4 | TextRange, | ||
5 | }; | ||
6 | |||
7 | use crate::assist_ctx::AssistBuilder; | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | ||
9 | |||
10 | pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
11 | let let_stmt = ctx.node_at_offset::<ast::LetStmt>()?; | ||
12 | let bind_pat = match let_stmt.pat()? { | ||
13 | ast::Pat::BindPat(pat) => pat, | ||
14 | _ => return None, | ||
15 | }; | ||
16 | if bind_pat.is_mutable() { | ||
17 | return None; | ||
18 | } | ||
19 | let initializer_expr = let_stmt.initializer()?; | ||
20 | let delete_range = if let Some(whitespace) = let_stmt | ||
21 | .syntax() | ||
22 | .next_sibling_or_token() | ||
23 | .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) | ||
24 | { | ||
25 | TextRange::from_to( | ||
26 | let_stmt.syntax().text_range().start(), | ||
27 | whitespace.syntax().text_range().end(), | ||
28 | ) | ||
29 | } else { | ||
30 | let_stmt.syntax().text_range() | ||
31 | }; | ||
32 | let analyzer = hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, bind_pat.syntax(), None); | ||
33 | let refs = analyzer.find_all_refs(&bind_pat); | ||
34 | |||
35 | let mut wrap_in_parens = vec![true; refs.len()]; | ||
36 | |||
37 | for (i, desc) in refs.iter().enumerate() { | ||
38 | let usage_node = ctx | ||
39 | .covering_node_for_range(desc.range) | ||
40 | .ancestors() | ||
41 | .find_map(|node| ast::PathExpr::cast(node))?; | ||
42 | let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast); | ||
43 | let usage_parent = match usage_parent_option { | ||
44 | Some(u) => u, | ||
45 | None => { | ||
46 | wrap_in_parens[i] = false; | ||
47 | continue; | ||
48 | } | ||
49 | }; | ||
50 | |||
51 | wrap_in_parens[i] = match (&initializer_expr, usage_parent) { | ||
52 | (ast::Expr::CallExpr(_), _) | ||
53 | | (ast::Expr::IndexExpr(_), _) | ||
54 | | (ast::Expr::MethodCallExpr(_), _) | ||
55 | | (ast::Expr::FieldExpr(_), _) | ||
56 | | (ast::Expr::TryExpr(_), _) | ||
57 | | (ast::Expr::RefExpr(_), _) | ||
58 | | (ast::Expr::Literal(_), _) | ||
59 | | (ast::Expr::TupleExpr(_), _) | ||
60 | | (ast::Expr::ArrayExpr(_), _) | ||
61 | | (ast::Expr::ParenExpr(_), _) | ||
62 | | (ast::Expr::PathExpr(_), _) | ||
63 | | (ast::Expr::BlockExpr(_), _) | ||
64 | | (_, ast::Expr::CallExpr(_)) | ||
65 | | (_, ast::Expr::TupleExpr(_)) | ||
66 | | (_, ast::Expr::ArrayExpr(_)) | ||
67 | | (_, ast::Expr::ParenExpr(_)) | ||
68 | | (_, ast::Expr::ForExpr(_)) | ||
69 | | (_, ast::Expr::WhileExpr(_)) | ||
70 | | (_, ast::Expr::BreakExpr(_)) | ||
71 | | (_, ast::Expr::ReturnExpr(_)) | ||
72 | | (_, ast::Expr::MatchExpr(_)) => false, | ||
73 | _ => true, | ||
74 | }; | ||
75 | } | ||
76 | |||
77 | let init_str = initializer_expr.syntax().text().to_string(); | ||
78 | let init_in_paren = format!("({})", &init_str); | ||
79 | |||
80 | ctx.add_action( | ||
81 | AssistId("inline_local_variable"), | ||
82 | "inline local variable", | ||
83 | move |edit: &mut AssistBuilder| { | ||
84 | edit.delete(delete_range); | ||
85 | for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { | ||
86 | if should_wrap { | ||
87 | edit.replace(desc.range, init_in_paren.clone()) | ||
88 | } else { | ||
89 | edit.replace(desc.range, init_str.clone()) | ||
90 | } | ||
91 | } | ||
92 | edit.set_cursor(delete_range.start()) | ||
93 | }, | ||
94 | ); | ||
95 | |||
96 | ctx.build() | ||
97 | } | ||
98 | |||
99 | #[cfg(test)] | ||
100 | mod tests { | ||
101 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
102 | |||
103 | use super::*; | ||
104 | |||
105 | #[test] | ||
106 | fn test_inline_let_bind_literal_expr() { | ||
107 | check_assist( | ||
108 | inline_local_varialbe, | ||
109 | " | ||
110 | fn bar(a: usize) {} | ||
111 | fn foo() { | ||
112 | let a<|> = 1; | ||
113 | a + 1; | ||
114 | if a > 10 { | ||
115 | } | ||
116 | |||
117 | while a > 10 { | ||
118 | |||
119 | } | ||
120 | let b = a * 10; | ||
121 | bar(a); | ||
122 | }", | ||
123 | " | ||
124 | fn bar(a: usize) {} | ||
125 | fn foo() { | ||
126 | <|>1 + 1; | ||
127 | if 1 > 10 { | ||
128 | } | ||
129 | |||
130 | while 1 > 10 { | ||
131 | |||
132 | } | ||
133 | let b = 1 * 10; | ||
134 | bar(1); | ||
135 | }", | ||
136 | ); | ||
137 | } | ||
138 | |||
139 | #[test] | ||
140 | fn test_inline_let_bind_bin_expr() { | ||
141 | check_assist( | ||
142 | inline_local_varialbe, | ||
143 | " | ||
144 | fn bar(a: usize) {} | ||
145 | fn foo() { | ||
146 | let a<|> = 1 + 1; | ||
147 | a + 1; | ||
148 | if a > 10 { | ||
149 | } | ||
150 | |||
151 | while a > 10 { | ||
152 | |||
153 | } | ||
154 | let b = a * 10; | ||
155 | bar(a); | ||
156 | }", | ||
157 | " | ||
158 | fn bar(a: usize) {} | ||
159 | fn foo() { | ||
160 | <|>(1 + 1) + 1; | ||
161 | if (1 + 1) > 10 { | ||
162 | } | ||
163 | |||
164 | while (1 + 1) > 10 { | ||
165 | |||
166 | } | ||
167 | let b = (1 + 1) * 10; | ||
168 | bar(1 + 1); | ||
169 | }", | ||
170 | ); | ||
171 | } | ||
172 | |||
173 | #[test] | ||
174 | fn test_inline_let_bind_function_call_expr() { | ||
175 | check_assist( | ||
176 | inline_local_varialbe, | ||
177 | " | ||
178 | fn bar(a: usize) {} | ||
179 | fn foo() { | ||
180 | let a<|> = bar(1); | ||
181 | a + 1; | ||
182 | if a > 10 { | ||
183 | } | ||
184 | |||
185 | while a > 10 { | ||
186 | |||
187 | } | ||
188 | let b = a * 10; | ||
189 | bar(a); | ||
190 | }", | ||
191 | " | ||
192 | fn bar(a: usize) {} | ||
193 | fn foo() { | ||
194 | <|>bar(1) + 1; | ||
195 | if bar(1) > 10 { | ||
196 | } | ||
197 | |||
198 | while bar(1) > 10 { | ||
199 | |||
200 | } | ||
201 | let b = bar(1) * 10; | ||
202 | bar(bar(1)); | ||
203 | }", | ||
204 | ); | ||
205 | } | ||
206 | |||
207 | #[test] | ||
208 | fn test_inline_let_bind_cast_expr() { | ||
209 | check_assist( | ||
210 | inline_local_varialbe, | ||
211 | " | ||
212 | fn bar(a: usize): usize { a } | ||
213 | fn foo() { | ||
214 | let a<|> = bar(1) as u64; | ||
215 | a + 1; | ||
216 | if a > 10 { | ||
217 | } | ||
218 | |||
219 | while a > 10 { | ||
220 | |||
221 | } | ||
222 | let b = a * 10; | ||
223 | bar(a); | ||
224 | }", | ||
225 | " | ||
226 | fn bar(a: usize): usize { a } | ||
227 | fn foo() { | ||
228 | <|>(bar(1) as u64) + 1; | ||
229 | if (bar(1) as u64) > 10 { | ||
230 | } | ||
231 | |||
232 | while (bar(1) as u64) > 10 { | ||
233 | |||
234 | } | ||
235 | let b = (bar(1) as u64) * 10; | ||
236 | bar(bar(1) as u64); | ||
237 | }", | ||
238 | ); | ||
239 | } | ||
240 | |||
241 | #[test] | ||
242 | fn test_inline_let_bind_block_expr() { | ||
243 | check_assist( | ||
244 | inline_local_varialbe, | ||
245 | " | ||
246 | fn foo() { | ||
247 | let a<|> = { 10 + 1 }; | ||
248 | a + 1; | ||
249 | if a > 10 { | ||
250 | } | ||
251 | |||
252 | while a > 10 { | ||
253 | |||
254 | } | ||
255 | let b = a * 10; | ||
256 | bar(a); | ||
257 | }", | ||
258 | " | ||
259 | fn foo() { | ||
260 | <|>{ 10 + 1 } + 1; | ||
261 | if { 10 + 1 } > 10 { | ||
262 | } | ||
263 | |||
264 | while { 10 + 1 } > 10 { | ||
265 | |||
266 | } | ||
267 | let b = { 10 + 1 } * 10; | ||
268 | bar({ 10 + 1 }); | ||
269 | }", | ||
270 | ); | ||
271 | } | ||
272 | |||
273 | #[test] | ||
274 | fn test_inline_let_bind_paren_expr() { | ||
275 | check_assist( | ||
276 | inline_local_varialbe, | ||
277 | " | ||
278 | fn foo() { | ||
279 | let a<|> = ( 10 + 1 ); | ||
280 | a + 1; | ||
281 | if a > 10 { | ||
282 | } | ||
283 | |||
284 | while a > 10 { | ||
285 | |||
286 | } | ||
287 | let b = a * 10; | ||
288 | bar(a); | ||
289 | }", | ||
290 | " | ||
291 | fn foo() { | ||
292 | <|>( 10 + 1 ) + 1; | ||
293 | if ( 10 + 1 ) > 10 { | ||
294 | } | ||
295 | |||
296 | while ( 10 + 1 ) > 10 { | ||
297 | |||
298 | } | ||
299 | let b = ( 10 + 1 ) * 10; | ||
300 | bar(( 10 + 1 )); | ||
301 | }", | ||
302 | ); | ||
303 | } | ||
304 | |||
305 | #[test] | ||
306 | fn test_not_inline_mut_variable() { | ||
307 | check_assist_not_applicable( | ||
308 | inline_local_varialbe, | ||
309 | " | ||
310 | fn foo() { | ||
311 | let mut a<|> = 1 + 1; | ||
312 | a + 1; | ||
313 | }", | ||
314 | ); | ||
315 | } | ||
316 | |||
317 | #[test] | ||
318 | fn test_call_expr() { | ||
319 | check_assist( | ||
320 | inline_local_varialbe, | ||
321 | " | ||
322 | fn foo() { | ||
323 | let a<|> = bar(10 + 1); | ||
324 | let b = a * 10; | ||
325 | let c = a as usize; | ||
326 | }", | ||
327 | " | ||
328 | fn foo() { | ||
329 | <|>let b = bar(10 + 1) * 10; | ||
330 | let c = bar(10 + 1) as usize; | ||
331 | }", | ||
332 | ); | ||
333 | } | ||
334 | |||
335 | #[test] | ||
336 | fn test_index_expr() { | ||
337 | check_assist( | ||
338 | inline_local_varialbe, | ||
339 | " | ||
340 | fn foo() { | ||
341 | let x = vec![1, 2, 3]; | ||
342 | let a<|> = x[0]; | ||
343 | let b = a * 10; | ||
344 | let c = a as usize; | ||
345 | }", | ||
346 | " | ||
347 | fn foo() { | ||
348 | let x = vec![1, 2, 3]; | ||
349 | <|>let b = x[0] * 10; | ||
350 | let c = x[0] as usize; | ||
351 | }", | ||
352 | ); | ||
353 | } | ||
354 | |||
355 | #[test] | ||
356 | fn test_method_call_expr() { | ||
357 | check_assist( | ||
358 | inline_local_varialbe, | ||
359 | " | ||
360 | fn foo() { | ||
361 | let bar = vec![1]; | ||
362 | let a<|> = bar.len(); | ||
363 | let b = a * 10; | ||
364 | let c = a as usize; | ||
365 | }", | ||
366 | " | ||
367 | fn foo() { | ||
368 | let bar = vec![1]; | ||
369 | <|>let b = bar.len() * 10; | ||
370 | let c = bar.len() as usize; | ||
371 | }", | ||
372 | ); | ||
373 | } | ||
374 | |||
375 | #[test] | ||
376 | fn test_field_expr() { | ||
377 | check_assist( | ||
378 | inline_local_varialbe, | ||
379 | " | ||
380 | struct Bar { | ||
381 | foo: usize | ||
382 | } | ||
383 | |||
384 | fn foo() { | ||
385 | let bar = Bar { foo: 1 }; | ||
386 | let a<|> = bar.foo; | ||
387 | let b = a * 10; | ||
388 | let c = a as usize; | ||
389 | }", | ||
390 | " | ||
391 | struct Bar { | ||
392 | foo: usize | ||
393 | } | ||
394 | |||
395 | fn foo() { | ||
396 | let bar = Bar { foo: 1 }; | ||
397 | <|>let b = bar.foo * 10; | ||
398 | let c = bar.foo as usize; | ||
399 | }", | ||
400 | ); | ||
401 | } | ||
402 | |||
403 | #[test] | ||
404 | fn test_try_expr() { | ||
405 | check_assist( | ||
406 | inline_local_varialbe, | ||
407 | " | ||
408 | fn foo() -> Option<usize> { | ||
409 | let bar = Some(1); | ||
410 | let a<|> = bar?; | ||
411 | let b = a * 10; | ||
412 | let c = a as usize; | ||
413 | None | ||
414 | }", | ||
415 | " | ||
416 | fn foo() -> Option<usize> { | ||
417 | let bar = Some(1); | ||
418 | <|>let b = bar? * 10; | ||
419 | let c = bar? as usize; | ||
420 | None | ||
421 | }", | ||
422 | ); | ||
423 | } | ||
424 | |||
425 | #[test] | ||
426 | fn test_ref_expr() { | ||
427 | check_assist( | ||
428 | inline_local_varialbe, | ||
429 | " | ||
430 | fn foo() { | ||
431 | let bar = 10; | ||
432 | let a<|> = &bar; | ||
433 | let b = a * 10; | ||
434 | }", | ||
435 | " | ||
436 | fn foo() { | ||
437 | let bar = 10; | ||
438 | <|>let b = &bar * 10; | ||
439 | }", | ||
440 | ); | ||
441 | } | ||
442 | |||
443 | #[test] | ||
444 | fn test_tuple_expr() { | ||
445 | check_assist( | ||
446 | inline_local_varialbe, | ||
447 | " | ||
448 | fn foo() { | ||
449 | let a<|> = (10, 20); | ||
450 | let b = a[0]; | ||
451 | }", | ||
452 | " | ||
453 | fn foo() { | ||
454 | <|>let b = (10, 20)[0]; | ||
455 | }", | ||
456 | ); | ||
457 | } | ||
458 | |||
459 | #[test] | ||
460 | fn test_array_expr() { | ||
461 | check_assist( | ||
462 | inline_local_varialbe, | ||
463 | " | ||
464 | fn foo() { | ||
465 | let a<|> = [1, 2, 3]; | ||
466 | let b = a.len(); | ||
467 | }", | ||
468 | " | ||
469 | fn foo() { | ||
470 | <|>let b = [1, 2, 3].len(); | ||
471 | }", | ||
472 | ); | ||
473 | } | ||
474 | |||
475 | #[test] | ||
476 | fn test_paren() { | ||
477 | check_assist( | ||
478 | inline_local_varialbe, | ||
479 | " | ||
480 | fn foo() { | ||
481 | let a<|> = (10 + 20); | ||
482 | let b = a * 10; | ||
483 | let c = a as usize; | ||
484 | }", | ||
485 | " | ||
486 | fn foo() { | ||
487 | <|>let b = (10 + 20) * 10; | ||
488 | let c = (10 + 20) as usize; | ||
489 | }", | ||
490 | ); | ||
491 | } | ||
492 | |||
493 | #[test] | ||
494 | fn test_path_expr() { | ||
495 | check_assist( | ||
496 | inline_local_varialbe, | ||
497 | " | ||
498 | fn foo() { | ||
499 | let d = 10; | ||
500 | let a<|> = d; | ||
501 | let b = a * 10; | ||
502 | let c = a as usize; | ||
503 | }", | ||
504 | " | ||
505 | fn foo() { | ||
506 | let d = 10; | ||
507 | <|>let b = d * 10; | ||
508 | let c = d as usize; | ||
509 | }", | ||
510 | ); | ||
511 | } | ||
512 | |||
513 | #[test] | ||
514 | fn test_block_expr() { | ||
515 | check_assist( | ||
516 | inline_local_varialbe, | ||
517 | " | ||
518 | fn foo() { | ||
519 | let a<|> = { 10 }; | ||
520 | let b = a * 10; | ||
521 | let c = a as usize; | ||
522 | }", | ||
523 | " | ||
524 | fn foo() { | ||
525 | <|>let b = { 10 } * 10; | ||
526 | let c = { 10 } as usize; | ||
527 | }", | ||
528 | ); | ||
529 | } | ||
530 | |||
531 | #[test] | ||
532 | fn test_used_in_different_expr1() { | ||
533 | check_assist( | ||
534 | inline_local_varialbe, | ||
535 | " | ||
536 | fn foo() { | ||
537 | let a<|> = 10 + 20; | ||
538 | let b = a * 10; | ||
539 | let c = (a, 20); | ||
540 | let d = [a, 10]; | ||
541 | let e = (a); | ||
542 | }", | ||
543 | " | ||
544 | fn foo() { | ||
545 | <|>let b = (10 + 20) * 10; | ||
546 | let c = (10 + 20, 20); | ||
547 | let d = [10 + 20, 10]; | ||
548 | let e = (10 + 20); | ||
549 | }", | ||
550 | ); | ||
551 | } | ||
552 | |||
553 | #[test] | ||
554 | fn test_used_in_for_expr() { | ||
555 | check_assist( | ||
556 | inline_local_varialbe, | ||
557 | " | ||
558 | fn foo() { | ||
559 | let a<|> = vec![10, 20]; | ||
560 | for i in a {} | ||
561 | }", | ||
562 | " | ||
563 | fn foo() { | ||
564 | <|>for i in vec![10, 20] {} | ||
565 | }", | ||
566 | ); | ||
567 | } | ||
568 | |||
569 | #[test] | ||
570 | fn test_used_in_while_expr() { | ||
571 | check_assist( | ||
572 | inline_local_varialbe, | ||
573 | " | ||
574 | fn foo() { | ||
575 | let a<|> = 1 > 0; | ||
576 | while a {} | ||
577 | }", | ||
578 | " | ||
579 | fn foo() { | ||
580 | <|>while 1 > 0 {} | ||
581 | }", | ||
582 | ); | ||
583 | } | ||
584 | |||
585 | #[test] | ||
586 | fn test_used_in_break_expr() { | ||
587 | check_assist( | ||
588 | inline_local_varialbe, | ||
589 | " | ||
590 | fn foo() { | ||
591 | let a<|> = 1 + 1; | ||
592 | loop { | ||
593 | break a; | ||
594 | } | ||
595 | }", | ||
596 | " | ||
597 | fn foo() { | ||
598 | <|>loop { | ||
599 | break 1 + 1; | ||
600 | } | ||
601 | }", | ||
602 | ); | ||
603 | } | ||
604 | |||
605 | #[test] | ||
606 | fn test_used_in_return_expr() { | ||
607 | check_assist( | ||
608 | inline_local_varialbe, | ||
609 | " | ||
610 | fn foo() { | ||
611 | let a<|> = 1 > 0; | ||
612 | return a; | ||
613 | }", | ||
614 | " | ||
615 | fn foo() { | ||
616 | <|>return 1 > 0; | ||
617 | }", | ||
618 | ); | ||
619 | } | ||
620 | |||
621 | #[test] | ||
622 | fn test_used_in_match_expr() { | ||
623 | check_assist( | ||
624 | inline_local_varialbe, | ||
625 | " | ||
626 | fn foo() { | ||
627 | let a<|> = 1 > 0; | ||
628 | match a {} | ||
629 | }", | ||
630 | " | ||
631 | fn foo() { | ||
632 | <|>match 1 > 0 {} | ||
633 | }", | ||
634 | ); | ||
635 | } | ||
636 | } | ||
diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/assists/introduce_variable.rs new file mode 100644 index 000000000..470ffe120 --- /dev/null +++ b/crates/ra_assists/src/assists/introduce_variable.rs | |||
@@ -0,0 +1,516 @@ | |||
1 | use format_buf::format; | ||
2 | use hir::db::HirDatabase; | ||
3 | use ra_syntax::{ | ||
4 | ast::{self, AstNode}, | ||
5 | SyntaxKind::{ | ||
6 | BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, | ||
7 | WHITESPACE, | ||
8 | }, | ||
9 | SyntaxNode, TextUnit, | ||
10 | }; | ||
11 | use test_utils::tested_by; | ||
12 | |||
13 | use crate::{Assist, AssistCtx, AssistId}; | ||
14 | |||
15 | pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
16 | if ctx.frange.range.is_empty() { | ||
17 | return None; | ||
18 | } | ||
19 | let node = ctx.covering_element(); | ||
20 | if node.kind() == COMMENT { | ||
21 | tested_by!(introduce_var_in_comment_is_not_applicable); | ||
22 | return None; | ||
23 | } | ||
24 | let expr = node.ancestors().find_map(valid_target_expr)?; | ||
25 | let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?; | ||
26 | let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone(); | ||
27 | if indent.kind() != WHITESPACE { | ||
28 | return None; | ||
29 | } | ||
30 | ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| { | ||
31 | let mut buf = String::new(); | ||
32 | |||
33 | let cursor_offset = if wrap_in_block { | ||
34 | buf.push_str("{ let var_name = "); | ||
35 | TextUnit::of_str("{ let ") | ||
36 | } else { | ||
37 | buf.push_str("let var_name = "); | ||
38 | TextUnit::of_str("let ") | ||
39 | }; | ||
40 | format!(buf, "{}", expr.syntax()); | ||
41 | let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); | ||
42 | let is_full_stmt = if let Some(expr_stmt) = &full_stmt { | ||
43 | Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) | ||
44 | } else { | ||
45 | false | ||
46 | }; | ||
47 | if is_full_stmt { | ||
48 | tested_by!(test_introduce_var_expr_stmt); | ||
49 | if !full_stmt.unwrap().has_semi() { | ||
50 | buf.push_str(";"); | ||
51 | } | ||
52 | edit.replace(expr.syntax().text_range(), buf); | ||
53 | } else { | ||
54 | buf.push_str(";"); | ||
55 | |||
56 | // We want to maintain the indent level, | ||
57 | // but we do not want to duplicate possible | ||
58 | // extra newlines in the indent block | ||
59 | let text = indent.text(); | ||
60 | if text.starts_with('\n') { | ||
61 | buf.push_str("\n"); | ||
62 | buf.push_str(text.trim_start_matches('\n')); | ||
63 | } else { | ||
64 | buf.push_str(text); | ||
65 | } | ||
66 | |||
67 | edit.target(expr.syntax().text_range()); | ||
68 | edit.replace(expr.syntax().text_range(), "var_name".to_string()); | ||
69 | edit.insert(anchor_stmt.text_range().start(), buf); | ||
70 | if wrap_in_block { | ||
71 | edit.insert(anchor_stmt.text_range().end(), " }"); | ||
72 | } | ||
73 | } | ||
74 | edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset); | ||
75 | }); | ||
76 | |||
77 | ctx.build() | ||
78 | } | ||
79 | |||
80 | /// Check whether the node is a valid expression which can be extracted to a variable. | ||
81 | /// In general that's true for any expression, but in some cases that would produce invalid code. | ||
82 | fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> { | ||
83 | match node.kind() { | ||
84 | PATH_EXPR | LOOP_EXPR => None, | ||
85 | BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), | ||
86 | RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), | ||
87 | BLOCK_EXPR => { | ||
88 | ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from) | ||
89 | } | ||
90 | _ => ast::Expr::cast(node), | ||
91 | } | ||
92 | } | ||
93 | |||
94 | /// Returns the syntax node which will follow the freshly introduced var | ||
95 | /// and a boolean indicating whether we have to wrap it within a { } block | ||
96 | /// to produce correct code. | ||
97 | /// It can be a statement, the last in a block expression or a wanna be block | ||
98 | /// expression like a lambda or match arm. | ||
99 | fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { | ||
100 | expr.syntax().ancestors().find_map(|node| { | ||
101 | if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) { | ||
102 | if expr.syntax() == &node { | ||
103 | tested_by!(test_introduce_var_last_expr); | ||
104 | return Some((node, false)); | ||
105 | } | ||
106 | } | ||
107 | |||
108 | if let Some(parent) = node.parent() { | ||
109 | if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { | ||
110 | return Some((node, true)); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | if ast::Stmt::cast(node.clone()).is_some() { | ||
115 | return Some((node, false)); | ||
116 | } | ||
117 | |||
118 | None | ||
119 | }) | ||
120 | } | ||
121 | |||
122 | #[cfg(test)] | ||
123 | mod tests { | ||
124 | use test_utils::covers; | ||
125 | |||
126 | use crate::helpers::{ | ||
127 | check_assist_range, check_assist_range_not_applicable, check_assist_range_target, | ||
128 | }; | ||
129 | |||
130 | use super::*; | ||
131 | |||
132 | #[test] | ||
133 | fn test_introduce_var_simple() { | ||
134 | check_assist_range( | ||
135 | introduce_variable, | ||
136 | " | ||
137 | fn foo() { | ||
138 | foo(<|>1 + 1<|>); | ||
139 | }", | ||
140 | " | ||
141 | fn foo() { | ||
142 | let <|>var_name = 1 + 1; | ||
143 | foo(var_name); | ||
144 | }", | ||
145 | ); | ||
146 | } | ||
147 | |||
148 | #[test] | ||
149 | fn introduce_var_in_comment_is_not_applicable() { | ||
150 | covers!(introduce_var_in_comment_is_not_applicable); | ||
151 | check_assist_range_not_applicable( | ||
152 | introduce_variable, | ||
153 | "fn main() { 1 + /* <|>comment<|> */ 1; }", | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | #[test] | ||
158 | fn test_introduce_var_expr_stmt() { | ||
159 | covers!(test_introduce_var_expr_stmt); | ||
160 | check_assist_range( | ||
161 | introduce_variable, | ||
162 | " | ||
163 | fn foo() { | ||
164 | <|>1 + 1<|>; | ||
165 | }", | ||
166 | " | ||
167 | fn foo() { | ||
168 | let <|>var_name = 1 + 1; | ||
169 | }", | ||
170 | ); | ||
171 | check_assist_range( | ||
172 | introduce_variable, | ||
173 | " | ||
174 | fn foo() { | ||
175 | <|>{ let x = 0; x }<|> | ||
176 | something_else(); | ||
177 | }", | ||
178 | " | ||
179 | fn foo() { | ||
180 | let <|>var_name = { let x = 0; x }; | ||
181 | something_else(); | ||
182 | }", | ||
183 | ); | ||
184 | } | ||
185 | |||
186 | #[test] | ||
187 | fn test_introduce_var_part_of_expr_stmt() { | ||
188 | check_assist_range( | ||
189 | introduce_variable, | ||
190 | " | ||
191 | fn foo() { | ||
192 | <|>1<|> + 1; | ||
193 | }", | ||
194 | " | ||
195 | fn foo() { | ||
196 | let <|>var_name = 1; | ||
197 | var_name + 1; | ||
198 | }", | ||
199 | ); | ||
200 | } | ||
201 | |||
202 | #[test] | ||
203 | fn test_introduce_var_last_expr() { | ||
204 | covers!(test_introduce_var_last_expr); | ||
205 | check_assist_range( | ||
206 | introduce_variable, | ||
207 | " | ||
208 | fn foo() { | ||
209 | bar(<|>1 + 1<|>) | ||
210 | }", | ||
211 | " | ||
212 | fn foo() { | ||
213 | let <|>var_name = 1 + 1; | ||
214 | bar(var_name) | ||
215 | }", | ||
216 | ); | ||
217 | check_assist_range( | ||
218 | introduce_variable, | ||
219 | " | ||
220 | fn foo() { | ||
221 | <|>bar(1 + 1)<|> | ||
222 | }", | ||
223 | " | ||
224 | fn foo() { | ||
225 | let <|>var_name = bar(1 + 1); | ||
226 | var_name | ||
227 | }", | ||
228 | ) | ||
229 | } | ||
230 | |||
231 | #[test] | ||
232 | fn test_introduce_var_in_match_arm_no_block() { | ||
233 | check_assist_range( | ||
234 | introduce_variable, | ||
235 | " | ||
236 | fn main() { | ||
237 | let x = true; | ||
238 | let tuple = match x { | ||
239 | true => (<|>2 + 2<|>, true) | ||
240 | _ => (0, false) | ||
241 | }; | ||
242 | } | ||
243 | ", | ||
244 | " | ||
245 | fn main() { | ||
246 | let x = true; | ||
247 | let tuple = match x { | ||
248 | true => { let <|>var_name = 2 + 2; (var_name, true) } | ||
249 | _ => (0, false) | ||
250 | }; | ||
251 | } | ||
252 | ", | ||
253 | ); | ||
254 | } | ||
255 | |||
256 | #[test] | ||
257 | fn test_introduce_var_in_match_arm_with_block() { | ||
258 | check_assist_range( | ||
259 | introduce_variable, | ||
260 | " | ||
261 | fn main() { | ||
262 | let x = true; | ||
263 | let tuple = match x { | ||
264 | true => { | ||
265 | let y = 1; | ||
266 | (<|>2 + y<|>, true) | ||
267 | } | ||
268 | _ => (0, false) | ||
269 | }; | ||
270 | } | ||
271 | ", | ||
272 | " | ||
273 | fn main() { | ||
274 | let x = true; | ||
275 | let tuple = match x { | ||
276 | true => { | ||
277 | let y = 1; | ||
278 | let <|>var_name = 2 + y; | ||
279 | (var_name, true) | ||
280 | } | ||
281 | _ => (0, false) | ||
282 | }; | ||
283 | } | ||
284 | ", | ||
285 | ); | ||
286 | } | ||
287 | |||
288 | #[test] | ||
289 | fn test_introduce_var_in_closure_no_block() { | ||
290 | check_assist_range( | ||
291 | introduce_variable, | ||
292 | " | ||
293 | fn main() { | ||
294 | let lambda = |x: u32| <|>x * 2<|>; | ||
295 | } | ||
296 | ", | ||
297 | " | ||
298 | fn main() { | ||
299 | let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; | ||
300 | } | ||
301 | ", | ||
302 | ); | ||
303 | } | ||
304 | |||
305 | #[test] | ||
306 | fn test_introduce_var_in_closure_with_block() { | ||
307 | check_assist_range( | ||
308 | introduce_variable, | ||
309 | " | ||
310 | fn main() { | ||
311 | let lambda = |x: u32| { <|>x * 2<|> }; | ||
312 | } | ||
313 | ", | ||
314 | " | ||
315 | fn main() { | ||
316 | let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; | ||
317 | } | ||
318 | ", | ||
319 | ); | ||
320 | } | ||
321 | |||
322 | #[test] | ||
323 | fn test_introduce_var_path_simple() { | ||
324 | check_assist_range( | ||
325 | introduce_variable, | ||
326 | " | ||
327 | fn main() { | ||
328 | let o = <|>Some(true)<|>; | ||
329 | } | ||
330 | ", | ||
331 | " | ||
332 | fn main() { | ||
333 | let <|>var_name = Some(true); | ||
334 | let o = var_name; | ||
335 | } | ||
336 | ", | ||
337 | ); | ||
338 | } | ||
339 | |||
340 | #[test] | ||
341 | fn test_introduce_var_path_method() { | ||
342 | check_assist_range( | ||
343 | introduce_variable, | ||
344 | " | ||
345 | fn main() { | ||
346 | let v = <|>bar.foo()<|>; | ||
347 | } | ||
348 | ", | ||
349 | " | ||
350 | fn main() { | ||
351 | let <|>var_name = bar.foo(); | ||
352 | let v = var_name; | ||
353 | } | ||
354 | ", | ||
355 | ); | ||
356 | } | ||
357 | |||
358 | #[test] | ||
359 | fn test_introduce_var_return() { | ||
360 | check_assist_range( | ||
361 | introduce_variable, | ||
362 | " | ||
363 | fn foo() -> u32 { | ||
364 | <|>return 2 + 2<|>; | ||
365 | } | ||
366 | ", | ||
367 | " | ||
368 | fn foo() -> u32 { | ||
369 | let <|>var_name = 2 + 2; | ||
370 | return var_name; | ||
371 | } | ||
372 | ", | ||
373 | ); | ||
374 | } | ||
375 | |||
376 | #[test] | ||
377 | fn test_introduce_var_does_not_add_extra_whitespace() { | ||
378 | check_assist_range( | ||
379 | introduce_variable, | ||
380 | " | ||
381 | fn foo() -> u32 { | ||
382 | |||
383 | |||
384 | <|>return 2 + 2<|>; | ||
385 | } | ||
386 | ", | ||
387 | " | ||
388 | fn foo() -> u32 { | ||
389 | |||
390 | |||
391 | let <|>var_name = 2 + 2; | ||
392 | return var_name; | ||
393 | } | ||
394 | ", | ||
395 | ); | ||
396 | |||
397 | check_assist_range( | ||
398 | introduce_variable, | ||
399 | " | ||
400 | fn foo() -> u32 { | ||
401 | |||
402 | <|>return 2 + 2<|>; | ||
403 | } | ||
404 | ", | ||
405 | " | ||
406 | fn foo() -> u32 { | ||
407 | |||
408 | let <|>var_name = 2 + 2; | ||
409 | return var_name; | ||
410 | } | ||
411 | ", | ||
412 | ); | ||
413 | |||
414 | check_assist_range( | ||
415 | introduce_variable, | ||
416 | " | ||
417 | fn foo() -> u32 { | ||
418 | let foo = 1; | ||
419 | |||
420 | // bar | ||
421 | |||
422 | |||
423 | <|>return 2 + 2<|>; | ||
424 | } | ||
425 | ", | ||
426 | " | ||
427 | fn foo() -> u32 { | ||
428 | let foo = 1; | ||
429 | |||
430 | // bar | ||
431 | |||
432 | |||
433 | let <|>var_name = 2 + 2; | ||
434 | return var_name; | ||
435 | } | ||
436 | ", | ||
437 | ); | ||
438 | } | ||
439 | |||
440 | #[test] | ||
441 | fn test_introduce_var_break() { | ||
442 | check_assist_range( | ||
443 | introduce_variable, | ||
444 | " | ||
445 | fn main() { | ||
446 | let result = loop { | ||
447 | <|>break 2 + 2<|>; | ||
448 | }; | ||
449 | } | ||
450 | ", | ||
451 | " | ||
452 | fn main() { | ||
453 | let result = loop { | ||
454 | let <|>var_name = 2 + 2; | ||
455 | break var_name; | ||
456 | }; | ||
457 | } | ||
458 | ", | ||
459 | ); | ||
460 | } | ||
461 | |||
462 | #[test] | ||
463 | fn test_introduce_var_for_cast() { | ||
464 | check_assist_range( | ||
465 | introduce_variable, | ||
466 | " | ||
467 | fn main() { | ||
468 | let v = <|>0f32 as u32<|>; | ||
469 | } | ||
470 | ", | ||
471 | " | ||
472 | fn main() { | ||
473 | let <|>var_name = 0f32 as u32; | ||
474 | let v = var_name; | ||
475 | } | ||
476 | ", | ||
477 | ); | ||
478 | } | ||
479 | |||
480 | #[test] | ||
481 | fn test_introduce_var_for_return_not_applicable() { | ||
482 | check_assist_range_not_applicable(introduce_variable, "fn foo() { <|>return<|>; } "); | ||
483 | } | ||
484 | |||
485 | #[test] | ||
486 | fn test_introduce_var_for_break_not_applicable() { | ||
487 | check_assist_range_not_applicable( | ||
488 | introduce_variable, | ||
489 | "fn main() { loop { <|>break<|>; }; }", | ||
490 | ); | ||
491 | } | ||
492 | |||
493 | // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic | ||
494 | #[test] | ||
495 | fn introduce_var_target() { | ||
496 | check_assist_range_target( | ||
497 | introduce_variable, | ||
498 | "fn foo() -> u32 { <|>return 2 + 2<|>; }", | ||
499 | "2 + 2", | ||
500 | ); | ||
501 | |||
502 | check_assist_range_target( | ||
503 | introduce_variable, | ||
504 | " | ||
505 | fn main() { | ||
506 | let x = true; | ||
507 | let tuple = match x { | ||
508 | true => (<|>2 + 2<|>, true) | ||
509 | _ => (0, false) | ||
510 | }; | ||
511 | } | ||
512 | ", | ||
513 | "2 + 2", | ||
514 | ); | ||
515 | } | ||
516 | } | ||
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs new file mode 100644 index 000000000..3b6a99895 --- /dev/null +++ b/crates/ra_assists/src/assists/merge_match_arms.rs | |||
@@ -0,0 +1,188 @@ | |||
1 | use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit}; | ||
2 | use hir::db::HirDatabase; | ||
3 | use ra_syntax::ast::{AstNode, MatchArm}; | ||
4 | |||
5 | pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
6 | let current_arm = ctx.node_at_offset::<MatchArm>()?; | ||
7 | |||
8 | // We check if the following match arm matches this one. We could, but don't, | ||
9 | // compare to the previous match arm as well. | ||
10 | let next = current_arm.syntax().next_sibling(); | ||
11 | let next_arm = MatchArm::cast(next?)?; | ||
12 | |||
13 | // Don't try to handle arms with guards for now - can add support for this later | ||
14 | if current_arm.guard().is_some() || next_arm.guard().is_some() { | ||
15 | return None; | ||
16 | } | ||
17 | |||
18 | let current_expr = current_arm.expr()?; | ||
19 | let next_expr = next_arm.expr()?; | ||
20 | |||
21 | // Check for match arm equality by comparing lengths and then string contents | ||
22 | if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() { | ||
23 | return None; | ||
24 | } | ||
25 | if current_expr.syntax().text() != next_expr.syntax().text() { | ||
26 | return None; | ||
27 | } | ||
28 | |||
29 | let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start(); | ||
30 | |||
31 | ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| { | ||
32 | fn contains_placeholder(a: &MatchArm) -> bool { | ||
33 | a.pats().any(|x| match x { | ||
34 | ra_syntax::ast::Pat::PlaceholderPat(..) => true, | ||
35 | _ => false, | ||
36 | }) | ||
37 | } | ||
38 | |||
39 | let pats = if contains_placeholder(¤t_arm) || contains_placeholder(&next_arm) { | ||
40 | "_".into() | ||
41 | } else { | ||
42 | let ps: Vec<String> = current_arm | ||
43 | .pats() | ||
44 | .map(|x| x.syntax().to_string()) | ||
45 | .chain(next_arm.pats().map(|x| x.syntax().to_string())) | ||
46 | .collect(); | ||
47 | ps.join(" | ") | ||
48 | }; | ||
49 | |||
50 | let arm = format!("{} => {}", pats, current_expr.syntax().text()); | ||
51 | let offset = TextUnit::from_usize(arm.len()) - cursor_to_end; | ||
52 | |||
53 | let start = current_arm.syntax().text_range().start(); | ||
54 | let end = next_arm.syntax().text_range().end(); | ||
55 | |||
56 | edit.target(current_arm.syntax().text_range()); | ||
57 | edit.replace(TextRange::from_to(start, end), arm); | ||
58 | edit.set_cursor(start + offset); | ||
59 | }); | ||
60 | |||
61 | ctx.build() | ||
62 | } | ||
63 | |||
64 | #[cfg(test)] | ||
65 | mod tests { | ||
66 | use super::merge_match_arms; | ||
67 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
68 | |||
69 | #[test] | ||
70 | fn merge_match_arms_single_patterns() { | ||
71 | check_assist( | ||
72 | merge_match_arms, | ||
73 | r#" | ||
74 | #[derive(Debug)] | ||
75 | enum X { A, B, C } | ||
76 | |||
77 | fn main() { | ||
78 | let x = X::A; | ||
79 | let y = match x { | ||
80 | X::A => { 1i32<|> } | ||
81 | X::B => { 1i32 } | ||
82 | X::C => { 2i32 } | ||
83 | } | ||
84 | } | ||
85 | "#, | ||
86 | r#" | ||
87 | #[derive(Debug)] | ||
88 | enum X { A, B, C } | ||
89 | |||
90 | fn main() { | ||
91 | let x = X::A; | ||
92 | let y = match x { | ||
93 | X::A | X::B => { 1i32<|> } | ||
94 | X::C => { 2i32 } | ||
95 | } | ||
96 | } | ||
97 | "#, | ||
98 | ); | ||
99 | } | ||
100 | |||
101 | #[test] | ||
102 | fn merge_match_arms_multiple_patterns() { | ||
103 | check_assist( | ||
104 | merge_match_arms, | ||
105 | r#" | ||
106 | #[derive(Debug)] | ||
107 | enum X { A, B, C, D, E } | ||
108 | |||
109 | fn main() { | ||
110 | let x = X::A; | ||
111 | let y = match x { | ||
112 | X::A | X::B => {<|> 1i32 }, | ||
113 | X::C | X::D => { 1i32 }, | ||
114 | X::E => { 2i32 }, | ||
115 | } | ||
116 | } | ||
117 | "#, | ||
118 | r#" | ||
119 | #[derive(Debug)] | ||
120 | enum X { A, B, C, D, E } | ||
121 | |||
122 | fn main() { | ||
123 | let x = X::A; | ||
124 | let y = match x { | ||
125 | X::A | X::B | X::C | X::D => {<|> 1i32 }, | ||
126 | X::E => { 2i32 }, | ||
127 | } | ||
128 | } | ||
129 | "#, | ||
130 | ); | ||
131 | } | ||
132 | |||
133 | #[test] | ||
134 | fn merge_match_arms_placeholder_pattern() { | ||
135 | check_assist( | ||
136 | merge_match_arms, | ||
137 | r#" | ||
138 | #[derive(Debug)] | ||
139 | enum X { A, B, C, D, E } | ||
140 | |||
141 | fn main() { | ||
142 | let x = X::A; | ||
143 | let y = match x { | ||
144 | X::A => { 1i32 }, | ||
145 | X::B => { 2i<|>32 }, | ||
146 | _ => { 2i32 } | ||
147 | } | ||
148 | } | ||
149 | "#, | ||
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 => { 1i32 }, | ||
158 | _ => { 2i<|>32 } | ||
159 | } | ||
160 | } | ||
161 | "#, | ||
162 | ); | ||
163 | } | ||
164 | |||
165 | #[test] | ||
166 | fn merge_match_arms_rejects_guards() { | ||
167 | check_assist_not_applicable( | ||
168 | merge_match_arms, | ||
169 | r#" | ||
170 | #[derive(Debug)] | ||
171 | enum X { | ||
172 | A(i32), | ||
173 | B, | ||
174 | C | ||
175 | } | ||
176 | |||
177 | fn main() { | ||
178 | let x = X::A; | ||
179 | let y = match x { | ||
180 | X::A(a) if a > 5 => { <|>1i32 }, | ||
181 | X::B => { 1i32 }, | ||
182 | X::C => { 2i32 } | ||
183 | } | ||
184 | } | ||
185 | "#, | ||
186 | ); | ||
187 | } | ||
188 | } | ||
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs new file mode 100644 index 000000000..6fd2fb72b --- /dev/null +++ b/crates/ra_assists/src/assists/move_bounds.rs | |||
@@ -0,0 +1,134 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, NameOwner, TypeBoundsOwner}, | ||
4 | SyntaxElement, | ||
5 | SyntaxKind::*, | ||
6 | TextRange, | ||
7 | }; | ||
8 | |||
9 | use crate::{ast_builder::Make, Assist, AssistCtx, AssistId}; | ||
10 | |||
11 | pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
12 | let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?; | ||
13 | |||
14 | let mut type_params = type_param_list.type_params(); | ||
15 | if type_params.all(|p| p.type_bound_list().is_none()) { | ||
16 | return None; | ||
17 | } | ||
18 | |||
19 | let parent = type_param_list.syntax().parent()?; | ||
20 | if parent.children_with_tokens().find(|it| it.kind() == WHERE_CLAUSE).is_some() { | ||
21 | return None; | ||
22 | } | ||
23 | |||
24 | let anchor: SyntaxElement = match parent.kind() { | ||
25 | FN_DEF => ast::FnDef::cast(parent)?.body()?.syntax().clone().into(), | ||
26 | TRAIT_DEF => ast::TraitDef::cast(parent)?.item_list()?.syntax().clone().into(), | ||
27 | IMPL_BLOCK => ast::ImplBlock::cast(parent)?.item_list()?.syntax().clone().into(), | ||
28 | ENUM_DEF => ast::EnumDef::cast(parent)?.variant_list()?.syntax().clone().into(), | ||
29 | STRUCT_DEF => parent | ||
30 | .children_with_tokens() | ||
31 | .find(|it| it.kind() == RECORD_FIELD_DEF_LIST || it.kind() == SEMI)?, | ||
32 | _ => return None, | ||
33 | }; | ||
34 | |||
35 | ctx.add_action( | ||
36 | AssistId("move_bounds_to_where_clause"), | ||
37 | "move_bounds_to_where_clause", | ||
38 | |edit| { | ||
39 | let type_params = type_param_list.type_params().collect::<Vec<_>>(); | ||
40 | |||
41 | for param in &type_params { | ||
42 | if let Some(bounds) = param.type_bound_list() { | ||
43 | let colon = param | ||
44 | .syntax() | ||
45 | .children_with_tokens() | ||
46 | .find(|it| it.kind() == COLON) | ||
47 | .unwrap(); | ||
48 | let start = colon.text_range().start(); | ||
49 | let end = bounds.syntax().text_range().end(); | ||
50 | edit.delete(TextRange::from_to(start, end)); | ||
51 | } | ||
52 | } | ||
53 | |||
54 | let predicates = type_params.iter().filter_map(build_predicate); | ||
55 | let where_clause = Make::<ast::WhereClause>::from_predicates(predicates); | ||
56 | |||
57 | let to_insert = match anchor.prev_sibling_or_token() { | ||
58 | Some(ref elem) if elem.kind() == WHITESPACE => { | ||
59 | format!("{} ", where_clause.syntax()) | ||
60 | } | ||
61 | _ => format!(" {}", where_clause.syntax()), | ||
62 | }; | ||
63 | edit.insert(anchor.text_range().start(), to_insert); | ||
64 | edit.target(type_param_list.syntax().text_range()); | ||
65 | }, | ||
66 | ); | ||
67 | |||
68 | ctx.build() | ||
69 | } | ||
70 | |||
71 | fn build_predicate(param: &ast::TypeParam) -> Option<ast::WherePred> { | ||
72 | let path = Make::<ast::Path>::from_name(param.name()?); | ||
73 | let predicate = Make::<ast::WherePred>::from(path, param.type_bound_list()?.bounds()); | ||
74 | Some(predicate) | ||
75 | } | ||
76 | |||
77 | #[cfg(test)] | ||
78 | mod tests { | ||
79 | use super::*; | ||
80 | |||
81 | use crate::helpers::check_assist; | ||
82 | |||
83 | #[test] | ||
84 | fn move_bounds_to_where_clause_fn() { | ||
85 | check_assist( | ||
86 | move_bounds_to_where_clause, | ||
87 | r#" | ||
88 | fn foo<T: u32, <|>F: FnOnce(T) -> T>() {} | ||
89 | "#, | ||
90 | r#" | ||
91 | fn foo<T, <|>F>() where T: u32, F: FnOnce(T) -> T {} | ||
92 | "#, | ||
93 | ); | ||
94 | } | ||
95 | |||
96 | #[test] | ||
97 | fn move_bounds_to_where_clause_impl() { | ||
98 | check_assist( | ||
99 | move_bounds_to_where_clause, | ||
100 | r#" | ||
101 | impl<U: u32, <|>T> A<U, T> {} | ||
102 | "#, | ||
103 | r#" | ||
104 | impl<U, <|>T> A<U, T> where U: u32 {} | ||
105 | "#, | ||
106 | ); | ||
107 | } | ||
108 | |||
109 | #[test] | ||
110 | fn move_bounds_to_where_clause_struct() { | ||
111 | check_assist( | ||
112 | move_bounds_to_where_clause, | ||
113 | r#" | ||
114 | struct A<<|>T: Iterator<Item = u32>> {} | ||
115 | "#, | ||
116 | r#" | ||
117 | struct A<<|>T> where T: Iterator<Item = u32> {} | ||
118 | "#, | ||
119 | ); | ||
120 | } | ||
121 | |||
122 | #[test] | ||
123 | fn move_bounds_to_where_clause_tuple_struct() { | ||
124 | check_assist( | ||
125 | move_bounds_to_where_clause, | ||
126 | r#" | ||
127 | struct Pair<<|>T: u32>(T, T); | ||
128 | "#, | ||
129 | r#" | ||
130 | struct Pair<<|>T>(T, T) where T: u32; | ||
131 | "#, | ||
132 | ); | ||
133 | } | ||
134 | } | ||
diff --git a/crates/ra_assists/src/assists/move_guard.rs b/crates/ra_assists/src/assists/move_guard.rs new file mode 100644 index 000000000..699221e33 --- /dev/null +++ b/crates/ra_assists/src/assists/move_guard.rs | |||
@@ -0,0 +1,261 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | ast, | ||
4 | ast::{AstNode, AstToken, IfExpr, MatchArm}, | ||
5 | TextUnit, | ||
6 | }; | ||
7 | |||
8 | use crate::{Assist, AssistCtx, AssistId}; | ||
9 | |||
10 | pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
11 | let match_arm = ctx.node_at_offset::<MatchArm>()?; | ||
12 | let guard = match_arm.guard()?; | ||
13 | let space_before_guard = guard.syntax().prev_sibling_or_token(); | ||
14 | |||
15 | let guard_conditions = guard.expr()?; | ||
16 | let arm_expr = match_arm.expr()?; | ||
17 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); | ||
18 | |||
19 | ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| { | ||
20 | edit.target(guard.syntax().text_range()); | ||
21 | let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { | ||
22 | Some(tok) => { | ||
23 | if let Some(_) = ast::Whitespace::cast(tok.clone()) { | ||
24 | let ele = tok.text_range(); | ||
25 | edit.delete(ele); | ||
26 | ele.len() | ||
27 | } else { | ||
28 | TextUnit::from(0) | ||
29 | } | ||
30 | } | ||
31 | _ => TextUnit::from(0), | ||
32 | }; | ||
33 | |||
34 | edit.delete(guard.syntax().text_range()); | ||
35 | edit.replace_node_and_indent(arm_expr.syntax(), buf); | ||
36 | edit.set_cursor( | ||
37 | arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, | ||
38 | ); | ||
39 | }); | ||
40 | ctx.build() | ||
41 | } | ||
42 | |||
43 | pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
44 | let match_arm: MatchArm = ctx.node_at_offset::<MatchArm>()?; | ||
45 | let last_match_pat = match_arm.pats().last()?; | ||
46 | |||
47 | let arm_body = match_arm.expr()?; | ||
48 | let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?; | ||
49 | let cond = if_expr.condition()?; | ||
50 | let then_block = if_expr.then_branch()?; | ||
51 | |||
52 | // Not support if with else branch | ||
53 | if let Some(_) = if_expr.else_branch() { | ||
54 | return None; | ||
55 | } | ||
56 | // Not support moving if let to arm guard | ||
57 | if let Some(_) = cond.pat() { | ||
58 | return None; | ||
59 | } | ||
60 | |||
61 | let buf = format!(" if {}", cond.syntax().text()); | ||
62 | |||
63 | ctx.add_action( | ||
64 | AssistId("move_arm_cond_to_match_guard"), | ||
65 | "move condition to match guard", | ||
66 | |edit| { | ||
67 | edit.target(if_expr.syntax().text_range()); | ||
68 | let then_only_expr = then_block.block().and_then(|it| it.statements().next()).is_none(); | ||
69 | |||
70 | match &then_block.block().and_then(|it| it.expr()) { | ||
71 | Some(then_expr) if then_only_expr => { | ||
72 | edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text()) | ||
73 | } | ||
74 | _ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()), | ||
75 | } | ||
76 | |||
77 | edit.insert(last_match_pat.syntax().text_range().end(), buf); | ||
78 | edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1)); | ||
79 | }, | ||
80 | ); | ||
81 | ctx.build() | ||
82 | } | ||
83 | |||
84 | #[cfg(test)] | ||
85 | mod tests { | ||
86 | use super::*; | ||
87 | |||
88 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
89 | |||
90 | #[test] | ||
91 | fn move_guard_to_arm_body_target() { | ||
92 | check_assist_target( | ||
93 | move_guard_to_arm_body, | ||
94 | r#" | ||
95 | fn f() { | ||
96 | let t = 'a'; | ||
97 | let chars = "abcd"; | ||
98 | match t { | ||
99 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
100 | _ => true | ||
101 | } | ||
102 | } | ||
103 | "#, | ||
104 | r#"if chars.clone().next() == Some('\n')"#, | ||
105 | ); | ||
106 | } | ||
107 | |||
108 | #[test] | ||
109 | fn move_guard_to_arm_body_works() { | ||
110 | check_assist( | ||
111 | move_guard_to_arm_body, | ||
112 | r#" | ||
113 | fn f() { | ||
114 | let t = 'a'; | ||
115 | let chars = "abcd"; | ||
116 | match t { | ||
117 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
118 | _ => true | ||
119 | } | ||
120 | } | ||
121 | "#, | ||
122 | r#" | ||
123 | fn f() { | ||
124 | let t = 'a'; | ||
125 | let chars = "abcd"; | ||
126 | match t { | ||
127 | '\r' => if chars.clone().next() == Some('\n') { <|>false }, | ||
128 | _ => true | ||
129 | } | ||
130 | } | ||
131 | "#, | ||
132 | ); | ||
133 | } | ||
134 | |||
135 | #[test] | ||
136 | fn move_guard_to_arm_body_works_complex_match() { | ||
137 | check_assist( | ||
138 | move_guard_to_arm_body, | ||
139 | r#" | ||
140 | fn f() { | ||
141 | match x { | ||
142 | <|>y @ 4 | y @ 5 if y > 5 => true, | ||
143 | _ => false | ||
144 | } | ||
145 | } | ||
146 | "#, | ||
147 | r#" | ||
148 | fn f() { | ||
149 | match x { | ||
150 | y @ 4 | y @ 5 => if y > 5 { <|>true }, | ||
151 | _ => false | ||
152 | } | ||
153 | } | ||
154 | "#, | ||
155 | ); | ||
156 | } | ||
157 | |||
158 | #[test] | ||
159 | fn move_arm_cond_to_match_guard_works() { | ||
160 | check_assist( | ||
161 | move_arm_cond_to_match_guard, | ||
162 | r#" | ||
163 | fn f() { | ||
164 | let t = 'a'; | ||
165 | let chars = "abcd"; | ||
166 | match t { | ||
167 | '\r' => if chars.clone().next() == Some('\n') { <|>false }, | ||
168 | _ => true | ||
169 | } | ||
170 | } | ||
171 | "#, | ||
172 | r#" | ||
173 | fn f() { | ||
174 | let t = 'a'; | ||
175 | let chars = "abcd"; | ||
176 | match t { | ||
177 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
178 | _ => true | ||
179 | } | ||
180 | } | ||
181 | "#, | ||
182 | ); | ||
183 | } | ||
184 | |||
185 | #[test] | ||
186 | fn move_arm_cond_to_match_guard_if_let_not_works() { | ||
187 | check_assist_not_applicable( | ||
188 | move_arm_cond_to_match_guard, | ||
189 | r#" | ||
190 | fn f() { | ||
191 | let t = 'a'; | ||
192 | let chars = "abcd"; | ||
193 | match t { | ||
194 | '\r' => if let Some(_) = chars.clone().next() { <|>false }, | ||
195 | _ => true | ||
196 | } | ||
197 | } | ||
198 | "#, | ||
199 | ); | ||
200 | } | ||
201 | |||
202 | #[test] | ||
203 | fn move_arm_cond_to_match_guard_if_empty_body_works() { | ||
204 | check_assist( | ||
205 | move_arm_cond_to_match_guard, | ||
206 | r#" | ||
207 | fn f() { | ||
208 | let t = 'a'; | ||
209 | let chars = "abcd"; | ||
210 | match t { | ||
211 | '\r' => if chars.clone().next().is_some() { <|> }, | ||
212 | _ => true | ||
213 | } | ||
214 | } | ||
215 | "#, | ||
216 | r#" | ||
217 | fn f() { | ||
218 | let t = 'a'; | ||
219 | let chars = "abcd"; | ||
220 | match t { | ||
221 | '\r' <|>if chars.clone().next().is_some() => { }, | ||
222 | _ => true | ||
223 | } | ||
224 | } | ||
225 | "#, | ||
226 | ); | ||
227 | } | ||
228 | |||
229 | #[test] | ||
230 | fn move_arm_cond_to_match_guard_if_multiline_body_works() { | ||
231 | check_assist( | ||
232 | move_arm_cond_to_match_guard, | ||
233 | r#" | ||
234 | fn f() { | ||
235 | let mut t = 'a'; | ||
236 | let chars = "abcd"; | ||
237 | match t { | ||
238 | '\r' => if chars.clone().next().is_some() { | ||
239 | t = 'e';<|> | ||
240 | false | ||
241 | }, | ||
242 | _ => true | ||
243 | } | ||
244 | } | ||
245 | "#, | ||
246 | r#" | ||
247 | fn f() { | ||
248 | let mut t = 'a'; | ||
249 | let chars = "abcd"; | ||
250 | match t { | ||
251 | '\r' <|>if chars.clone().next().is_some() => { | ||
252 | t = 'e'; | ||
253 | false | ||
254 | }, | ||
255 | _ => true | ||
256 | } | ||
257 | } | ||
258 | "#, | ||
259 | ); | ||
260 | } | ||
261 | } | ||
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs new file mode 100644 index 000000000..965a64c98 --- /dev/null +++ b/crates/ra_assists/src/assists/raw_string.rs | |||
@@ -0,0 +1,370 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; | ||
3 | |||
4 | use crate::{Assist, AssistCtx, AssistId}; | ||
5 | |||
6 | pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
7 | let literal = ctx.node_at_offset::<Literal>()?; | ||
8 | if literal.token().kind() != ra_syntax::SyntaxKind::STRING { | ||
9 | return None; | ||
10 | } | ||
11 | ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { | ||
12 | edit.target(literal.syntax().text_range()); | ||
13 | edit.insert(literal.syntax().text_range().start(), "r"); | ||
14 | }); | ||
15 | ctx.build() | ||
16 | } | ||
17 | |||
18 | fn find_usual_string_range(s: &str) -> Option<TextRange> { | ||
19 | Some(TextRange::from_to( | ||
20 | TextUnit::from(s.find('"')? as u32), | ||
21 | TextUnit::from(s.rfind('"')? as u32), | ||
22 | )) | ||
23 | } | ||
24 | |||
25 | pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
26 | let literal = ctx.node_at_offset::<Literal>()?; | ||
27 | if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { | ||
28 | return None; | ||
29 | } | ||
30 | let token = literal.token(); | ||
31 | let text = token.text().as_str(); | ||
32 | let usual_string_range = find_usual_string_range(text)?; | ||
33 | ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| { | ||
34 | edit.target(literal.syntax().text_range()); | ||
35 | // parse inside string to escape `"` | ||
36 | let start_of_inside = usual_string_range.start().to_usize() + 1; | ||
37 | let end_of_inside = usual_string_range.end().to_usize(); | ||
38 | let inside_str = &text[start_of_inside..end_of_inside]; | ||
39 | let escaped = inside_str.escape_default().to_string(); | ||
40 | edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped)); | ||
41 | }); | ||
42 | ctx.build() | ||
43 | } | ||
44 | |||
45 | pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
46 | let literal = ctx.node_at_offset::<Literal>()?; | ||
47 | if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { | ||
48 | return None; | ||
49 | } | ||
50 | ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| { | ||
51 | edit.target(literal.syntax().text_range()); | ||
52 | edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#"); | ||
53 | edit.insert(literal.syntax().text_range().end(), "#"); | ||
54 | }); | ||
55 | ctx.build() | ||
56 | } | ||
57 | |||
58 | pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
59 | let literal = ctx.node_at_offset::<Literal>()?; | ||
60 | if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING { | ||
61 | return None; | ||
62 | } | ||
63 | let token = literal.token(); | ||
64 | let text = token.text().as_str(); | ||
65 | if text.starts_with("r\"") { | ||
66 | // no hash to remove | ||
67 | return None; | ||
68 | } | ||
69 | ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| { | ||
70 | edit.target(literal.syntax().text_range()); | ||
71 | let result = &text[2..text.len() - 1]; | ||
72 | let result = if result.starts_with("\"") { | ||
73 | // no more hash, escape | ||
74 | let internal_str = &result[1..result.len() - 1]; | ||
75 | format!("\"{}\"", internal_str.escape_default().to_string()) | ||
76 | } else { | ||
77 | result.to_owned() | ||
78 | }; | ||
79 | edit.replace(literal.syntax().text_range(), format!("r{}", result)); | ||
80 | }); | ||
81 | ctx.build() | ||
82 | } | ||
83 | |||
84 | #[cfg(test)] | ||
85 | mod test { | ||
86 | use super::*; | ||
87 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
88 | |||
89 | #[test] | ||
90 | fn make_raw_string_target() { | ||
91 | check_assist_target( | ||
92 | make_raw_string, | ||
93 | r#" | ||
94 | fn f() { | ||
95 | let s = <|>"random string"; | ||
96 | } | ||
97 | "#, | ||
98 | r#""random string""#, | ||
99 | ); | ||
100 | } | ||
101 | |||
102 | #[test] | ||
103 | fn make_raw_string_works() { | ||
104 | check_assist( | ||
105 | make_raw_string, | ||
106 | r#" | ||
107 | fn f() { | ||
108 | let s = <|>"random string"; | ||
109 | } | ||
110 | "#, | ||
111 | r#" | ||
112 | fn f() { | ||
113 | let s = <|>r"random string"; | ||
114 | } | ||
115 | "#, | ||
116 | ) | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn make_raw_string_with_escaped_works() { | ||
121 | check_assist( | ||
122 | make_raw_string, | ||
123 | r#" | ||
124 | fn f() { | ||
125 | let s = <|>"random\nstring"; | ||
126 | } | ||
127 | "#, | ||
128 | r#" | ||
129 | fn f() { | ||
130 | let s = <|>r"random\nstring"; | ||
131 | } | ||
132 | "#, | ||
133 | ) | ||
134 | } | ||
135 | |||
136 | #[test] | ||
137 | fn make_raw_string_not_works() { | ||
138 | check_assist_not_applicable( | ||
139 | make_raw_string, | ||
140 | r#" | ||
141 | fn f() { | ||
142 | let s = <|>r"random string"; | ||
143 | } | ||
144 | "#, | ||
145 | ); | ||
146 | } | ||
147 | |||
148 | #[test] | ||
149 | fn add_hash_target() { | ||
150 | check_assist_target( | ||
151 | add_hash, | ||
152 | r#" | ||
153 | fn f() { | ||
154 | let s = <|>r"random string"; | ||
155 | } | ||
156 | "#, | ||
157 | r#"r"random string""#, | ||
158 | ); | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn add_hash_works() { | ||
163 | check_assist( | ||
164 | add_hash, | ||
165 | r#" | ||
166 | fn f() { | ||
167 | let s = <|>r"random string"; | ||
168 | } | ||
169 | "#, | ||
170 | r##" | ||
171 | fn f() { | ||
172 | let s = <|>r#"random string"#; | ||
173 | } | ||
174 | "##, | ||
175 | ) | ||
176 | } | ||
177 | |||
178 | #[test] | ||
179 | fn add_more_hash_works() { | ||
180 | check_assist( | ||
181 | add_hash, | ||
182 | r##" | ||
183 | fn f() { | ||
184 | let s = <|>r#"random"string"#; | ||
185 | } | ||
186 | "##, | ||
187 | r###" | ||
188 | fn f() { | ||
189 | let s = <|>r##"random"string"##; | ||
190 | } | ||
191 | "###, | ||
192 | ) | ||
193 | } | ||
194 | |||
195 | #[test] | ||
196 | fn add_hash_not_works() { | ||
197 | check_assist_not_applicable( | ||
198 | add_hash, | ||
199 | r#" | ||
200 | fn f() { | ||
201 | let s = <|>"random string"; | ||
202 | } | ||
203 | "#, | ||
204 | ); | ||
205 | } | ||
206 | |||
207 | #[test] | ||
208 | fn remove_hash_target() { | ||
209 | check_assist_target( | ||
210 | remove_hash, | ||
211 | r##" | ||
212 | fn f() { | ||
213 | let s = <|>r#"random string"#; | ||
214 | } | ||
215 | "##, | ||
216 | r##"r#"random string"#"##, | ||
217 | ); | ||
218 | } | ||
219 | |||
220 | #[test] | ||
221 | fn remove_hash_works() { | ||
222 | check_assist( | ||
223 | remove_hash, | ||
224 | r##" | ||
225 | fn f() { | ||
226 | let s = <|>r#"random string"#; | ||
227 | } | ||
228 | "##, | ||
229 | r#" | ||
230 | fn f() { | ||
231 | let s = <|>r"random string"; | ||
232 | } | ||
233 | "#, | ||
234 | ) | ||
235 | } | ||
236 | |||
237 | #[test] | ||
238 | fn remove_hash_with_quote_works() { | ||
239 | check_assist( | ||
240 | remove_hash, | ||
241 | r##" | ||
242 | fn f() { | ||
243 | let s = <|>r#"random"str"ing"#; | ||
244 | } | ||
245 | "##, | ||
246 | r#" | ||
247 | fn f() { | ||
248 | let s = <|>r"random\"str\"ing"; | ||
249 | } | ||
250 | "#, | ||
251 | ) | ||
252 | } | ||
253 | |||
254 | #[test] | ||
255 | fn remove_more_hash_works() { | ||
256 | check_assist( | ||
257 | remove_hash, | ||
258 | r###" | ||
259 | fn f() { | ||
260 | let s = <|>r##"random string"##; | ||
261 | } | ||
262 | "###, | ||
263 | r##" | ||
264 | fn f() { | ||
265 | let s = <|>r#"random string"#; | ||
266 | } | ||
267 | "##, | ||
268 | ) | ||
269 | } | ||
270 | |||
271 | #[test] | ||
272 | fn remove_hash_not_works() { | ||
273 | check_assist_not_applicable( | ||
274 | remove_hash, | ||
275 | r#" | ||
276 | fn f() { | ||
277 | let s = <|>"random string"; | ||
278 | } | ||
279 | "#, | ||
280 | ); | ||
281 | } | ||
282 | |||
283 | #[test] | ||
284 | fn remove_hash_no_hash_not_works() { | ||
285 | check_assist_not_applicable( | ||
286 | remove_hash, | ||
287 | r#" | ||
288 | fn f() { | ||
289 | let s = <|>r"random string"; | ||
290 | } | ||
291 | "#, | ||
292 | ); | ||
293 | } | ||
294 | |||
295 | #[test] | ||
296 | fn make_usual_string_target() { | ||
297 | check_assist_target( | ||
298 | make_usual_string, | ||
299 | r##" | ||
300 | fn f() { | ||
301 | let s = <|>r#"random string"#; | ||
302 | } | ||
303 | "##, | ||
304 | r##"r#"random string"#"##, | ||
305 | ); | ||
306 | } | ||
307 | |||
308 | #[test] | ||
309 | fn make_usual_string_works() { | ||
310 | check_assist( | ||
311 | make_usual_string, | ||
312 | r##" | ||
313 | fn f() { | ||
314 | let s = <|>r#"random string"#; | ||
315 | } | ||
316 | "##, | ||
317 | r#" | ||
318 | fn f() { | ||
319 | let s = <|>"random string"; | ||
320 | } | ||
321 | "#, | ||
322 | ) | ||
323 | } | ||
324 | |||
325 | #[test] | ||
326 | fn make_usual_string_with_quote_works() { | ||
327 | check_assist( | ||
328 | make_usual_string, | ||
329 | r##" | ||
330 | fn f() { | ||
331 | let s = <|>r#"random"str"ing"#; | ||
332 | } | ||
333 | "##, | ||
334 | r#" | ||
335 | fn f() { | ||
336 | let s = <|>"random\"str\"ing"; | ||
337 | } | ||
338 | "#, | ||
339 | ) | ||
340 | } | ||
341 | |||
342 | #[test] | ||
343 | fn make_usual_string_more_hash_works() { | ||
344 | check_assist( | ||
345 | make_usual_string, | ||
346 | r###" | ||
347 | fn f() { | ||
348 | let s = <|>r##"random string"##; | ||
349 | } | ||
350 | "###, | ||
351 | r##" | ||
352 | fn f() { | ||
353 | let s = <|>"random string"; | ||
354 | } | ||
355 | "##, | ||
356 | ) | ||
357 | } | ||
358 | |||
359 | #[test] | ||
360 | fn make_usual_string_not_works() { | ||
361 | check_assist_not_applicable( | ||
362 | make_usual_string, | ||
363 | r#" | ||
364 | fn f() { | ||
365 | let s = <|>"random string"; | ||
366 | } | ||
367 | "#, | ||
368 | ); | ||
369 | } | ||
370 | } | ||
diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/assists/remove_dbg.rs new file mode 100644 index 000000000..870133fda --- /dev/null +++ b/crates/ra_assists/src/assists/remove_dbg.rs | |||
@@ -0,0 +1,137 @@ | |||
1 | use crate::{Assist, AssistCtx, AssistId}; | ||
2 | use hir::db::HirDatabase; | ||
3 | use ra_syntax::{ | ||
4 | ast::{self, AstNode}, | ||
5 | TextUnit, T, | ||
6 | }; | ||
7 | |||
8 | pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
9 | let macro_call = ctx.node_at_offset::<ast::MacroCall>()?; | ||
10 | |||
11 | if !is_valid_macrocall(¯o_call, "dbg")? { | ||
12 | return None; | ||
13 | } | ||
14 | |||
15 | let macro_range = macro_call.syntax().text_range(); | ||
16 | |||
17 | // If the cursor is inside the macro call, we'll try to maintain the cursor | ||
18 | // position by subtracting the length of dbg!( from the start of the file | ||
19 | // range, otherwise we'll default to using the start of the macro call | ||
20 | let cursor_pos = { | ||
21 | let file_range = ctx.frange.range; | ||
22 | |||
23 | let offset_start = file_range | ||
24 | .start() | ||
25 | .checked_sub(macro_range.start()) | ||
26 | .unwrap_or_else(|| TextUnit::from(0)); | ||
27 | |||
28 | let dbg_size = TextUnit::of_str("dbg!("); | ||
29 | |||
30 | if offset_start > dbg_size { | ||
31 | file_range.start() - dbg_size | ||
32 | } else { | ||
33 | macro_range.start() | ||
34 | } | ||
35 | }; | ||
36 | |||
37 | let macro_content = { | ||
38 | let macro_args = macro_call.token_tree()?.syntax().clone(); | ||
39 | |||
40 | let text = macro_args.text(); | ||
41 | let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')'); | ||
42 | text.slice(without_parens).to_string() | ||
43 | }; | ||
44 | |||
45 | ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| { | ||
46 | edit.target(macro_call.syntax().text_range()); | ||
47 | edit.replace(macro_range, macro_content); | ||
48 | edit.set_cursor(cursor_pos); | ||
49 | }); | ||
50 | |||
51 | ctx.build() | ||
52 | } | ||
53 | |||
54 | /// Verifies that the given macro_call actually matches the given name | ||
55 | /// and contains proper ending tokens | ||
56 | fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> { | ||
57 | let path = macro_call.path()?; | ||
58 | let name_ref = path.segment()?.name_ref()?; | ||
59 | |||
60 | // Make sure it is actually a dbg-macro call, dbg followed by ! | ||
61 | let excl = path.syntax().next_sibling_or_token()?; | ||
62 | |||
63 | if name_ref.text() != macro_name || excl.kind() != T![!] { | ||
64 | return None; | ||
65 | } | ||
66 | |||
67 | let node = macro_call.token_tree()?.syntax().clone(); | ||
68 | let first_child = node.first_child_or_token()?; | ||
69 | let last_child = node.last_child_or_token()?; | ||
70 | |||
71 | match (first_child.kind(), last_child.kind()) { | ||
72 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), | ||
73 | _ => Some(false), | ||
74 | } | ||
75 | } | ||
76 | |||
77 | #[cfg(test)] | ||
78 | mod tests { | ||
79 | use super::*; | ||
80 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
81 | |||
82 | #[test] | ||
83 | fn test_remove_dbg() { | ||
84 | check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1"); | ||
85 | |||
86 | check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)"); | ||
87 | |||
88 | check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1"); | ||
89 | |||
90 | check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1"); | ||
91 | |||
92 | check_assist( | ||
93 | remove_dbg, | ||
94 | " | ||
95 | fn foo(n: usize) { | ||
96 | if let Some(_) = dbg!(n.<|>checked_sub(4)) { | ||
97 | // ... | ||
98 | } | ||
99 | } | ||
100 | ", | ||
101 | " | ||
102 | fn foo(n: usize) { | ||
103 | if let Some(_) = n.<|>checked_sub(4) { | ||
104 | // ... | ||
105 | } | ||
106 | } | ||
107 | ", | ||
108 | ); | ||
109 | } | ||
110 | #[test] | ||
111 | fn test_remove_dbg_with_brackets_and_braces() { | ||
112 | check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1"); | ||
113 | check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1"); | ||
114 | } | ||
115 | |||
116 | #[test] | ||
117 | fn test_remove_dbg_not_applicable() { | ||
118 | check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]"); | ||
119 | check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)"); | ||
120 | check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7"); | ||
121 | } | ||
122 | |||
123 | #[test] | ||
124 | fn remove_dbg_target() { | ||
125 | check_assist_target( | ||
126 | remove_dbg, | ||
127 | " | ||
128 | fn foo(n: usize) { | ||
129 | if let Some(_) = dbg!(n.<|>checked_sub(4)) { | ||
130 | // ... | ||
131 | } | ||
132 | } | ||
133 | ", | ||
134 | "dbg!(n.checked_sub(4))", | ||
135 | ); | ||
136 | } | ||
137 | } | ||
diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/assists/replace_if_let_with_match.rs new file mode 100644 index 000000000..401835c57 --- /dev/null +++ b/crates/ra_assists/src/assists/replace_if_let_with_match.rs | |||
@@ -0,0 +1,102 @@ | |||
1 | use format_buf::format; | ||
2 | use hir::db::HirDatabase; | ||
3 | use ra_fmt::extract_trivial_expression; | ||
4 | use ra_syntax::{ast, AstNode}; | ||
5 | |||
6 | use crate::{Assist, AssistCtx, AssistId}; | ||
7 | |||
8 | pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
9 | let if_expr: ast::IfExpr = ctx.node_at_offset()?; | ||
10 | let cond = if_expr.condition()?; | ||
11 | let pat = cond.pat()?; | ||
12 | let expr = cond.expr()?; | ||
13 | let then_block = if_expr.then_branch()?; | ||
14 | let else_block = match if_expr.else_branch()? { | ||
15 | ast::ElseBranch::Block(it) => it, | ||
16 | ast::ElseBranch::IfExpr(_) => return None, | ||
17 | }; | ||
18 | |||
19 | ctx.add_action(AssistId("replace_if_let_with_match"), "replace with match", |edit| { | ||
20 | let match_expr = build_match_expr(expr, pat, then_block, else_block); | ||
21 | edit.target(if_expr.syntax().text_range()); | ||
22 | edit.replace_node_and_indent(if_expr.syntax(), match_expr); | ||
23 | edit.set_cursor(if_expr.syntax().text_range().start()) | ||
24 | }); | ||
25 | |||
26 | ctx.build() | ||
27 | } | ||
28 | |||
29 | fn build_match_expr( | ||
30 | expr: ast::Expr, | ||
31 | pat1: ast::Pat, | ||
32 | arm1: ast::BlockExpr, | ||
33 | arm2: ast::BlockExpr, | ||
34 | ) -> String { | ||
35 | let mut buf = String::new(); | ||
36 | format!(buf, "match {} {{\n", expr.syntax().text()); | ||
37 | format!(buf, " {} => {}\n", pat1.syntax().text(), format_arm(&arm1)); | ||
38 | format!(buf, " _ => {}\n", format_arm(&arm2)); | ||
39 | buf.push_str("}"); | ||
40 | buf | ||
41 | } | ||
42 | |||
43 | fn format_arm(block: &ast::BlockExpr) -> String { | ||
44 | match extract_trivial_expression(block) { | ||
45 | None => block.syntax().text().to_string(), | ||
46 | Some(e) => format!("{},", e.syntax().text()), | ||
47 | } | ||
48 | } | ||
49 | |||
50 | #[cfg(test)] | ||
51 | mod tests { | ||
52 | use super::*; | ||
53 | use crate::helpers::{check_assist, check_assist_target}; | ||
54 | |||
55 | #[test] | ||
56 | fn test_replace_if_let_with_match_unwraps_simple_expressions() { | ||
57 | check_assist( | ||
58 | replace_if_let_with_match, | ||
59 | " | ||
60 | impl VariantData { | ||
61 | pub fn is_struct(&self) -> bool { | ||
62 | if <|>let VariantData::Struct(..) = *self { | ||
63 | true | ||
64 | } else { | ||
65 | false | ||
66 | } | ||
67 | } | ||
68 | } ", | ||
69 | " | ||
70 | impl VariantData { | ||
71 | pub fn is_struct(&self) -> bool { | ||
72 | <|>match *self { | ||
73 | VariantData::Struct(..) => true, | ||
74 | _ => false, | ||
75 | } | ||
76 | } | ||
77 | } ", | ||
78 | ) | ||
79 | } | ||
80 | |||
81 | #[test] | ||
82 | fn replace_if_let_with_match_target() { | ||
83 | check_assist_target( | ||
84 | replace_if_let_with_match, | ||
85 | " | ||
86 | impl VariantData { | ||
87 | pub fn is_struct(&self) -> bool { | ||
88 | if <|>let VariantData::Struct(..) = *self { | ||
89 | true | ||
90 | } else { | ||
91 | false | ||
92 | } | ||
93 | } | ||
94 | } ", | ||
95 | "if let VariantData::Struct(..) = *self { | ||
96 | true | ||
97 | } else { | ||
98 | false | ||
99 | }", | ||
100 | ); | ||
101 | } | ||
102 | } | ||
diff --git a/crates/ra_assists/src/assists/split_import.rs b/crates/ra_assists/src/assists/split_import.rs new file mode 100644 index 000000000..2c1edddb9 --- /dev/null +++ b/crates/ra_assists/src/assists/split_import.rs | |||
@@ -0,0 +1,61 @@ | |||
1 | use std::iter::successors; | ||
2 | |||
3 | use hir::db::HirDatabase; | ||
4 | use ra_syntax::{ast, AstNode, TextUnit, T}; | ||
5 | |||
6 | use crate::{Assist, AssistCtx, AssistId}; | ||
7 | |||
8 | pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
9 | let colon_colon = ctx.token_at_offset().find(|leaf| leaf.kind() == T![::])?; | ||
10 | let path = ast::Path::cast(colon_colon.parent())?; | ||
11 | let top_path = successors(Some(path), |it| it.parent_path()).last()?; | ||
12 | |||
13 | let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast); | ||
14 | if use_tree.is_none() { | ||
15 | return None; | ||
16 | } | ||
17 | |||
18 | let l_curly = colon_colon.text_range().end(); | ||
19 | let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) { | ||
20 | Some(tree) => tree.syntax().text_range().end(), | ||
21 | None => top_path.syntax().text_range().end(), | ||
22 | }; | ||
23 | |||
24 | ctx.add_action(AssistId("split_import"), "split import", |edit| { | ||
25 | edit.target(colon_colon.text_range()); | ||
26 | edit.insert(l_curly, "{"); | ||
27 | edit.insert(r_curly, "}"); | ||
28 | edit.set_cursor(l_curly + TextUnit::of_str("{")); | ||
29 | }); | ||
30 | |||
31 | ctx.build() | ||
32 | } | ||
33 | |||
34 | #[cfg(test)] | ||
35 | mod tests { | ||
36 | use super::*; | ||
37 | use crate::helpers::{check_assist, check_assist_target}; | ||
38 | |||
39 | #[test] | ||
40 | fn test_split_import() { | ||
41 | check_assist( | ||
42 | split_import, | ||
43 | "use crate::<|>db::RootDatabase;", | ||
44 | "use crate::{<|>db::RootDatabase};", | ||
45 | ) | ||
46 | } | ||
47 | |||
48 | #[test] | ||
49 | fn split_import_works_with_trees() { | ||
50 | check_assist( | ||
51 | split_import, | ||
52 | "use algo:<|>:visitor::{Visitor, visit}", | ||
53 | "use algo::{<|>visitor::{Visitor, visit}}", | ||
54 | ) | ||
55 | } | ||
56 | |||
57 | #[test] | ||
58 | fn split_import_target() { | ||
59 | check_assist_target(split_import, "use algo::<|>visitor::{Visitor, visit}", "::"); | ||
60 | } | ||
61 | } | ||