mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
7051: Check dbg! macro in tidy_test r=matklad a=edwin0cheng
Same as `check_todo` but for dbg! macro
r? @matklad
7219: Refactor rename name checking r=matklad a=Veykril
Improves the user facing error messages a bit and prevents renaming to `_` when the name is referenced as this would change source to not compile anymore since `_` is only a pattern, not a proper identifier.
7245: Encourage gifs r=matklad a=matklad
bors r+
🤖
7246: Unfreeze cargo_metadata r=matklad a=kjeremy
It now pulls in a newer version of semver-parser.
This does add a dependency on `cargo-platform` in the interest of correctness.
Co-authored-by: Edwin Cheng <edwin0cheng@gmail.com>
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
Co-authored-by: kjeremy <kjeremy@gmail.com>
This commit is contained in:
commit
8ad325a806
9 changed files with 215 additions and 99 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -127,12 +127,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cargo_metadata"
|
name = "cargo-platform"
|
||||||
version = "0.12.0"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5a5f7b42f606b7f23674f6f4d877628350682bc40687d3fae65679a58d55345"
|
checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cargo_metadata"
|
||||||
|
version = "0.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11a47b6286279a9998588ef7050d1ebc2500c69892a557c90fe5d071c64415dc"
|
||||||
|
dependencies = [
|
||||||
|
"cargo-platform",
|
||||||
"semver",
|
"semver",
|
||||||
|
"semver-parser",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
|
@ -373,20 +373,20 @@ fn foo(a: A) {
|
||||||
fn macro_expansion_resilient() {
|
fn macro_expansion_resilient() {
|
||||||
check(
|
check(
|
||||||
r#"
|
r#"
|
||||||
macro_rules! dbg {
|
macro_rules! d {
|
||||||
() => {};
|
() => {};
|
||||||
($val:expr) => {
|
($val:expr) => {
|
||||||
match $val { tmp => { tmp } }
|
match $val { tmp => { tmp } }
|
||||||
};
|
};
|
||||||
// Trailing comma with single argument is ignored
|
// Trailing comma with single argument is ignored
|
||||||
($val:expr,) => { $crate::dbg!($val) };
|
($val:expr,) => { $crate::d!($val) };
|
||||||
($($val:expr),+ $(,)?) => {
|
($($val:expr),+ $(,)?) => {
|
||||||
($($crate::dbg!($val)),+,)
|
($($crate::d!($val)),+,)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
struct A { the_field: u32 }
|
struct A { the_field: u32 }
|
||||||
fn foo(a: A) {
|
fn foo(a: A) {
|
||||||
dbg!(a.$0)
|
d!(a.$0)
|
||||||
}
|
}
|
||||||
"#,
|
"#,
|
||||||
expect![[r#"
|
expect![[r#"
|
||||||
|
|
|
@ -12,7 +12,7 @@ doctest = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam-channel = "0.5.0"
|
crossbeam-channel = "0.5.0"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
cargo_metadata = "=0.12.0"
|
cargo_metadata = "0.12.2"
|
||||||
serde_json = "1.0.48"
|
serde_json = "1.0.48"
|
||||||
jod-thread = "0.1.1"
|
jod-thread = "0.1.1"
|
||||||
|
|
||||||
|
|
|
@ -175,12 +175,7 @@ fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<Hov
|
||||||
Definition::SelfType(it) => it.target_ty(db).as_adt(),
|
Definition::SelfType(it) => it.target_ty(db).as_adt(),
|
||||||
_ => None,
|
_ => None,
|
||||||
}?;
|
}?;
|
||||||
match adt {
|
adt.try_to_nav(db).map(to_action)
|
||||||
Adt::Struct(it) => it.try_to_nav(db),
|
|
||||||
Adt::Union(it) => it.try_to_nav(db),
|
|
||||||
Adt::Enum(it) => it.try_to_nav(db),
|
|
||||||
}
|
|
||||||
.map(to_action)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runnable_action(
|
fn runnable_action(
|
||||||
|
|
|
@ -14,16 +14,17 @@ use ide_db::{
|
||||||
use syntax::{
|
use syntax::{
|
||||||
algo::find_node_at_offset,
|
algo::find_node_at_offset,
|
||||||
ast::{self, NameOwner},
|
ast::{self, NameOwner},
|
||||||
lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
|
lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T,
|
||||||
};
|
};
|
||||||
use test_utils::mark;
|
use test_utils::mark;
|
||||||
use text_edit::TextEdit;
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
|
FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult,
|
||||||
SourceChange, SourceFileEdit, TextRange, TextSize,
|
SourceChange, SourceFileEdit, TextRange, TextSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RenameResult<T> = Result<T, RenameError>;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RenameError(pub(crate) String);
|
pub struct RenameError(pub(crate) String);
|
||||||
|
|
||||||
|
@ -35,24 +36,30 @@ impl fmt::Display for RenameError {
|
||||||
|
|
||||||
impl Error for RenameError {}
|
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(
|
pub(crate) fn prepare_rename(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
) -> Result<RangeInfo<()>, RenameError> {
|
) -> RenameResult<RangeInfo<()>> {
|
||||||
let sema = Semantics::new(db);
|
let sema = Semantics::new(db);
|
||||||
let source_file = sema.parse(position.file_id);
|
let source_file = sema.parse(position.file_id);
|
||||||
let syntax = source_file.syntax();
|
let syntax = source_file.syntax();
|
||||||
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, "dummy")
|
rename_mod(&sema, position, module, "dummy")
|
||||||
} else if let Some(self_token) =
|
} else if let Some(self_token) =
|
||||||
syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
|
syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
|
||||||
{
|
{
|
||||||
rename_self_to_param(&sema, position, self_token, "dummy")
|
rename_self_to_param(&sema, position, self_token, "dummy")
|
||||||
} else {
|
} else {
|
||||||
let range = match find_all_refs(&sema, position, None) {
|
let RangeInfo { range, .. } = find_all_refs(&sema, position)?;
|
||||||
Some(RangeInfo { range, .. }) => range,
|
|
||||||
None => return Err(RenameError("No references found at position".to_string())),
|
|
||||||
};
|
|
||||||
Ok(RangeInfo::new(range, SourceChange::from(vec![])))
|
Ok(RangeInfo::new(range, SourceChange::from(vec![])))
|
||||||
}
|
}
|
||||||
.map(|info| RangeInfo::new(info.range, ()))
|
.map(|info| RangeInfo::new(info.range, ()))
|
||||||
|
@ -62,7 +69,7 @@ pub(crate) fn rename(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
) -> RenameResult<RangeInfo<SourceChange>> {
|
||||||
let sema = Semantics::new(db);
|
let sema = Semantics::new(db);
|
||||||
rename_with_semantics(&sema, position, new_name)
|
rename_with_semantics(&sema, position, new_name)
|
||||||
}
|
}
|
||||||
|
@ -71,42 +78,18 @@ pub(crate) fn rename_with_semantics(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
) -> RenameResult<RangeInfo<SourceChange>> {
|
||||||
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))),
|
|
||||||
};
|
|
||||||
|
|
||||||
let source_file = sema.parse(position.file_id);
|
let source_file = sema.parse(position.file_id);
|
||||||
let syntax = source_file.syntax();
|
let syntax = source_file.syntax();
|
||||||
// this is here to prevent lifetime renames from happening on modules and self
|
|
||||||
if is_lifetime_name {
|
if let Some(module) = find_module_at_offset(&sema, position, syntax) {
|
||||||
rename_reference(&sema, position, new_name, is_lifetime_name)
|
|
||||||
} else if let Some(module) = find_module_at_offset(&sema, position, syntax) {
|
|
||||||
rename_mod(&sema, position, module, new_name)
|
rename_mod(&sema, position, module, new_name)
|
||||||
} else if let Some(self_token) =
|
} else if let Some(self_token) =
|
||||||
syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
|
syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
|
||||||
{
|
{
|
||||||
rename_self_to_param(&sema, position, self_token, new_name)
|
rename_self_to_param(&sema, position, self_token, new_name)
|
||||||
} else {
|
} else {
|
||||||
rename_reference(&sema, position, new_name, is_lifetime_name)
|
rename_reference(&sema, position, new_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,6 +110,33 @@ pub(crate) fn will_rename_file(
|
||||||
Some(change)
|
Some(change)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(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(
|
fn find_module_at_offset(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
|
@ -155,6 +165,14 @@ fn find_module_at_offset(
|
||||||
Some(module)
|
Some(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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_reference(
|
fn source_edit_from_reference(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
reference: Reference,
|
reference: Reference,
|
||||||
|
@ -223,7 +241,10 @@ fn rename_mod(
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
module: Module,
|
module: Module,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
) -> RenameResult<RangeInfo<SourceChange>> {
|
||||||
|
if IdentifierKind::Ident != check_identifier(new_name)? {
|
||||||
|
bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
|
||||||
|
}
|
||||||
let mut source_file_edits = Vec::new();
|
let mut source_file_edits = Vec::new();
|
||||||
let mut file_system_edits = Vec::new();
|
let mut file_system_edits = Vec::new();
|
||||||
|
|
||||||
|
@ -254,8 +275,7 @@ fn rename_mod(
|
||||||
source_file_edits.push(edit);
|
source_file_edits.push(edit);
|
||||||
}
|
}
|
||||||
|
|
||||||
let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
|
let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
|
||||||
.ok_or_else(|| RenameError("No references found at position".to_string()))?;
|
|
||||||
let ref_edits = refs
|
let ref_edits = refs
|
||||||
.references
|
.references
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -274,27 +294,26 @@ fn rename_to_self(
|
||||||
|
|
||||||
let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset)
|
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)))
|
.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
|
let param_range = fn_ast
|
||||||
.param_list()
|
.param_list()
|
||||||
.and_then(|p| p.params().next())
|
.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()
|
.syntax()
|
||||||
.text_range();
|
.text_range();
|
||||||
if !param_range.contains(position.offset) {
|
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)
|
let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset)
|
||||||
.and_then(|def| sema.to_def(&def))
|
.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() {
|
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 params = fn_def.assoc_fn_params(sema.db);
|
||||||
let first_param =
|
let first_param = params.first().ok_or_else(|| format_err!("Method has no parameters"))?;
|
||||||
params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?;
|
|
||||||
let first_param_ty = first_param.ty();
|
let first_param_ty = first_param.ty();
|
||||||
let impl_ty = impl_block.target_ty(sema.db);
|
let impl_ty = impl_block.target_ty(sema.db);
|
||||||
let (ty, self_param) = if impl_ty.remove_ref().is_some() {
|
let (ty, self_param) = if impl_ty.remove_ref().is_some() {
|
||||||
|
@ -307,18 +326,17 @@ fn rename_to_self(
|
||||||
};
|
};
|
||||||
|
|
||||||
if ty != impl_ty {
|
if ty != impl_ty {
|
||||||
return Err(RenameError("Parameter type differs from impl block type".to_string()));
|
bail!("Parameter type differs from impl block type");
|
||||||
}
|
}
|
||||||
|
|
||||||
let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
|
let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
|
||||||
.ok_or_else(|| RenameError("No reference found at position".to_string()))?;
|
|
||||||
|
|
||||||
let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
|
let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.partition(|reference| param_range.intersect(reference.file_range.range).is_some());
|
.partition(|reference| param_range.intersect(reference.file_range.range).is_some());
|
||||||
|
|
||||||
if param_ref.is_empty() {
|
if param_ref.is_empty() {
|
||||||
return Err(RenameError("Parameter to rename not found".to_string()));
|
bail!("Parameter to rename not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut edits = usages
|
let mut edits = usages
|
||||||
|
@ -367,12 +385,22 @@ fn rename_self_to_param(
|
||||||
self_token: SyntaxToken,
|
self_token: SyntaxToken,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
||||||
|
let ident_kind = check_identifier(new_name)?;
|
||||||
|
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(self_token.text_range(), SourceChange::default()));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
let source_file = sema.parse(position.file_id);
|
let source_file = sema.parse(position.file_id);
|
||||||
let syn = source_file.syntax();
|
let syn = source_file.syntax();
|
||||||
|
|
||||||
let text = sema.db.file_text(position.file_id);
|
let text = sema.db.file_text(position.file_id);
|
||||||
let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
|
let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
|
||||||
.ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
|
.ok_or_else(|| format_err!("No surrounding method declaration found"))?;
|
||||||
let search_range = fn_def.syntax().text_range();
|
let search_range = fn_def.syntax().text_range();
|
||||||
|
|
||||||
let mut edits: Vec<SourceFileEdit> = vec![];
|
let mut edits: Vec<SourceFileEdit> = vec![];
|
||||||
|
@ -382,12 +410,10 @@ fn rename_self_to_param(
|
||||||
if !search_range.contains_inclusive(offset) {
|
if !search_range.contains_inclusive(offset) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(ref usage) =
|
if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) {
|
||||||
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()) {
|
let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
|
||||||
text_edit_from_self_param(syn, self_param, new_name)
|
text_edit_from_self_param(syn, self_param, new_name)
|
||||||
.ok_or_else(|| RenameError("No target type found".to_string()))?
|
.ok_or_else(|| format_err!("No target type found"))?
|
||||||
} else {
|
} else {
|
||||||
TextEdit::replace(usage.text_range(), String::from(new_name))
|
TextEdit::replace(usage.text_range(), String::from(new_name))
|
||||||
};
|
};
|
||||||
|
@ -395,6 +421,10 @@ fn rename_self_to_param(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if edits.len() > 1 && ident_kind == IdentifierKind::Underscore {
|
||||||
|
bail!("Cannot rename reference to `_` as it is being referenced multiple times");
|
||||||
|
}
|
||||||
|
|
||||||
let range = ast::SelfParam::cast(self_token.parent())
|
let range = ast::SelfParam::cast(self_token.parent())
|
||||||
.map_or(self_token.text_range(), |p| p.syntax().text_range());
|
.map_or(self_token.text_range(), |p| p.syntax().text_range());
|
||||||
|
|
||||||
|
@ -405,24 +435,34 @@ fn rename_reference(
|
||||||
sema: &Semantics<RootDatabase>,
|
sema: &Semantics<RootDatabase>,
|
||||||
position: FilePosition,
|
position: FilePosition,
|
||||||
new_name: &str,
|
new_name: &str,
|
||||||
is_lifetime_name: bool,
|
|
||||||
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
) -> Result<RangeInfo<SourceChange>, RenameError> {
|
||||||
let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
|
let ident_kind = check_identifier(new_name)?;
|
||||||
Some(range_info) => range_info,
|
let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
|
||||||
None => return Err(RenameError("No references found at position".to_string())),
|
|
||||||
};
|
|
||||||
|
|
||||||
match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) {
|
match (ident_kind, &refs.declaration.kind) {
|
||||||
(true, false) => {
|
(IdentifierKind::ToSelf, ReferenceKind::Lifetime)
|
||||||
return Err(RenameError(format!(
|
| (IdentifierKind::Underscore, ReferenceKind::Lifetime)
|
||||||
"Invalid name `{}`: not a lifetime identifier",
|
| (IdentifierKind::Ident, ReferenceKind::Lifetime) => {
|
||||||
new_name
|
mark::hit!(rename_not_a_lifetime_ident_ref);
|
||||||
)))
|
bail!("Invalid name `{}`: not a lifetime identifier", new_name)
|
||||||
}
|
}
|
||||||
(false, true) => {
|
(IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime),
|
||||||
return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
|
(IdentifierKind::Lifetime, _) => {
|
||||||
|
mark::hit!(rename_not_an_ident_ref);
|
||||||
|
bail!("Invalid name `{}`: not an identifier", new_name)
|
||||||
}
|
}
|
||||||
_ => (),
|
(IdentifierKind::ToSelf, ReferenceKind::SelfKw) => {
|
||||||
|
unreachable!("rename_self_to_param should've been called instead")
|
||||||
|
}
|
||||||
|
(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
|
let edit = refs
|
||||||
|
@ -430,10 +470,6 @@ fn rename_reference(
|
||||||
.map(|reference| source_edit_from_reference(sema, reference, new_name))
|
.map(|reference| source_edit_from_reference(sema, reference, new_name))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if edit.is_empty() {
|
|
||||||
return Err(RenameError("No references found at position".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RangeInfo::new(range, SourceChange::from(edit)))
|
Ok(RangeInfo::new(range, SourceChange::from(edit)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,10 +498,12 @@ mod tests {
|
||||||
text_edit_builder.replace(indel.delete, indel.insert);
|
text_edit_builder.replace(indel.delete, indel.insert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
|
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);
|
text_edit_builder.finish().apply(&mut result);
|
||||||
assert_eq_text!(ra_fixture_after, &*result);
|
assert_eq_text!(ra_fixture_after, &*result);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if ra_fixture_after.starts_with("error:") {
|
if ra_fixture_after.starts_with("error:") {
|
||||||
let error_message = ra_fixture_after
|
let error_message = ra_fixture_after
|
||||||
|
@ -530,6 +568,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rename_to_invalid_identifier_lifetime() {
|
fn test_rename_to_invalid_identifier_lifetime() {
|
||||||
|
mark::check!(rename_not_an_ident_ref);
|
||||||
check(
|
check(
|
||||||
"'foo",
|
"'foo",
|
||||||
r#"fn main() { let i$0 = 1; }"#,
|
r#"fn main() { let i$0 = 1; }"#,
|
||||||
|
@ -539,6 +578,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rename_to_invalid_identifier_lifetime2() {
|
fn test_rename_to_invalid_identifier_lifetime2() {
|
||||||
|
mark::check!(rename_not_a_lifetime_ident_ref);
|
||||||
check(
|
check(
|
||||||
"foo",
|
"foo",
|
||||||
r#"fn main<'a>(_: &'a$0 ()) {}"#,
|
r#"fn main<'a>(_: &'a$0 ()) {}"#,
|
||||||
|
@ -546,8 +586,28 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn test_rename_for_local() {
|
fn test_rename_for_local() {
|
||||||
|
mark::check!(rename_ident);
|
||||||
check(
|
check(
|
||||||
"k",
|
"k",
|
||||||
r#"
|
r#"
|
||||||
|
@ -1178,6 +1238,7 @@ fn foo(f: foo::Foo) {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parameter_to_self() {
|
fn test_parameter_to_self() {
|
||||||
|
mark::check!(rename_to_self);
|
||||||
check(
|
check(
|
||||||
"self",
|
"self",
|
||||||
r#"
|
r#"
|
||||||
|
@ -1481,6 +1542,7 @@ fn foo(Foo { i: bar }: foo) -> i32 {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rename_lifetimes() {
|
fn test_rename_lifetimes() {
|
||||||
|
mark::check!(rename_lifetime);
|
||||||
check(
|
check(
|
||||||
"'yeeee",
|
"'yeeee",
|
||||||
r#"
|
r#"
|
||||||
|
@ -1562,6 +1624,26 @@ fn foo<'a>() -> &'a () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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) {}
|
||||||
|
}
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ proc_macro_api = { path = "../proc_macro_api", version = "0.0.0" }
|
||||||
test_utils = { path = "../test_utils", version = "0.0.0" }
|
test_utils = { path = "../test_utils", version = "0.0.0" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
cargo_metadata = "=0.12.0"
|
cargo_metadata = "0.12.2"
|
||||||
|
|
||||||
# used as proc macro test targets
|
# used as proc macro test targets
|
||||||
serde_derive = "1.0.106"
|
serde_derive = "1.0.106"
|
||||||
|
|
|
@ -12,7 +12,7 @@ doctest = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
cargo_metadata = "=0.12.0"
|
cargo_metadata = "0.12.2"
|
||||||
serde = { version = "1.0.106", features = ["derive"] }
|
serde = { version = "1.0.106", features = ["derive"] }
|
||||||
serde_json = "1.0.48"
|
serde_json = "1.0.48"
|
||||||
anyhow = "1.0.26"
|
anyhow = "1.0.26"
|
||||||
|
|
|
@ -78,6 +78,8 @@ Use original span for FileId
|
||||||
|
|
||||||
This makes it easier to prepare a changelog.
|
This makes it easier to prepare a changelog.
|
||||||
|
|
||||||
|
If the change adds a new user-visible functionality, consider recording a GIF with [peek](https://github.com/phw/peek) and pasting it into the PR description.
|
||||||
|
|
||||||
**Rationale:** clean history is potentially useful, but rarely used.
|
**Rationale:** clean history is potentially useful, but rarely used.
|
||||||
But many users read changelogs.
|
But many users read changelogs.
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@ fn rust_files_are_tidy() {
|
||||||
for path in rust_files(&project_root().join("crates")) {
|
for path in rust_files(&project_root().join("crates")) {
|
||||||
let text = read_file(&path).unwrap();
|
let text = read_file(&path).unwrap();
|
||||||
check_todo(&path, &text);
|
check_todo(&path, &text);
|
||||||
|
check_dbg(&path, &text);
|
||||||
check_trailing_ws(&path, &text);
|
check_trailing_ws(&path, &text);
|
||||||
deny_clippy(&path, &text);
|
deny_clippy(&path, &text);
|
||||||
tidy_docs.visit(&path, &text);
|
tidy_docs.visit(&path, &text);
|
||||||
|
@ -94,8 +95,7 @@ fn rust_files_are_tidy() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_merge_commits() {
|
fn check_merge_commits() {
|
||||||
let stdout =
|
let stdout = cmd!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19..")
|
||||||
dbg!(cmd!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19.."))
|
|
||||||
.read()
|
.read()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if !stdout.is_empty() {
|
if !stdout.is_empty() {
|
||||||
|
@ -224,7 +224,7 @@ Zlib OR Apache-2.0 OR MIT
|
||||||
fn check_todo(path: &Path, text: &str) {
|
fn check_todo(path: &Path, text: &str) {
|
||||||
let need_todo = &[
|
let need_todo = &[
|
||||||
// This file itself obviously needs to use todo (<- like this!).
|
// This file itself obviously needs to use todo (<- like this!).
|
||||||
"tests/cli.rs",
|
"tests/tidy.rs",
|
||||||
// Some of our assists generate `todo!()`.
|
// Some of our assists generate `todo!()`.
|
||||||
"handlers/add_turbo_fish.rs",
|
"handlers/add_turbo_fish.rs",
|
||||||
"handlers/generate_function.rs",
|
"handlers/generate_function.rs",
|
||||||
|
@ -252,6 +252,32 @@ fn check_todo(path: &Path, text: &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_dbg(path: &Path, text: &str) {
|
||||||
|
let need_dbg = &[
|
||||||
|
// This file itself obviously needs to use dbg.
|
||||||
|
"tests/tidy.rs",
|
||||||
|
// Assists to remove `dbg!()`
|
||||||
|
"handlers/remove_dbg.rs",
|
||||||
|
// We have .dbg postfix
|
||||||
|
"completion/src/completions/postfix.rs",
|
||||||
|
// The documentation in string literals may contain anything for its own purposes
|
||||||
|
"completion/src/lib.rs",
|
||||||
|
"completion/src/generated_lint_completions.rs",
|
||||||
|
// test for doc test for remove_dbg
|
||||||
|
"src/tests/generated.rs",
|
||||||
|
];
|
||||||
|
if need_dbg.iter().any(|p| path.ends_with(p)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if text.contains("dbg!") {
|
||||||
|
panic!(
|
||||||
|
"\ndbg! macros should not be committed to the master branch,\n\
|
||||||
|
{}\n",
|
||||||
|
path.display(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn check_trailing_ws(path: &Path, text: &str) {
|
fn check_trailing_ws(path: &Path, text: &str) {
|
||||||
if is_exclude_dir(path, &["test_data"]) {
|
if is_exclude_dir(path, &["test_data"]) {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in a new issue