diff --git a/example.nix b/example.nix index c2aeeef..fc31b64 100644 --- a/example.nix +++ b/example.nix @@ -9,10 +9,12 @@ let used4 = { t = used_inherit; }; shadowed = 42; _unused = unused: false; + _used = 23; in { x = { unusedArg2, x ? args.y, ... }@args: used1 + x; inherit used2; "${used3}" = true; y = used4.t; z = let shadowed = 23; in shadowed; + inherit _used; } diff --git a/src/dead_code.rs b/src/dead_code.rs index 87ee13a..dee3455 100644 --- a/src/dead_code.rs +++ b/src/dead_code.rs @@ -15,11 +15,17 @@ pub struct DeadCode { pub scope: Scope, /// The [`Binding`] that is found to be unused pub binding: Binding, + /// Used or unused? + unused: bool, } impl fmt::Display for DeadCode { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "Unused {}: {}", self.scope, self.binding.name) + if self.unused { + write!(fmt, "Unused {}: {}", self.scope, self.binding.name) + } else { + write!(fmt, "Used {}: {}", self.scope, self.binding.name) + } } } @@ -32,6 +38,8 @@ pub struct Settings { pub no_lambda_pattern_names: bool, /// Ignore all `Binding` that start with `_` pub no_underscore: bool, + /// Warn on used binding that starts with `_` + pub warn_used_underscore: bool, } impl Settings { @@ -71,25 +79,31 @@ impl Settings { continue; } - if binding.is_mortal() - && scope.bodies().all(|body| - // remove this binding's own node - body == binding.decl_node - // excluding already unused results - || dead.contains(&body) - || is_dead_inherit(dead, &body) - // or not used anywhere - || ! usage::find(&binding.name, &body) - ) - && ! binding.has_pragma_skip() { - dead.insert(binding.decl_node.clone()); - results.insert( - binding.decl_node.clone(), - DeadCode { - scope: scope.clone(), - binding, - }, + if binding.is_mortal() && ! binding.has_pragma_skip() { + let unused = scope.bodies().all(|body| + // remove this binding's own node + body == binding.decl_node + // excluding already unused results + || dead.contains(&body) + || is_dead_inherit(dead, &body) + // or not used anywhere + || ! usage::find(&binding.name, &body) ); + if unused || ( + self.warn_used_underscore && + binding.name.syntax().text().char_at(0.into()) == Some('_') && + ! unused + ) { + dead.insert(binding.decl_node.clone()); + results.insert( + binding.decl_node.clone(), + DeadCode { + scope: scope.clone(), + binding, + unused, + }, + ); + } } } } diff --git a/src/dead_code_tests.rs b/src/dead_code_tests.rs index 7155a44..636b08a 100644 --- a/src/dead_code_tests.rs +++ b/src/dead_code_tests.rs @@ -3,16 +3,20 @@ use rowan::ast::AstNode; use crate::dead_code::{DeadCode, Settings}; -fn run(content: &str) -> Vec { +fn run_settings(content: &str, settings: Settings) -> Vec { let ast = rnix::Root::parse(content); assert_eq!(0, ast.errors().len()); - Settings { + settings.find_dead_code(&ast.syntax()) +} + +fn run(content: &str) -> Vec { + run_settings(content, Settings { no_lambda_arg: false, no_lambda_pattern_names: false, no_underscore: false, - } - .find_dead_code(&ast.syntax()) + warn_used_underscore: false, + }) } #[test] @@ -482,3 +486,17 @@ fn let_args_string_splice() { "); assert_eq!(0, results.len()); } + +#[test] +fn used_underscore_let() { + let results = run_settings(" + let _x = 23; + in _x + ", Settings { + no_lambda_arg: false, + no_lambda_pattern_names: false, + no_underscore: false, + warn_used_underscore: true, + }); + assert_eq!(1, results.len()); +} diff --git a/src/edit_tests.rs b/src/edit_tests.rs index 1b91084..18aa8b3 100644 --- a/src/edit_tests.rs +++ b/src/edit_tests.rs @@ -10,6 +10,7 @@ fn run(content: &str) -> (String, bool) { no_lambda_arg: false, no_lambda_pattern_names: false, no_underscore: false, + warn_used_underscore: false, } .find_dead_code(&ast.syntax()); crate::edit::edit_dead_code(content, results.into_iter()) diff --git a/src/lib.rs b/src/lib.rs index c9dc4fa..6b22aa7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ //! no_lambda_arg: false, //! no_lambda_pattern_names: false, //! no_underscore: false, +//! warn_used_underscore: false, //! }.find_dead_code(&ast.syntax()); //! //! for dead_code in &results { diff --git a/src/main.rs b/src/main.rs index e37f665..94bc4ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,13 @@ fn main() { (Lambda arguments starting with _ are not checked anyway.)" ), ) + .arg( + Arg::new("WARN_USED_UNDERSCORE") + .action(ArgAction::SetTrue) + .short('W') + .long("warn-used-underscore") + .help("Warn if bindings are referenced that start with '_'"), + ) .arg( Arg::new("QUIET") .action(ArgAction::SetTrue) @@ -112,6 +119,7 @@ fn main() { no_lambda_arg: matches.get_flag("NO_LAMBDA_ARG"), no_lambda_pattern_names: matches.get_flag("NO_LAMBDA_PATTERN_NAMES"), no_underscore: matches.get_flag("NO_UNDERSCORE"), + warn_used_underscore: matches.get_flag("WARN_USED_UNDERSCORE"), }; let quiet = matches.get_flag("QUIET"); let edit = matches.get_flag("EDIT");