diff options
Diffstat (limited to 'crates/ide/src/references')
-rw-r--r-- | crates/ide/src/references/rename.rs | 341 |
1 files changed, 212 insertions, 129 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 53d79333c..c3ae568c2 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -1,29 +1,30 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | use std::{ | 2 | use std::{ |
3 | convert::TryInto, | 3 | convert::TryInto, |
4 | error::Error, | ||
5 | fmt::{self, Display}, | 4 | fmt::{self, Display}, |
6 | }; | 5 | }; |
7 | 6 | ||
8 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 7 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; |
9 | use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt}; | ||
10 | use ide_db::{ | 8 | use ide_db::{ |
9 | base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt}, | ||
11 | defs::{Definition, NameClass, NameRefClass}, | 10 | defs::{Definition, NameClass, NameRefClass}, |
11 | search::FileReference, | ||
12 | RootDatabase, | 12 | RootDatabase, |
13 | }; | 13 | }; |
14 | use syntax::{ | 14 | use syntax::{ |
15 | algo::find_node_at_offset, | 15 | algo::find_node_at_offset, |
16 | ast::{self, NameOwner}, | 16 | ast::{self, NameOwner}, |
17 | lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, | 17 | lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T, |
18 | }; | 18 | }; |
19 | use test_utils::mark; | 19 | use test_utils::mark; |
20 | use text_edit::TextEdit; | 20 | use text_edit::TextEdit; |
21 | 21 | ||
22 | use crate::{ | 22 | use crate::{ |
23 | references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, | 23 | FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange, |
24 | SourceChange, SourceFileEdit, TextRange, TextSize, | 24 | SourceFileEdit, TextRange, TextSize, |
25 | }; | 25 | }; |
26 | 26 | ||
27 | type RenameResult<T> = Result<T, RenameError>; | ||
27 | #[derive(Debug)] | 28 | #[derive(Debug)] |
28 | pub struct RenameError(pub(crate) String); | 29 | pub struct RenameError(pub(crate) String); |
29 | 30 | ||
@@ -33,26 +34,30 @@ impl fmt::Display for RenameError { | |||
33 | } | 34 | } |
34 | } | 35 | } |
35 | 36 | ||
36 | impl Error for RenameError {} | 37 | macro_rules! format_err { |
38 | ($fmt:expr) => {RenameError(format!($fmt))}; | ||
39 | ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))} | ||
40 | } | ||
41 | |||
42 | macro_rules! bail { | ||
43 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} | ||
44 | } | ||
37 | 45 | ||
38 | pub(crate) fn prepare_rename( | 46 | pub(crate) fn prepare_rename( |
39 | db: &RootDatabase, | 47 | db: &RootDatabase, |
40 | position: FilePosition, | 48 | position: FilePosition, |
41 | ) -> Result<RangeInfo<()>, RenameError> { | 49 | ) -> RenameResult<RangeInfo<()>> { |
42 | let sema = Semantics::new(db); | 50 | let sema = Semantics::new(db); |
43 | let source_file = sema.parse(position.file_id); | 51 | let source_file = sema.parse(position.file_id); |
44 | let syntax = source_file.syntax(); | 52 | let syntax = source_file.syntax(); |
45 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | 53 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { |
46 | rename_mod(&sema, position, module, "dummy") | 54 | rename_mod(&sema, position, module, "dummy") |
47 | } else if let Some(self_token) = | 55 | } else if let Some(self_token) = |
48 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | 56 | syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self]) |
49 | { | 57 | { |
50 | rename_self_to_param(&sema, position, self_token, "dummy") | 58 | rename_self_to_param(&sema, position, self_token, "dummy") |
51 | } else { | 59 | } else { |
52 | let range = match find_all_refs(&sema, position, None) { | 60 | let RangeInfo { range, .. } = find_all_refs(&sema, position)?; |
53 | Some(RangeInfo { range, .. }) => range, | ||
54 | None => return Err(RenameError("No references found at position".to_string())), | ||
55 | }; | ||
56 | Ok(RangeInfo::new(range, SourceChange::from(vec![]))) | 61 | Ok(RangeInfo::new(range, SourceChange::from(vec![]))) |
57 | } | 62 | } |
58 | .map(|info| RangeInfo::new(info.range, ())) | 63 | .map(|info| RangeInfo::new(info.range, ())) |
@@ -62,7 +67,7 @@ pub(crate) fn rename( | |||
62 | db: &RootDatabase, | 67 | db: &RootDatabase, |
63 | position: FilePosition, | 68 | position: FilePosition, |
64 | new_name: &str, | 69 | new_name: &str, |
65 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 70 | ) -> RenameResult<RangeInfo<SourceChange>> { |
66 | let sema = Semantics::new(db); | 71 | let sema = Semantics::new(db); |
67 | rename_with_semantics(&sema, position, new_name) | 72 | rename_with_semantics(&sema, position, new_name) |
68 | } | 73 | } |
@@ -71,42 +76,18 @@ pub(crate) fn rename_with_semantics( | |||
71 | sema: &Semantics<RootDatabase>, | 76 | sema: &Semantics<RootDatabase>, |
72 | position: FilePosition, | 77 | position: FilePosition, |
73 | new_name: &str, | 78 | new_name: &str, |
74 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 79 | ) -> RenameResult<RangeInfo<SourceChange>> { |
75 | let is_lifetime_name = match lex_single_syntax_kind(new_name) { | ||
76 | Some(res) => match res { | ||
77 | (SyntaxKind::IDENT, _) => false, | ||
78 | (SyntaxKind::UNDERSCORE, _) => false, | ||
79 | (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position), | ||
80 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true, | ||
81 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
82 | return Err(RenameError(format!( | ||
83 | "Invalid name `{0}`: Cannot rename lifetime to {0}", | ||
84 | new_name | ||
85 | ))) | ||
86 | } | ||
87 | (_, Some(syntax_error)) => { | ||
88 | return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error))) | ||
89 | } | ||
90 | (_, None) => { | ||
91 | return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) | ||
92 | } | ||
93 | }, | ||
94 | None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))), | ||
95 | }; | ||
96 | |||
97 | let source_file = sema.parse(position.file_id); | 80 | let source_file = sema.parse(position.file_id); |
98 | let syntax = source_file.syntax(); | 81 | let syntax = source_file.syntax(); |
99 | // this is here to prevent lifetime renames from happening on modules and self | 82 | |
100 | if is_lifetime_name { | 83 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { |
101 | rename_reference(&sema, position, new_name, is_lifetime_name) | ||
102 | } else if let Some(module) = find_module_at_offset(&sema, position, syntax) { | ||
103 | rename_mod(&sema, position, module, new_name) | 84 | rename_mod(&sema, position, module, new_name) |
104 | } else if let Some(self_token) = | 85 | } else if let Some(self_token) = |
105 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | 86 | syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self]) |
106 | { | 87 | { |
107 | rename_self_to_param(&sema, position, self_token, new_name) | 88 | rename_self_to_param(&sema, position, self_token, new_name) |
108 | } else { | 89 | } else { |
109 | rename_reference(&sema, position, new_name, is_lifetime_name) | 90 | rename_reference(&sema, position, new_name) |
110 | } | 91 | } |
111 | } | 92 | } |
112 | 93 | ||
@@ -127,6 +108,33 @@ pub(crate) fn will_rename_file( | |||
127 | Some(change) | 108 | Some(change) |
128 | } | 109 | } |
129 | 110 | ||
111 | #[derive(Debug, PartialEq)] | ||
112 | enum IdentifierKind { | ||
113 | Ident, | ||
114 | Lifetime, | ||
115 | ToSelf, | ||
116 | Underscore, | ||
117 | } | ||
118 | |||
119 | fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | ||
120 | match lex_single_syntax_kind(new_name) { | ||
121 | Some(res) => match res { | ||
122 | (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident), | ||
123 | (T![_], _) => Ok(IdentifierKind::Underscore), | ||
124 | (T![self], _) => Ok(IdentifierKind::ToSelf), | ||
125 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { | ||
126 | Ok(IdentifierKind::Lifetime) | ||
127 | } | ||
128 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
129 | bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name) | ||
130 | } | ||
131 | (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), | ||
132 | (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), | ||
133 | }, | ||
134 | None => bail!("Invalid name `{}`: not an identifier", new_name), | ||
135 | } | ||
136 | } | ||
137 | |||
130 | fn find_module_at_offset( | 138 | fn find_module_at_offset( |
131 | sema: &Semantics<RootDatabase>, | 139 | sema: &Semantics<RootDatabase>, |
132 | position: FilePosition, | 140 | position: FilePosition, |
@@ -155,39 +163,54 @@ fn find_module_at_offset( | |||
155 | Some(module) | 163 | Some(module) |
156 | } | 164 | } |
157 | 165 | ||
158 | fn source_edit_from_reference( | 166 | fn find_all_refs( |
167 | sema: &Semantics<RootDatabase>, | ||
168 | position: FilePosition, | ||
169 | ) -> RenameResult<RangeInfo<ReferenceSearchResult>> { | ||
170 | crate::references::find_all_refs(sema, position, None) | ||
171 | .ok_or_else(|| format_err!("No references found at position")) | ||
172 | } | ||
173 | |||
174 | fn source_edit_from_references( | ||
159 | sema: &Semantics<RootDatabase>, | 175 | sema: &Semantics<RootDatabase>, |
160 | reference: Reference, | 176 | file_id: FileId, |
177 | references: &[FileReference], | ||
161 | new_name: &str, | 178 | new_name: &str, |
162 | ) -> SourceFileEdit { | 179 | ) -> SourceFileEdit { |
163 | let mut replacement_text = String::new(); | 180 | let mut edit = TextEdit::builder(); |
164 | let range = match reference.kind { | 181 | for reference in references { |
165 | ReferenceKind::FieldShorthandForField => { | 182 | let mut replacement_text = String::new(); |
166 | mark::hit!(test_rename_struct_field_for_shorthand); | 183 | let range = match reference.kind { |
167 | replacement_text.push_str(new_name); | 184 | ReferenceKind::FieldShorthandForField => { |
168 | replacement_text.push_str(": "); | 185 | mark::hit!(test_rename_struct_field_for_shorthand); |
169 | TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) | 186 | replacement_text.push_str(new_name); |
170 | } | 187 | replacement_text.push_str(": "); |
171 | ReferenceKind::FieldShorthandForLocal => { | 188 | TextRange::new(reference.range.start(), reference.range.start()) |
172 | mark::hit!(test_rename_local_for_field_shorthand); | 189 | } |
173 | replacement_text.push_str(": "); | 190 | ReferenceKind::FieldShorthandForLocal => { |
174 | replacement_text.push_str(new_name); | 191 | mark::hit!(test_rename_local_for_field_shorthand); |
175 | TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) | 192 | replacement_text.push_str(": "); |
176 | } | 193 | replacement_text.push_str(new_name); |
177 | ReferenceKind::RecordFieldExprOrPat => { | 194 | TextRange::new(reference.range.end(), reference.range.end()) |
178 | mark::hit!(test_rename_field_expr_pat); | 195 | } |
179 | replacement_text.push_str(new_name); | 196 | ReferenceKind::RecordFieldExprOrPat => { |
180 | edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name) | 197 | mark::hit!(test_rename_field_expr_pat); |
181 | } | 198 | replacement_text.push_str(new_name); |
182 | _ => { | 199 | edit_text_range_for_record_field_expr_or_pat( |
183 | replacement_text.push_str(new_name); | 200 | sema, |
184 | reference.file_range.range | 201 | FileRange { file_id, range: reference.range }, |
185 | } | 202 | new_name, |
186 | }; | 203 | ) |
187 | SourceFileEdit { | 204 | } |
188 | file_id: reference.file_range.file_id, | 205 | _ => { |
189 | edit: TextEdit::replace(range, replacement_text), | 206 | replacement_text.push_str(new_name); |
207 | reference.range | ||
208 | } | ||
209 | }; | ||
210 | edit.replace(range, replacement_text); | ||
190 | } | 211 | } |
212 | |||
213 | SourceFileEdit { file_id, edit: edit.finish() } | ||
191 | } | 214 | } |
192 | 215 | ||
193 | fn edit_text_range_for_record_field_expr_or_pat( | 216 | fn edit_text_range_for_record_field_expr_or_pat( |
@@ -223,7 +246,10 @@ fn rename_mod( | |||
223 | position: FilePosition, | 246 | position: FilePosition, |
224 | module: Module, | 247 | module: Module, |
225 | new_name: &str, | 248 | new_name: &str, |
226 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 249 | ) -> RenameResult<RangeInfo<SourceChange>> { |
250 | if IdentifierKind::Ident != check_identifier(new_name)? { | ||
251 | bail!("Invalid name `{0}`: cannot rename module to {0}", new_name); | ||
252 | } | ||
227 | let mut source_file_edits = Vec::new(); | 253 | let mut source_file_edits = Vec::new(); |
228 | let mut file_system_edits = Vec::new(); | 254 | let mut file_system_edits = Vec::new(); |
229 | 255 | ||
@@ -254,12 +280,10 @@ fn rename_mod( | |||
254 | source_file_edits.push(edit); | 280 | source_file_edits.push(edit); |
255 | } | 281 | } |
256 | 282 | ||
257 | let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) | 283 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
258 | .ok_or_else(|| RenameError("No references found at position".to_string()))?; | 284 | let ref_edits = refs.references().iter().map(|(&file_id, references)| { |
259 | let ref_edits = refs | 285 | source_edit_from_references(sema, file_id, references, new_name) |
260 | .references | 286 | }); |
261 | .into_iter() | ||
262 | .map(|reference| source_edit_from_reference(sema, reference, new_name)); | ||
263 | source_file_edits.extend(ref_edits); | 287 | source_file_edits.extend(ref_edits); |
264 | 288 | ||
265 | Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) | 289 | Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) |
@@ -274,27 +298,26 @@ fn rename_to_self( | |||
274 | 298 | ||
275 | let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) | 299 | let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) |
276 | .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) | 300 | .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) |
277 | .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; | 301 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; |
278 | let param_range = fn_ast | 302 | let param_range = fn_ast |
279 | .param_list() | 303 | .param_list() |
280 | .and_then(|p| p.params().next()) | 304 | .and_then(|p| p.params().next()) |
281 | .ok_or_else(|| RenameError("Method has no parameters".to_string()))? | 305 | .ok_or_else(|| format_err!("Method has no parameters"))? |
282 | .syntax() | 306 | .syntax() |
283 | .text_range(); | 307 | .text_range(); |
284 | if !param_range.contains(position.offset) { | 308 | if !param_range.contains(position.offset) { |
285 | return Err(RenameError("Only the first parameter can be self".to_string())); | 309 | bail!("Only the first parameter can be self"); |
286 | } | 310 | } |
287 | 311 | ||
288 | let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) | 312 | let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) |
289 | .and_then(|def| sema.to_def(&def)) | 313 | .and_then(|def| sema.to_def(&def)) |
290 | .ok_or_else(|| RenameError("No impl block found for function".to_string()))?; | 314 | .ok_or_else(|| format_err!("No impl block found for function"))?; |
291 | if fn_def.self_param(sema.db).is_some() { | 315 | if fn_def.self_param(sema.db).is_some() { |
292 | return Err(RenameError("Method already has a self parameter".to_string())); | 316 | bail!("Method already has a self parameter"); |
293 | } | 317 | } |
294 | 318 | ||
295 | let params = fn_def.assoc_fn_params(sema.db); | 319 | let params = fn_def.assoc_fn_params(sema.db); |
296 | let first_param = | 320 | let first_param = params.first().ok_or_else(|| format_err!("Method has no parameters"))?; |
297 | params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?; | ||
298 | let first_param_ty = first_param.ty(); | 321 | let first_param_ty = first_param.ty(); |
299 | let impl_ty = impl_block.target_ty(sema.db); | 322 | let impl_ty = impl_block.target_ty(sema.db); |
300 | let (ty, self_param) = if impl_ty.remove_ref().is_some() { | 323 | let (ty, self_param) = if impl_ty.remove_ref().is_some() { |
@@ -307,23 +330,17 @@ fn rename_to_self( | |||
307 | }; | 330 | }; |
308 | 331 | ||
309 | if ty != impl_ty { | 332 | if ty != impl_ty { |
310 | return Err(RenameError("Parameter type differs from impl block type".to_string())); | 333 | bail!("Parameter type differs from impl block type"); |
311 | } | 334 | } |
312 | 335 | ||
313 | let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) | 336 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
314 | .ok_or_else(|| RenameError("No reference found at position".to_string()))?; | ||
315 | |||
316 | let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs | ||
317 | .into_iter() | ||
318 | .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); | ||
319 | |||
320 | if param_ref.is_empty() { | ||
321 | return Err(RenameError("Parameter to rename not found".to_string())); | ||
322 | } | ||
323 | 337 | ||
324 | let mut edits = usages | 338 | let mut edits = refs |
325 | .into_iter() | 339 | .references() |
326 | .map(|reference| source_edit_from_reference(sema, reference, "self")) | 340 | .iter() |
341 | .map(|(&file_id, references)| { | ||
342 | source_edit_from_references(sema, file_id, references, "self") | ||
343 | }) | ||
327 | .collect::<Vec<_>>(); | 344 | .collect::<Vec<_>>(); |
328 | 345 | ||
329 | edits.push(SourceFileEdit { | 346 | edits.push(SourceFileEdit { |
@@ -367,12 +384,22 @@ fn rename_self_to_param( | |||
367 | self_token: SyntaxToken, | 384 | self_token: SyntaxToken, |
368 | new_name: &str, | 385 | new_name: &str, |
369 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 386 | ) -> Result<RangeInfo<SourceChange>, RenameError> { |
387 | let ident_kind = check_identifier(new_name)?; | ||
388 | match ident_kind { | ||
389 | IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name), | ||
390 | IdentifierKind::ToSelf => { | ||
391 | // no-op | ||
392 | mark::hit!(rename_self_to_self); | ||
393 | return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default())); | ||
394 | } | ||
395 | _ => (), | ||
396 | } | ||
370 | let source_file = sema.parse(position.file_id); | 397 | let source_file = sema.parse(position.file_id); |
371 | let syn = source_file.syntax(); | 398 | let syn = source_file.syntax(); |
372 | 399 | ||
373 | let text = sema.db.file_text(position.file_id); | 400 | let text = sema.db.file_text(position.file_id); |
374 | let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) | 401 | let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) |
375 | .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; | 402 | .ok_or_else(|| format_err!("No surrounding method declaration found"))?; |
376 | let search_range = fn_def.syntax().text_range(); | 403 | let search_range = fn_def.syntax().text_range(); |
377 | 404 | ||
378 | let mut edits: Vec<SourceFileEdit> = vec![]; | 405 | let mut edits: Vec<SourceFileEdit> = vec![]; |
@@ -382,12 +409,10 @@ fn rename_self_to_param( | |||
382 | if !search_range.contains_inclusive(offset) { | 409 | if !search_range.contains_inclusive(offset) { |
383 | continue; | 410 | continue; |
384 | } | 411 | } |
385 | if let Some(ref usage) = | 412 | if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) { |
386 | syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | ||
387 | { | ||
388 | let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { | 413 | let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { |
389 | text_edit_from_self_param(syn, self_param, new_name) | 414 | text_edit_from_self_param(syn, self_param, new_name) |
390 | .ok_or_else(|| RenameError("No target type found".to_string()))? | 415 | .ok_or_else(|| format_err!("No target type found"))? |
391 | } else { | 416 | } else { |
392 | TextEdit::replace(usage.text_range(), String::from(new_name)) | 417 | TextEdit::replace(usage.text_range(), String::from(new_name)) |
393 | }; | 418 | }; |
@@ -395,6 +420,10 @@ fn rename_self_to_param( | |||
395 | } | 420 | } |
396 | } | 421 | } |
397 | 422 | ||
423 | if edits.len() > 1 && ident_kind == IdentifierKind::Underscore { | ||
424 | bail!("Cannot rename reference to `_` as it is being referenced multiple times"); | ||
425 | } | ||
426 | |||
398 | let range = ast::SelfParam::cast(self_token.parent()) | 427 | let range = ast::SelfParam::cast(self_token.parent()) |
399 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); | 428 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); |
400 | 429 | ||
@@ -405,35 +434,43 @@ fn rename_reference( | |||
405 | sema: &Semantics<RootDatabase>, | 434 | sema: &Semantics<RootDatabase>, |
406 | position: FilePosition, | 435 | position: FilePosition, |
407 | new_name: &str, | 436 | new_name: &str, |
408 | is_lifetime_name: bool, | ||
409 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 437 | ) -> Result<RangeInfo<SourceChange>, RenameError> { |
410 | let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { | 438 | let ident_kind = check_identifier(new_name)?; |
411 | Some(range_info) => range_info, | 439 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
412 | None => return Err(RenameError("No references found at position".to_string())), | 440 | |
413 | }; | 441 | match (ident_kind, &refs.declaration.kind) { |
414 | 442 | (IdentifierKind::ToSelf, ReferenceKind::Lifetime) | |
415 | match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) { | 443 | | (IdentifierKind::Underscore, ReferenceKind::Lifetime) |
416 | (true, false) => { | 444 | | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { |
417 | return Err(RenameError(format!( | 445 | mark::hit!(rename_not_a_lifetime_ident_ref); |
418 | "Invalid name `{}`: not a lifetime identifier", | 446 | bail!("Invalid name `{}`: not a lifetime identifier", new_name) |
419 | new_name | ||
420 | ))) | ||
421 | } | 447 | } |
422 | (false, true) => { | 448 | (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime), |
423 | return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) | 449 | (IdentifierKind::Lifetime, _) => { |
450 | mark::hit!(rename_not_an_ident_ref); | ||
451 | bail!("Invalid name `{}`: not an identifier", new_name) | ||
424 | } | 452 | } |
425 | _ => (), | 453 | (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => { |
454 | unreachable!("rename_self_to_param should've been called instead") | ||
455 | } | ||
456 | (IdentifierKind::ToSelf, _) => { | ||
457 | mark::hit!(rename_to_self); | ||
458 | return rename_to_self(sema, position); | ||
459 | } | ||
460 | (IdentifierKind::Underscore, _) if !refs.references.is_empty() => { | ||
461 | mark::hit!(rename_underscore_multiple); | ||
462 | bail!("Cannot rename reference to `_` as it is being referenced multiple times") | ||
463 | } | ||
464 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), | ||
426 | } | 465 | } |
427 | 466 | ||
428 | let edit = refs | 467 | let edit = refs |
429 | .into_iter() | 468 | .into_iter() |
430 | .map(|reference| source_edit_from_reference(sema, reference, new_name)) | 469 | .map(|(file_id, references)| { |
470 | source_edit_from_references(sema, file_id, &references, new_name) | ||
471 | }) | ||
431 | .collect::<Vec<_>>(); | 472 | .collect::<Vec<_>>(); |
432 | 473 | ||
433 | if edit.is_empty() { | ||
434 | return Err(RenameError("No references found at position".to_string())); | ||
435 | } | ||
436 | |||
437 | Ok(RangeInfo::new(range, SourceChange::from(edit))) | 474 | Ok(RangeInfo::new(range, SourceChange::from(edit))) |
438 | } | 475 | } |
439 | 476 | ||
@@ -462,9 +499,11 @@ mod tests { | |||
462 | text_edit_builder.replace(indel.delete, indel.insert); | 499 | text_edit_builder.replace(indel.delete, indel.insert); |
463 | } | 500 | } |
464 | } | 501 | } |
465 | let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); | 502 | if let Some(file_id) = file_id { |
466 | text_edit_builder.finish().apply(&mut result); | 503 | let mut result = analysis.file_text(file_id).unwrap().to_string(); |
467 | assert_eq_text!(ra_fixture_after, &*result); | 504 | text_edit_builder.finish().apply(&mut result); |
505 | assert_eq_text!(ra_fixture_after, &*result); | ||
506 | } | ||
468 | } | 507 | } |
469 | Err(err) => { | 508 | Err(err) => { |
470 | if ra_fixture_after.starts_with("error:") { | 509 | if ra_fixture_after.starts_with("error:") { |
@@ -530,6 +569,7 @@ mod tests { | |||
530 | 569 | ||
531 | #[test] | 570 | #[test] |
532 | fn test_rename_to_invalid_identifier_lifetime() { | 571 | fn test_rename_to_invalid_identifier_lifetime() { |
572 | mark::check!(rename_not_an_ident_ref); | ||
533 | check( | 573 | check( |
534 | "'foo", | 574 | "'foo", |
535 | r#"fn main() { let i$0 = 1; }"#, | 575 | r#"fn main() { let i$0 = 1; }"#, |
@@ -539,6 +579,7 @@ mod tests { | |||
539 | 579 | ||
540 | #[test] | 580 | #[test] |
541 | fn test_rename_to_invalid_identifier_lifetime2() { | 581 | fn test_rename_to_invalid_identifier_lifetime2() { |
582 | mark::check!(rename_not_a_lifetime_ident_ref); | ||
542 | check( | 583 | check( |
543 | "foo", | 584 | "foo", |
544 | r#"fn main<'a>(_: &'a$0 ()) {}"#, | 585 | r#"fn main<'a>(_: &'a$0 ()) {}"#, |
@@ -547,7 +588,27 @@ mod tests { | |||
547 | } | 588 | } |
548 | 589 | ||
549 | #[test] | 590 | #[test] |
591 | fn test_rename_to_underscore_invalid() { | ||
592 | mark::check!(rename_underscore_multiple); | ||
593 | check( | ||
594 | "_", | ||
595 | r#"fn main(foo$0: ()) {foo;}"#, | ||
596 | "error: Cannot rename reference to `_` as it is being referenced multiple times", | ||
597 | ); | ||
598 | } | ||
599 | |||
600 | #[test] | ||
601 | fn test_rename_mod_invalid() { | ||
602 | check( | ||
603 | "'foo", | ||
604 | r#"mod foo$0 {}"#, | ||
605 | "error: Invalid name `'foo`: cannot rename module to 'foo", | ||
606 | ); | ||
607 | } | ||
608 | |||
609 | #[test] | ||
550 | fn test_rename_for_local() { | 610 | fn test_rename_for_local() { |
611 | mark::check!(rename_ident); | ||
551 | check( | 612 | check( |
552 | "k", | 613 | "k", |
553 | r#" | 614 | r#" |
@@ -945,7 +1006,7 @@ use crate::foo$0::FooContent; | |||
945 | //- /lib.rs | 1006 | //- /lib.rs |
946 | mod fo$0o; | 1007 | mod fo$0o; |
947 | //- /foo/mod.rs | 1008 | //- /foo/mod.rs |
948 | // emtpy | 1009 | // empty |
949 | "#, | 1010 | "#, |
950 | expect![[r#" | 1011 | expect![[r#" |
951 | RangeInfo { | 1012 | RangeInfo { |
@@ -995,7 +1056,7 @@ mod fo$0o; | |||
995 | mod outer { mod fo$0o; } | 1056 | mod outer { mod fo$0o; } |
996 | 1057 | ||
997 | //- /outer/foo.rs | 1058 | //- /outer/foo.rs |
998 | // emtpy | 1059 | // empty |
999 | "#, | 1060 | "#, |
1000 | expect![[r#" | 1061 | expect![[r#" |
1001 | RangeInfo { | 1062 | RangeInfo { |
@@ -1178,6 +1239,7 @@ fn foo(f: foo::Foo) { | |||
1178 | 1239 | ||
1179 | #[test] | 1240 | #[test] |
1180 | fn test_parameter_to_self() { | 1241 | fn test_parameter_to_self() { |
1242 | mark::check!(rename_to_self); | ||
1181 | check( | 1243 | check( |
1182 | "self", | 1244 | "self", |
1183 | r#" | 1245 | r#" |
@@ -1481,6 +1543,7 @@ fn foo(Foo { i: bar }: foo) -> i32 { | |||
1481 | 1543 | ||
1482 | #[test] | 1544 | #[test] |
1483 | fn test_rename_lifetimes() { | 1545 | fn test_rename_lifetimes() { |
1546 | mark::check!(rename_lifetime); | ||
1484 | check( | 1547 | check( |
1485 | "'yeeee", | 1548 | "'yeeee", |
1486 | r#" | 1549 | r#" |
@@ -1565,4 +1628,24 @@ fn foo<'a>() -> &'a () { | |||
1565 | "#, | 1628 | "#, |
1566 | ) | 1629 | ) |
1567 | } | 1630 | } |
1631 | |||
1632 | #[test] | ||
1633 | fn test_self_to_self() { | ||
1634 | mark::check!(rename_self_to_self); | ||
1635 | check( | ||
1636 | "self", | ||
1637 | r#" | ||
1638 | struct Foo; | ||
1639 | impl Foo { | ||
1640 | fn foo(self$0) {} | ||
1641 | } | ||
1642 | "#, | ||
1643 | r#" | ||
1644 | struct Foo; | ||
1645 | impl Foo { | ||
1646 | fn foo(self) {} | ||
1647 | } | ||
1648 | "#, | ||
1649 | ) | ||
1650 | } | ||
1568 | } | 1651 | } |