diff options
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/handlers/add_turbo_fish.rs | 108 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/apply_demorgan.rs | 45 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/convert_comment_block.rs | 419 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/extract_variable.rs | 208 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/utils/suggest_name.rs | 729 |
7 files changed, 1504 insertions, 9 deletions
diff --git a/crates/ide_assists/src/handlers/add_turbo_fish.rs b/crates/ide_assists/src/handlers/add_turbo_fish.rs index 8e9ea4fad..a08b55ebb 100644 --- a/crates/ide_assists/src/handlers/add_turbo_fish.rs +++ b/crates/ide_assists/src/handlers/add_turbo_fish.rs | |||
@@ -31,6 +31,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
31 | return None; | 31 | return None; |
32 | } | 32 | } |
33 | mark::hit!(add_turbo_fish_after_call); | 33 | mark::hit!(add_turbo_fish_after_call); |
34 | mark::hit!(add_type_ascription_after_call); | ||
34 | arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT) | 35 | arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT) |
35 | })?; | 36 | })?; |
36 | let next_token = ident.next_token()?; | 37 | let next_token = ident.next_token()?; |
@@ -52,6 +53,24 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
52 | mark::hit!(add_turbo_fish_non_generic); | 53 | mark::hit!(add_turbo_fish_non_generic); |
53 | return None; | 54 | return None; |
54 | } | 55 | } |
56 | |||
57 | if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() { | ||
58 | if let_stmt.colon_token().is_none() { | ||
59 | let type_pos = let_stmt.pat()?.syntax().last_token()?.text_range().end(); | ||
60 | acc.add( | ||
61 | AssistId("add_type_ascription", AssistKind::RefactorRewrite), | ||
62 | "Add `: _` before assignment operator", | ||
63 | ident.text_range(), | ||
64 | |builder| match ctx.config.snippet_cap { | ||
65 | Some(cap) => builder.insert_snippet(cap, type_pos, ": ${0:_}"), | ||
66 | None => builder.insert(type_pos, ": _"), | ||
67 | }, | ||
68 | )? | ||
69 | } else { | ||
70 | mark::hit!(add_type_ascription_already_typed); | ||
71 | } | ||
72 | } | ||
73 | |||
55 | acc.add( | 74 | acc.add( |
56 | AssistId("add_turbo_fish", AssistKind::RefactorRewrite), | 75 | AssistId("add_turbo_fish", AssistKind::RefactorRewrite), |
57 | "Add `::<>`", | 76 | "Add `::<>`", |
@@ -65,7 +84,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
65 | 84 | ||
66 | #[cfg(test)] | 85 | #[cfg(test)] |
67 | mod tests { | 86 | mod tests { |
68 | use crate::tests::{check_assist, check_assist_not_applicable}; | 87 | use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable}; |
69 | 88 | ||
70 | use super::*; | 89 | use super::*; |
71 | use test_utils::mark; | 90 | use test_utils::mark; |
@@ -161,4 +180,91 @@ fn main() { | |||
161 | "#, | 180 | "#, |
162 | ); | 181 | ); |
163 | } | 182 | } |
183 | |||
184 | #[test] | ||
185 | fn add_type_ascription_function() { | ||
186 | check_assist_by_label( | ||
187 | add_turbo_fish, | ||
188 | r#" | ||
189 | fn make<T>() -> T {} | ||
190 | fn main() { | ||
191 | let x = make$0(); | ||
192 | } | ||
193 | "#, | ||
194 | r#" | ||
195 | fn make<T>() -> T {} | ||
196 | fn main() { | ||
197 | let x: ${0:_} = make(); | ||
198 | } | ||
199 | "#, | ||
200 | "Add `: _` before assignment operator", | ||
201 | ); | ||
202 | } | ||
203 | |||
204 | #[test] | ||
205 | fn add_type_ascription_after_call() { | ||
206 | mark::check!(add_type_ascription_after_call); | ||
207 | check_assist_by_label( | ||
208 | add_turbo_fish, | ||
209 | r#" | ||
210 | fn make<T>() -> T {} | ||
211 | fn main() { | ||
212 | let x = make()$0; | ||
213 | } | ||
214 | "#, | ||
215 | r#" | ||
216 | fn make<T>() -> T {} | ||
217 | fn main() { | ||
218 | let x: ${0:_} = make(); | ||
219 | } | ||
220 | "#, | ||
221 | "Add `: _` before assignment operator", | ||
222 | ); | ||
223 | } | ||
224 | |||
225 | #[test] | ||
226 | fn add_type_ascription_method() { | ||
227 | check_assist_by_label( | ||
228 | add_turbo_fish, | ||
229 | r#" | ||
230 | struct S; | ||
231 | impl S { | ||
232 | fn make<T>(&self) -> T {} | ||
233 | } | ||
234 | fn main() { | ||
235 | let x = S.make$0(); | ||
236 | } | ||
237 | "#, | ||
238 | r#" | ||
239 | struct S; | ||
240 | impl S { | ||
241 | fn make<T>(&self) -> T {} | ||
242 | } | ||
243 | fn main() { | ||
244 | let x: ${0:_} = S.make(); | ||
245 | } | ||
246 | "#, | ||
247 | "Add `: _` before assignment operator", | ||
248 | ); | ||
249 | } | ||
250 | |||
251 | #[test] | ||
252 | fn add_type_ascription_already_typed() { | ||
253 | mark::check!(add_type_ascription_already_typed); | ||
254 | check_assist( | ||
255 | add_turbo_fish, | ||
256 | r#" | ||
257 | fn make<T>() -> T {} | ||
258 | fn main() { | ||
259 | let x: () = make$0(); | ||
260 | } | ||
261 | "#, | ||
262 | r#" | ||
263 | fn make<T>() -> T {} | ||
264 | fn main() { | ||
265 | let x: () = make::<${0:_}>(); | ||
266 | } | ||
267 | "#, | ||
268 | ); | ||
269 | } | ||
164 | } | 270 | } |
diff --git a/crates/ide_assists/src/handlers/apply_demorgan.rs b/crates/ide_assists/src/handlers/apply_demorgan.rs index 6997ea048..128b1eb56 100644 --- a/crates/ide_assists/src/handlers/apply_demorgan.rs +++ b/crates/ide_assists/src/handlers/apply_demorgan.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | use syntax::ast::{self, AstNode}; | 1 | use syntax::ast::{self, AstNode}; |
2 | use test_utils::mark; | ||
2 | 3 | ||
3 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; | 4 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; |
4 | 5 | ||
@@ -43,9 +44,36 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
43 | "Apply De Morgan's law", | 44 | "Apply De Morgan's law", |
44 | op_range, | 45 | op_range, |
45 | |edit| { | 46 | |edit| { |
47 | let paren_expr = expr.syntax().parent().and_then(|parent| ast::ParenExpr::cast(parent)); | ||
48 | |||
49 | let neg_expr = paren_expr | ||
50 | .clone() | ||
51 | .and_then(|paren_expr| paren_expr.syntax().parent()) | ||
52 | .and_then(|parent| ast::PrefixExpr::cast(parent)) | ||
53 | .and_then(|prefix_expr| { | ||
54 | if prefix_expr.op_kind().unwrap() == ast::PrefixOp::Not { | ||
55 | Some(prefix_expr) | ||
56 | } else { | ||
57 | None | ||
58 | } | ||
59 | }); | ||
60 | |||
46 | edit.replace(op_range, opposite_op); | 61 | edit.replace(op_range, opposite_op); |
47 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | 62 | |
48 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | 63 | if let Some(paren_expr) = paren_expr { |
64 | edit.replace(lhs_range, not_lhs.syntax().text()); | ||
65 | edit.replace(rhs_range, not_rhs.syntax().text()); | ||
66 | if let Some(neg_expr) = neg_expr { | ||
67 | mark::hit!(demorgan_double_negation); | ||
68 | edit.replace(neg_expr.op_token().unwrap().text_range(), ""); | ||
69 | } else { | ||
70 | mark::hit!(demorgan_double_parens); | ||
71 | edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!("); | ||
72 | } | ||
73 | } else { | ||
74 | edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); | ||
75 | edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); | ||
76 | } | ||
49 | }, | 77 | }, |
50 | ) | 78 | ) |
51 | } | 79 | } |
@@ -62,6 +90,7 @@ fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> { | |||
62 | #[cfg(test)] | 90 | #[cfg(test)] |
63 | mod tests { | 91 | mod tests { |
64 | use ide_db::helpers::FamousDefs; | 92 | use ide_db::helpers::FamousDefs; |
93 | use test_utils::mark; | ||
65 | 94 | ||
66 | use super::*; | 95 | use super::*; |
67 | 96 | ||
@@ -156,4 +185,16 @@ fn f() { | |||
156 | fn demorgan_doesnt_apply_with_cursor_not_on_op() { | 185 | fn demorgan_doesnt_apply_with_cursor_not_on_op() { |
157 | check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }") | 186 | check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }") |
158 | } | 187 | } |
188 | |||
189 | #[test] | ||
190 | fn demorgan_doesnt_double_negation() { | ||
191 | mark::check!(demorgan_double_negation); | ||
192 | check_assist(apply_demorgan, "fn f() { !(x ||$0 x) }", "fn f() { (!x && !x) }") | ||
193 | } | ||
194 | |||
195 | #[test] | ||
196 | fn demorgan_doesnt_double_parens() { | ||
197 | mark::check!(demorgan_double_parens); | ||
198 | check_assist(apply_demorgan, "fn f() { (x ||$0 x) }", "fn f() { !(!x && !x) }") | ||
199 | } | ||
159 | } | 200 | } |
diff --git a/crates/ide_assists/src/handlers/convert_comment_block.rs b/crates/ide_assists/src/handlers/convert_comment_block.rs new file mode 100644 index 000000000..cdc45fc42 --- /dev/null +++ b/crates/ide_assists/src/handlers/convert_comment_block.rs | |||
@@ -0,0 +1,419 @@ | |||
1 | use itertools::Itertools; | ||
2 | use std::convert::identity; | ||
3 | use syntax::{ | ||
4 | ast::{ | ||
5 | self, | ||
6 | edit::IndentLevel, | ||
7 | Comment, CommentKind, | ||
8 | CommentPlacement::{Inner, Outer}, | ||
9 | CommentShape::{self, Block, Line}, | ||
10 | Whitespace, | ||
11 | }, | ||
12 | AstToken, Direction, SyntaxElement, TextRange, | ||
13 | }; | ||
14 | |||
15 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
16 | |||
17 | /// Assist: line_to_block | ||
18 | /// | ||
19 | /// Converts comments between block and single-line form | ||
20 | /// | ||
21 | /// ``` | ||
22 | /// // Multi-line | ||
23 | /// // comment | ||
24 | /// ``` | ||
25 | /// -> | ||
26 | /// ``` | ||
27 | /// /** | ||
28 | /// Multi-line | ||
29 | /// comment | ||
30 | /// */ | ||
31 | /// ``` | ||
32 | pub(crate) fn convert_comment_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
33 | if let Some(comment) = ctx.find_token_at_offset::<ast::Comment>() { | ||
34 | // Only allow comments which are alone on their line | ||
35 | if let Some(prev) = comment.syntax().prev_token() { | ||
36 | if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() { | ||
37 | return None; | ||
38 | } | ||
39 | } | ||
40 | |||
41 | return match comment.kind().shape { | ||
42 | ast::CommentShape::Block => block_to_line(acc, comment), | ||
43 | ast::CommentShape::Line => line_to_block(acc, comment), | ||
44 | }; | ||
45 | } | ||
46 | |||
47 | return None; | ||
48 | } | ||
49 | |||
50 | fn block_to_line(acc: &mut Assists, comment: ast::Comment) -> Option<()> { | ||
51 | let target = comment.syntax().text_range(); | ||
52 | |||
53 | acc.add( | ||
54 | AssistId("block_to_line", AssistKind::RefactorRewrite), | ||
55 | "Replace block comment with line comments", | ||
56 | target, | ||
57 | |edit| { | ||
58 | let indentation = IndentLevel::from_token(comment.syntax()); | ||
59 | let line_prefix = | ||
60 | comment_kind_prefix(CommentKind { shape: CommentShape::Line, ..comment.kind() }); | ||
61 | |||
62 | let text = comment.text(); | ||
63 | let text = &text[comment.prefix().len()..(text.len() - "*/".len())].trim(); | ||
64 | |||
65 | let lines = text.lines().peekable(); | ||
66 | |||
67 | let indent_spaces = indentation.to_string(); | ||
68 | let output = lines | ||
69 | .map(|l| l.trim_start_matches(&indent_spaces)) | ||
70 | .map(|l| { | ||
71 | // Don't introduce trailing whitespace | ||
72 | if l.is_empty() { | ||
73 | line_prefix.to_string() | ||
74 | } else { | ||
75 | format!("{} {}", line_prefix, l.trim_start_matches(&indent_spaces)) | ||
76 | } | ||
77 | }) | ||
78 | .join(&format!("\n{}", indent_spaces)); | ||
79 | |||
80 | edit.replace(target, output) | ||
81 | }, | ||
82 | ) | ||
83 | } | ||
84 | |||
85 | fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> { | ||
86 | // Find all the comments we'll be collapsing into a block | ||
87 | let comments = relevant_line_comments(&comment); | ||
88 | |||
89 | // Establish the target of our edit based on the comments we found | ||
90 | let target = TextRange::new( | ||
91 | comments[0].syntax().text_range().start(), | ||
92 | comments.last().unwrap().syntax().text_range().end(), | ||
93 | ); | ||
94 | |||
95 | acc.add( | ||
96 | AssistId("line_to_block", AssistKind::RefactorRewrite), | ||
97 | "Replace line comments with a single block comment", | ||
98 | target, | ||
99 | |edit| { | ||
100 | // We pick a single indentation level for the whole block comment based on the | ||
101 | // comment where the assist was invoked. This will be prepended to the | ||
102 | // contents of each line comment when they're put into the block comment. | ||
103 | let indentation = IndentLevel::from_token(&comment.syntax()); | ||
104 | |||
105 | let block_comment_body = | ||
106 | comments.into_iter().map(|c| line_comment_text(indentation, c)).join("\n"); | ||
107 | |||
108 | let block_prefix = | ||
109 | comment_kind_prefix(CommentKind { shape: CommentShape::Block, ..comment.kind() }); | ||
110 | |||
111 | let output = | ||
112 | format!("{}\n{}\n{}*/", block_prefix, block_comment_body, indentation.to_string()); | ||
113 | |||
114 | edit.replace(target, output) | ||
115 | }, | ||
116 | ) | ||
117 | } | ||
118 | |||
119 | /// The line -> block assist can be invoked from anywhere within a sequence of line comments. | ||
120 | /// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will | ||
121 | /// be joined. | ||
122 | fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> { | ||
123 | // The prefix identifies the kind of comment we're dealing with | ||
124 | let prefix = comment.prefix(); | ||
125 | let same_prefix = |c: &ast::Comment| c.prefix() == prefix; | ||
126 | |||
127 | // These tokens are allowed to exist between comments | ||
128 | let skippable = |not: &SyntaxElement| { | ||
129 | not.clone() | ||
130 | .into_token() | ||
131 | .and_then(Whitespace::cast) | ||
132 | .map(|w| !w.spans_multiple_lines()) | ||
133 | .unwrap_or(false) | ||
134 | }; | ||
135 | |||
136 | // Find all preceding comments (in reverse order) that have the same prefix | ||
137 | let prev_comments = comment | ||
138 | .syntax() | ||
139 | .siblings_with_tokens(Direction::Prev) | ||
140 | .filter(|s| !skippable(s)) | ||
141 | .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) | ||
142 | .take_while(|opt_com| opt_com.is_some()) | ||
143 | .filter_map(identity) | ||
144 | .skip(1); // skip the first element so we don't duplicate it in next_comments | ||
145 | |||
146 | let next_comments = comment | ||
147 | .syntax() | ||
148 | .siblings_with_tokens(Direction::Next) | ||
149 | .filter(|s| !skippable(s)) | ||
150 | .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) | ||
151 | .take_while(|opt_com| opt_com.is_some()) | ||
152 | .filter_map(identity); | ||
153 | |||
154 | let mut comments: Vec<_> = prev_comments.collect(); | ||
155 | comments.reverse(); | ||
156 | comments.extend(next_comments); | ||
157 | comments | ||
158 | } | ||
159 | |||
160 | // Line comments usually begin with a single space character following the prefix as seen here: | ||
161 | //^ | ||
162 | // But comments can also include indented text: | ||
163 | // > Hello there | ||
164 | // | ||
165 | // We handle this by stripping *AT MOST* one space character from the start of the line | ||
166 | // This has its own problems because it can cause alignment issues: | ||
167 | // | ||
168 | // /* | ||
169 | // a ----> a | ||
170 | //b ----> b | ||
171 | // */ | ||
172 | // | ||
173 | // But since such comments aren't idiomatic we're okay with this. | ||
174 | fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String { | ||
175 | let contents_without_prefix = comm.text().strip_prefix(comm.prefix()).unwrap(); | ||
176 | let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix); | ||
177 | |||
178 | // Don't add the indentation if the line is empty | ||
179 | if contents.is_empty() { | ||
180 | contents.to_owned() | ||
181 | } else { | ||
182 | indentation.to_string() + &contents | ||
183 | } | ||
184 | } | ||
185 | |||
186 | fn comment_kind_prefix(ck: ast::CommentKind) -> &'static str { | ||
187 | match (ck.shape, ck.doc) { | ||
188 | (Line, Some(Inner)) => "//!", | ||
189 | (Line, Some(Outer)) => "///", | ||
190 | (Line, None) => "//", | ||
191 | (Block, Some(Inner)) => "/*!", | ||
192 | (Block, Some(Outer)) => "/**", | ||
193 | (Block, None) => "/*", | ||
194 | } | ||
195 | } | ||
196 | |||
197 | #[cfg(test)] | ||
198 | mod tests { | ||
199 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
200 | |||
201 | use super::*; | ||
202 | |||
203 | #[test] | ||
204 | fn single_line_to_block() { | ||
205 | check_assist( | ||
206 | convert_comment_block, | ||
207 | r#" | ||
208 | // line$0 comment | ||
209 | fn main() { | ||
210 | foo(); | ||
211 | } | ||
212 | "#, | ||
213 | r#" | ||
214 | /* | ||
215 | line comment | ||
216 | */ | ||
217 | fn main() { | ||
218 | foo(); | ||
219 | } | ||
220 | "#, | ||
221 | ); | ||
222 | } | ||
223 | |||
224 | #[test] | ||
225 | fn single_line_to_block_indented() { | ||
226 | check_assist( | ||
227 | convert_comment_block, | ||
228 | r#" | ||
229 | fn main() { | ||
230 | // line$0 comment | ||
231 | foo(); | ||
232 | } | ||
233 | "#, | ||
234 | r#" | ||
235 | fn main() { | ||
236 | /* | ||
237 | line comment | ||
238 | */ | ||
239 | foo(); | ||
240 | } | ||
241 | "#, | ||
242 | ); | ||
243 | } | ||
244 | |||
245 | #[test] | ||
246 | fn multiline_to_block() { | ||
247 | check_assist( | ||
248 | convert_comment_block, | ||
249 | r#" | ||
250 | fn main() { | ||
251 | // above | ||
252 | // line$0 comment | ||
253 | // | ||
254 | // below | ||
255 | foo(); | ||
256 | } | ||
257 | "#, | ||
258 | r#" | ||
259 | fn main() { | ||
260 | /* | ||
261 | above | ||
262 | line comment | ||
263 | |||
264 | below | ||
265 | */ | ||
266 | foo(); | ||
267 | } | ||
268 | "#, | ||
269 | ); | ||
270 | } | ||
271 | |||
272 | #[test] | ||
273 | fn end_of_line_to_block() { | ||
274 | check_assist_not_applicable( | ||
275 | convert_comment_block, | ||
276 | r#" | ||
277 | fn main() { | ||
278 | foo(); // end-of-line$0 comment | ||
279 | } | ||
280 | "#, | ||
281 | ); | ||
282 | } | ||
283 | |||
284 | #[test] | ||
285 | fn single_line_different_kinds() { | ||
286 | check_assist( | ||
287 | convert_comment_block, | ||
288 | r#" | ||
289 | fn main() { | ||
290 | /// different prefix | ||
291 | // line$0 comment | ||
292 | // below | ||
293 | foo(); | ||
294 | } | ||
295 | "#, | ||
296 | r#" | ||
297 | fn main() { | ||
298 | /// different prefix | ||
299 | /* | ||
300 | line comment | ||
301 | below | ||
302 | */ | ||
303 | foo(); | ||
304 | } | ||
305 | "#, | ||
306 | ); | ||
307 | } | ||
308 | |||
309 | #[test] | ||
310 | fn single_line_separate_chunks() { | ||
311 | check_assist( | ||
312 | convert_comment_block, | ||
313 | r#" | ||
314 | fn main() { | ||
315 | // different chunk | ||
316 | |||
317 | // line$0 comment | ||
318 | // below | ||
319 | foo(); | ||
320 | } | ||
321 | "#, | ||
322 | r#" | ||
323 | fn main() { | ||
324 | // different chunk | ||
325 | |||
326 | /* | ||
327 | line comment | ||
328 | below | ||
329 | */ | ||
330 | foo(); | ||
331 | } | ||
332 | "#, | ||
333 | ); | ||
334 | } | ||
335 | |||
336 | #[test] | ||
337 | fn doc_block_comment_to_lines() { | ||
338 | check_assist( | ||
339 | convert_comment_block, | ||
340 | r#" | ||
341 | /** | ||
342 | hi$0 there | ||
343 | */ | ||
344 | "#, | ||
345 | r#" | ||
346 | /// hi there | ||
347 | "#, | ||
348 | ); | ||
349 | } | ||
350 | |||
351 | #[test] | ||
352 | fn block_comment_to_lines() { | ||
353 | check_assist( | ||
354 | convert_comment_block, | ||
355 | r#" | ||
356 | /* | ||
357 | hi$0 there | ||
358 | */ | ||
359 | "#, | ||
360 | r#" | ||
361 | // hi there | ||
362 | "#, | ||
363 | ); | ||
364 | } | ||
365 | |||
366 | #[test] | ||
367 | fn inner_doc_block_to_lines() { | ||
368 | check_assist( | ||
369 | convert_comment_block, | ||
370 | r#" | ||
371 | /*! | ||
372 | hi$0 there | ||
373 | */ | ||
374 | "#, | ||
375 | r#" | ||
376 | //! hi there | ||
377 | "#, | ||
378 | ); | ||
379 | } | ||
380 | |||
381 | #[test] | ||
382 | fn block_to_lines_indent() { | ||
383 | check_assist( | ||
384 | convert_comment_block, | ||
385 | r#" | ||
386 | fn main() { | ||
387 | /*! | ||
388 | hi$0 there | ||
389 | |||
390 | ``` | ||
391 | code_sample | ||
392 | ``` | ||
393 | */ | ||
394 | } | ||
395 | "#, | ||
396 | r#" | ||
397 | fn main() { | ||
398 | //! hi there | ||
399 | //! | ||
400 | //! ``` | ||
401 | //! code_sample | ||
402 | //! ``` | ||
403 | } | ||
404 | "#, | ||
405 | ); | ||
406 | } | ||
407 | |||
408 | #[test] | ||
409 | fn end_of_line_block_to_line() { | ||
410 | check_assist_not_applicable( | ||
411 | convert_comment_block, | ||
412 | r#" | ||
413 | fn main() { | ||
414 | foo(); /* end-of-line$0 comment */ | ||
415 | } | ||
416 | "#, | ||
417 | ); | ||
418 | } | ||
419 | } | ||
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs index 98f3dc6ca..312ac7ac4 100644 --- a/crates/ide_assists/src/handlers/extract_variable.rs +++ b/crates/ide_assists/src/handlers/extract_variable.rs | |||
@@ -8,7 +8,7 @@ use syntax::{ | |||
8 | }; | 8 | }; |
9 | use test_utils::mark; | 9 | use test_utils::mark; |
10 | 10 | ||
11 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 11 | use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists}; |
12 | 12 | ||
13 | // Assist: extract_variable | 13 | // Assist: extract_variable |
14 | // | 14 | // |
@@ -54,7 +54,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option | |||
54 | 54 | ||
55 | let var_name = match &field_shorthand { | 55 | let var_name = match &field_shorthand { |
56 | Some(it) => it.to_string(), | 56 | Some(it) => it.to_string(), |
57 | None => "var_name".to_string(), | 57 | None => suggest_name::variable(&to_extract, &ctx.sema), |
58 | }; | 58 | }; |
59 | let expr_range = match &field_shorthand { | 59 | let expr_range = match &field_shorthand { |
60 | Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), | 60 | Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), |
@@ -274,8 +274,8 @@ fn foo() { | |||
274 | "#, | 274 | "#, |
275 | r#" | 275 | r#" |
276 | fn foo() { | 276 | fn foo() { |
277 | let $0var_name = bar(1 + 1); | 277 | let $0bar = bar(1 + 1); |
278 | var_name | 278 | bar |
279 | } | 279 | } |
280 | "#, | 280 | "#, |
281 | ) | 281 | ) |
@@ -401,8 +401,8 @@ fn main() { | |||
401 | ", | 401 | ", |
402 | " | 402 | " |
403 | fn main() { | 403 | fn main() { |
404 | let $0var_name = bar.foo(); | 404 | let $0foo = bar.foo(); |
405 | let v = var_name; | 405 | let v = foo; |
406 | } | 406 | } |
407 | ", | 407 | ", |
408 | ); | 408 | ); |
@@ -557,6 +557,202 @@ fn main() { | |||
557 | } | 557 | } |
558 | 558 | ||
559 | #[test] | 559 | #[test] |
560 | fn extract_var_name_from_type() { | ||
561 | check_assist( | ||
562 | extract_variable, | ||
563 | r#" | ||
564 | struct Test(i32); | ||
565 | |||
566 | fn foo() -> Test { | ||
567 | $0{ Test(10) }$0 | ||
568 | } | ||
569 | "#, | ||
570 | r#" | ||
571 | struct Test(i32); | ||
572 | |||
573 | fn foo() -> Test { | ||
574 | let $0test = { Test(10) }; | ||
575 | test | ||
576 | } | ||
577 | "#, | ||
578 | ) | ||
579 | } | ||
580 | |||
581 | #[test] | ||
582 | fn extract_var_name_from_parameter() { | ||
583 | check_assist( | ||
584 | extract_variable, | ||
585 | r#" | ||
586 | fn bar(test: u32, size: u32) | ||
587 | |||
588 | fn foo() { | ||
589 | bar(1, $01+1$0); | ||
590 | } | ||
591 | "#, | ||
592 | r#" | ||
593 | fn bar(test: u32, size: u32) | ||
594 | |||
595 | fn foo() { | ||
596 | let $0size = 1+1; | ||
597 | bar(1, size); | ||
598 | } | ||
599 | "#, | ||
600 | ) | ||
601 | } | ||
602 | |||
603 | #[test] | ||
604 | fn extract_var_parameter_name_has_precedence_over_type() { | ||
605 | check_assist( | ||
606 | extract_variable, | ||
607 | r#" | ||
608 | struct TextSize(u32); | ||
609 | fn bar(test: u32, size: TextSize) | ||
610 | |||
611 | fn foo() { | ||
612 | bar(1, $0{ TextSize(1+1) }$0); | ||
613 | } | ||
614 | "#, | ||
615 | r#" | ||
616 | struct TextSize(u32); | ||
617 | fn bar(test: u32, size: TextSize) | ||
618 | |||
619 | fn foo() { | ||
620 | let $0size = { TextSize(1+1) }; | ||
621 | bar(1, size); | ||
622 | } | ||
623 | "#, | ||
624 | ) | ||
625 | } | ||
626 | |||
627 | #[test] | ||
628 | fn extract_var_name_from_function() { | ||
629 | check_assist( | ||
630 | extract_variable, | ||
631 | r#" | ||
632 | fn is_required(test: u32, size: u32) -> bool | ||
633 | |||
634 | fn foo() -> bool { | ||
635 | $0is_required(1, 2)$0 | ||
636 | } | ||
637 | "#, | ||
638 | r#" | ||
639 | fn is_required(test: u32, size: u32) -> bool | ||
640 | |||
641 | fn foo() -> bool { | ||
642 | let $0is_required = is_required(1, 2); | ||
643 | is_required | ||
644 | } | ||
645 | "#, | ||
646 | ) | ||
647 | } | ||
648 | |||
649 | #[test] | ||
650 | fn extract_var_name_from_method() { | ||
651 | check_assist( | ||
652 | extract_variable, | ||
653 | r#" | ||
654 | struct S; | ||
655 | impl S { | ||
656 | fn bar(&self, n: u32) -> u32 { n } | ||
657 | } | ||
658 | |||
659 | fn foo() -> u32 { | ||
660 | $0S.bar(1)$0 | ||
661 | } | ||
662 | "#, | ||
663 | r#" | ||
664 | struct S; | ||
665 | impl S { | ||
666 | fn bar(&self, n: u32) -> u32 { n } | ||
667 | } | ||
668 | |||
669 | fn foo() -> u32 { | ||
670 | let $0bar = S.bar(1); | ||
671 | bar | ||
672 | } | ||
673 | "#, | ||
674 | ) | ||
675 | } | ||
676 | |||
677 | #[test] | ||
678 | fn extract_var_name_from_method_param() { | ||
679 | check_assist( | ||
680 | extract_variable, | ||
681 | r#" | ||
682 | struct S; | ||
683 | impl S { | ||
684 | fn bar(&self, n: u32, size: u32) { n } | ||
685 | } | ||
686 | |||
687 | fn foo() { | ||
688 | S.bar($01 + 1$0, 2) | ||
689 | } | ||
690 | "#, | ||
691 | r#" | ||
692 | struct S; | ||
693 | impl S { | ||
694 | fn bar(&self, n: u32, size: u32) { n } | ||
695 | } | ||
696 | |||
697 | fn foo() { | ||
698 | let $0n = 1 + 1; | ||
699 | S.bar(n, 2) | ||
700 | } | ||
701 | "#, | ||
702 | ) | ||
703 | } | ||
704 | |||
705 | #[test] | ||
706 | fn extract_var_name_from_ufcs_method_param() { | ||
707 | check_assist( | ||
708 | extract_variable, | ||
709 | r#" | ||
710 | struct S; | ||
711 | impl S { | ||
712 | fn bar(&self, n: u32, size: u32) { n } | ||
713 | } | ||
714 | |||
715 | fn foo() { | ||
716 | S::bar(&S, $01 + 1$0, 2) | ||
717 | } | ||
718 | "#, | ||
719 | r#" | ||
720 | struct S; | ||
721 | impl S { | ||
722 | fn bar(&self, n: u32, size: u32) { n } | ||
723 | } | ||
724 | |||
725 | fn foo() { | ||
726 | let $0n = 1 + 1; | ||
727 | S::bar(&S, n, 2) | ||
728 | } | ||
729 | "#, | ||
730 | ) | ||
731 | } | ||
732 | |||
733 | #[test] | ||
734 | fn extract_var_parameter_name_has_precedence_over_function() { | ||
735 | check_assist( | ||
736 | extract_variable, | ||
737 | r#" | ||
738 | fn bar(test: u32, size: u32) | ||
739 | |||
740 | fn foo() { | ||
741 | bar(1, $0symbol_size(1, 2)$0); | ||
742 | } | ||
743 | "#, | ||
744 | r#" | ||
745 | fn bar(test: u32, size: u32) | ||
746 | |||
747 | fn foo() { | ||
748 | let $0size = symbol_size(1, 2); | ||
749 | bar(1, size); | ||
750 | } | ||
751 | "#, | ||
752 | ) | ||
753 | } | ||
754 | |||
755 | #[test] | ||
560 | fn test_extract_var_for_return_not_applicable() { | 756 | fn test_extract_var_for_return_not_applicable() { |
561 | check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } "); | 757 | check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } "); |
562 | } | 758 | } |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index 53542d433..9c8148462 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -115,6 +115,7 @@ mod handlers { | |||
115 | mod auto_import; | 115 | mod auto_import; |
116 | mod change_visibility; | 116 | mod change_visibility; |
117 | mod convert_integer_literal; | 117 | mod convert_integer_literal; |
118 | mod convert_comment_block; | ||
118 | mod early_return; | 119 | mod early_return; |
119 | mod expand_glob_import; | 120 | mod expand_glob_import; |
120 | mod extract_function; | 121 | mod extract_function; |
@@ -178,6 +179,7 @@ mod handlers { | |||
178 | auto_import::auto_import, | 179 | auto_import::auto_import, |
179 | change_visibility::change_visibility, | 180 | change_visibility::change_visibility, |
180 | convert_integer_literal::convert_integer_literal, | 181 | convert_integer_literal::convert_integer_literal, |
182 | convert_comment_block::convert_comment_block, | ||
181 | early_return::convert_to_guarded_return, | 183 | early_return::convert_to_guarded_return, |
182 | expand_glob_import::expand_glob_import, | 184 | expand_glob_import::expand_glob_import, |
183 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 185 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 880ab6fe3..62f959082 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | //! Assorted functions shared by several assists. | 1 | //! Assorted functions shared by several assists. |
2 | 2 | ||
3 | pub(crate) mod suggest_name; | ||
4 | |||
3 | use std::ops; | 5 | use std::ops; |
4 | 6 | ||
5 | use ast::TypeBoundsOwner; | 7 | use ast::TypeBoundsOwner; |
diff --git a/crates/ide_assists/src/utils/suggest_name.rs b/crates/ide_assists/src/utils/suggest_name.rs new file mode 100644 index 000000000..533624c1f --- /dev/null +++ b/crates/ide_assists/src/utils/suggest_name.rs | |||
@@ -0,0 +1,729 @@ | |||
1 | //! This module contains functions to suggest names for expressions, functions and other items | ||
2 | |||
3 | use hir::Semantics; | ||
4 | use ide_db::RootDatabase; | ||
5 | use itertools::Itertools; | ||
6 | use stdx::to_lower_snake_case; | ||
7 | use syntax::{ | ||
8 | ast::{self, NameOwner}, | ||
9 | match_ast, AstNode, | ||
10 | }; | ||
11 | |||
12 | /// Trait names, that will be ignored when in `impl Trait` and `dyn Trait` | ||
13 | const USELESS_TRAITS: &[&str] = &["Send", "Sync", "Copy", "Clone", "Eq", "PartialEq"]; | ||
14 | |||
15 | /// Identifier names that won't be suggested, ever | ||
16 | /// | ||
17 | /// **NOTE**: they all must be snake lower case | ||
18 | const USELESS_NAMES: &[&str] = | ||
19 | &["new", "default", "option", "some", "none", "ok", "err", "str", "string"]; | ||
20 | |||
21 | /// Generic types replaced by their first argument | ||
22 | /// | ||
23 | /// # Examples | ||
24 | /// `Option<Name>` -> `Name` | ||
25 | /// `Result<User, Error>` -> `User` | ||
26 | const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"]; | ||
27 | |||
28 | /// Prefixes to strip from methods names | ||
29 | /// | ||
30 | /// # Examples | ||
31 | /// `vec.as_slice()` -> `slice` | ||
32 | /// `args.into_config()` -> `config` | ||
33 | /// `bytes.to_vec()` -> `vec` | ||
34 | const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"]; | ||
35 | |||
36 | /// Useless methods that are stripped from expression | ||
37 | /// | ||
38 | /// # Examples | ||
39 | /// `var.name().to_string()` -> `var.name()` | ||
40 | const USELESS_METHODS: &[&str] = &[ | ||
41 | "to_string", | ||
42 | "as_str", | ||
43 | "to_owned", | ||
44 | "as_ref", | ||
45 | "clone", | ||
46 | "cloned", | ||
47 | "expect", | ||
48 | "expect_none", | ||
49 | "unwrap", | ||
50 | "unwrap_none", | ||
51 | "unwrap_or", | ||
52 | "unwrap_or_default", | ||
53 | "unwrap_or_else", | ||
54 | "unwrap_unchecked", | ||
55 | "iter", | ||
56 | "into_iter", | ||
57 | "iter_mut", | ||
58 | ]; | ||
59 | |||
60 | /// Suggest name of variable for given expression | ||
61 | /// | ||
62 | /// **NOTE**: it is caller's responsibility to guarantee uniqueness of the name. | ||
63 | /// I.e. it doesn't look for names in scope. | ||
64 | /// | ||
65 | /// # Current implementation | ||
66 | /// | ||
67 | /// In current implementation, the function tries to get the name from | ||
68 | /// the following sources: | ||
69 | /// | ||
70 | /// * if expr is an argument to function/method, use paramter name | ||
71 | /// * if expr is a function/method call, use function name | ||
72 | /// * expression type name if it exists (E.g. `()`, `fn() -> ()` or `!` do not have names) | ||
73 | /// * fallback: `var_name` | ||
74 | /// | ||
75 | /// It also applies heuristics to filter out less informative names | ||
76 | /// | ||
77 | /// Currently it sticks to the first name found. | ||
78 | pub(crate) fn variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String { | ||
79 | // `from_param` does not benifit from stripping | ||
80 | // it need the largest context possible | ||
81 | // so we check firstmost | ||
82 | if let Some(name) = from_param(expr, sema) { | ||
83 | return name; | ||
84 | } | ||
85 | |||
86 | let mut next_expr = Some(expr.clone()); | ||
87 | while let Some(expr) = next_expr { | ||
88 | let name = from_call(&expr).or_else(|| from_type(&expr, sema)); | ||
89 | if let Some(name) = name { | ||
90 | return name; | ||
91 | } | ||
92 | |||
93 | match expr { | ||
94 | ast::Expr::RefExpr(inner) => next_expr = inner.expr(), | ||
95 | ast::Expr::BoxExpr(inner) => next_expr = inner.expr(), | ||
96 | ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(), | ||
97 | // ast::Expr::BlockExpr(block) => expr = block.tail_expr(), | ||
98 | ast::Expr::CastExpr(inner) => next_expr = inner.expr(), | ||
99 | ast::Expr::MethodCallExpr(method) if is_useless_method(&method) => { | ||
100 | next_expr = method.receiver(); | ||
101 | } | ||
102 | ast::Expr::ParenExpr(inner) => next_expr = inner.expr(), | ||
103 | ast::Expr::TryExpr(inner) => next_expr = inner.expr(), | ||
104 | ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::PrefixOp::Deref) => { | ||
105 | next_expr = prefix.expr() | ||
106 | } | ||
107 | _ => break, | ||
108 | } | ||
109 | } | ||
110 | |||
111 | "var_name".to_string() | ||
112 | } | ||
113 | |||
114 | fn normalize(name: &str) -> Option<String> { | ||
115 | let name = to_lower_snake_case(name); | ||
116 | |||
117 | if USELESS_NAMES.contains(&name.as_str()) { | ||
118 | return None; | ||
119 | } | ||
120 | |||
121 | if !is_valid_name(&name) { | ||
122 | return None; | ||
123 | } | ||
124 | |||
125 | Some(name) | ||
126 | } | ||
127 | |||
128 | fn is_valid_name(name: &str) -> bool { | ||
129 | match syntax::lex_single_syntax_kind(name) { | ||
130 | Some((syntax::SyntaxKind::IDENT, _error)) => true, | ||
131 | _ => false, | ||
132 | } | ||
133 | } | ||
134 | |||
135 | fn is_useless_method(method: &ast::MethodCallExpr) -> bool { | ||
136 | let ident = method.name_ref().and_then(|it| it.ident_token()); | ||
137 | |||
138 | if let Some(ident) = ident { | ||
139 | USELESS_METHODS.contains(&ident.text()) | ||
140 | } else { | ||
141 | false | ||
142 | } | ||
143 | } | ||
144 | |||
145 | fn from_call(expr: &ast::Expr) -> Option<String> { | ||
146 | from_func_call(expr).or_else(|| from_method_call(expr)) | ||
147 | } | ||
148 | |||
149 | fn from_func_call(expr: &ast::Expr) -> Option<String> { | ||
150 | let call = match expr { | ||
151 | ast::Expr::CallExpr(call) => call, | ||
152 | _ => return None, | ||
153 | }; | ||
154 | let func = match call.expr()? { | ||
155 | ast::Expr::PathExpr(path) => path, | ||
156 | _ => return None, | ||
157 | }; | ||
158 | let ident = func.path()?.segment()?.name_ref()?.ident_token()?; | ||
159 | normalize(ident.text()) | ||
160 | } | ||
161 | |||
162 | fn from_method_call(expr: &ast::Expr) -> Option<String> { | ||
163 | let method = match expr { | ||
164 | ast::Expr::MethodCallExpr(call) => call, | ||
165 | _ => return None, | ||
166 | }; | ||
167 | let ident = method.name_ref()?.ident_token()?; | ||
168 | let mut name = ident.text(); | ||
169 | |||
170 | if USELESS_METHODS.contains(&name) { | ||
171 | return None; | ||
172 | } | ||
173 | |||
174 | for prefix in USELESS_METHOD_PREFIXES { | ||
175 | if let Some(suffix) = name.strip_prefix(prefix) { | ||
176 | name = suffix; | ||
177 | break; | ||
178 | } | ||
179 | } | ||
180 | |||
181 | normalize(&name) | ||
182 | } | ||
183 | |||
184 | fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> { | ||
185 | let arg_list = expr.syntax().parent().and_then(ast::ArgList::cast)?; | ||
186 | let args_parent = arg_list.syntax().parent()?; | ||
187 | let func = match_ast! { | ||
188 | match args_parent { | ||
189 | ast::CallExpr(call) => { | ||
190 | let func = call.expr()?; | ||
191 | let func_ty = sema.type_of_expr(&func)?; | ||
192 | func_ty.as_callable(sema.db)? | ||
193 | }, | ||
194 | ast::MethodCallExpr(method) => sema.resolve_method_call_as_callable(&method)?, | ||
195 | _ => return None, | ||
196 | } | ||
197 | }; | ||
198 | |||
199 | let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap(); | ||
200 | let (pat, _) = func.params(sema.db).into_iter().nth(idx)?; | ||
201 | let pat = match pat? { | ||
202 | either::Either::Right(pat) => pat, | ||
203 | _ => return None, | ||
204 | }; | ||
205 | let name = var_name_from_pat(&pat)?; | ||
206 | normalize(&name.to_string()) | ||
207 | } | ||
208 | |||
209 | fn var_name_from_pat(pat: &ast::Pat) -> Option<ast::Name> { | ||
210 | match pat { | ||
211 | ast::Pat::IdentPat(var) => var.name(), | ||
212 | ast::Pat::RefPat(ref_pat) => var_name_from_pat(&ref_pat.pat()?), | ||
213 | ast::Pat::BoxPat(box_pat) => var_name_from_pat(&box_pat.pat()?), | ||
214 | _ => None, | ||
215 | } | ||
216 | } | ||
217 | |||
218 | fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> { | ||
219 | let ty = sema.type_of_expr(expr)?; | ||
220 | let ty = ty.remove_ref().unwrap_or(ty); | ||
221 | |||
222 | name_of_type(&ty, sema.db) | ||
223 | } | ||
224 | |||
225 | fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option<String> { | ||
226 | let name = if let Some(adt) = ty.as_adt() { | ||
227 | let name = adt.name(db).to_string(); | ||
228 | |||
229 | if WRAPPER_TYPES.contains(&name.as_str()) { | ||
230 | let inner_ty = ty.type_parameters().next()?; | ||
231 | return name_of_type(&inner_ty, db); | ||
232 | } | ||
233 | |||
234 | name | ||
235 | } else if let Some(trait_) = ty.as_dyn_trait() { | ||
236 | trait_name(&trait_, db)? | ||
237 | } else if let Some(traits) = ty.as_impl_traits(db) { | ||
238 | let mut iter = traits.into_iter().filter_map(|t| trait_name(&t, db)); | ||
239 | let name = iter.next()?; | ||
240 | if iter.next().is_some() { | ||
241 | return None; | ||
242 | } | ||
243 | name | ||
244 | } else { | ||
245 | return None; | ||
246 | }; | ||
247 | normalize(&name) | ||
248 | } | ||
249 | |||
250 | fn trait_name(trait_: &hir::Trait, db: &RootDatabase) -> Option<String> { | ||
251 | let name = trait_.name(db).to_string(); | ||
252 | if USELESS_TRAITS.contains(&name.as_str()) { | ||
253 | return None; | ||
254 | } | ||
255 | Some(name) | ||
256 | } | ||
257 | |||
258 | #[cfg(test)] | ||
259 | mod tests { | ||
260 | use ide_db::base_db::{fixture::WithFixture, FileRange}; | ||
261 | |||
262 | use super::*; | ||
263 | |||
264 | #[track_caller] | ||
265 | fn check(ra_fixture: &str, expected: &str) { | ||
266 | let (db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture); | ||
267 | let frange = FileRange { file_id, range: range_or_offset.into() }; | ||
268 | |||
269 | let sema = Semantics::new(&db); | ||
270 | let source_file = sema.parse(frange.file_id); | ||
271 | let element = source_file.syntax().covering_element(frange.range); | ||
272 | let expr = | ||
273 | element.ancestors().find_map(ast::Expr::cast).expect("selection is not an expression"); | ||
274 | assert_eq!( | ||
275 | expr.syntax().text_range(), | ||
276 | frange.range, | ||
277 | "selection is not an expression(yet contained in one)" | ||
278 | ); | ||
279 | let name = variable(&expr, &sema); | ||
280 | assert_eq!(&name, expected); | ||
281 | } | ||
282 | |||
283 | #[test] | ||
284 | fn no_args() { | ||
285 | check(r#"fn foo() { $0bar()$0 }"#, "bar"); | ||
286 | check(r#"fn foo() { $0bar.frobnicate()$0 }"#, "frobnicate"); | ||
287 | } | ||
288 | |||
289 | #[test] | ||
290 | fn single_arg() { | ||
291 | check(r#"fn foo() { $0bar(1)$0 }"#, "bar"); | ||
292 | } | ||
293 | |||
294 | #[test] | ||
295 | fn many_args() { | ||
296 | check(r#"fn foo() { $0bar(1, 2, 3)$0 }"#, "bar"); | ||
297 | } | ||
298 | |||
299 | #[test] | ||
300 | fn path() { | ||
301 | check(r#"fn foo() { $0i32::bar(1, 2, 3)$0 }"#, "bar"); | ||
302 | } | ||
303 | |||
304 | #[test] | ||
305 | fn generic_params() { | ||
306 | check(r#"fn foo() { $0bar::<i32>(1, 2, 3)$0 }"#, "bar"); | ||
307 | check(r#"fn foo() { $0bar.frobnicate::<i32, u32>()$0 }"#, "frobnicate"); | ||
308 | } | ||
309 | |||
310 | #[test] | ||
311 | fn to_name() { | ||
312 | check( | ||
313 | r#" | ||
314 | struct Args; | ||
315 | struct Config; | ||
316 | impl Args { | ||
317 | fn to_config(&self) -> Config {} | ||
318 | } | ||
319 | fn foo() { | ||
320 | $0Args.to_config()$0; | ||
321 | } | ||
322 | "#, | ||
323 | "config", | ||
324 | ); | ||
325 | } | ||
326 | |||
327 | #[test] | ||
328 | fn plain_func() { | ||
329 | check( | ||
330 | r#" | ||
331 | fn bar(n: i32, m: u32); | ||
332 | fn foo() { bar($01$0, 2) } | ||
333 | "#, | ||
334 | "n", | ||
335 | ); | ||
336 | } | ||
337 | |||
338 | #[test] | ||
339 | fn mut_param() { | ||
340 | check( | ||
341 | r#" | ||
342 | fn bar(mut n: i32, m: u32); | ||
343 | fn foo() { bar($01$0, 2) } | ||
344 | "#, | ||
345 | "n", | ||
346 | ); | ||
347 | } | ||
348 | |||
349 | #[test] | ||
350 | fn func_does_not_exist() { | ||
351 | check(r#"fn foo() { bar($01$0, 2) }"#, "var_name"); | ||
352 | } | ||
353 | |||
354 | #[test] | ||
355 | fn unnamed_param() { | ||
356 | check( | ||
357 | r#" | ||
358 | fn bar(_: i32, m: u32); | ||
359 | fn foo() { bar($01$0, 2) } | ||
360 | "#, | ||
361 | "var_name", | ||
362 | ); | ||
363 | } | ||
364 | |||
365 | #[test] | ||
366 | fn tuple_pat() { | ||
367 | check( | ||
368 | r#" | ||
369 | fn bar((n, k): (i32, i32), m: u32); | ||
370 | fn foo() { | ||
371 | bar($0(1, 2)$0, 3) | ||
372 | } | ||
373 | "#, | ||
374 | "var_name", | ||
375 | ); | ||
376 | } | ||
377 | |||
378 | #[test] | ||
379 | fn ref_pat() { | ||
380 | check( | ||
381 | r#" | ||
382 | fn bar(&n: &i32, m: u32); | ||
383 | fn foo() { bar($0&1$0, 3) } | ||
384 | "#, | ||
385 | "n", | ||
386 | ); | ||
387 | } | ||
388 | |||
389 | #[test] | ||
390 | fn box_pat() { | ||
391 | check( | ||
392 | r#" | ||
393 | fn bar(box n: &i32, m: u32); | ||
394 | fn foo() { bar($01$0, 3) } | ||
395 | "#, | ||
396 | "n", | ||
397 | ); | ||
398 | } | ||
399 | |||
400 | #[test] | ||
401 | fn param_out_of_index() { | ||
402 | check( | ||
403 | r#" | ||
404 | fn bar(n: i32, m: u32); | ||
405 | fn foo() { bar(1, 2, $03$0) } | ||
406 | "#, | ||
407 | "var_name", | ||
408 | ); | ||
409 | } | ||
410 | |||
411 | #[test] | ||
412 | fn generic_param_resolved() { | ||
413 | check( | ||
414 | r#" | ||
415 | fn bar<T>(n: T, m: u32); | ||
416 | fn foo() { bar($01$0, 2) } | ||
417 | "#, | ||
418 | "n", | ||
419 | ); | ||
420 | } | ||
421 | |||
422 | #[test] | ||
423 | fn generic_param_unresolved() { | ||
424 | check( | ||
425 | r#" | ||
426 | fn bar<T>(n: T, m: u32); | ||
427 | fn foo<T>(x: T) { bar($0x$0, 2) } | ||
428 | "#, | ||
429 | "n", | ||
430 | ); | ||
431 | } | ||
432 | |||
433 | #[test] | ||
434 | fn method() { | ||
435 | check( | ||
436 | r#" | ||
437 | struct S; | ||
438 | impl S { fn bar(&self, n: i32, m: u32); } | ||
439 | fn foo() { S.bar($01$0, 2) } | ||
440 | "#, | ||
441 | "n", | ||
442 | ); | ||
443 | } | ||
444 | |||
445 | #[test] | ||
446 | fn method_ufcs() { | ||
447 | check( | ||
448 | r#" | ||
449 | struct S; | ||
450 | impl S { fn bar(&self, n: i32, m: u32); } | ||
451 | fn foo() { S::bar(&S, $01$0, 2) } | ||
452 | "#, | ||
453 | "n", | ||
454 | ); | ||
455 | } | ||
456 | |||
457 | #[test] | ||
458 | fn method_self() { | ||
459 | check( | ||
460 | r#" | ||
461 | struct S; | ||
462 | impl S { fn bar(&self, n: i32, m: u32); } | ||
463 | fn foo() { S::bar($0&S$0, 1, 2) } | ||
464 | "#, | ||
465 | "s", | ||
466 | ); | ||
467 | } | ||
468 | |||
469 | #[test] | ||
470 | fn method_self_named() { | ||
471 | check( | ||
472 | r#" | ||
473 | struct S; | ||
474 | impl S { fn bar(strukt: &Self, n: i32, m: u32); } | ||
475 | fn foo() { S::bar($0&S$0, 1, 2) } | ||
476 | "#, | ||
477 | "strukt", | ||
478 | ); | ||
479 | } | ||
480 | |||
481 | #[test] | ||
482 | fn i32() { | ||
483 | check(r#"fn foo() { let _: i32 = $01$0; }"#, "var_name"); | ||
484 | } | ||
485 | |||
486 | #[test] | ||
487 | fn u64() { | ||
488 | check(r#"fn foo() { let _: u64 = $01$0; }"#, "var_name"); | ||
489 | } | ||
490 | |||
491 | #[test] | ||
492 | fn bool() { | ||
493 | check(r#"fn foo() { let _: bool = $0true$0; }"#, "var_name"); | ||
494 | } | ||
495 | |||
496 | #[test] | ||
497 | fn struct_unit() { | ||
498 | check( | ||
499 | r#" | ||
500 | struct Seed; | ||
501 | fn foo() { let _ = $0Seed$0; } | ||
502 | "#, | ||
503 | "seed", | ||
504 | ); | ||
505 | } | ||
506 | |||
507 | #[test] | ||
508 | fn struct_unit_to_snake() { | ||
509 | check( | ||
510 | r#" | ||
511 | struct SeedState; | ||
512 | fn foo() { let _ = $0SeedState$0; } | ||
513 | "#, | ||
514 | "seed_state", | ||
515 | ); | ||
516 | } | ||
517 | |||
518 | #[test] | ||
519 | fn struct_single_arg() { | ||
520 | check( | ||
521 | r#" | ||
522 | struct Seed(u32); | ||
523 | fn foo() { let _ = $0Seed(0)$0; } | ||
524 | "#, | ||
525 | "seed", | ||
526 | ); | ||
527 | } | ||
528 | |||
529 | #[test] | ||
530 | fn struct_with_fields() { | ||
531 | check( | ||
532 | r#" | ||
533 | struct Seed { value: u32 } | ||
534 | fn foo() { let _ = $0Seed { value: 0 }$0; } | ||
535 | "#, | ||
536 | "seed", | ||
537 | ); | ||
538 | } | ||
539 | |||
540 | #[test] | ||
541 | fn enum_() { | ||
542 | check( | ||
543 | r#" | ||
544 | enum Kind { A, B } | ||
545 | fn foo() { let _ = $0Kind::A$0; } | ||
546 | "#, | ||
547 | "kind", | ||
548 | ); | ||
549 | } | ||
550 | |||
551 | #[test] | ||
552 | fn enum_generic_resolved() { | ||
553 | check( | ||
554 | r#" | ||
555 | enum Kind<T> { A { x: T }, B } | ||
556 | fn foo() { let _ = $0Kind::A { x:1 }$0; } | ||
557 | "#, | ||
558 | "kind", | ||
559 | ); | ||
560 | } | ||
561 | |||
562 | #[test] | ||
563 | fn enum_generic_unresolved() { | ||
564 | check( | ||
565 | r#" | ||
566 | enum Kind<T> { A { x: T }, B } | ||
567 | fn foo<T>(x: T) { let _ = $0Kind::A { x }$0; } | ||
568 | "#, | ||
569 | "kind", | ||
570 | ); | ||
571 | } | ||
572 | |||
573 | #[test] | ||
574 | fn dyn_trait() { | ||
575 | check( | ||
576 | r#" | ||
577 | trait DynHandler {} | ||
578 | fn bar() -> dyn DynHandler {} | ||
579 | fn foo() { $0(bar())$0; } | ||
580 | "#, | ||
581 | "dyn_handler", | ||
582 | ); | ||
583 | } | ||
584 | |||
585 | #[test] | ||
586 | fn impl_trait() { | ||
587 | check( | ||
588 | r#" | ||
589 | trait StaticHandler {} | ||
590 | fn bar() -> impl StaticHandler {} | ||
591 | fn foo() { $0(bar())$0; } | ||
592 | "#, | ||
593 | "static_handler", | ||
594 | ); | ||
595 | } | ||
596 | |||
597 | #[test] | ||
598 | fn impl_trait_plus_clone() { | ||
599 | check( | ||
600 | r#" | ||
601 | trait StaticHandler {} | ||
602 | trait Clone {} | ||
603 | fn bar() -> impl StaticHandler + Clone {} | ||
604 | fn foo() { $0(bar())$0; } | ||
605 | "#, | ||
606 | "static_handler", | ||
607 | ); | ||
608 | } | ||
609 | |||
610 | #[test] | ||
611 | fn impl_trait_plus_lifetime() { | ||
612 | check( | ||
613 | r#" | ||
614 | trait StaticHandler {} | ||
615 | trait Clone {} | ||
616 | fn bar<'a>(&'a i32) -> impl StaticHandler + 'a {} | ||
617 | fn foo() { $0(bar(&1))$0; } | ||
618 | "#, | ||
619 | "static_handler", | ||
620 | ); | ||
621 | } | ||
622 | |||
623 | #[test] | ||
624 | fn impl_trait_plus_trait() { | ||
625 | check( | ||
626 | r#" | ||
627 | trait Handler {} | ||
628 | trait StaticHandler {} | ||
629 | fn bar() -> impl StaticHandler + Handler {} | ||
630 | fn foo() { $0(bar())$0; } | ||
631 | "#, | ||
632 | "bar", | ||
633 | ); | ||
634 | } | ||
635 | |||
636 | #[test] | ||
637 | fn ref_value() { | ||
638 | check( | ||
639 | r#" | ||
640 | struct Seed; | ||
641 | fn bar() -> &Seed {} | ||
642 | fn foo() { $0(bar())$0; } | ||
643 | "#, | ||
644 | "seed", | ||
645 | ); | ||
646 | } | ||
647 | |||
648 | #[test] | ||
649 | fn box_value() { | ||
650 | check( | ||
651 | r#" | ||
652 | struct Box<T>(*const T); | ||
653 | struct Seed; | ||
654 | fn bar() -> Box<Seed> {} | ||
655 | fn foo() { $0(bar())$0; } | ||
656 | "#, | ||
657 | "seed", | ||
658 | ); | ||
659 | } | ||
660 | |||
661 | #[test] | ||
662 | fn box_generic() { | ||
663 | check( | ||
664 | r#" | ||
665 | struct Box<T>(*const T); | ||
666 | fn bar<T>() -> Box<T> {} | ||
667 | fn foo<T>() { $0(bar::<T>())$0; } | ||
668 | "#, | ||
669 | "bar", | ||
670 | ); | ||
671 | } | ||
672 | |||
673 | #[test] | ||
674 | fn option_value() { | ||
675 | check( | ||
676 | r#" | ||
677 | enum Option<T> { Some(T) } | ||
678 | struct Seed; | ||
679 | fn bar() -> Option<Seed> {} | ||
680 | fn foo() { $0(bar())$0; } | ||
681 | "#, | ||
682 | "seed", | ||
683 | ); | ||
684 | } | ||
685 | |||
686 | #[test] | ||
687 | fn result_value() { | ||
688 | check( | ||
689 | r#" | ||
690 | enum Result<T, E> { Ok(T), Err(E) } | ||
691 | struct Seed; | ||
692 | struct Error; | ||
693 | fn bar() -> Result<Seed, Error> {} | ||
694 | fn foo() { $0(bar())$0; } | ||
695 | "#, | ||
696 | "seed", | ||
697 | ); | ||
698 | } | ||
699 | |||
700 | #[test] | ||
701 | fn ref_call() { | ||
702 | check( | ||
703 | r#" | ||
704 | fn foo() { $0&bar(1, 3)$0 } | ||
705 | "#, | ||
706 | "bar", | ||
707 | ); | ||
708 | } | ||
709 | |||
710 | #[test] | ||
711 | fn name_to_string() { | ||
712 | check( | ||
713 | r#" | ||
714 | fn foo() { $0function.name().to_string()$0 } | ||
715 | "#, | ||
716 | "name", | ||
717 | ); | ||
718 | } | ||
719 | |||
720 | #[test] | ||
721 | fn nested_useless_method() { | ||
722 | check( | ||
723 | r#" | ||
724 | fn foo() { $0function.name().as_ref().unwrap().to_string()$0 } | ||
725 | "#, | ||
726 | "name", | ||
727 | ); | ||
728 | } | ||
729 | } | ||