diff options
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 1 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/early_return.rs | 276 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/fill_match_arms.rs | 18 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/move_bounds.rs | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/assists/raw_string.rs | 110 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 |
6 files changed, 383 insertions, 26 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index 189cad7d0..e270c5d60 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -138,6 +138,7 @@ impl AssistBuilder { | |||
138 | 138 | ||
139 | /// Replaces specified `node` of text with a given string, reindenting the | 139 | /// Replaces specified `node` of text with a given string, reindenting the |
140 | /// string to maintain `node`'s existing indent. | 140 | /// string to maintain `node`'s existing indent. |
141 | // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent | ||
141 | pub(crate) fn replace_node_and_indent( | 142 | pub(crate) fn replace_node_and_indent( |
142 | &mut self, | 143 | &mut self, |
143 | node: &SyntaxNode, | 144 | node: &SyntaxNode, |
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs new file mode 100644 index 000000000..8c975714c --- /dev/null +++ b/crates/ra_assists/src/assists/early_return.rs | |||
@@ -0,0 +1,276 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::{ | ||
4 | assist_ctx::{Assist, AssistCtx}, | ||
5 | AssistId, | ||
6 | }; | ||
7 | use hir::db::HirDatabase; | ||
8 | use ra_syntax::{ | ||
9 | algo::replace_children, | ||
10 | ast::edit::IndentLevel, | ||
11 | ast::make, | ||
12 | ast::Block, | ||
13 | ast::ContinueExpr, | ||
14 | ast::IfExpr, | ||
15 | ast::ReturnExpr, | ||
16 | AstNode, | ||
17 | SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, | ||
18 | }; | ||
19 | use std::ops::RangeInclusive; | ||
20 | |||
21 | pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
22 | let if_expr: IfExpr = ctx.node_at_offset()?; | ||
23 | let expr = if_expr.condition()?.expr()?; | ||
24 | let then_block = if_expr.then_branch()?.block()?; | ||
25 | if if_expr.else_branch().is_some() { | ||
26 | return None; | ||
27 | } | ||
28 | |||
29 | let parent_block = if_expr.syntax().parent()?.ancestors().find_map(Block::cast)?; | ||
30 | |||
31 | if parent_block.expr()? != if_expr.clone().into() { | ||
32 | return None; | ||
33 | } | ||
34 | |||
35 | // check for early return and continue | ||
36 | let first_in_then_block = then_block.syntax().first_child()?.clone(); | ||
37 | if ReturnExpr::can_cast(first_in_then_block.kind()) | ||
38 | || ContinueExpr::can_cast(first_in_then_block.kind()) | ||
39 | || first_in_then_block | ||
40 | .children() | ||
41 | .any(|x| ReturnExpr::can_cast(x.kind()) || ContinueExpr::can_cast(x.kind())) | ||
42 | { | ||
43 | return None; | ||
44 | } | ||
45 | |||
46 | let parent_container = parent_block.syntax().parent()?.parent()?; | ||
47 | |||
48 | let early_expression = match parent_container.kind() { | ||
49 | WHILE_EXPR | LOOP_EXPR => Some("continue;"), | ||
50 | FN_DEF => Some("return;"), | ||
51 | _ => None, | ||
52 | }?; | ||
53 | |||
54 | if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() { | ||
55 | return None; | ||
56 | } | ||
57 | |||
58 | then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; | ||
59 | let cursor_position = ctx.frange.range.start(); | ||
60 | |||
61 | ctx.add_action(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { | ||
62 | let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); | ||
63 | let new_if_expr = | ||
64 | if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); | ||
65 | let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone()); | ||
66 | let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); | ||
67 | let end_of_then = | ||
68 | if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { | ||
69 | end_of_then.prev_sibling_or_token().unwrap() | ||
70 | } else { | ||
71 | end_of_then | ||
72 | }; | ||
73 | let mut new_if_and_then_statements = new_if_expr.syntax().children_with_tokens().chain( | ||
74 | then_block_items | ||
75 | .syntax() | ||
76 | .children_with_tokens() | ||
77 | .skip(1) | ||
78 | .take_while(|i| *i != end_of_then), | ||
79 | ); | ||
80 | let new_block = replace_children( | ||
81 | &parent_block.syntax(), | ||
82 | RangeInclusive::new( | ||
83 | if_expr.clone().syntax().clone().into(), | ||
84 | if_expr.syntax().clone().into(), | ||
85 | ), | ||
86 | &mut new_if_and_then_statements, | ||
87 | ); | ||
88 | edit.target(if_expr.syntax().text_range()); | ||
89 | edit.replace_ast(parent_block, Block::cast(new_block).unwrap()); | ||
90 | edit.set_cursor(cursor_position); | ||
91 | }); | ||
92 | ctx.build() | ||
93 | } | ||
94 | |||
95 | #[cfg(test)] | ||
96 | mod tests { | ||
97 | use super::*; | ||
98 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
99 | |||
100 | #[test] | ||
101 | fn convert_inside_fn() { | ||
102 | check_assist( | ||
103 | convert_to_guarded_return, | ||
104 | r#" | ||
105 | fn main() { | ||
106 | bar(); | ||
107 | if<|> true { | ||
108 | foo(); | ||
109 | |||
110 | //comment | ||
111 | bar(); | ||
112 | } | ||
113 | } | ||
114 | "#, | ||
115 | r#" | ||
116 | fn main() { | ||
117 | bar(); | ||
118 | if<|> !true { | ||
119 | return; | ||
120 | } | ||
121 | foo(); | ||
122 | |||
123 | //comment | ||
124 | bar(); | ||
125 | } | ||
126 | "#, | ||
127 | ); | ||
128 | } | ||
129 | |||
130 | #[test] | ||
131 | fn convert_inside_while() { | ||
132 | check_assist( | ||
133 | convert_to_guarded_return, | ||
134 | r#" | ||
135 | fn main() { | ||
136 | while true { | ||
137 | if<|> true { | ||
138 | foo(); | ||
139 | bar(); | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | "#, | ||
144 | r#" | ||
145 | fn main() { | ||
146 | while true { | ||
147 | if<|> !true { | ||
148 | continue; | ||
149 | } | ||
150 | foo(); | ||
151 | bar(); | ||
152 | } | ||
153 | } | ||
154 | "#, | ||
155 | ); | ||
156 | } | ||
157 | |||
158 | #[test] | ||
159 | fn convert_inside_loop() { | ||
160 | check_assist( | ||
161 | convert_to_guarded_return, | ||
162 | r#" | ||
163 | fn main() { | ||
164 | loop { | ||
165 | if<|> true { | ||
166 | foo(); | ||
167 | bar(); | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | "#, | ||
172 | r#" | ||
173 | fn main() { | ||
174 | loop { | ||
175 | if<|> !true { | ||
176 | continue; | ||
177 | } | ||
178 | foo(); | ||
179 | bar(); | ||
180 | } | ||
181 | } | ||
182 | "#, | ||
183 | ); | ||
184 | } | ||
185 | |||
186 | #[test] | ||
187 | fn ignore_already_converted_if() { | ||
188 | check_assist_not_applicable( | ||
189 | convert_to_guarded_return, | ||
190 | r#" | ||
191 | fn main() { | ||
192 | if<|> true { | ||
193 | return; | ||
194 | } | ||
195 | } | ||
196 | "#, | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | #[test] | ||
201 | fn ignore_already_converted_loop() { | ||
202 | check_assist_not_applicable( | ||
203 | convert_to_guarded_return, | ||
204 | r#" | ||
205 | fn main() { | ||
206 | loop { | ||
207 | if<|> true { | ||
208 | continue; | ||
209 | } | ||
210 | } | ||
211 | } | ||
212 | "#, | ||
213 | ); | ||
214 | } | ||
215 | |||
216 | #[test] | ||
217 | fn ignore_return() { | ||
218 | check_assist_not_applicable( | ||
219 | convert_to_guarded_return, | ||
220 | r#" | ||
221 | fn main() { | ||
222 | if<|> true { | ||
223 | return | ||
224 | } | ||
225 | } | ||
226 | "#, | ||
227 | ); | ||
228 | } | ||
229 | |||
230 | #[test] | ||
231 | fn ignore_else_branch() { | ||
232 | check_assist_not_applicable( | ||
233 | convert_to_guarded_return, | ||
234 | r#" | ||
235 | fn main() { | ||
236 | if<|> true { | ||
237 | foo(); | ||
238 | } else { | ||
239 | bar() | ||
240 | } | ||
241 | } | ||
242 | "#, | ||
243 | ); | ||
244 | } | ||
245 | |||
246 | #[test] | ||
247 | fn ignore_statements_aftert_if() { | ||
248 | check_assist_not_applicable( | ||
249 | convert_to_guarded_return, | ||
250 | r#" | ||
251 | fn main() { | ||
252 | if<|> true { | ||
253 | foo(); | ||
254 | } | ||
255 | bar(); | ||
256 | } | ||
257 | "#, | ||
258 | ); | ||
259 | } | ||
260 | |||
261 | #[test] | ||
262 | fn ignore_statements_inside_if() { | ||
263 | check_assist_not_applicable( | ||
264 | convert_to_guarded_return, | ||
265 | r#" | ||
266 | fn main() { | ||
267 | if false { | ||
268 | if<|> true { | ||
269 | foo(); | ||
270 | } | ||
271 | } | ||
272 | } | ||
273 | "#, | ||
274 | ); | ||
275 | } | ||
276 | } | ||
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs index 7335cce09..e3f30b5de 100644 --- a/crates/ra_assists/src/assists/fill_match_arms.rs +++ b/crates/ra_assists/src/assists/fill_match_arms.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use std::iter; | 3 | use std::iter; |
4 | 4 | ||
5 | use hir::{db::HirDatabase, Adt, HasSource}; | 5 | use hir::{db::HirDatabase, Adt, HasSource}; |
6 | use ra_syntax::ast::{self, make, AstNode, NameOwner}; | 6 | use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner}; |
7 | 7 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 8 | use crate::{Assist, AssistCtx, AssistId}; |
9 | 9 | ||
@@ -30,15 +30,19 @@ pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
30 | let variant_list = enum_def.variant_list()?; | 30 | let variant_list = enum_def.variant_list()?; |
31 | 31 | ||
32 | ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { | 32 | ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| { |
33 | let variants = variant_list.variants(); | 33 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); |
34 | let arms = variants | 34 | |
35 | .filter_map(build_pat) | 35 | let new_arm_list = { |
36 | .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())); | 36 | let variants = variant_list.variants(); |
37 | let new_arm_list = make::match_arm_list(arms); | 37 | let arms = variants |
38 | .filter_map(build_pat) | ||
39 | .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())); | ||
40 | indent_level.increase_indent(make::match_arm_list(arms)) | ||
41 | }; | ||
38 | 42 | ||
39 | edit.target(match_expr.syntax().text_range()); | 43 | edit.target(match_expr.syntax().text_range()); |
40 | edit.set_cursor(expr.syntax().text_range().start()); | 44 | edit.set_cursor(expr.syntax().text_range().start()); |
41 | edit.replace_node_and_indent(match_arm_list.syntax(), new_arm_list.syntax().text()); | 45 | edit.replace_ast(match_arm_list, new_arm_list); |
42 | }); | 46 | }); |
43 | 47 | ||
44 | ctx.build() | 48 | ctx.build() |
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs index f791d22b0..d2444b6b9 100644 --- a/crates/ra_assists/src/assists/move_bounds.rs +++ b/crates/ra_assists/src/assists/move_bounds.rs | |||
@@ -18,7 +18,7 @@ pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) | |||
18 | } | 18 | } |
19 | 19 | ||
20 | let parent = type_param_list.syntax().parent()?; | 20 | let parent = type_param_list.syntax().parent()?; |
21 | if parent.children_with_tokens().find(|it| it.kind() == WHERE_CLAUSE).is_some() { | 21 | if parent.children_with_tokens().any(|it| it.kind() == WHERE_CLAUSE) { |
22 | return None; | 22 | return None; |
23 | } | 23 | } |
24 | 24 | ||
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs index 388ee7e97..2d2e31e51 100644 --- a/crates/ra_assists/src/assists/raw_string.rs +++ b/crates/ra_assists/src/assists/raw_string.rs | |||
@@ -2,6 +2,7 @@ | |||
2 | 2 | ||
3 | use hir::db::HirDatabase; | 3 | use hir::db::HirDatabase; |
4 | use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; | 4 | use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; |
5 | use rustc_lexer; | ||
5 | 6 | ||
6 | use crate::{Assist, AssistCtx, AssistId}; | 7 | use crate::{Assist, AssistCtx, AssistId}; |
7 | 8 | ||
@@ -10,13 +11,51 @@ pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<As | |||
10 | if literal.token().kind() != ra_syntax::SyntaxKind::STRING { | 11 | if literal.token().kind() != ra_syntax::SyntaxKind::STRING { |
11 | return None; | 12 | return None; |
12 | } | 13 | } |
14 | let token = literal.token(); | ||
15 | let text = token.text().as_str(); | ||
16 | let usual_string_range = find_usual_string_range(text)?; | ||
17 | let start_of_inside = usual_string_range.start().to_usize() + 1; | ||
18 | let end_of_inside = usual_string_range.end().to_usize(); | ||
19 | let inside_str = &text[start_of_inside..end_of_inside]; | ||
20 | let mut unescaped = String::with_capacity(inside_str.len()); | ||
21 | let mut error = Ok(()); | ||
22 | rustc_lexer::unescape::unescape_str( | ||
23 | inside_str, | ||
24 | &mut |_, unescaped_char| match unescaped_char { | ||
25 | Ok(c) => unescaped.push(c), | ||
26 | Err(_) => error = Err(()), | ||
27 | }, | ||
28 | ); | ||
29 | if error.is_err() { | ||
30 | return None; | ||
31 | } | ||
13 | ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { | 32 | ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| { |
14 | edit.target(literal.syntax().text_range()); | 33 | edit.target(literal.syntax().text_range()); |
15 | edit.insert(literal.syntax().text_range().start(), "r"); | 34 | let max_hash_streak = count_hashes(&unescaped); |
35 | let mut hashes = String::with_capacity(max_hash_streak + 1); | ||
36 | for _ in 0..hashes.capacity() { | ||
37 | hashes.push('#'); | ||
38 | } | ||
39 | edit.replace( | ||
40 | literal.syntax().text_range(), | ||
41 | format!("r{}\"{}\"{}", hashes, unescaped, hashes), | ||
42 | ); | ||
16 | }); | 43 | }); |
17 | ctx.build() | 44 | ctx.build() |
18 | } | 45 | } |
19 | 46 | ||
47 | fn count_hashes(s: &str) -> usize { | ||
48 | let mut max_hash_streak = 0usize; | ||
49 | for idx in s.match_indices("\"#").map(|(i, _)| i) { | ||
50 | let (_, sub) = s.split_at(idx + 1); | ||
51 | let nb_hash = sub.chars().take_while(|c| *c == '#').count(); | ||
52 | if nb_hash > max_hash_streak { | ||
53 | max_hash_streak = nb_hash; | ||
54 | } | ||
55 | } | ||
56 | max_hash_streak | ||
57 | } | ||
58 | |||
20 | fn find_usual_string_range(s: &str) -> Option<TextRange> { | 59 | fn find_usual_string_range(s: &str) -> Option<TextRange> { |
21 | Some(TextRange::from_to( | 60 | Some(TextRange::from_to( |
22 | TextUnit::from(s.find('"')? as u32), | 61 | TextUnit::from(s.find('"')? as u32), |
@@ -94,10 +133,10 @@ mod test { | |||
94 | make_raw_string, | 133 | make_raw_string, |
95 | r#" | 134 | r#" |
96 | fn f() { | 135 | fn f() { |
97 | let s = <|>"random string"; | 136 | let s = <|>"random\nstring"; |
98 | } | 137 | } |
99 | "#, | 138 | "#, |
100 | r#""random string""#, | 139 | r#""random\nstring""#, |
101 | ); | 140 | ); |
102 | } | 141 | } |
103 | 142 | ||
@@ -107,44 +146,69 @@ mod test { | |||
107 | make_raw_string, | 146 | make_raw_string, |
108 | r#" | 147 | r#" |
109 | fn f() { | 148 | fn f() { |
110 | let s = <|>"random string"; | 149 | let s = <|>"random\nstring"; |
111 | } | 150 | } |
112 | "#, | 151 | "#, |
113 | r#" | 152 | r##" |
114 | fn f() { | 153 | fn f() { |
115 | let s = <|>r"random string"; | 154 | let s = <|>r#"random |
155 | string"#; | ||
116 | } | 156 | } |
117 | "#, | 157 | "##, |
118 | ) | 158 | ) |
119 | } | 159 | } |
120 | 160 | ||
121 | #[test] | 161 | #[test] |
122 | fn make_raw_string_with_escaped_works() { | 162 | fn make_raw_string_hashes_inside_works() { |
123 | check_assist( | 163 | check_assist( |
124 | make_raw_string, | 164 | make_raw_string, |
125 | r#" | 165 | r###" |
126 | fn f() { | 166 | fn f() { |
127 | let s = <|>"random\nstring"; | 167 | let s = <|>"#random##\nstring"; |
128 | } | 168 | } |
129 | "#, | 169 | "###, |
130 | r#" | 170 | r####" |
131 | fn f() { | 171 | fn f() { |
132 | let s = <|>r"random\nstring"; | 172 | let s = <|>r#"#random## |
173 | string"#; | ||
133 | } | 174 | } |
134 | "#, | 175 | "####, |
135 | ) | 176 | ) |
136 | } | 177 | } |
137 | 178 | ||
138 | #[test] | 179 | #[test] |
139 | fn make_raw_string_not_works() { | 180 | fn make_raw_string_closing_hashes_inside_works() { |
140 | check_assist_not_applicable( | 181 | check_assist( |
182 | make_raw_string, | ||
183 | r###" | ||
184 | fn f() { | ||
185 | let s = <|>"#random\"##\nstring"; | ||
186 | } | ||
187 | "###, | ||
188 | r####" | ||
189 | fn f() { | ||
190 | let s = <|>r###"#random"## | ||
191 | string"###; | ||
192 | } | ||
193 | "####, | ||
194 | ) | ||
195 | } | ||
196 | |||
197 | #[test] | ||
198 | fn make_raw_string_nothing_to_unescape_works() { | ||
199 | check_assist( | ||
141 | make_raw_string, | 200 | make_raw_string, |
142 | r#" | 201 | r#" |
143 | fn f() { | 202 | fn f() { |
144 | let s = <|>r"random string"; | 203 | let s = <|>"random string"; |
145 | } | 204 | } |
146 | "#, | 205 | "#, |
147 | ); | 206 | r##" |
207 | fn f() { | ||
208 | let s = <|>r#"random string"#; | ||
209 | } | ||
210 | "##, | ||
211 | ) | ||
148 | } | 212 | } |
149 | 213 | ||
150 | #[test] | 214 | #[test] |
@@ -369,4 +433,14 @@ mod test { | |||
369 | "#, | 433 | "#, |
370 | ); | 434 | ); |
371 | } | 435 | } |
436 | |||
437 | #[test] | ||
438 | fn count_hashes_test() { | ||
439 | assert_eq!(0, count_hashes("abc")); | ||
440 | assert_eq!(0, count_hashes("###")); | ||
441 | assert_eq!(1, count_hashes("\"#abc")); | ||
442 | assert_eq!(0, count_hashes("#abc")); | ||
443 | assert_eq!(2, count_hashes("#ab\"##c")); | ||
444 | assert_eq!(4, count_hashes("#ab\"##\"####c")); | ||
445 | } | ||
372 | } | 446 | } |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index d2376c475..ab77b46a9 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -108,6 +108,7 @@ mod assists { | |||
108 | mod add_missing_impl_members; | 108 | mod add_missing_impl_members; |
109 | mod move_guard; | 109 | mod move_guard; |
110 | mod move_bounds; | 110 | mod move_bounds; |
111 | mod early_return; | ||
111 | 112 | ||
112 | pub(crate) fn all<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { | 113 | pub(crate) fn all<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { |
113 | &[ | 114 | &[ |
@@ -135,6 +136,7 @@ mod assists { | |||
135 | raw_string::make_raw_string, | 136 | raw_string::make_raw_string, |
136 | raw_string::make_usual_string, | 137 | raw_string::make_usual_string, |
137 | raw_string::remove_hash, | 138 | raw_string::remove_hash, |
139 | early_return::convert_to_guarded_return, | ||
138 | ] | 140 | ] |
139 | } | 141 | } |
140 | } | 142 | } |