feat(natu): detect returned let bindings

This commit is contained in:
2026-10-04 21:21:21 -04:00
parent 0aac56e0b9
commit 2ec7f12864
3 changed files with 31 additions and 10 deletions

View File

@@ -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

View File

@@ -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,
});

View File

@@ -45,11 +45,18 @@ fn check_expr(expr: &ast::Expr, findings: &mut Vec<Finding>) {
}
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 {