aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assist_ctx.rs1
-rw-r--r--crates/ra_assists/src/assists/early_return.rs276
-rw-r--r--crates/ra_assists/src/assists/fill_match_arms.rs18
-rw-r--r--crates/ra_assists/src/assists/move_bounds.rs2
-rw-r--r--crates/ra_assists/src/assists/raw_string.rs110
-rw-r--r--crates/ra_assists/src/lib.rs2
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
3use crate::{
4 assist_ctx::{Assist, AssistCtx},
5 AssistId,
6};
7use hir::db::HirDatabase;
8use 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};
19use std::ops::RangeInclusive;
20
21pub(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)]
96mod 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 @@
3use std::iter; 3use std::iter;
4 4
5use hir::{db::HirDatabase, Adt, HasSource}; 5use hir::{db::HirDatabase, Adt, HasSource};
6use ra_syntax::ast::{self, make, AstNode, NameOwner}; 6use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use 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
3use hir::db::HirDatabase; 3use hir::db::HirDatabase;
4use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit}; 4use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit};
5use rustc_lexer;
5 6
6use crate::{Assist, AssistCtx, AssistId}; 7use 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
47fn 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
20fn find_usual_string_range(s: &str) -> Option<TextRange> { 59fn 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
155string"#;
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##
173string"#;
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"##
191string"###;
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}