diff options
Diffstat (limited to 'crates/ra_assists/src/handlers')
25 files changed, 7149 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_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..2701eddb8 --- /dev/null +++ b/crates/ra_assists/src/handlers/add_new.rs | |||
@@ -0,0 +1,436 @@ | |||
1 | use format_buf::format; | ||
2 | use hir::{Adt, 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_def = { | ||
139 | let src = InFile { file_id: ctx.frange.file_id.into(), value: strukt.clone() }; | ||
140 | sb.to_def(src)? | ||
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 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
148 | // (we currently use the wrong type parameter) | ||
149 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
150 | let same_ty = match blk.target_ty(db).as_adt() { | ||
151 | Some(def) => def == Adt::Struct(struct_def), | ||
152 | None => false, | ||
153 | }; | ||
154 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
155 | |||
156 | if !(same_ty && not_trait_impl) { | ||
157 | None | ||
158 | } else { | ||
159 | Some(impl_blk) | ||
160 | } | ||
161 | }); | ||
162 | |||
163 | if let Some(ref impl_blk) = block { | ||
164 | if has_new_fn(impl_blk) { | ||
165 | return None; | ||
166 | } | ||
167 | } | ||
168 | |||
169 | Some(block) | ||
170 | } | ||
171 | |||
172 | fn has_new_fn(imp: &ast::ImplBlock) -> bool { | ||
173 | if let Some(il) = imp.item_list() { | ||
174 | for item in il.impl_items() { | ||
175 | if let ast::ImplItem::FnDef(f) = item { | ||
176 | if let Some(name) = f.name() { | ||
177 | if name.text().eq_ignore_ascii_case("new") { | ||
178 | return true; | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | |||
185 | false | ||
186 | } | ||
187 | |||
188 | #[cfg(test)] | ||
189 | mod tests { | ||
190 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
191 | |||
192 | use super::*; | ||
193 | |||
194 | #[test] | ||
195 | #[rustfmt::skip] | ||
196 | fn test_add_new() { | ||
197 | // Check output of generation | ||
198 | check_assist( | ||
199 | add_new, | ||
200 | "struct Foo {<|>}", | ||
201 | "struct Foo {} | ||
202 | |||
203 | impl Foo { | ||
204 | fn new() -> Self { Self { } }<|> | ||
205 | } | ||
206 | ", | ||
207 | ); | ||
208 | check_assist( | ||
209 | add_new, | ||
210 | "struct Foo<T: Clone> {<|>}", | ||
211 | "struct Foo<T: Clone> {} | ||
212 | |||
213 | impl<T: Clone> Foo<T> { | ||
214 | fn new() -> Self { Self { } }<|> | ||
215 | } | ||
216 | ", | ||
217 | ); | ||
218 | check_assist( | ||
219 | add_new, | ||
220 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
221 | "struct Foo<'a, T: Foo<'a>> {} | ||
222 | |||
223 | impl<'a, T: Foo<'a>> Foo<'a, T> { | ||
224 | fn new() -> Self { Self { } }<|> | ||
225 | } | ||
226 | ", | ||
227 | ); | ||
228 | check_assist( | ||
229 | add_new, | ||
230 | "struct Foo { baz: String <|>}", | ||
231 | "struct Foo { baz: String } | ||
232 | |||
233 | impl Foo { | ||
234 | fn new(baz: String) -> Self { Self { baz } }<|> | ||
235 | } | ||
236 | ", | ||
237 | ); | ||
238 | check_assist( | ||
239 | add_new, | ||
240 | "struct Foo { baz: String, qux: Vec<i32> <|>}", | ||
241 | "struct Foo { baz: String, qux: Vec<i32> } | ||
242 | |||
243 | impl Foo { | ||
244 | fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> | ||
245 | } | ||
246 | ", | ||
247 | ); | ||
248 | |||
249 | // Check that visibility modifiers don't get brought in for fields | ||
250 | check_assist( | ||
251 | add_new, | ||
252 | "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", | ||
253 | "struct Foo { pub baz: String, pub qux: Vec<i32> } | ||
254 | |||
255 | impl Foo { | ||
256 | fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> | ||
257 | } | ||
258 | ", | ||
259 | ); | ||
260 | |||
261 | // Check that it reuses existing impls | ||
262 | check_assist( | ||
263 | add_new, | ||
264 | "struct Foo {<|>} | ||
265 | |||
266 | impl Foo {} | ||
267 | ", | ||
268 | "struct Foo {} | ||
269 | |||
270 | impl Foo { | ||
271 | fn new() -> Self { Self { } }<|> | ||
272 | } | ||
273 | ", | ||
274 | ); | ||
275 | check_assist( | ||
276 | add_new, | ||
277 | "struct Foo {<|>} | ||
278 | |||
279 | impl Foo { | ||
280 | fn qux(&self) {} | ||
281 | } | ||
282 | ", | ||
283 | "struct Foo {} | ||
284 | |||
285 | impl Foo { | ||
286 | fn new() -> Self { Self { } }<|> | ||
287 | |||
288 | fn qux(&self) {} | ||
289 | } | ||
290 | ", | ||
291 | ); | ||
292 | |||
293 | check_assist( | ||
294 | add_new, | ||
295 | "struct Foo {<|>} | ||
296 | |||
297 | impl Foo { | ||
298 | fn qux(&self) {} | ||
299 | fn baz() -> i32 { | ||
300 | 5 | ||
301 | } | ||
302 | } | ||
303 | ", | ||
304 | "struct Foo {} | ||
305 | |||
306 | impl Foo { | ||
307 | fn new() -> Self { Self { } }<|> | ||
308 | |||
309 | fn qux(&self) {} | ||
310 | fn baz() -> i32 { | ||
311 | 5 | ||
312 | } | ||
313 | } | ||
314 | ", | ||
315 | ); | ||
316 | |||
317 | // Check visibility of new fn based on struct | ||
318 | check_assist( | ||
319 | add_new, | ||
320 | "pub struct Foo {<|>}", | ||
321 | "pub struct Foo {} | ||
322 | |||
323 | impl Foo { | ||
324 | pub fn new() -> Self { Self { } }<|> | ||
325 | } | ||
326 | ", | ||
327 | ); | ||
328 | check_assist( | ||
329 | add_new, | ||
330 | "pub(crate) struct Foo {<|>}", | ||
331 | "pub(crate) struct Foo {} | ||
332 | |||
333 | impl Foo { | ||
334 | pub(crate) fn new() -> Self { Self { } }<|> | ||
335 | } | ||
336 | ", | ||
337 | ); | ||
338 | } | ||
339 | |||
340 | #[test] | ||
341 | fn add_new_not_applicable_if_fn_exists() { | ||
342 | check_assist_not_applicable( | ||
343 | add_new, | ||
344 | " | ||
345 | struct Foo {<|>} | ||
346 | |||
347 | impl Foo { | ||
348 | fn new() -> Self { | ||
349 | Self | ||
350 | } | ||
351 | }", | ||
352 | ); | ||
353 | |||
354 | check_assist_not_applicable( | ||
355 | add_new, | ||
356 | " | ||
357 | struct Foo {<|>} | ||
358 | |||
359 | impl Foo { | ||
360 | fn New() -> Self { | ||
361 | Self | ||
362 | } | ||
363 | }", | ||
364 | ); | ||
365 | } | ||
366 | |||
367 | #[test] | ||
368 | fn add_new_target() { | ||
369 | check_assist_target( | ||
370 | add_new, | ||
371 | " | ||
372 | struct SomeThingIrrelevant; | ||
373 | /// Has a lifetime parameter | ||
374 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
375 | struct EvenMoreIrrelevant; | ||
376 | ", | ||
377 | "/// Has a lifetime parameter | ||
378 | struct Foo<'a, T: Foo<'a>> {}", | ||
379 | ); | ||
380 | } | ||
381 | |||
382 | #[test] | ||
383 | fn test_unrelated_new() { | ||
384 | check_assist( | ||
385 | add_new, | ||
386 | r##" | ||
387 | pub struct AstId<N: AstNode> { | ||
388 | file_id: HirFileId, | ||
389 | file_ast_id: FileAstId<N>, | ||
390 | } | ||
391 | |||
392 | impl<N: AstNode> AstId<N> { | ||
393 | pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { | ||
394 | AstId { file_id, file_ast_id } | ||
395 | } | ||
396 | } | ||
397 | |||
398 | pub struct Source<T> { | ||
399 | pub file_id: HirFileId,<|> | ||
400 | pub ast: T, | ||
401 | } | ||
402 | |||
403 | impl<T> Source<T> { | ||
404 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | ||
405 | Source { file_id: self.file_id, ast: f(self.ast) } | ||
406 | } | ||
407 | } | ||
408 | "##, | ||
409 | r##" | ||
410 | pub struct AstId<N: AstNode> { | ||
411 | file_id: HirFileId, | ||
412 | file_ast_id: FileAstId<N>, | ||
413 | } | ||
414 | |||
415 | impl<N: AstNode> AstId<N> { | ||
416 | pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { | ||
417 | AstId { file_id, file_ast_id } | ||
418 | } | ||
419 | } | ||
420 | |||
421 | pub struct Source<T> { | ||
422 | pub file_id: HirFileId, | ||
423 | pub ast: T, | ||
424 | } | ||
425 | |||
426 | impl<T> Source<T> { | ||
427 | pub fn new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }<|> | ||
428 | |||
429 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | ||
430 | Source { file_id: self.file_id, ast: f(self.ast) } | ||
431 | } | ||
432 | } | ||
433 | "##, | ||
434 | ); | ||
435 | } | ||
436 | } | ||
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..1fb701da5 --- /dev/null +++ b/crates/ra_assists/src/handlers/auto_import.rs | |||
@@ -0,0 +1,293 @@ | |||
1 | use ra_ide_db::imports_locator::ImportsLocator; | ||
2 | use ra_syntax::ast::{self, AstNode}; | ||
3 | |||
4 | use crate::{ | ||
5 | assist_ctx::{Assist, AssistCtx}, | ||
6 | insert_use_statement, AssistId, | ||
7 | }; | ||
8 | use std::collections::BTreeSet; | ||
9 | |||
10 | // Assist: auto_import | ||
11 | // | ||
12 | // If the name is unresolved, provides all possible imports for it. | ||
13 | // | ||
14 | // ``` | ||
15 | // fn main() { | ||
16 | // let map = HashMap<|>::new(); | ||
17 | // } | ||
18 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | ||
19 | // ``` | ||
20 | // -> | ||
21 | // ``` | ||
22 | // use std::collections::HashMap; | ||
23 | // | ||
24 | // fn main() { | ||
25 | // let map = HashMap::new(); | ||
26 | // } | ||
27 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | ||
28 | // ``` | ||
29 | pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | ||
30 | let path_under_caret: ast::Path = ctx.find_node_at_offset()?; | ||
31 | if path_under_caret.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { | ||
32 | return None; | ||
33 | } | ||
34 | |||
35 | let module = path_under_caret.syntax().ancestors().find_map(ast::Module::cast); | ||
36 | let position = match module.and_then(|it| it.item_list()) { | ||
37 | Some(item_list) => item_list.syntax().clone(), | ||
38 | None => { | ||
39 | let current_file = | ||
40 | path_under_caret.syntax().ancestors().find_map(ast::SourceFile::cast)?; | ||
41 | current_file.syntax().clone() | ||
42 | } | ||
43 | }; | ||
44 | let source_analyzer = ctx.source_analyzer(&position, None); | ||
45 | let module_with_name_to_import = source_analyzer.module()?; | ||
46 | |||
47 | let name_ref_to_import = | ||
48 | path_under_caret.syntax().descendants().find_map(ast::NameRef::cast)?; | ||
49 | if source_analyzer | ||
50 | .resolve_path(ctx.db, &name_ref_to_import.syntax().ancestors().find_map(ast::Path::cast)?) | ||
51 | .is_some() | ||
52 | { | ||
53 | return None; | ||
54 | } | ||
55 | |||
56 | let name_to_import = name_ref_to_import.syntax().to_string(); | ||
57 | let proposed_imports = ImportsLocator::new(ctx.db) | ||
58 | .find_imports(&name_to_import) | ||
59 | .into_iter() | ||
60 | .filter_map(|module_def| module_with_name_to_import.find_use_path(ctx.db, module_def)) | ||
61 | .filter(|use_path| !use_path.segments.is_empty()) | ||
62 | .take(20) | ||
63 | .collect::<BTreeSet<_>>(); | ||
64 | |||
65 | if proposed_imports.is_empty() { | ||
66 | return None; | ||
67 | } | ||
68 | |||
69 | let mut group = ctx.add_assist_group(format!("Import {}", name_to_import)); | ||
70 | for import in proposed_imports { | ||
71 | group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| { | ||
72 | edit.target(path_under_caret.syntax().text_range()); | ||
73 | insert_use_statement( | ||
74 | &position, | ||
75 | path_under_caret.syntax(), | ||
76 | &import, | ||
77 | edit.text_edit_builder(), | ||
78 | ); | ||
79 | }); | ||
80 | } | ||
81 | group.finish() | ||
82 | } | ||
83 | |||
84 | #[cfg(test)] | ||
85 | mod tests { | ||
86 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
87 | |||
88 | use super::*; | ||
89 | |||
90 | #[test] | ||
91 | fn applicable_when_found_an_import() { | ||
92 | check_assist( | ||
93 | auto_import, | ||
94 | r" | ||
95 | <|>PubStruct | ||
96 | |||
97 | pub mod PubMod { | ||
98 | pub struct PubStruct; | ||
99 | } | ||
100 | ", | ||
101 | r" | ||
102 | <|>use PubMod::PubStruct; | ||
103 | |||
104 | PubStruct | ||
105 | |||
106 | pub mod PubMod { | ||
107 | pub struct PubStruct; | ||
108 | } | ||
109 | ", | ||
110 | ); | ||
111 | } | ||
112 | |||
113 | #[test] | ||
114 | fn auto_imports_are_merged() { | ||
115 | check_assist( | ||
116 | auto_import, | ||
117 | r" | ||
118 | use PubMod::PubStruct1; | ||
119 | |||
120 | struct Test { | ||
121 | test: Pub<|>Struct2<u8>, | ||
122 | } | ||
123 | |||
124 | pub mod PubMod { | ||
125 | pub struct PubStruct1; | ||
126 | pub struct PubStruct2<T> { | ||
127 | _t: T, | ||
128 | } | ||
129 | } | ||
130 | ", | ||
131 | r" | ||
132 | use PubMod::{PubStruct2, PubStruct1}; | ||
133 | |||
134 | struct Test { | ||
135 | test: Pub<|>Struct2<u8>, | ||
136 | } | ||
137 | |||
138 | pub mod PubMod { | ||
139 | pub struct PubStruct1; | ||
140 | pub struct PubStruct2<T> { | ||
141 | _t: T, | ||
142 | } | ||
143 | } | ||
144 | ", | ||
145 | ); | ||
146 | } | ||
147 | |||
148 | #[test] | ||
149 | fn applicable_when_found_multiple_imports() { | ||
150 | check_assist( | ||
151 | auto_import, | ||
152 | r" | ||
153 | PubSt<|>ruct | ||
154 | |||
155 | pub mod PubMod1 { | ||
156 | pub struct PubStruct; | ||
157 | } | ||
158 | pub mod PubMod2 { | ||
159 | pub struct PubStruct; | ||
160 | } | ||
161 | pub mod PubMod3 { | ||
162 | pub struct PubStruct; | ||
163 | } | ||
164 | ", | ||
165 | r" | ||
166 | use PubMod1::PubStruct; | ||
167 | |||
168 | PubSt<|>ruct | ||
169 | |||
170 | pub mod PubMod1 { | ||
171 | pub struct PubStruct; | ||
172 | } | ||
173 | pub mod PubMod2 { | ||
174 | pub struct PubStruct; | ||
175 | } | ||
176 | pub mod PubMod3 { | ||
177 | pub struct PubStruct; | ||
178 | } | ||
179 | ", | ||
180 | ); | ||
181 | } | ||
182 | |||
183 | #[test] | ||
184 | fn not_applicable_for_already_imported_types() { | ||
185 | check_assist_not_applicable( | ||
186 | auto_import, | ||
187 | r" | ||
188 | use PubMod::PubStruct; | ||
189 | |||
190 | PubStruct<|> | ||
191 | |||
192 | pub mod PubMod { | ||
193 | pub struct PubStruct; | ||
194 | } | ||
195 | ", | ||
196 | ); | ||
197 | } | ||
198 | |||
199 | #[test] | ||
200 | fn not_applicable_for_types_with_private_paths() { | ||
201 | check_assist_not_applicable( | ||
202 | auto_import, | ||
203 | r" | ||
204 | PrivateStruct<|> | ||
205 | |||
206 | pub mod PubMod { | ||
207 | struct PrivateStruct; | ||
208 | } | ||
209 | ", | ||
210 | ); | ||
211 | } | ||
212 | |||
213 | #[test] | ||
214 | fn not_applicable_when_no_imports_found() { | ||
215 | check_assist_not_applicable( | ||
216 | auto_import, | ||
217 | " | ||
218 | PubStruct<|>", | ||
219 | ); | ||
220 | } | ||
221 | |||
222 | #[test] | ||
223 | fn not_applicable_in_import_statements() { | ||
224 | check_assist_not_applicable( | ||
225 | auto_import, | ||
226 | r" | ||
227 | use PubStruct<|>; | ||
228 | |||
229 | pub mod PubMod { | ||
230 | pub struct PubStruct; | ||
231 | }", | ||
232 | ); | ||
233 | } | ||
234 | |||
235 | #[test] | ||
236 | fn function_import() { | ||
237 | check_assist( | ||
238 | auto_import, | ||
239 | r" | ||
240 | test_function<|> | ||
241 | |||
242 | pub mod PubMod { | ||
243 | pub fn test_function() {}; | ||
244 | } | ||
245 | ", | ||
246 | r" | ||
247 | use PubMod::test_function; | ||
248 | |||
249 | test_function<|> | ||
250 | |||
251 | pub mod PubMod { | ||
252 | pub fn test_function() {}; | ||
253 | } | ||
254 | ", | ||
255 | ); | ||
256 | } | ||
257 | |||
258 | #[test] | ||
259 | fn auto_import_target() { | ||
260 | check_assist_target( | ||
261 | auto_import, | ||
262 | r" | ||
263 | struct AssistInfo { | ||
264 | group_label: Option<<|>GroupLabel>, | ||
265 | } | ||
266 | |||
267 | mod m { pub struct GroupLabel; } | ||
268 | ", | ||
269 | "GroupLabel", | ||
270 | ) | ||
271 | } | ||
272 | |||
273 | #[test] | ||
274 | fn not_applicable_when_path_start_is_imported() { | ||
275 | check_assist_not_applicable( | ||
276 | auto_import, | ||
277 | r" | ||
278 | pub mod mod1 { | ||
279 | pub mod mod2 { | ||
280 | pub mod mod3 { | ||
281 | pub struct TestStruct; | ||
282 | } | ||
283 | } | ||
284 | } | ||
285 | |||
286 | use mod1::mod2; | ||
287 | fn main() { | ||
288 | mod2::mod3::TestStruct<|> | ||
289 | } | ||
290 | ", | ||
291 | ); | ||
292 | } | ||
293 | } | ||
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/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs new file mode 100644 index 000000000..b70c88ec2 --- /dev/null +++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs | |||
@@ -0,0 +1,965 @@ | |||
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 | /// Creates and inserts a use statement for the given path to import. | ||
16 | /// The use statement is inserted in the scope most appropriate to the | ||
17 | /// the cursor position given, additionally merged with the existing use imports. | ||
18 | pub fn insert_use_statement( | ||
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: replace_qualified_name_with_use | ||
41 | // | ||
42 | // Adds a use statement for a given fully-qualified name. | ||
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 replace_qualified_name_with_use(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( | ||
76 | AssistId("replace_qualified_name_with_use"), | ||
77 | "Replace qualified path with use", | ||
78 | |edit| { | ||
79 | replace_with_use(&position, &path, &segments, edit.text_edit_builder()); | ||
80 | }, | ||
81 | ) | ||
82 | } | ||
83 | |||
84 | fn collect_path_segments_raw( | ||
85 | segments: &mut Vec<ast::PathSegment>, | ||
86 | mut path: ast::Path, | ||
87 | ) -> Option<usize> { | ||
88 | let oldlen = segments.len(); | ||
89 | loop { | ||
90 | let mut children = path.syntax().children_with_tokens(); | ||
91 | let (first, second, third) = ( | ||
92 | children.next().map(|n| (n.clone(), n.kind())), | ||
93 | children.next().map(|n| (n.clone(), n.kind())), | ||
94 | children.next().map(|n| (n.clone(), n.kind())), | ||
95 | ); | ||
96 | match (first, second, third) { | ||
97 | (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => { | ||
98 | path = ast::Path::cast(subpath.as_node()?.clone())?; | ||
99 | segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?); | ||
100 | } | ||
101 | (Some((segment, PATH_SEGMENT)), _, _) => { | ||
102 | segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?); | ||
103 | break; | ||
104 | } | ||
105 | (_, _, _) => return None, | ||
106 | } | ||
107 | } | ||
108 | // We need to reverse only the new added segments | ||
109 | let only_new_segments = segments.split_at_mut(oldlen).1; | ||
110 | only_new_segments.reverse(); | ||
111 | Some(segments.len() - oldlen) | ||
112 | } | ||
113 | |||
114 | fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { | ||
115 | let mut iter = segments.iter(); | ||
116 | if let Some(s) = iter.next() { | ||
117 | buf.push_str(s); | ||
118 | } | ||
119 | for s in iter { | ||
120 | buf.push_str("::"); | ||
121 | buf.push_str(s); | ||
122 | } | ||
123 | } | ||
124 | |||
125 | /// Returns the number of common segments. | ||
126 | fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize { | ||
127 | left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count() | ||
128 | } | ||
129 | |||
130 | fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { | ||
131 | if let Some(kb) = b.kind() { | ||
132 | match kb { | ||
133 | ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), | ||
134 | ast::PathSegmentKind::SelfKw => a == "self", | ||
135 | ast::PathSegmentKind::SuperKw => a == "super", | ||
136 | ast::PathSegmentKind::CrateKw => a == "crate", | ||
137 | ast::PathSegmentKind::Type { .. } => false, // not allowed in imports | ||
138 | } | ||
139 | } else { | ||
140 | false | ||
141 | } | ||
142 | } | ||
143 | |||
144 | fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool { | ||
145 | a == b.text() | ||
146 | } | ||
147 | |||
148 | #[derive(Clone, Debug)] | ||
149 | enum ImportAction { | ||
150 | Nothing, | ||
151 | // Add a brand new use statement. | ||
152 | AddNewUse { | ||
153 | anchor: Option<SyntaxNode>, // anchor node | ||
154 | add_after_anchor: bool, | ||
155 | }, | ||
156 | |||
157 | // To split an existing use statement creating a nested import. | ||
158 | AddNestedImport { | ||
159 | // how may segments matched with the target path | ||
160 | common_segments: usize, | ||
161 | path_to_split: ast::Path, | ||
162 | // the first segment of path_to_split we want to add into the new nested list | ||
163 | first_segment_to_split: Option<ast::PathSegment>, | ||
164 | // Wether to add 'self' in addition to the target path | ||
165 | add_self: bool, | ||
166 | }, | ||
167 | // To add the target path to an existing nested import tree list. | ||
168 | AddInTreeList { | ||
169 | common_segments: usize, | ||
170 | // The UseTreeList where to add the target path | ||
171 | tree_list: ast::UseTreeList, | ||
172 | add_self: bool, | ||
173 | }, | ||
174 | } | ||
175 | |||
176 | impl ImportAction { | ||
177 | fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self { | ||
178 | ImportAction::AddNewUse { anchor, add_after_anchor } | ||
179 | } | ||
180 | |||
181 | fn add_nested_import( | ||
182 | common_segments: usize, | ||
183 | path_to_split: ast::Path, | ||
184 | first_segment_to_split: Option<ast::PathSegment>, | ||
185 | add_self: bool, | ||
186 | ) -> Self { | ||
187 | ImportAction::AddNestedImport { | ||
188 | common_segments, | ||
189 | path_to_split, | ||
190 | first_segment_to_split, | ||
191 | add_self, | ||
192 | } | ||
193 | } | ||
194 | |||
195 | fn add_in_tree_list( | ||
196 | common_segments: usize, | ||
197 | tree_list: ast::UseTreeList, | ||
198 | add_self: bool, | ||
199 | ) -> Self { | ||
200 | ImportAction::AddInTreeList { common_segments, tree_list, add_self } | ||
201 | } | ||
202 | |||
203 | fn better(left: ImportAction, right: ImportAction) -> ImportAction { | ||
204 | if left.is_better(&right) { | ||
205 | left | ||
206 | } else { | ||
207 | right | ||
208 | } | ||
209 | } | ||
210 | |||
211 | fn is_better(&self, other: &ImportAction) -> bool { | ||
212 | match (self, other) { | ||
213 | (ImportAction::Nothing, _) => true, | ||
214 | (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, | ||
215 | ( | ||
216 | ImportAction::AddNestedImport { common_segments: n, .. }, | ||
217 | ImportAction::AddInTreeList { common_segments: m, .. }, | ||
218 | ) | ||
219 | | ( | ||
220 | ImportAction::AddInTreeList { common_segments: n, .. }, | ||
221 | ImportAction::AddNestedImport { common_segments: m, .. }, | ||
222 | ) | ||
223 | | ( | ||
224 | ImportAction::AddInTreeList { common_segments: n, .. }, | ||
225 | ImportAction::AddInTreeList { common_segments: m, .. }, | ||
226 | ) | ||
227 | | ( | ||
228 | ImportAction::AddNestedImport { common_segments: n, .. }, | ||
229 | ImportAction::AddNestedImport { common_segments: m, .. }, | ||
230 | ) => n > m, | ||
231 | (ImportAction::AddInTreeList { .. }, _) => true, | ||
232 | (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false, | ||
233 | (ImportAction::AddNestedImport { .. }, _) => true, | ||
234 | (ImportAction::AddNewUse { .. }, _) => false, | ||
235 | } | ||
236 | } | ||
237 | } | ||
238 | |||
239 | // Find out the best ImportAction to import target path against current_use_tree. | ||
240 | // If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList. | ||
241 | fn walk_use_tree_for_best_action( | ||
242 | current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments | ||
243 | current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import | ||
244 | current_use_tree: ast::UseTree, // the use tree we are currently examinating | ||
245 | target: &[SmolStr], // the path we want to import | ||
246 | ) -> ImportAction { | ||
247 | // We save the number of segments in the buffer so we can restore the correct segments | ||
248 | // before returning. Recursive call will add segments so we need to delete them. | ||
249 | let prev_len = current_path_segments.len(); | ||
250 | |||
251 | let tree_list = current_use_tree.use_tree_list(); | ||
252 | let alias = current_use_tree.alias(); | ||
253 | |||
254 | let path = match current_use_tree.path() { | ||
255 | Some(path) => path, | ||
256 | None => { | ||
257 | // If the use item don't have a path, it means it's broken (syntax error) | ||
258 | return ImportAction::add_new_use( | ||
259 | current_use_tree | ||
260 | .syntax() | ||
261 | .ancestors() | ||
262 | .find_map(ast::UseItem::cast) | ||
263 | .map(|it| it.syntax().clone()), | ||
264 | true, | ||
265 | ); | ||
266 | } | ||
267 | }; | ||
268 | |||
269 | // This can happen only if current_use_tree is a direct child of a UseItem | ||
270 | if let Some(name) = alias.and_then(|it| it.name()) { | ||
271 | if compare_path_segment_with_name(&target[0], &name) { | ||
272 | return ImportAction::Nothing; | ||
273 | } | ||
274 | } | ||
275 | |||
276 | collect_path_segments_raw(current_path_segments, path.clone()); | ||
277 | |||
278 | // We compare only the new segments added in the line just above. | ||
279 | // The first prev_len segments were already compared in 'parent' recursive calls. | ||
280 | let left = target.split_at(prev_len).1; | ||
281 | let right = current_path_segments.split_at(prev_len).1; | ||
282 | let common = compare_path_segments(left, &right); | ||
283 | let mut action = match common { | ||
284 | 0 => ImportAction::add_new_use( | ||
285 | // e.g: target is std::fmt and we can have | ||
286 | // use foo::bar | ||
287 | // We add a brand new use statement | ||
288 | current_use_tree | ||
289 | .syntax() | ||
290 | .ancestors() | ||
291 | .find_map(ast::UseItem::cast) | ||
292 | .map(|it| it.syntax().clone()), | ||
293 | true, | ||
294 | ), | ||
295 | common if common == left.len() && left.len() == right.len() => { | ||
296 | // e.g: target is std::fmt and we can have | ||
297 | // 1- use std::fmt; | ||
298 | // 2- use std::fmt::{ ... } | ||
299 | if let Some(list) = tree_list { | ||
300 | // In case 2 we need to add self to the nested list | ||
301 | // unless it's already there | ||
302 | let has_self = list.use_trees().map(|it| it.path()).any(|p| { | ||
303 | p.and_then(|it| it.segment()) | ||
304 | .and_then(|it| it.kind()) | ||
305 | .filter(|k| *k == ast::PathSegmentKind::SelfKw) | ||
306 | .is_some() | ||
307 | }); | ||
308 | |||
309 | if has_self { | ||
310 | ImportAction::Nothing | ||
311 | } else { | ||
312 | ImportAction::add_in_tree_list(current_path_segments.len(), list, true) | ||
313 | } | ||
314 | } else { | ||
315 | // Case 1 | ||
316 | ImportAction::Nothing | ||
317 | } | ||
318 | } | ||
319 | common if common != left.len() && left.len() == right.len() => { | ||
320 | // e.g: target is std::fmt and we have | ||
321 | // use std::io; | ||
322 | // We need to split. | ||
323 | let segments_to_split = current_path_segments.split_at(prev_len + common).1; | ||
324 | ImportAction::add_nested_import( | ||
325 | prev_len + common, | ||
326 | path, | ||
327 | Some(segments_to_split[0].clone()), | ||
328 | false, | ||
329 | ) | ||
330 | } | ||
331 | common if common == right.len() && left.len() > right.len() => { | ||
332 | // e.g: target is std::fmt and we can have | ||
333 | // 1- use std; | ||
334 | // 2- use std::{ ... }; | ||
335 | |||
336 | // fallback action | ||
337 | let mut better_action = ImportAction::add_new_use( | ||
338 | current_use_tree | ||
339 | .syntax() | ||
340 | .ancestors() | ||
341 | .find_map(ast::UseItem::cast) | ||
342 | .map(|it| it.syntax().clone()), | ||
343 | true, | ||
344 | ); | ||
345 | if let Some(list) = tree_list { | ||
346 | // Case 2, check recursively if the path is already imported in the nested list | ||
347 | for u in list.use_trees() { | ||
348 | let child_action = walk_use_tree_for_best_action( | ||
349 | current_path_segments, | ||
350 | Some(list.clone()), | ||
351 | u, | ||
352 | target, | ||
353 | ); | ||
354 | if child_action.is_better(&better_action) { | ||
355 | better_action = child_action; | ||
356 | if let ImportAction::Nothing = better_action { | ||
357 | return better_action; | ||
358 | } | ||
359 | } | ||
360 | } | ||
361 | } else { | ||
362 | // Case 1, split adding self | ||
363 | better_action = ImportAction::add_nested_import(prev_len + common, path, None, true) | ||
364 | } | ||
365 | better_action | ||
366 | } | ||
367 | common if common == left.len() && left.len() < right.len() => { | ||
368 | // e.g: target is std::fmt and we can have | ||
369 | // use std::fmt::Debug; | ||
370 | let segments_to_split = current_path_segments.split_at(prev_len + common).1; | ||
371 | ImportAction::add_nested_import( | ||
372 | prev_len + common, | ||
373 | path, | ||
374 | Some(segments_to_split[0].clone()), | ||
375 | true, | ||
376 | ) | ||
377 | } | ||
378 | common if common < left.len() && common < right.len() => { | ||
379 | // e.g: target is std::fmt::nested::Debug | ||
380 | // use std::fmt::Display | ||
381 | let segments_to_split = current_path_segments.split_at(prev_len + common).1; | ||
382 | ImportAction::add_nested_import( | ||
383 | prev_len + common, | ||
384 | path, | ||
385 | Some(segments_to_split[0].clone()), | ||
386 | false, | ||
387 | ) | ||
388 | } | ||
389 | _ => unreachable!(), | ||
390 | }; | ||
391 | |||
392 | // If we are inside a UseTreeList adding a use statement become adding to the existing | ||
393 | // tree list. | ||
394 | action = match (current_parent_use_tree_list, action.clone()) { | ||
395 | (Some(use_tree_list), ImportAction::AddNewUse { .. }) => { | ||
396 | ImportAction::add_in_tree_list(prev_len, use_tree_list, false) | ||
397 | } | ||
398 | (_, _) => action, | ||
399 | }; | ||
400 | |||
401 | // We remove the segments added | ||
402 | current_path_segments.truncate(prev_len); | ||
403 | action | ||
404 | } | ||
405 | |||
406 | fn best_action_for_target( | ||
407 | container: SyntaxNode, | ||
408 | anchor: SyntaxNode, | ||
409 | target: &[SmolStr], | ||
410 | ) -> ImportAction { | ||
411 | let mut storage = Vec::with_capacity(16); // this should be the only allocation | ||
412 | let best_action = container | ||
413 | .children() | ||
414 | .filter_map(ast::UseItem::cast) | ||
415 | .filter_map(|it| it.use_tree()) | ||
416 | .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) | ||
417 | .fold(None, |best, a| match best { | ||
418 | Some(best) => Some(ImportAction::better(best, a)), | ||
419 | None => Some(a), | ||
420 | }); | ||
421 | |||
422 | match best_action { | ||
423 | Some(action) => action, | ||
424 | None => { | ||
425 | // We have no action and no UseItem was found in container so we find | ||
426 | // another item and we use it as anchor. | ||
427 | // If there are no items above, we choose the target path itself as anchor. | ||
428 | // todo: we should include even whitespace blocks as anchor candidates | ||
429 | let anchor = container | ||
430 | .children() | ||
431 | .find(|n| n.text_range().start() < anchor.text_range().start()) | ||
432 | .or_else(|| Some(anchor)); | ||
433 | |||
434 | ImportAction::add_new_use(anchor, false) | ||
435 | } | ||
436 | } | ||
437 | } | ||
438 | |||
439 | fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { | ||
440 | match action { | ||
441 | ImportAction::AddNewUse { anchor, add_after_anchor } => { | ||
442 | make_assist_add_new_use(anchor, *add_after_anchor, target, edit) | ||
443 | } | ||
444 | ImportAction::AddInTreeList { common_segments, tree_list, add_self } => { | ||
445 | // We know that the fist n segments already exists in the use statement we want | ||
446 | // to modify, so we want to add only the last target.len() - n segments. | ||
447 | let segments_to_add = target.split_at(*common_segments).1; | ||
448 | make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit) | ||
449 | } | ||
450 | ImportAction::AddNestedImport { | ||
451 | common_segments, | ||
452 | path_to_split, | ||
453 | first_segment_to_split, | ||
454 | add_self, | ||
455 | } => { | ||
456 | let segments_to_add = target.split_at(*common_segments).1; | ||
457 | make_assist_add_nested_import( | ||
458 | path_to_split, | ||
459 | first_segment_to_split, | ||
460 | segments_to_add, | ||
461 | *add_self, | ||
462 | edit, | ||
463 | ) | ||
464 | } | ||
465 | _ => {} | ||
466 | } | ||
467 | } | ||
468 | |||
469 | fn make_assist_add_new_use( | ||
470 | anchor: &Option<SyntaxNode>, | ||
471 | after: bool, | ||
472 | target: &[SmolStr], | ||
473 | edit: &mut TextEditBuilder, | ||
474 | ) { | ||
475 | if let Some(anchor) = anchor { | ||
476 | let indent = ra_fmt::leading_indent(anchor); | ||
477 | let mut buf = String::new(); | ||
478 | if after { | ||
479 | buf.push_str("\n"); | ||
480 | if let Some(spaces) = &indent { | ||
481 | buf.push_str(spaces); | ||
482 | } | ||
483 | } | ||
484 | buf.push_str("use "); | ||
485 | fmt_segments_raw(target, &mut buf); | ||
486 | buf.push_str(";"); | ||
487 | if !after { | ||
488 | buf.push_str("\n\n"); | ||
489 | if let Some(spaces) = &indent { | ||
490 | buf.push_str(&spaces); | ||
491 | } | ||
492 | } | ||
493 | let position = if after { anchor.text_range().end() } else { anchor.text_range().start() }; | ||
494 | edit.insert(position, buf); | ||
495 | } | ||
496 | } | ||
497 | |||
498 | fn make_assist_add_in_tree_list( | ||
499 | tree_list: &ast::UseTreeList, | ||
500 | target: &[SmolStr], | ||
501 | add_self: bool, | ||
502 | edit: &mut TextEditBuilder, | ||
503 | ) { | ||
504 | let last = tree_list.use_trees().last(); | ||
505 | if let Some(last) = last { | ||
506 | let mut buf = String::new(); | ||
507 | let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]); | ||
508 | let offset = if let Some(comma) = comma { | ||
509 | comma.text_range().end() | ||
510 | } else { | ||
511 | buf.push_str(","); | ||
512 | last.syntax().text_range().end() | ||
513 | }; | ||
514 | if add_self { | ||
515 | buf.push_str(" self") | ||
516 | } else { | ||
517 | buf.push_str(" "); | ||
518 | } | ||
519 | fmt_segments_raw(target, &mut buf); | ||
520 | edit.insert(offset, buf); | ||
521 | } else { | ||
522 | } | ||
523 | } | ||
524 | |||
525 | fn make_assist_add_nested_import( | ||
526 | path: &ast::Path, | ||
527 | first_segment_to_split: &Option<ast::PathSegment>, | ||
528 | target: &[SmolStr], | ||
529 | add_self: bool, | ||
530 | edit: &mut TextEditBuilder, | ||
531 | ) { | ||
532 | let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); | ||
533 | if let Some(use_tree) = use_tree { | ||
534 | let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split | ||
535 | { | ||
536 | (first_segment_to_split.syntax().text_range().start(), false) | ||
537 | } else { | ||
538 | (use_tree.syntax().text_range().end(), true) | ||
539 | }; | ||
540 | let end = use_tree.syntax().text_range().end(); | ||
541 | |||
542 | let mut buf = String::new(); | ||
543 | if add_colon_colon { | ||
544 | buf.push_str("::"); | ||
545 | } | ||
546 | buf.push_str("{"); | ||
547 | if add_self { | ||
548 | buf.push_str("self, "); | ||
549 | } | ||
550 | fmt_segments_raw(target, &mut buf); | ||
551 | if !target.is_empty() { | ||
552 | buf.push_str(", "); | ||
553 | } | ||
554 | edit.insert(start, buf); | ||
555 | edit.insert(end, "}".to_string()); | ||
556 | } | ||
557 | } | ||
558 | |||
559 | fn replace_with_use( | ||
560 | container: &SyntaxNode, | ||
561 | path: &ast::Path, | ||
562 | target: &[SmolStr], | ||
563 | edit: &mut TextEditBuilder, | ||
564 | ) { | ||
565 | let action = best_action_for_target(container.clone(), path.syntax().clone(), target); | ||
566 | make_assist(&action, target, edit); | ||
567 | if let Some(last) = path.segment() { | ||
568 | // Here we are assuming the assist will provide a correct use statement | ||
569 | // so we can delete the path qualifier | ||
570 | edit.delete(TextRange::from_to( | ||
571 | path.syntax().text_range().start(), | ||
572 | last.syntax().text_range().start(), | ||
573 | )); | ||
574 | } | ||
575 | } | ||
576 | |||
577 | fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { | ||
578 | let mut ps = Vec::<SmolStr>::with_capacity(10); | ||
579 | match path.kind() { | ||
580 | hir::PathKind::Abs => ps.push("".into()), | ||
581 | hir::PathKind::Crate => ps.push("crate".into()), | ||
582 | hir::PathKind::Plain => {} | ||
583 | hir::PathKind::Super(0) => ps.push("self".into()), | ||
584 | hir::PathKind::Super(lvl) => { | ||
585 | let mut chain = "super".to_string(); | ||
586 | for _ in 0..*lvl { | ||
587 | chain += "::super"; | ||
588 | } | ||
589 | ps.push(chain.into()); | ||
590 | } | ||
591 | hir::PathKind::DollarCrate(_) => return None, | ||
592 | } | ||
593 | ps.extend(path.segments().iter().map(|it| it.name.to_string().into())); | ||
594 | Some(ps) | ||
595 | } | ||
596 | |||
597 | #[cfg(test)] | ||
598 | mod tests { | ||
599 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
600 | |||
601 | use super::*; | ||
602 | |||
603 | #[test] | ||
604 | fn test_replace_add_use_no_anchor() { | ||
605 | check_assist( | ||
606 | replace_qualified_name_with_use, | ||
607 | " | ||
608 | std::fmt::Debug<|> | ||
609 | ", | ||
610 | " | ||
611 | use std::fmt::Debug; | ||
612 | |||
613 | Debug<|> | ||
614 | ", | ||
615 | ); | ||
616 | } | ||
617 | #[test] | ||
618 | fn test_replace_add_use_no_anchor_with_item_below() { | ||
619 | check_assist( | ||
620 | replace_qualified_name_with_use, | ||
621 | " | ||
622 | std::fmt::Debug<|> | ||
623 | |||
624 | fn main() { | ||
625 | } | ||
626 | ", | ||
627 | " | ||
628 | use std::fmt::Debug; | ||
629 | |||
630 | Debug<|> | ||
631 | |||
632 | fn main() { | ||
633 | } | ||
634 | ", | ||
635 | ); | ||
636 | } | ||
637 | |||
638 | #[test] | ||
639 | fn test_replace_add_use_no_anchor_with_item_above() { | ||
640 | check_assist( | ||
641 | replace_qualified_name_with_use, | ||
642 | " | ||
643 | fn main() { | ||
644 | } | ||
645 | |||
646 | std::fmt::Debug<|> | ||
647 | ", | ||
648 | " | ||
649 | use std::fmt::Debug; | ||
650 | |||
651 | fn main() { | ||
652 | } | ||
653 | |||
654 | Debug<|> | ||
655 | ", | ||
656 | ); | ||
657 | } | ||
658 | |||
659 | #[test] | ||
660 | fn test_replace_add_use_no_anchor_2seg() { | ||
661 | check_assist( | ||
662 | replace_qualified_name_with_use, | ||
663 | " | ||
664 | std::fmt<|>::Debug | ||
665 | ", | ||
666 | " | ||
667 | use std::fmt; | ||
668 | |||
669 | fmt<|>::Debug | ||
670 | ", | ||
671 | ); | ||
672 | } | ||
673 | |||
674 | #[test] | ||
675 | fn test_replace_add_use() { | ||
676 | check_assist( | ||
677 | replace_qualified_name_with_use, | ||
678 | " | ||
679 | use stdx; | ||
680 | |||
681 | impl std::fmt::Debug<|> for Foo { | ||
682 | } | ||
683 | ", | ||
684 | " | ||
685 | use stdx; | ||
686 | use std::fmt::Debug; | ||
687 | |||
688 | impl Debug<|> for Foo { | ||
689 | } | ||
690 | ", | ||
691 | ); | ||
692 | } | ||
693 | |||
694 | #[test] | ||
695 | fn test_replace_file_use_other_anchor() { | ||
696 | check_assist( | ||
697 | replace_qualified_name_with_use, | ||
698 | " | ||
699 | impl std::fmt::Debug<|> for Foo { | ||
700 | } | ||
701 | ", | ||
702 | " | ||
703 | use std::fmt::Debug; | ||
704 | |||
705 | impl Debug<|> for Foo { | ||
706 | } | ||
707 | ", | ||
708 | ); | ||
709 | } | ||
710 | |||
711 | #[test] | ||
712 | fn test_replace_add_use_other_anchor_indent() { | ||
713 | check_assist( | ||
714 | replace_qualified_name_with_use, | ||
715 | " | ||
716 | impl std::fmt::Debug<|> for Foo { | ||
717 | } | ||
718 | ", | ||
719 | " | ||
720 | use std::fmt::Debug; | ||
721 | |||
722 | impl Debug<|> for Foo { | ||
723 | } | ||
724 | ", | ||
725 | ); | ||
726 | } | ||
727 | |||
728 | #[test] | ||
729 | fn test_replace_split_different() { | ||
730 | check_assist( | ||
731 | replace_qualified_name_with_use, | ||
732 | " | ||
733 | use std::fmt; | ||
734 | |||
735 | impl std::io<|> for Foo { | ||
736 | } | ||
737 | ", | ||
738 | " | ||
739 | use std::{io, fmt}; | ||
740 | |||
741 | impl io<|> for Foo { | ||
742 | } | ||
743 | ", | ||
744 | ); | ||
745 | } | ||
746 | |||
747 | #[test] | ||
748 | fn test_replace_split_self_for_use() { | ||
749 | check_assist( | ||
750 | replace_qualified_name_with_use, | ||
751 | " | ||
752 | use std::fmt; | ||
753 | |||
754 | impl std::fmt::Debug<|> for Foo { | ||
755 | } | ||
756 | ", | ||
757 | " | ||
758 | use std::fmt::{self, Debug, }; | ||
759 | |||
760 | impl Debug<|> for Foo { | ||
761 | } | ||
762 | ", | ||
763 | ); | ||
764 | } | ||
765 | |||
766 | #[test] | ||
767 | fn test_replace_split_self_for_target() { | ||
768 | check_assist( | ||
769 | replace_qualified_name_with_use, | ||
770 | " | ||
771 | use std::fmt::Debug; | ||
772 | |||
773 | impl std::fmt<|> for Foo { | ||
774 | } | ||
775 | ", | ||
776 | " | ||
777 | use std::fmt::{self, Debug}; | ||
778 | |||
779 | impl fmt<|> for Foo { | ||
780 | } | ||
781 | ", | ||
782 | ); | ||
783 | } | ||
784 | |||
785 | #[test] | ||
786 | fn test_replace_add_to_nested_self_nested() { | ||
787 | check_assist( | ||
788 | replace_qualified_name_with_use, | ||
789 | " | ||
790 | use std::fmt::{Debug, nested::{Display}}; | ||
791 | |||
792 | impl std::fmt::nested<|> for Foo { | ||
793 | } | ||
794 | ", | ||
795 | " | ||
796 | use std::fmt::{Debug, nested::{Display, self}}; | ||
797 | |||
798 | impl nested<|> for Foo { | ||
799 | } | ||
800 | ", | ||
801 | ); | ||
802 | } | ||
803 | |||
804 | #[test] | ||
805 | fn test_replace_add_to_nested_self_already_included() { | ||
806 | check_assist( | ||
807 | replace_qualified_name_with_use, | ||
808 | " | ||
809 | use std::fmt::{Debug, nested::{self, Display}}; | ||
810 | |||
811 | impl std::fmt::nested<|> for Foo { | ||
812 | } | ||
813 | ", | ||
814 | " | ||
815 | use std::fmt::{Debug, nested::{self, Display}}; | ||
816 | |||
817 | impl nested<|> for Foo { | ||
818 | } | ||
819 | ", | ||
820 | ); | ||
821 | } | ||
822 | |||
823 | #[test] | ||
824 | fn test_replace_add_to_nested_nested() { | ||
825 | check_assist( | ||
826 | replace_qualified_name_with_use, | ||
827 | " | ||
828 | use std::fmt::{Debug, nested::{Display}}; | ||
829 | |||
830 | impl std::fmt::nested::Debug<|> for Foo { | ||
831 | } | ||
832 | ", | ||
833 | " | ||
834 | use std::fmt::{Debug, nested::{Display, Debug}}; | ||
835 | |||
836 | impl Debug<|> for Foo { | ||
837 | } | ||
838 | ", | ||
839 | ); | ||
840 | } | ||
841 | |||
842 | #[test] | ||
843 | fn test_replace_split_common_target_longer() { | ||
844 | check_assist( | ||
845 | replace_qualified_name_with_use, | ||
846 | " | ||
847 | use std::fmt::Debug; | ||
848 | |||
849 | impl std::fmt::nested::Display<|> for Foo { | ||
850 | } | ||
851 | ", | ||
852 | " | ||
853 | use std::fmt::{nested::Display, Debug}; | ||
854 | |||
855 | impl Display<|> for Foo { | ||
856 | } | ||
857 | ", | ||
858 | ); | ||
859 | } | ||
860 | |||
861 | #[test] | ||
862 | fn test_replace_split_common_use_longer() { | ||
863 | check_assist( | ||
864 | replace_qualified_name_with_use, | ||
865 | " | ||
866 | use std::fmt::nested::Debug; | ||
867 | |||
868 | impl std::fmt::Display<|> for Foo { | ||
869 | } | ||
870 | ", | ||
871 | " | ||
872 | use std::fmt::{Display, nested::Debug}; | ||
873 | |||
874 | impl Display<|> for Foo { | ||
875 | } | ||
876 | ", | ||
877 | ); | ||
878 | } | ||
879 | |||
880 | #[test] | ||
881 | fn test_replace_use_nested_import() { | ||
882 | check_assist( | ||
883 | replace_qualified_name_with_use, | ||
884 | " | ||
885 | use crate::{ | ||
886 | ty::{Substs, Ty}, | ||
887 | AssocItem, | ||
888 | }; | ||
889 | |||
890 | fn foo() { crate::ty::lower<|>::trait_env() } | ||
891 | ", | ||
892 | " | ||
893 | use crate::{ | ||
894 | ty::{Substs, Ty, lower}, | ||
895 | AssocItem, | ||
896 | }; | ||
897 | |||
898 | fn foo() { lower<|>::trait_env() } | ||
899 | ", | ||
900 | ); | ||
901 | } | ||
902 | |||
903 | #[test] | ||
904 | fn test_replace_alias() { | ||
905 | check_assist( | ||
906 | replace_qualified_name_with_use, | ||
907 | " | ||
908 | use std::fmt as foo; | ||
909 | |||
910 | impl foo::Debug<|> for Foo { | ||
911 | } | ||
912 | ", | ||
913 | " | ||
914 | use std::fmt as foo; | ||
915 | |||
916 | impl Debug<|> for Foo { | ||
917 | } | ||
918 | ", | ||
919 | ); | ||
920 | } | ||
921 | |||
922 | #[test] | ||
923 | fn test_replace_not_applicable_one_segment() { | ||
924 | check_assist_not_applicable( | ||
925 | replace_qualified_name_with_use, | ||
926 | " | ||
927 | impl foo<|> for Foo { | ||
928 | } | ||
929 | ", | ||
930 | ); | ||
931 | } | ||
932 | |||
933 | #[test] | ||
934 | fn test_replace_not_applicable_in_use() { | ||
935 | check_assist_not_applicable( | ||
936 | replace_qualified_name_with_use, | ||
937 | " | ||
938 | use std::fmt<|>; | ||
939 | ", | ||
940 | ); | ||
941 | } | ||
942 | |||
943 | #[test] | ||
944 | fn test_replace_add_use_no_anchor_in_mod_mod() { | ||
945 | check_assist( | ||
946 | replace_qualified_name_with_use, | ||
947 | " | ||
948 | mod foo { | ||
949 | mod bar { | ||
950 | std::fmt::Debug<|> | ||
951 | } | ||
952 | } | ||
953 | ", | ||
954 | " | ||
955 | mod foo { | ||
956 | mod bar { | ||
957 | use std::fmt::Debug; | ||
958 | |||
959 | Debug<|> | ||
960 | } | ||
961 | } | ||
962 | ", | ||
963 | ); | ||
964 | } | ||
965 | } | ||
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 | } | ||