//! FIXME: write short doc here
-use std::{
- convert::TryInto,
- error::Error,
- fmt::{self, Display},
-};
+use std::fmt::{self, Display};
use hir::{Module, ModuleDef, ModuleSource, Semantics};
-use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt};
use ide_db::{
+ base_db::{AnchoredPathBuf, FileId, FileRange},
defs::{Definition, NameClass, NameRefClass},
+ search::FileReference,
RootDatabase,
};
use syntax::{
algo::find_node_at_offset,
ast::{self, NameOwner},
- lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
+ lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, T,
};
use test_utils::mark;
use text_edit::TextEdit;
use crate::{
- references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
- SourceChange, SourceFileEdit, TextRange, TextSize,
+ FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
+ TextRange,
};
+type RenameResult<T> = Result<T, RenameError>;
#[derive(Debug)]
pub struct RenameError(pub(crate) String);
}
}
-impl Error for RenameError {}
+macro_rules! format_err {
+ ($fmt:expr) => {RenameError(format!($fmt))};
+ ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))}
+}
+
+macro_rules! bail {
+ ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))}
+}
pub(crate) fn prepare_rename(
db: &RootDatabase,
position: FilePosition,
-) -> Result<RangeInfo<()>, RenameError> {
+) -> RenameResult<RangeInfo<()>> {
let sema = Semantics::new(db);
let source_file = sema.parse(position.file_id);
let syntax = source_file.syntax();
if let Some(module) = find_module_at_offset(&sema, position, syntax) {
rename_mod(&sema, position, module, "dummy")
- } else if let Some(self_token) =
- syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
- {
- rename_self_to_param(&sema, position, self_token, "dummy")
} else {
- let range = match find_all_refs(&sema, position, None) {
- Some(RangeInfo { range, .. }) => range,
- None => return Err(RenameError("No references found at position".to_string())),
- };
- Ok(RangeInfo::new(range, SourceChange::from(vec![])))
+ let RangeInfo { range, .. } = find_all_refs(&sema, position)?;
+ Ok(RangeInfo::new(range, SourceChange::default()))
}
.map(|info| RangeInfo::new(info.range, ()))
}
db: &RootDatabase,
position: FilePosition,
new_name: &str,
-) -> Result<RangeInfo<SourceChange>, RenameError> {
+) -> RenameResult<RangeInfo<SourceChange>> {
let sema = Semantics::new(db);
rename_with_semantics(&sema, position, new_name)
}
sema: &Semantics<RootDatabase>,
position: FilePosition,
new_name: &str,
-) -> Result<RangeInfo<SourceChange>, RenameError> {
- let is_lifetime_name = match lex_single_syntax_kind(new_name) {
- Some(res) => match res {
- (SyntaxKind::IDENT, _) => false,
- (SyntaxKind::UNDERSCORE, _) => false,
- (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
- (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true,
- (SyntaxKind::LIFETIME_IDENT, _) => {
- return Err(RenameError(format!(
- "Invalid name `{0}`: Cannot rename lifetime to {0}",
- new_name
- )))
- }
- (_, Some(syntax_error)) => {
- return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
- }
- (_, None) => {
- return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
- }
- },
- None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
- };
-
+) -> RenameResult<RangeInfo<SourceChange>> {
let source_file = sema.parse(position.file_id);
let syntax = source_file.syntax();
- // this is here to prevent lifetime renames from happening on modules and self
- if is_lifetime_name {
- rename_reference(&sema, position, new_name, is_lifetime_name)
- } else if let Some(module) = find_module_at_offset(&sema, position, syntax) {
+
+ if let Some(module) = find_module_at_offset(&sema, position, syntax) {
rename_mod(&sema, position, module, new_name)
- } else if let Some(self_token) =
- syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
- {
- rename_self_to_param(&sema, position, self_token, new_name)
} else {
- rename_reference(&sema, position, new_name, is_lifetime_name)
+ rename_reference(&sema, position, new_name)
}
}
Some(change)
}
+#[derive(Copy, Clone, Debug, PartialEq)]
+enum IdentifierKind {
+ Ident,
+ Lifetime,
+ ToSelf,
+ Underscore,
+}
+
+fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
+ match lex_single_syntax_kind(new_name) {
+ Some(res) => match res {
+ (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident),
+ (T![_], _) => Ok(IdentifierKind::Underscore),
+ (T![self], _) => Ok(IdentifierKind::ToSelf),
+ (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
+ Ok(IdentifierKind::Lifetime)
+ }
+ (SyntaxKind::LIFETIME_IDENT, _) => {
+ bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name)
+ }
+ (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
+ (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
+ },
+ None => bail!("Invalid name `{}`: not an identifier", new_name),
+ }
+}
+
fn find_module_at_offset(
sema: &Semantics<RootDatabase>,
position: FilePosition,
Some(module)
}
-fn source_edit_from_reference(
+fn find_all_refs(
+ sema: &Semantics<RootDatabase>,
+ position: FilePosition,
+) -> RenameResult<RangeInfo<ReferenceSearchResult>> {
+ crate::references::find_all_refs(sema, position, None)
+ .ok_or_else(|| format_err!("No references found at position"))
+}
+
+fn source_edit_from_references(
sema: &Semantics<RootDatabase>,
- reference: Reference,
+ file_id: FileId,
+ references: &[FileReference],
new_name: &str,
-) -> SourceFileEdit {
- let mut replacement_text = String::new();
- let range = match reference.kind {
- ReferenceKind::FieldShorthandForField => {
- mark::hit!(test_rename_struct_field_for_shorthand);
- replacement_text.push_str(new_name);
- replacement_text.push_str(": ");
- TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
- }
- ReferenceKind::FieldShorthandForLocal => {
- mark::hit!(test_rename_local_for_field_shorthand);
- replacement_text.push_str(": ");
- replacement_text.push_str(new_name);
- TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
- }
- ReferenceKind::RecordFieldExprOrPat => {
- mark::hit!(test_rename_field_expr_pat);
- replacement_text.push_str(new_name);
- edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name)
- }
- _ => {
- replacement_text.push_str(new_name);
- reference.file_range.range
- }
- };
- SourceFileEdit {
- file_id: reference.file_range.file_id,
- edit: TextEdit::replace(range, replacement_text),
+) -> (FileId, TextEdit) {
+ let mut edit = TextEdit::builder();
+ for reference in references {
+ let mut replacement_text = String::new();
+ let range = match reference.kind {
+ ReferenceKind::FieldShorthandForField => {
+ mark::hit!(test_rename_struct_field_for_shorthand);
+ replacement_text.push_str(new_name);
+ replacement_text.push_str(": ");
+ TextRange::new(reference.range.start(), reference.range.start())
+ }
+ ReferenceKind::FieldShorthandForLocal => {
+ mark::hit!(test_rename_local_for_field_shorthand);
+ replacement_text.push_str(": ");
+ replacement_text.push_str(new_name);
+ TextRange::new(reference.range.end(), reference.range.end())
+ }
+ ReferenceKind::RecordFieldExprOrPat => {
+ mark::hit!(test_rename_field_expr_pat);
+ replacement_text.push_str(new_name);
+ edit_text_range_for_record_field_expr_or_pat(
+ sema,
+ FileRange { file_id, range: reference.range },
+ new_name,
+ )
+ }
+ _ => {
+ replacement_text.push_str(new_name);
+ reference.range
+ }
+ };
+ edit.replace(range, replacement_text);
}
+ (file_id, edit.finish())
}
fn edit_text_range_for_record_field_expr_or_pat(
position: FilePosition,
module: Module,
new_name: &str,
-) -> Result<RangeInfo<SourceChange>, RenameError> {
- let mut source_file_edits = Vec::new();
- let mut file_system_edits = Vec::new();
+) -> RenameResult<RangeInfo<SourceChange>> {
+ if IdentifierKind::Ident != check_identifier(new_name)? {
+ bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
+ }
+
+ let mut source_change = SourceChange::default();
let src = module.definition_source(sema.db);
let file_id = src.file_id.original_file(sema.db);
};
let dst = AnchoredPathBuf { anchor: file_id, path };
let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
- file_system_edits.push(move_file);
+ source_change.push_file_system_edit(move_file);
}
ModuleSource::Module(..) => {}
}
if let Some(src) = module.declaration_source(sema.db) {
let file_id = src.file_id.original_file(sema.db);
let name = src.value.name().unwrap();
- let edit = SourceFileEdit {
+ source_change.insert_source_edit(
file_id,
- edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
- };
- source_file_edits.push(edit);
+ TextEdit::replace(name.syntax().text_range(), new_name.into()),
+ );
}
- let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
- .ok_or_else(|| RenameError("No references found at position".to_string()))?;
- let ref_edits = refs
- .references
- .into_iter()
- .map(|reference| source_edit_from_reference(sema, reference, new_name));
- source_file_edits.extend(ref_edits);
+ let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
+ let ref_edits = refs.references().iter().map(|(&file_id, references)| {
+ source_edit_from_references(sema, file_id, references, new_name)
+ });
+ source_change.extend(ref_edits);
- Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
+ Ok(RangeInfo::new(range, source_change))
}
fn rename_to_self(
let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset)
.and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
- .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
+ .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
let param_range = fn_ast
.param_list()
.and_then(|p| p.params().next())
- .ok_or_else(|| RenameError("Method has no parameters".to_string()))?
+ .ok_or_else(|| format_err!("Method has no parameters"))?
.syntax()
.text_range();
if !param_range.contains(position.offset) {
- return Err(RenameError("Only the first parameter can be self".to_string()));
+ bail!("Only the first parameter can be self");
}
let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset)
.and_then(|def| sema.to_def(&def))
- .ok_or_else(|| RenameError("No impl block found for function".to_string()))?;
+ .ok_or_else(|| format_err!("No impl block found for function"))?;
if fn_def.self_param(sema.db).is_some() {
- return Err(RenameError("Method already has a self parameter".to_string()));
+ bail!("Method already has a self parameter");
}
let params = fn_def.assoc_fn_params(sema.db);
- let first_param =
- params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?;
+ let first_param = params.first().ok_or_else(|| format_err!("Method has no parameters"))?;
let first_param_ty = first_param.ty();
let impl_ty = impl_block.target_ty(sema.db);
let (ty, self_param) = if impl_ty.remove_ref().is_some() {
};
if ty != impl_ty {
- return Err(RenameError("Parameter type differs from impl block type".to_string()));
- }
-
- let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
- .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
-
- let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
- .into_iter()
- .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
-
- if param_ref.is_empty() {
- return Err(RenameError("Parameter to rename not found".to_string()));
+ bail!("Parameter type differs from impl block type");
}
- let mut edits = usages
- .into_iter()
- .map(|reference| source_edit_from_reference(sema, reference, "self"))
- .collect::<Vec<_>>();
+ let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
- edits.push(SourceFileEdit {
- file_id: position.file_id,
- edit: TextEdit::replace(param_range, String::from(self_param)),
- });
+ let mut source_change = SourceChange::default();
+ source_change.extend(refs.references().iter().map(|(&file_id, references)| {
+ source_edit_from_references(sema, file_id, references, "self")
+ }));
+ source_change.insert_source_edit(
+ position.file_id,
+ TextEdit::replace(param_range, String::from(self_param)),
+ );
- Ok(RangeInfo::new(range, SourceChange::from(edits)))
+ Ok(RangeInfo::new(range, source_change))
}
fn text_edit_from_self_param(
fn rename_self_to_param(
sema: &Semantics<RootDatabase>,
position: FilePosition,
- self_token: SyntaxToken,
new_name: &str,
+ ident_kind: IdentifierKind,
+ range: TextRange,
+ refs: ReferenceSearchResult,
) -> Result<RangeInfo<SourceChange>, RenameError> {
+ match ident_kind {
+ IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name),
+ IdentifierKind::ToSelf => {
+ // no-op
+ mark::hit!(rename_self_to_self);
+ return Ok(RangeInfo::new(range, SourceChange::default()));
+ }
+ _ => (),
+ }
let source_file = sema.parse(position.file_id);
let syn = source_file.syntax();
- let text = sema.db.file_text(position.file_id);
let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
- .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
- let search_range = fn_def.syntax().text_range();
+ .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
+
+ let mut source_change = SourceChange::default();
+ if let Some(self_param) = fn_def.param_list().and_then(|it| it.self_param()) {
+ if self_param
+ .syntax()
+ .text_range()
+ .contains_range(refs.declaration().nav.focus_or_full_range())
+ {
+ let edit = text_edit_from_self_param(syn, &self_param, new_name)
+ .ok_or_else(|| format_err!("No target type found"))?;
+ source_change.insert_source_edit(position.file_id, edit);
- let mut edits: Vec<SourceFileEdit> = vec![];
+ source_change.extend(refs.references().iter().map(|(&file_id, references)| {
+ source_edit_from_references(sema, file_id, &references, new_name)
+ }));
- for (idx, _) in text.match_indices("self") {
- let offset: TextSize = idx.try_into().unwrap();
- if !search_range.contains_inclusive(offset) {
- continue;
- }
- if let Some(ref usage) =
- syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
- {
- let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
- text_edit_from_self_param(syn, self_param, new_name)
- .ok_or_else(|| RenameError("No target type found".to_string()))?
- } else {
- TextEdit::replace(usage.text_range(), String::from(new_name))
- };
- edits.push(SourceFileEdit { file_id: position.file_id, edit });
+ if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore
+ {
+ bail!("Cannot rename reference to `_` as it is being referenced multiple times");
+ }
+
+ return Ok(RangeInfo::new(range, source_change));
}
}
-
- let range = ast::SelfParam::cast(self_token.parent())
- .map_or(self_token.text_range(), |p| p.syntax().text_range());
-
- Ok(RangeInfo::new(range, SourceChange::from(edits)))
+ Err(format_err!("Method has no self param"))
}
fn rename_reference(
sema: &Semantics<RootDatabase>,
position: FilePosition,
new_name: &str,
- is_lifetime_name: bool,
) -> Result<RangeInfo<SourceChange>, RenameError> {
- let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
- Some(range_info) => range_info,
- None => return Err(RenameError("No references found at position".to_string())),
- };
-
- match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) {
- (true, false) => {
- return Err(RenameError(format!(
- "Invalid name `{}`: not a lifetime identifier",
- new_name
- )))
+ let ident_kind = check_identifier(new_name)?;
+ let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
+
+ match (ident_kind, &refs.declaration.kind) {
+ (IdentifierKind::ToSelf, ReferenceKind::Lifetime)
+ | (IdentifierKind::Underscore, ReferenceKind::Lifetime)
+ | (IdentifierKind::Ident, ReferenceKind::Lifetime) => {
+ mark::hit!(rename_not_a_lifetime_ident_ref);
+ bail!("Invalid name `{}`: not a lifetime identifier", new_name)
}
- (false, true) => {
- return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
+ (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime),
+ (IdentifierKind::Lifetime, _) => {
+ mark::hit!(rename_not_an_ident_ref);
+ bail!("Invalid name `{}`: not an identifier", new_name)
}
- _ => (),
+ (_, ReferenceKind::SelfParam) => {
+ mark::hit!(rename_self_to_param);
+ return rename_self_to_param(sema, position, new_name, ident_kind, range, refs);
+ }
+ (IdentifierKind::ToSelf, _) => {
+ mark::hit!(rename_to_self);
+ return rename_to_self(sema, position);
+ }
+ (IdentifierKind::Underscore, _) if !refs.references.is_empty() => {
+ mark::hit!(rename_underscore_multiple);
+ bail!("Cannot rename reference to `_` as it is being referenced multiple times")
+ }
+ (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
}
- let edit = refs
- .into_iter()
- .map(|reference| source_edit_from_reference(sema, reference, new_name))
- .collect::<Vec<_>>();
-
- if edit.is_empty() {
- return Err(RenameError("No references found at position".to_string()));
- }
+ let mut source_change = SourceChange::default();
+ source_change.extend(refs.into_iter().map(|(file_id, references)| {
+ source_edit_from_references(sema, file_id, &references, new_name)
+ }));
- Ok(RangeInfo::new(range, SourceChange::from(edit)))
+ Ok(RangeInfo::new(range, source_change))
}
#[cfg(test)]
let mut text_edit_builder = TextEdit::builder();
let mut file_id: Option<FileId> = None;
for edit in source_change.info.source_file_edits {
- file_id = Some(edit.file_id);
- for indel in edit.edit.into_iter() {
+ file_id = Some(edit.0);
+ for indel in edit.1.into_iter() {
text_edit_builder.replace(indel.delete, indel.insert);
}
}
- let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
- text_edit_builder.finish().apply(&mut result);
- assert_eq_text!(ra_fixture_after, &*result);
+ if let Some(file_id) = file_id {
+ let mut result = analysis.file_text(file_id).unwrap().to_string();
+ text_edit_builder.finish().apply(&mut result);
+ assert_eq_text!(ra_fixture_after, &*result);
+ }
}
Err(err) => {
if ra_fixture_after.starts_with("error:") {
#[test]
fn test_rename_to_invalid_identifier_lifetime() {
+ mark::check!(rename_not_an_ident_ref);
check(
"'foo",
r#"fn main() { let i$0 = 1; }"#,
#[test]
fn test_rename_to_invalid_identifier_lifetime2() {
+ mark::check!(rename_not_a_lifetime_ident_ref);
check(
"foo",
r#"fn main<'a>(_: &'a$0 ()) {}"#,
);
}
+ #[test]
+ fn test_rename_to_underscore_invalid() {
+ mark::check!(rename_underscore_multiple);
+ check(
+ "_",
+ r#"fn main(foo$0: ()) {foo;}"#,
+ "error: Cannot rename reference to `_` as it is being referenced multiple times",
+ );
+ }
+
+ #[test]
+ fn test_rename_mod_invalid() {
+ check(
+ "'foo",
+ r#"mod foo$0 {}"#,
+ "error: Invalid name `'foo`: cannot rename module to 'foo",
+ );
+ }
+
#[test]
fn test_rename_for_local() {
+ mark::check!(rename_ident);
check(
"k",
r#"
RangeInfo {
range: 4..7,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 1,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 4..7,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 1,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
RangeInfo {
range: 11..14,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 0,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "quux",
- delete: 8..11,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "quux",
+ delete: 8..11,
+ },
+ ],
},
- SourceFileEdit {
- file_id: FileId(
- 2,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "quux",
- delete: 11..14,
- },
- ],
- },
+ FileId(
+ 2,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "quux",
+ delete: 11..14,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
//- /lib.rs
mod fo$0o;
//- /foo/mod.rs
-// emtpy
+// empty
"#,
expect![[r#"
RangeInfo {
range: 4..7,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 0,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 4..7,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 4..7,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
mod outer { mod fo$0o; }
//- /outer/foo.rs
-// emtpy
+// empty
"#,
expect![[r#"
RangeInfo {
range: 16..19,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 0,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "bar",
- delete: 16..19,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "bar",
+ delete: 16..19,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
RangeInfo {
range: 8..11,
info: SourceChange {
- source_file_edits: [
- SourceFileEdit {
- file_id: FileId(
- 1,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 8..11,
- },
- ],
- },
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 27..30,
+ },
+ ],
},
- SourceFileEdit {
- file_id: FileId(
- 0,
- ),
- edit: TextEdit {
- indels: [
- Indel {
- insert: "foo2",
- delete: 27..30,
- },
- ],
- },
+ FileId(
+ 1,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo2",
+ delete: 8..11,
+ },
+ ],
},
- ],
+ },
file_system_edits: [
MoveFile {
src: FileId(
#[test]
fn test_parameter_to_self() {
+ mark::check!(rename_to_self);
check(
"self",
r#"
#[test]
fn test_owned_self_to_parameter() {
+ mark::check!(rename_self_to_param);
check(
"foo",
r#"
#[test]
fn test_rename_lifetimes() {
+ mark::check!(rename_lifetime);
check(
"'yeeee",
r#"
}
}
}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_self_to_self() {
+ mark::check!(rename_self_to_self);
+ check(
+ "self",
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(self$0) {}
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(self) {}
+}
"#,
)
}