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