ide_diagnostics/handlers/
unused_variables.rsuse hir::Name;
use ide_db::text_edit::TextEdit;
use ide_db::{
assists::{Assist, AssistId, AssistKind},
label::Label,
source_change::SourceChange,
FileRange, RootDatabase,
};
use syntax::{Edition, TextRange};
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
pub(crate) fn unused_variables(
ctx: &DiagnosticsContext<'_>,
d: &hir::UnusedVariable,
) -> Option<Diagnostic> {
let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
if ast.file_id.macro_file().is_some() {
return None;
}
let diagnostic_range = ctx.sema.diagnostics_display_range(ast);
let name_range = d
.local
.primary_source(ctx.sema.db)
.name()
.map(|v| v.syntax().original_file_range_rooted(ctx.sema.db))
.filter(|it| {
Some(it.file_id) == ast.file_id.file_id()
&& diagnostic_range.range.contains_range(it.range)
});
let var_name = d.local.name(ctx.sema.db);
Some(
Diagnostic::new_with_syntax_node_ptr(
ctx,
DiagnosticCode::RustcLint("unused_variables"),
"unused variable",
ast,
)
.with_fixes(name_range.and_then(|it| {
fixes(
ctx.sema.db,
var_name,
it.range,
diagnostic_range.into(),
ast.file_id.is_macro(),
ctx.edition,
)
}))
.experimental(),
)
}
fn fixes(
db: &RootDatabase,
var_name: Name,
name_range: TextRange,
diagnostic_range: FileRange,
is_in_marco: bool,
edition: Edition,
) -> Option<Vec<Assist>> {
if is_in_marco {
return None;
}
Some(vec![Assist {
id: AssistId("unscore_unused_variable_name", AssistKind::QuickFix),
label: Label::new(format!(
"Rename unused {} to _{}",
var_name.display(db, edition),
var_name.display(db, edition)
)),
group: None,
target: diagnostic_range.range,
source_change: Some(SourceChange::from_text_edit(
diagnostic_range.file_id,
TextEdit::replace(name_range, format!("_{}", var_name.display(db, edition))),
)),
command: None,
}])
}
#[cfg(test)]
mod tests {
use crate::tests::{check_diagnostics, check_fix};
#[test]
fn unused_variables_simple() {
check_diagnostics(
r#"
//- minicore: fn
struct Foo { f1: i32, f2: i64 }
fn f(kkk: i32) {}
//^^^ 💡 warn: unused variable
fn main() {
let a = 2;
//^ 💡 warn: unused variable
let b = 5;
// note: `unused variable` implies `unused mut`, so we should not emit both at the same time.
let mut c = f(b);
//^^^^^ 💡 warn: unused variable
let (d, e) = (3, 5);
//^ 💡 warn: unused variable
let _ = e;
let f1 = 2;
let f2 = 5;
let f = Foo { f1, f2 };
match f {
Foo { f1, f2 } => {
//^^ 💡 warn: unused variable
_ = f2;
}
}
let g = false;
if g {}
let h: fn() -> i32 = || 2;
let i = h();
//^ 💡 warn: unused variable
}
"#,
);
}
#[test]
fn unused_self() {
check_diagnostics(
r#"
struct S {
}
impl S {
fn owned_self(self, u: i32) {}
//^ 💡 warn: unused variable
fn ref_self(&self, u: i32) {}
//^ 💡 warn: unused variable
fn ref_mut_self(&mut self, u: i32) {}
//^ 💡 warn: unused variable
fn owned_mut_self(mut self) {}
//^^^^^^^^ 💡 warn: variable does not need to be mutable
}
"#,
);
}
#[test]
fn allow_unused_variables_for_identifiers_starting_with_underline() {
check_diagnostics(
r#"
fn main() {
let _x = 2;
}
"#,
);
}
#[test]
fn respect_lint_attributes_for_unused_variables() {
check_diagnostics(
r#"
fn main() {
#[allow(unused_variables)]
let x = 2;
}
#[deny(unused)]
fn main2() {
let x = 2;
//^ 💡 error: unused variable
}
"#,
);
}
#[test]
fn fix_unused_variable() {
check_fix(
r#"
fn main() {
let x$0 = 2;
}
"#,
r#"
fn main() {
let _x = 2;
}
"#,
);
check_fix(
r#"
fn main() {
let ($0d, _e) = (3, 5);
}
"#,
r#"
fn main() {
let (_d, _e) = (3, 5);
}
"#,
);
check_fix(
r#"
struct Foo { f1: i32, f2: i64 }
fn main() {
let f = Foo { f1: 0, f2: 0 };
match f {
Foo { f1$0, f2 } => {
_ = f2;
}
}
}
"#,
r#"
struct Foo { f1: i32, f2: i64 }
fn main() {
let f = Foo { f1: 0, f2: 0 };
match f {
Foo { _f1, f2 } => {
_ = f2;
}
}
}
"#,
);
}
#[test]
fn no_fix_for_marco() {
check_diagnostics(
r#"
macro_rules! my_macro {
() => {
let x = 3;
};
}
fn main() {
my_macro!();
}
"#,
);
}
#[test]
fn unused_variable_in_array_destructure() {
check_fix(
r#"
fn main() {
let arr = [1, 2, 3, 4, 5];
let [_x, y$0 @ ..] = arr;
}
"#,
r#"
fn main() {
let arr = [1, 2, 3, 4, 5];
let [_x, _y @ ..] = arr;
}
"#,
);
}
}