aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/references/rename.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/references/rename.rs')
-rw-r--r--crates/ide/src/references/rename.rs182
1 files changed, 136 insertions, 46 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 8cbe1ae5a..f9a11e43d 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -6,11 +6,16 @@ use ide_db::{
6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, 6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
7 RootDatabase, 7 RootDatabase,
8}; 8};
9use std::convert::TryInto; 9
10use std::{
11 convert::TryInto,
12 error::Error,
13 fmt::{self, Display},
14};
10use syntax::{ 15use syntax::{
11 algo::find_node_at_offset, 16 algo::find_node_at_offset,
12 ast::{self, NameOwner}, 17 ast::{self, NameOwner},
13 lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 18 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
14}; 19};
15use test_utils::mark; 20use test_utils::mark;
16use text_edit::TextEdit; 21use text_edit::TextEdit;
@@ -20,17 +25,44 @@ use crate::{
20 SourceChange, SourceFileEdit, TextRange, TextSize, 25 SourceChange, SourceFileEdit, TextRange, TextSize,
21}; 26};
22 27
28#[derive(Debug)]
29pub struct RenameError(pub(crate) String);
30
31impl fmt::Display for RenameError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 Display::fmt(&self.0, f)
34 }
35}
36
37impl Error for RenameError {}
38
23pub(crate) fn rename( 39pub(crate) fn rename(
24 db: &RootDatabase, 40 db: &RootDatabase,
25 position: FilePosition, 41 position: FilePosition,
26 new_name: &str, 42 new_name: &str,
27) -> Option<RangeInfo<SourceChange>> { 43) -> Result<RangeInfo<SourceChange>, RenameError> {
28 let sema = Semantics::new(db); 44 let sema = Semantics::new(db);
45 rename_with_semantics(&sema, position, new_name)
46}
29 47
30 match lex_single_valid_syntax_kind(new_name)? { 48pub(crate) fn rename_with_semantics(
31 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), 49 sema: &Semantics<RootDatabase>,
32 SyntaxKind::SELF_KW => return rename_to_self(&sema, position), 50 position: FilePosition,
33 _ => return None, 51 new_name: &str,
52) -> Result<RangeInfo<SourceChange>, RenameError> {
53 match lex_single_syntax_kind(new_name) {
54 Some(res) => match res {
55 (SyntaxKind::IDENT, _) => (),
56 (SyntaxKind::UNDERSCORE, _) => (),
57 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
58 (_, Some(syntax_error)) => {
59 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
60 }
61 (_, None) => {
62 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
63 }
64 },
65 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
34 } 66 }
35 67
36 let source_file = sema.parse(position.file_id); 68 let source_file = sema.parse(position.file_id);
@@ -103,7 +135,7 @@ fn rename_mod(
103 position: FilePosition, 135 position: FilePosition,
104 module: Module, 136 module: Module,
105 new_name: &str, 137 new_name: &str,
106) -> Option<RangeInfo<SourceChange>> { 138) -> Result<RangeInfo<SourceChange>, RenameError> {
107 let mut source_file_edits = Vec::new(); 139 let mut source_file_edits = Vec::new();
108 let mut file_system_edits = Vec::new(); 140 let mut file_system_edits = Vec::new();
109 141
@@ -125,7 +157,7 @@ fn rename_mod(
125 157
126 if let Some(src) = module.declaration_source(sema.db) { 158 if let Some(src) = module.declaration_source(sema.db) {
127 let file_id = src.file_id.original_file(sema.db); 159 let file_id = src.file_id.original_file(sema.db);
128 let name = src.value.name()?; 160 let name = src.value.name().unwrap();
129 let edit = SourceFileEdit { 161 let edit = SourceFileEdit {
130 file_id, 162 file_id,
131 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), 163 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
@@ -133,35 +165,40 @@ fn rename_mod(
133 source_file_edits.push(edit); 165 source_file_edits.push(edit);
134 } 166 }
135 167
136 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 168 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
169 .ok_or_else(|| RenameError("No references found at position".to_string()))?;
137 let ref_edits = refs 170 let ref_edits = refs
138 .references 171 .references
139 .into_iter() 172 .into_iter()
140 .map(|reference| source_edit_from_reference(reference, new_name)); 173 .map(|reference| source_edit_from_reference(reference, new_name));
141 source_file_edits.extend(ref_edits); 174 source_file_edits.extend(ref_edits);
142 175
143 Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 176 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
144} 177}
145 178
146fn rename_to_self( 179fn rename_to_self(
147 sema: &Semantics<RootDatabase>, 180 sema: &Semantics<RootDatabase>,
148 position: FilePosition, 181 position: FilePosition,
149) -> Option<RangeInfo<SourceChange>> { 182) -> Result<RangeInfo<SourceChange>, RenameError> {
150 let source_file = sema.parse(position.file_id); 183 let source_file = sema.parse(position.file_id);
151 let syn = source_file.syntax(); 184 let syn = source_file.syntax();
152 185
153 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 186 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
154 let params = fn_def.param_list()?; 187 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
188 let params =
189 fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?;
155 if params.self_param().is_some() { 190 if params.self_param().is_some() {
156 return None; // method already has self param 191 return Err(RenameError("Method already has a self parameter".to_string()));
157 } 192 }
158 let first_param = params.params().next()?; 193 let first_param =
194 params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?;
159 let mutable = match first_param.ty() { 195 let mutable = match first_param.ty() {
160 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(), 196 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
161 _ => return None, // not renaming other types 197 _ => return Err(RenameError("Not renaming other types".to_string())),
162 }; 198 };
163 199
164 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 200 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
201 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
165 202
166 let param_range = first_param.syntax().text_range(); 203 let param_range = first_param.syntax().text_range();
167 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs 204 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
@@ -169,7 +206,7 @@ fn rename_to_self(
169 .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); 206 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
170 207
171 if param_ref.is_empty() { 208 if param_ref.is_empty() {
172 return None; 209 return Err(RenameError("Parameter to rename not found".to_string()));
173 } 210 }
174 211
175 let mut edits = usages 212 let mut edits = usages
@@ -185,7 +222,7 @@ fn rename_to_self(
185 ), 222 ),
186 }); 223 });
187 224
188 Some(RangeInfo::new(range, SourceChange::from(edits))) 225 Ok(RangeInfo::new(range, SourceChange::from(edits)))
189} 226}
190 227
191fn text_edit_from_self_param( 228fn text_edit_from_self_param(
@@ -216,12 +253,13 @@ fn rename_self_to_param(
216 position: FilePosition, 253 position: FilePosition,
217 self_token: SyntaxToken, 254 self_token: SyntaxToken,
218 new_name: &str, 255 new_name: &str,
219) -> Option<RangeInfo<SourceChange>> { 256) -> Result<RangeInfo<SourceChange>, RenameError> {
220 let source_file = sema.parse(position.file_id); 257 let source_file = sema.parse(position.file_id);
221 let syn = source_file.syntax(); 258 let syn = source_file.syntax();
222 259
223 let text = sema.db.file_text(position.file_id); 260 let text = sema.db.file_text(position.file_id);
224 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 261 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
262 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
225 let search_range = fn_def.syntax().text_range(); 263 let search_range = fn_def.syntax().text_range();
226 264
227 let mut edits: Vec<SourceFileEdit> = vec![]; 265 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -235,7 +273,8 @@ fn rename_self_to_param(
235 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 273 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
236 { 274 {
237 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 275 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
238 text_edit_from_self_param(syn, self_param, new_name)? 276 text_edit_from_self_param(syn, self_param, new_name)
277 .ok_or_else(|| RenameError("No target type found".to_string()))?
239 } else { 278 } else {
240 TextEdit::replace(usage.text_range(), String::from(new_name)) 279 TextEdit::replace(usage.text_range(), String::from(new_name))
241 }; 280 };
@@ -246,15 +285,18 @@ fn rename_self_to_param(
246 let range = ast::SelfParam::cast(self_token.parent()) 285 let range = ast::SelfParam::cast(self_token.parent())
247 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 286 .map_or(self_token.text_range(), |p| p.syntax().text_range());
248 287
249 Some(RangeInfo::new(range, SourceChange::from(edits))) 288 Ok(RangeInfo::new(range, SourceChange::from(edits)))
250} 289}
251 290
252fn rename_reference( 291fn rename_reference(
253 sema: &Semantics<RootDatabase>, 292 sema: &Semantics<RootDatabase>,
254 position: FilePosition, 293 position: FilePosition,
255 new_name: &str, 294 new_name: &str,
256) -> Option<RangeInfo<SourceChange>> { 295) -> Result<RangeInfo<SourceChange>, RenameError> {
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 296 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
297 Some(range_info) => range_info,
298 None => return Err(RenameError("No references found at position".to_string())),
299 };
258 300
259 let edit = refs 301 let edit = refs
260 .into_iter() 302 .into_iter()
@@ -262,10 +304,10 @@ fn rename_reference(
262 .collect::<Vec<_>>(); 304 .collect::<Vec<_>>();
263 305
264 if edit.is_empty() { 306 if edit.is_empty() {
265 return None; 307 return Err(RenameError("No references found at position".to_string()));
266 } 308 }
267 309
268 Some(RangeInfo::new(range, SourceChange::from(edit))) 310 Ok(RangeInfo::new(range, SourceChange::from(edit)))
269} 311}
270 312
271#[cfg(test)] 313#[cfg(test)]
@@ -280,25 +322,45 @@ mod tests {
280 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 322 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
281 let ra_fixture_after = &trim_indent(ra_fixture_after); 323 let ra_fixture_after = &trim_indent(ra_fixture_after);
282 let (analysis, position) = fixture::position(ra_fixture_before); 324 let (analysis, position) = fixture::position(ra_fixture_before);
283 let source_change = analysis.rename(position, new_name).unwrap(); 325 let rename_result = analysis
284 let mut text_edit_builder = TextEdit::builder(); 326 .rename(position, new_name)
285 let mut file_id: Option<FileId> = None; 327 .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err));
286 if let Some(change) = source_change { 328 match rename_result {
287 for edit in change.info.source_file_edits { 329 Ok(source_change) => {
288 file_id = Some(edit.file_id); 330 let mut text_edit_builder = TextEdit::builder();
289 for indel in edit.edit.into_iter() { 331 let mut file_id: Option<FileId> = None;
290 text_edit_builder.replace(indel.delete, indel.insert); 332 for edit in source_change.info.source_file_edits {
333 file_id = Some(edit.file_id);
334 for indel in edit.edit.into_iter() {
335 text_edit_builder.replace(indel.delete, indel.insert);
336 }
291 } 337 }
338 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
339 text_edit_builder.finish().apply(&mut result);
340 assert_eq_text!(ra_fixture_after, &*result);
292 } 341 }
293 } 342 Err(err) => {
294 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 343 if ra_fixture_after.starts_with("error:") {
295 text_edit_builder.finish().apply(&mut result); 344 let error_message = ra_fixture_after
296 assert_eq_text!(ra_fixture_after, &*result); 345 .chars()
346 .into_iter()
347 .skip("error:".len())
348 .collect::<String>();
349 assert_eq!(error_message.trim(), err.to_string());
350 return;
351 } else {
352 panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
353 }
354 }
355 };
297 } 356 }
298 357
299 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 358 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
300 let (analysis, position) = fixture::position(ra_fixture); 359 let (analysis, position) = fixture::position(ra_fixture);
301 let source_change = analysis.rename(position, new_name).unwrap().unwrap(); 360 let source_change = analysis
361 .rename(position, new_name)
362 .unwrap()
363 .expect("Expect returned RangeInfo to be Some, but was None");
302 expect.assert_debug_eq(&source_change) 364 expect.assert_debug_eq(&source_change)
303 } 365 }
304 366
@@ -313,11 +375,30 @@ mod tests {
313 } 375 }
314 376
315 #[test] 377 #[test]
316 fn test_rename_to_invalid_identifier() { 378 fn test_rename_to_invalid_identifier1() {
317 let (analysis, position) = fixture::position(r#"fn main() { let i<|> = 1; }"#); 379 check(
318 let new_name = "invalid!"; 380 "invalid!",
319 let source_change = analysis.rename(position, new_name).unwrap(); 381 r#"fn main() { let i<|> = 1; }"#,
320 assert!(source_change.is_none()); 382 "error: Invalid name `invalid!`: not an identifier",
383 );
384 }
385
386 #[test]
387 fn test_rename_to_invalid_identifier2() {
388 check(
389 "multiple tokens",
390 r#"fn main() { let i<|> = 1; }"#,
391 "error: Invalid name `multiple tokens`: not an identifier",
392 );
393 }
394
395 #[test]
396 fn test_rename_to_invalid_identifier3() {
397 check(
398 "let",
399 r#"fn main() { let i<|> = 1; }"#,
400 "error: Invalid name `let`: not an identifier",
401 );
321 } 402 }
322 403
323 #[test] 404 #[test]
@@ -350,6 +431,15 @@ fn main() {
350 } 431 }
351 432
352 #[test] 433 #[test]
434 fn test_rename_unresolved_reference() {
435 check(
436 "new_name",
437 r#"fn main() { let _ = unresolved_ref<|>; }"#,
438 "error: No references found at position",
439 );
440 }
441
442 #[test]
353 fn test_rename_for_macro_args() { 443 fn test_rename_for_macro_args() {
354 check( 444 check(
355 "b", 445 "b",