1 //! FIXME: write short doc here
3 use hir::{ModuleSource, Semantics};
4 use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt};
5 use ra_ide_db::RootDatabase;
7 algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind,
8 AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
10 use ra_text_edit::TextEdit;
11 use std::convert::TryInto;
15 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
16 SourceChange, SourceFileEdit, TextRange, TextSize,
21 position: FilePosition,
23 ) -> Option<RangeInfo<SourceChange>> {
24 match lex_single_valid_syntax_kind(new_name)? {
25 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (),
26 SyntaxKind::SELF_KW => return rename_to_self(db, position),
30 let sema = Semantics::new(db);
31 let source_file = sema.parse(position.file_id);
32 let syntax = source_file.syntax();
33 if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) {
34 let range = ast_name.syntax().text_range();
35 rename_mod(&sema, &ast_name, &ast_module, position, new_name)
36 .map(|info| RangeInfo::new(range, info))
37 } else if let Some(self_token) =
38 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
40 rename_self_to_param(db, position, self_token, new_name)
42 rename_reference(sema.db, position, new_name)
46 fn find_name_and_module_at_offset(
48 position: FilePosition,
49 ) -> Option<(ast::Name, ast::Module)> {
50 let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?;
51 let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?;
52 Some((ast_name, ast_module))
55 fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit {
56 let mut replacement_text = String::new();
57 let file_id = reference.file_range.file_id;
58 let range = match reference.kind {
59 ReferenceKind::FieldShorthandForField => {
60 mark::hit!(test_rename_struct_field_for_shorthand);
61 replacement_text.push_str(new_name);
62 replacement_text.push_str(": ");
63 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
65 ReferenceKind::FieldShorthandForLocal => {
66 mark::hit!(test_rename_local_for_field_shorthand);
67 replacement_text.push_str(": ");
68 replacement_text.push_str(new_name);
69 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
72 replacement_text.push_str(new_name);
73 reference.file_range.range
76 SourceFileEdit { file_id, edit: TextEdit::replace(range, replacement_text) }
80 sema: &Semantics<RootDatabase>,
82 ast_module: &ast::Module,
83 position: FilePosition,
85 ) -> Option<SourceChange> {
86 let mut source_file_edits = Vec::new();
87 let mut file_system_edits = Vec::new();
88 if let Some(module) = sema.to_def(ast_module) {
89 let src = module.definition_source(sema.db);
90 let file_id = src.file_id.original_file(sema.db);
92 ModuleSource::SourceFile(..) => {
93 let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id);
94 // mod is defined in path/to/dir/mod.rs
95 let dst_path = if mod_path.file_stem() == Some("mod") {
98 .and_then(|p| p.parent())
99 .or_else(|| Some(RelativePath::new("")))
100 .map(|p| p.join(new_name).join("mod.rs"))
102 Some(mod_path.with_file_name(new_name).with_extension("rs"))
104 if let Some(path) = dst_path {
105 let move_file = FileSystemEdit::MoveFile {
107 dst_source_root: sema.db.file_source_root(position.file_id),
110 file_system_edits.push(move_file);
113 ModuleSource::Module(..) => {}
117 let edit = SourceFileEdit {
118 file_id: position.file_id,
119 edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()),
121 source_file_edits.push(edit);
123 if let Some(RangeInfo { range: _, info: refs }) = find_all_refs(sema.db, position, None) {
127 .map(|reference| source_edit_from_reference(reference, new_name));
128 source_file_edits.extend(ref_edits);
131 Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits))
134 fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
135 let sema = Semantics::new(db);
136 let source_file = sema.parse(position.file_id);
137 let syn = source_file.syntax();
139 let fn_def = find_node_at_offset::<ast::FnDef>(syn, position.offset)?;
140 let params = fn_def.param_list()?;
141 if params.self_param().is_some() {
142 return None; // method already has self param
144 let first_param = params.params().next()?;
145 let mutable = match first_param.ascribed_type() {
146 Some(ast::TypeRef::ReferenceType(rt)) => rt.mut_token().is_some(),
147 _ => return None, // not renaming other types
150 let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?;
152 let param_range = first_param.syntax().text_range();
153 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
155 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
157 if param_ref.is_empty() {
161 let mut edits = usages
163 .map(|reference| source_edit_from_reference(reference, "self"))
164 .collect::<Vec<_>>();
166 edits.push(SourceFileEdit {
167 file_id: position.file_id,
168 edit: TextEdit::replace(
170 String::from(if mutable { "&mut self" } else { "&self" }),
174 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits)))
177 fn text_edit_from_self_param(
179 self_param: &ast::SelfParam,
181 ) -> Option<TextEdit> {
182 fn target_type_name(impl_def: &ast::ImplDef) -> Option<String> {
183 if let Some(ast::TypeRef::PathType(p)) = impl_def.target_type() {
184 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
190 find_node_at_offset::<ast::ImplDef>(syn, self_param.syntax().text_range().start())?;
191 let type_name = target_type_name(&impl_def)?;
193 let mut replacement_text = String::from(new_name);
194 replacement_text.push_str(": ");
195 replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut "));
196 replacement_text.push_str(type_name.as_str());
198 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
201 fn rename_self_to_param(
203 position: FilePosition,
204 self_token: SyntaxToken,
206 ) -> Option<RangeInfo<SourceChange>> {
207 let sema = Semantics::new(db);
208 let source_file = sema.parse(position.file_id);
209 let syn = source_file.syntax();
211 let text = db.file_text(position.file_id);
212 let fn_def = find_node_at_offset::<ast::FnDef>(syn, position.offset)?;
213 let search_range = fn_def.syntax().text_range();
215 let mut edits: Vec<SourceFileEdit> = vec![];
217 for (idx, _) in text.match_indices("self") {
218 let offset: TextSize = idx.try_into().unwrap();
219 if !search_range.contains_inclusive(offset) {
222 if let Some(ref usage) =
223 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
225 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
226 text_edit_from_self_param(syn, self_param, new_name)?
228 TextEdit::replace(usage.text_range(), String::from(new_name))
230 edits.push(SourceFileEdit { file_id: position.file_id, edit });
234 let range = ast::SelfParam::cast(self_token.parent())
235 .map_or(self_token.text_range(), |p| p.syntax().text_range());
237 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits)))
242 position: FilePosition,
244 ) -> Option<RangeInfo<SourceChange>> {
245 let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?;
249 .map(|reference| source_edit_from_reference(reference, new_name))
250 .collect::<Vec<_>>();
256 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit)))
261 use insta::assert_debug_snapshot;
262 use ra_text_edit::TextEditBuilder;
263 use test_utils::{assert_eq_text, mark};
266 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId,
270 fn test_rename_to_underscore() {
285 fn test_rename_to_raw_identifier() {
300 fn test_rename_to_invalid_identifier() {
301 let (analysis, position) = single_file_with_position(
307 let new_name = "invalid!";
308 let source_change = analysis.rename(position, new_name).unwrap();
309 assert!(source_change.is_none());
313 fn test_rename_for_local() {
344 fn test_rename_for_macro_args() {
347 macro_rules! foo {($i:ident) => {$i} }
354 macro_rules! foo {($i:ident) => {$i} }
363 fn test_rename_for_macro_args_rev() {
366 macro_rules! foo {($i:ident) => {$i} }
373 macro_rules! foo {($i:ident) => {$i} }
382 fn test_rename_for_macro_define_fn() {
385 macro_rules! define_fn {($id:ident) => { fn $id{} }}
392 macro_rules! define_fn {($id:ident) => { fn $id{} }}
401 fn test_rename_for_macro_define_fn_rev() {
404 macro_rules! define_fn {($id:ident) => { fn $id{} }}
411 macro_rules! define_fn {($id:ident) => { fn $id{} }}
420 fn test_rename_for_param_inside() {
423 fn foo(i : u32) -> u32 {
428 fn foo(j : u32) -> u32 {
435 fn test_rename_refs_for_fn_param() {
438 fn foo(i<|> : u32) -> u32 {
443 fn foo(new_name : u32) -> u32 {
450 fn test_rename_for_mut_param() {
453 fn foo(mut i<|> : u32) -> u32 {
458 fn foo(mut new_name : u32) -> u32 {
465 fn test_rename_struct_field() {
473 fn new(i: i32) -> Self {
485 fn new(i: i32) -> Self {
494 fn test_rename_struct_field_for_shorthand() {
495 mark::check!(test_rename_struct_field_for_shorthand);
503 fn new(i: i32) -> Self {
515 fn new(i: i32) -> Self {
524 fn test_rename_local_for_field_shorthand() {
525 mark::check!(test_rename_local_for_field_shorthand);
533 fn new(i<|>: i32) -> Self {
545 fn new(j: i32) -> Self {
554 fn test_field_shorthand_correct_struct() {
566 fn new(i: i32) -> Self {
582 fn new(i: i32) -> Self {
591 fn test_shadow_local_for_struct_shorthand() {
598 fn baz(i<|>: i32) -> Self {
612 fn baz(j: i32) -> Self {
613 let x = Foo { i: j };
624 fn test_rename_mod() {
625 let (analysis, position) = analysis_and_position(
637 let new_name = "foo2";
638 let source_change = analysis.rename(position, new_name).unwrap();
639 assert_debug_snapshot!(&source_change,
666 dst_source_root: SourceRootId(
669 dst_path: "bar/foo2.rs",
680 fn test_rename_mod_in_dir() {
681 let (analysis, position) = analysis_and_position(
689 let new_name = "foo2";
690 let source_change = analysis.rename(position, new_name).unwrap();
691 assert_debug_snapshot!(&source_change,
718 dst_source_root: SourceRootId(
721 dst_path: "foo2/mod.rs",
733 fn test_module_rename_in_path() {
756 fn test_rename_mod_filename_and_path() {
757 let (analysis, position) = analysis_and_position(
772 let new_name = "foo2";
773 let source_change = analysis.rename(position, new_name).unwrap();
774 assert_debug_snapshot!(&source_change,
814 dst_source_root: SourceRootId(
817 dst_path: "bar/foo2.rs",
828 fn test_enum_variant_from_module_1() {
837 fn func(f: foo::Foo) {
851 fn func(f: foo::Foo) {
861 fn test_enum_variant_from_module_2() {
870 fn foo(f: foo::Foo) {
882 fn foo(f: foo::Foo) {
890 fn test_parameter_to_self() {
898 fn f(foo<|>: &mut Foo) -> i32 {
910 fn f(&mut self) -> i32 {
919 fn test_self_to_parameter() {
927 fn f(&mut <|>self) -> i32 {
939 fn f(foo: &mut Foo) -> i32 {
948 fn test_self_in_path_to_parameter() {
969 fn f(foo: &Foo) -> i32 {
978 fn test_rename(text: &str, new_name: &str, expected: &str) {
979 let (analysis, position) = single_file_with_position(text);
980 let source_change = analysis.rename(position, new_name).unwrap();
981 let mut text_edit_builder = TextEditBuilder::default();
982 let mut file_id: Option<FileId> = None;
983 if let Some(change) = source_change {
984 for edit in change.info.source_file_edits {
985 file_id = Some(edit.file_id);
986 for indel in edit.edit.as_indels() {
987 text_edit_builder.replace(indel.delete, indel.insert.clone());
991 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
992 text_edit_builder.finish().apply(&mut result);
993 assert_eq_text!(expected, &*result);