From 2ec7f12864daedd959c3cd2998c5c8659d32a289 Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 4 Oct 2026 21:21:21 -0400 Subject: [PATCH] feat(natu): detect returned let bindings --- src/natu/natu_test.rs | 28 +++++++++++++++++++++------- src/natu/rules.rs | 2 +- src/natu/rules/redundant_let.rs | 11 +++++++++-- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/natu/natu_test.rs b/src/natu/natu_test.rs index 15411ca..ba97834 100644 --- a/src/natu/natu_test.rs +++ b/src/natu/natu_test.rs @@ -69,7 +69,7 @@ fn natu_reports_out_of_order_let_bindings() -> googletest::Result<()> { fn natu_preserves_suppressed_bindings() -> googletest::Result<()> { let report = natu::future_sight(vec![SourceFile { path: PathBuf::from("testdata/nolint.nix"), - source: "{ config, ... }: let\n cfg = config.example; # NOLINT(let_order)\n value = cfg;\nin value\n".into(), + source: "{ config, ... }: let # NOLINT(redundant_let)\n cfg = config.example; # NOLINT(let_order)\n value = cfg;\nin value\n".into(), }]) .or_fail()?; @@ -107,7 +107,8 @@ fn natu_reports_redundant_let() -> googletest::Result<()> { path: PathBuf::from("testdata/redundant_let.nix"), source: r#" let - value = 1; + helper = 1; + value = helper; in value "# @@ -126,16 +127,29 @@ value #[gtest] fn natu_accepts_nonredundant_let() -> googletest::Result<()> { - let report = natu::future_sight(vec![SourceFile { - path: PathBuf::from("testdata/nonredundant_let.nix"), - source: r#" + let report = natu::future_sight(vec![ + SourceFile { + path: PathBuf::from("testdata/recursive_let.nix"), + source: r#" let recursive = recursive; in recursive "# - .into(), - }]) + .into(), + }, + SourceFile { + path: PathBuf::from("testdata/dependent_let.nix"), + source: r#" +let + value = 1; + dependent = value; +in +value +"# + .into(), + }, + ]) .or_fail()?; let count = report diff --git a/src/natu/rules.rs b/src/natu/rules.rs index c036b8b..1ba952a 100644 --- a/src/natu/rules.rs +++ b/src/natu/rules.rs @@ -97,7 +97,7 @@ pub fn run_rules(file: &ast::ParsedFile, graph: &ScopeGraph, source: &str) -> Ve .map(|finding| Finding { rule_id: "redundant_let", range: finding.range, - message: "Inline this single-binding let expression", + message: "Inline this returned let binding", binding: None, expected_order: None, }); diff --git a/src/natu/rules/redundant_let.rs b/src/natu/rules/redundant_let.rs index 9b027e3..8a19057 100644 --- a/src/natu/rules/redundant_let.rs +++ b/src/natu/rules/redundant_let.rs @@ -45,11 +45,18 @@ fn check_expr(expr: &ast::Expr, findings: &mut Vec) { } fn is_redundant(bindings: &[ast::Binding], body: &ast::Expr) -> bool { - let [binding] = bindings else { + let Some((binding_index, binding)) = bindings + .iter() + .enumerate() + .find(|(_, binding)| is_ident(body, &binding.key.name)) + else { return false; }; - is_ident(body, &binding.key.name) && !references_name(&binding.value, &binding.key.name) + !references_name(&binding.value, &binding.key.name) + && !bindings.iter().enumerate().any(|(candidate_index, candidate)| { + candidate_index != binding_index && references_name(&candidate.value, &binding.key.name) + }) } fn is_ident(expr: &ast::Expr, name: &str) -> bool {