//! FIXME: write short doc here
+use std::fmt::{self, Display};
use hir::{Module, ModuleDef, ModuleSource, Semantics};
-use ide_db::base_db::{FileRange, SourceDatabaseExt};
use ide_db::{
+ base_db::{AnchoredPathBuf, FileId, FileRange},
defs::{Definition, NameClass, NameRefClass},
+ search::FileReference,
RootDatabase,
};
-
-use std::{
- convert::TryInto,
- error::Error,
- fmt::{self, Display},
-};
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,
+) -> 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 {
+ let RangeInfo { range, .. } = find_all_refs(&sema, position)?;
+ Ok(RangeInfo::new(range, SourceChange::default()))
+ }
+ .map(|info| RangeInfo::new(info.range, ()))
+}
pub(crate) fn rename(
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> {
- match lex_single_syntax_kind(new_name) {
- Some(res) => match res {
- (SyntaxKind::IDENT, _) => (),
- (SyntaxKind::UNDERSCORE, _) => (),
- (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
- (_, 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();
+
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)
}
}
+pub(crate) fn will_rename_file(
+ db: &RootDatabase,
+ file_id: FileId,
+ new_name_stem: &str,
+) -> Option<SourceChange> {
+ let sema = Semantics::new(db);
+ let module = sema.to_module_def(file_id)?;
+
+ let decl = module.declaration_source(db)?;
+ let range = decl.value.name()?.syntax().text_range();
+
+ let position = FilePosition { file_id: decl.file_id.original_file(db), offset: range.start() };
+ let mut change = rename_mod(&sema, position, module, new_name_stem).ok()?.info;
+ change.file_system_edits.clear();
+ 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 => {
- 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(
file_range: FileRange,
new_name: &str,
) -> TextRange {
- let mut range = file_range.range;
let source_file = sema.parse(file_range.file_id);
let file_syntax = source_file.syntax();
- if let Some(field_expr) =
- syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, range)
- {
- match field_expr.expr().and_then(|e| e.name_ref()) {
- Some(name) if &name.to_string() == new_name => range = field_expr.syntax().text_range(),
- _ => (),
- }
- } else if let Some(field_pat) =
- syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, range)
- {
- match field_pat.pat() {
- Some(ast::Pat::IdentPat(pat))
- if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) =>
- {
- range = field_pat.syntax().text_range()
- }
- _ => (),
- }
- }
- range
+ let original_range = file_range.range;
+
+ syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, original_range)
+ .and_then(|field_expr| match field_expr.expr().and_then(|e| e.name_ref()) {
+ Some(name) if &name.to_string() == new_name => Some(field_expr.syntax().text_range()),
+ _ => None,
+ })
+ .or_else(|| {
+ syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, original_range)
+ .and_then(|field_pat| match field_pat.pat() {
+ Some(ast::Pat::IdentPat(pat))
+ if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) =>
+ {
+ Some(field_pat.syntax().text_range())
+ }
+ _ => None,
+ })
+ })
+ .unwrap_or(original_range)
}
fn rename_mod(
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);
match src.value {
ModuleSource::SourceFile(..) => {
// mod is defined in path/to/dir/mod.rs
- let dst = if module.is_mod_rs(sema.db) {
+ let path = if module.is_mod_rs(sema.db) {
format!("../{}/mod.rs", new_name)
} else {
format!("{}.rs", new_name)
};
- let move_file = FileSystemEdit::MoveFile { src: file_id, anchor: file_id, dst };
- file_system_edits.push(move_file);
+ let dst = AnchoredPathBuf { anchor: file_id, path };
+ let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
+ 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 source_file = sema.parse(position.file_id);
let syn = source_file.syntax();
- let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
- .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
- let params =
- fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?;
- if params.self_param().is_some() {
- return Err(RenameError("Method already has a self parameter".to_string()));
- }
- let first_param =
- params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?;
- let mutable = match first_param.ty() {
- Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
- _ => return Err(RenameError("Not renaming other types".to_string())),
+ 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(|| format_err!("No surrounding method declaration found"))?;
+ let param_range = fn_ast
+ .param_list()
+ .and_then(|p| p.params().next())
+ .ok_or_else(|| format_err!("Method has no parameters"))?
+ .syntax()
+ .text_range();
+ if !param_range.contains(position.offset) {
+ 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(|| format_err!("No impl block found for function"))?;
+ if fn_def.self_param(sema.db).is_some() {
+ bail!("Method already has a self parameter");
+ }
+
+ let params = fn_def.assoc_fn_params(sema.db);
+ 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 the impl is a ref to the type we can just match the `&T` with self directly
+ (first_param_ty.clone(), "self")
+ } else {
+ first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| {
+ (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })
+ })
};
- let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
- .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
-
- let param_range = first_param.syntax().text_range();
- 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()));
+ if ty != impl_ty {
+ 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(if mutable { "&mut self" } else { "&self" }),
- ),
- });
+ 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(
let mut replacement_text = String::from(new_name);
replacement_text.push_str(": ");
- replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut "));
+ match (self_param.amp_token(), self_param.mut_token()) {
+ (None, None) => (),
+ (Some(_), None) => replacement_text.push('&'),
+ (_, Some(_)) => replacement_text.push_str("&mut "),
+ };
replacement_text.push_str(type_name.as_str());
Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
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(
position: FilePosition,
new_name: &str,
) -> 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())),
- };
-
- 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 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)
+ }
+ (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),
}
- Ok(RangeInfo::new(range, SourceChange::from(edit)))
+ 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, 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_underscore() {
- check("_", r#"fn main() { let i<|> = 1; }"#, r#"fn main() { let _ = 1; }"#);
+ check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);
}
#[test]
fn test_rename_to_raw_identifier() {
- check("r#fn", r#"fn main() { let i<|> = 1; }"#, r#"fn main() { let r#fn = 1; }"#);
+ check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#);
}
#[test]
fn test_rename_to_invalid_identifier1() {
check(
"invalid!",
- r#"fn main() { let i<|> = 1; }"#,
+ r#"fn main() { let i$0 = 1; }"#,
"error: Invalid name `invalid!`: not an identifier",
);
}
fn test_rename_to_invalid_identifier2() {
check(
"multiple tokens",
- r#"fn main() { let i<|> = 1; }"#,
+ r#"fn main() { let i$0 = 1; }"#,
"error: Invalid name `multiple tokens`: not an identifier",
);
}
fn test_rename_to_invalid_identifier3() {
check(
"let",
- r#"fn main() { let i<|> = 1; }"#,
+ r#"fn main() { let i$0 = 1; }"#,
"error: Invalid name `let`: not an identifier",
);
}
+ #[test]
+ fn test_rename_to_invalid_identifier_lifetime() {
+ mark::check!(rename_not_an_ident_ref);
+ check(
+ "'foo",
+ r#"fn main() { let i$0 = 1; }"#,
+ "error: Invalid name `'foo`: not an identifier",
+ );
+ }
+
+ #[test]
+ fn test_rename_to_invalid_identifier_lifetime2() {
+ mark::check!(rename_not_a_lifetime_ident_ref);
+ check(
+ "foo",
+ r#"fn main<'a>(_: &'a$0 ()) {}"#,
+ "error: Invalid name `foo`: not a lifetime identifier",
+ );
+ }
+
+ #[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#"
fn main() {
let mut i = 1;
let j = 1;
- i = i<|> + j;
+ i = i$0 + j;
{ i = 0; }
fn test_rename_unresolved_reference() {
check(
"new_name",
- r#"fn main() { let _ = unresolved_ref<|>; }"#,
+ r#"fn main() { let _ = unresolved_ref$0; }"#,
"error: No references found at position",
);
}
r#"
macro_rules! foo {($i:ident) => {$i} }
fn main() {
- let a<|> = "test";
+ let a$0 = "test";
foo!(a);
}
"#,
macro_rules! foo {($i:ident) => {$i} }
fn main() {
let a = "test";
- foo!(a<|>);
+ foo!(a$0);
}
"#,
r#"
macro_rules! define_fn {($id:ident) => { fn $id{} }}
define_fn!(foo);
fn main() {
- fo<|>o();
+ fo$0o();
}
"#,
r#"
"bar",
r#"
macro_rules! define_fn {($id:ident) => { fn $id{} }}
-define_fn!(fo<|>o);
+define_fn!(fo$0o);
fn main() {
foo();
}
#[test]
fn test_rename_for_param_inside() {
- check("j", r#"fn foo(i : u32) -> u32 { i<|> }"#, r#"fn foo(j : u32) -> u32 { j }"#);
+ check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#);
}
#[test]
fn test_rename_refs_for_fn_param() {
- check("j", r#"fn foo(i<|> : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);
+ check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);
}
#[test]
fn test_rename_for_mut_param() {
- check("j", r#"fn foo(mut i<|> : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);
+ check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);
}
#[test]
check(
"j",
r#"
-struct Foo { i<|>: i32 }
+struct Foo { i$0: i32 }
impl Foo {
fn new(i: i32) -> Self {
check(
"j",
r#"
-struct Foo { i<|>: i32 }
+struct Foo { i$0: i32 }
impl Foo {
fn new(i: i32) -> Self {
struct Foo { i: i32 }
impl Foo {
- fn new(i<|>: i32) -> Self {
+ fn new(i$0: i32) -> Self {
Self { i }
}
}
check(
"j",
r#"
-struct Foo { i<|>: i32 }
+struct Foo { i$0: i32 }
struct Bar { i: i32 }
impl Bar {
r#"
struct Foo { i: i32 }
-fn baz(i<|>: i32) -> Self {
+fn baz(i$0: i32) -> Self {
let x = Foo { i };
{
let i = 0;
mod bar;
//- /bar.rs
-mod foo<|>;
+mod foo$0;
//- /bar/foo.rs
// empty
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(
2,
),
- anchor: FileId(
- 2,
- ),
- dst: "foo2.rs",
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 2,
+ ),
+ path: "foo2.rs",
+ },
},
],
is_snippet: false,
pub struct FooContent;
//- /bar.rs
-use crate::foo<|>::FooContent;
+use crate::foo$0::FooContent;
"#,
expect![[r#"
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(
1,
),
- anchor: FileId(
- 1,
- ),
- dst: "quux.rs",
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "quux.rs",
+ },
},
],
is_snippet: false,
"foo2",
r#"
//- /lib.rs
-mod fo<|>o;
+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(
1,
),
- anchor: FileId(
- 1,
- ),
- dst: "../foo2/mod.rs",
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "../foo2/mod.rs",
+ },
},
],
is_snippet: false,
"bar",
r#"
//- /lib.rs
-mod outer { mod fo<|>o; }
+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(
1,
),
- anchor: FileId(
- 1,
- ),
- dst: "bar.rs",
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "bar.rs",
+ },
},
],
is_snippet: false,
check(
"baz",
r#"
-mod <|>foo { pub fn bar() {} }
+mod $0foo { pub fn bar() {} }
fn main() { foo::bar(); }
"#,
}
//- /bar.rs
-pub mod foo<|>;
+pub mod foo$0;
//- /bar/foo.rs
// pub fn fun() {}
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(
2,
),
- anchor: FileId(
- 2,
- ),
- dst: "foo2.rs",
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 2,
+ ),
+ path: "foo2.rs",
+ },
},
],
is_snippet: false,
"Baz",
r#"
mod foo {
- pub enum Foo { Bar<|> }
+ pub enum Foo { Bar$0 }
}
fn func(f: foo::Foo) {
"baz",
r#"
mod foo {
- pub struct Foo { pub bar<|>: uint }
+ pub struct Foo { pub bar$0: uint }
}
fn foo(f: foo::Foo) {
#[test]
fn test_parameter_to_self() {
+ mark::check!(rename_to_self);
check(
"self",
r#"
struct Foo { i: i32 }
impl Foo {
- fn f(foo<|>: &mut Foo) -> i32 {
+ fn f(foo$0: &mut Foo) -> i32 {
foo.i
}
}
self.i
}
}
+"#,
+ );
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(foo$0: Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(self) -> i32 {
+ self.i
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_parameter_to_self_error_no_impl() {
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+
+fn f(foo$0: &mut Foo) -> i32 {
+ foo.i
+}
+"#,
+ "error: No impl block found for function",
+ );
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+struct Bar;
+
+impl Bar {
+ fn f(foo$0: &mut Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ "error: Parameter type differs from impl block type",
+ );
+ }
+
+ #[test]
+ fn test_parameter_to_self_error_not_first() {
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+impl Foo {
+ fn f(x: (), foo$0: &mut Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ "error: Only the first parameter can be self",
+ );
+ }
+
+ #[test]
+ fn test_parameter_to_self_impl_ref() {
+ check(
+ "self",
+ r#"
+struct Foo { i: i32 }
+impl &Foo {
+ fn f(foo$0: &Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+impl &Foo {
+ fn f(self) -> i32 {
+ self.i
+ }
+}
"#,
);
}
struct Foo { i: i32 }
impl Foo {
- fn f(&mut <|>self) -> i32 {
+ fn f(&mut $0self) -> i32 {
self.i
}
}
);
}
+ #[test]
+ fn test_owned_self_to_parameter() {
+ mark::check!(rename_self_to_param);
+ check(
+ "foo",
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f($0self) -> i32 {
+ self.i
+ }
+}
+"#,
+ r#"
+struct Foo { i: i32 }
+
+impl Foo {
+ fn f(foo: Foo) -> i32 {
+ foo.i
+ }
+}
+"#,
+ );
+ }
+
#[test]
fn test_self_in_path_to_parameter() {
check(
impl Foo {
fn f(&self) -> i32 {
let self_var = 1;
- self<|>.i
+ self$0.i
}
}
"#,
#[test]
fn test_initializer_use_field_init_shorthand() {
+ mark::check!(test_rename_field_expr_pat);
check(
"bar",
r#"
-struct Foo { i<|>: i32 }
+struct Foo { i$0: i32 }
fn foo(bar: i32) -> Foo {
Foo { i: bar }
}
#[test]
- fn test_rename_binding_in_destructure_pat_shorthand() {
+ fn test_struct_field_destructure_into_shorthand() {
check(
- "bar",
+ "baz",
r#"
-struct Foo {
- i: i32,
-}
+struct Foo { i$0: i32 }
fn foo(foo: Foo) {
- let Foo { i } = foo;
- let _ = i<|>;
+ let Foo { i: baz } = foo;
+ let _ = baz;
}
"#,
r#"
-struct Foo {
- i: i32,
-}
+struct Foo { baz: i32 }
fn foo(foo: Foo) {
- let Foo { i: bar } = foo;
- let _ = bar;
+ let Foo { baz } = foo;
+ let _ = baz;
}
"#,
);
#[test]
fn test_rename_binding_in_destructure_pat() {
+ let expected_fixture = r#"
+struct Foo {
+ i: i32,
+}
+
+fn foo(foo: Foo) {
+ let Foo { i: bar } = foo;
+ let _ = bar;
+}
+"#;
check(
"bar",
r#"
fn foo(foo: Foo) {
let Foo { i: b } = foo;
- let _ = b<|>;
+ let _ = b$0;
}
"#,
+ expected_fixture,
+ );
+ check(
+ "bar",
r#"
struct Foo {
i: i32,
}
fn foo(foo: Foo) {
- let Foo { i: bar } = foo;
- let _ = bar;
+ let Foo { i } = foo;
+ let _ = i$0;
}
"#,
+ expected_fixture,
);
}
#[test]
- fn test_struct_field_destructure_into_shorthand() {
+ fn test_rename_binding_in_destructure_param_pat() {
check(
- "baz",
+ "bar",
r#"
-struct Foo { i<|>: i32 }
+struct Foo {
+ i: i32
+}
-fn foo(foo: Foo) {
- let Foo { i: baz } = foo;
- let _ = baz;
+fn foo(Foo { i }: foo) -> i32 {
+ i$0
}
"#,
r#"
-struct Foo { baz: i32 }
+struct Foo {
+ i: i32
+}
-fn foo(foo: Foo) {
- let Foo { baz } = foo;
- let _ = baz;
+fn foo(Foo { i: bar }: foo) -> i32 {
+ bar
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn test_rename_lifetimes() {
+ mark::check!(rename_lifetime);
+ check(
+ "'yeeee",
+ r#"
+trait Foo<'a> {
+ fn foo() -> &'a ();
+}
+impl<'a> Foo<'a> for &'a () {
+ fn foo() -> &'a$0 () {
+ unimplemented!()
+ }
+}
+"#,
+ r#"
+trait Foo<'a> {
+ fn foo() -> &'a ();
+}
+impl<'yeeee> Foo<'yeeee> for &'yeeee () {
+ fn foo() -> &'yeeee () {
+ unimplemented!()
+ }
}
"#,
+ )
+ }
+
+ #[test]
+ fn test_rename_bind_pat() {
+ check(
+ "new_name",
+ r#"
+fn main() {
+ enum CustomOption<T> {
+ None,
+ Some(T),
+ }
+
+ let test_variable = CustomOption::Some(22);
+
+ match test_variable {
+ CustomOption::Some(foo$0) if foo == 11 => {}
+ _ => (),
+ }
+}"#,
+ r#"
+fn main() {
+ enum CustomOption<T> {
+ None,
+ Some(T),
+ }
+
+ let test_variable = CustomOption::Some(22);
+
+ match test_variable {
+ CustomOption::Some(new_name) if new_name == 11 => {}
+ _ => (),
+ }
+}"#,
);
}
+
+ #[test]
+ fn test_rename_label() {
+ check(
+ "'foo",
+ r#"
+fn foo<'a>() -> &'a () {
+ 'a: {
+ 'b: loop {
+ break 'a$0;
+ }
+ }
+}
+"#,
+ r#"
+fn foo<'a>() -> &'a () {
+ 'foo: {
+ 'b: loop {
+ break 'foo;
+ }
+ }
+}
+"#,
+ )
+ }
+
+ #[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) {}
+}
+"#,
+ )
+ }
}