use hir::Name; use ide_db::{ assists::{Assist, AssistId, AssistKind}, base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase, }; use syntax::TextRange; use text_edit::TextEdit; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext}; // Diagnostic: unused-variables // // This diagnostic is triggered when a local variable is not used. pub(crate) fn unused_variables( ctx: &DiagnosticsContext<'_>, d: &hir::UnusedVariable, ) -> Option { let ast = d.local.primary_source(ctx.sema.db).syntax_ptr(); if ast.file_id.macro_file().is_some() { // FIXME: Our infra can't handle allow from within macro expansions rn return None; } let diagnostic_range = ctx.sema.diagnostics_display_range(ast); // The range for the Actual Name. We don't want to replace the entire declarition. Using the diagnostic range causes issues within in Array Destructuring. let name_range = d.local.primary_source(ctx.sema.db).name().map(|v| v.syntax().value.text_range())?; // Make sure we are within the diagnostic range for the variable if !diagnostic_range.range.contains_range(name_range) { return None; } 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(fixes( ctx.sema.db, var_name, name_range, diagnostic_range, ast.file_id.is_macro(), )) .experimental(), ) } fn fixes( db: &RootDatabase, var_name: Name, name_range: TextRange, diagnostic_range: FileRange, is_in_marco: bool, ) -> Option> { 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), var_name.display(db) )), 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))), )), trigger_signature_help: false, }]) } #[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; } "#, ); } }