From dc514a2348d2e3062e12d42583cfb11ffd7a5f37 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 26 Sep 2026 23:19:23 -0400 Subject: [PATCH] fix(natu): report let locations --- src/natu/ast.rs | 1 + src/natu/natu.rs | 2 +- src/natu/natu_test.rs | 4 ++-- src/natu/parser.rs | 1 + src/natu/rules/let_order.rs | 2 +- src/natu/scope_graph.rs | 9 ++++++++- 6 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/natu/ast.rs b/src/natu/ast.rs index c24b52c..61038d9 100644 --- a/src/natu/ast.rs +++ b/src/natu/ast.rs @@ -49,6 +49,7 @@ pub enum Expr { Ident(Ident), Select(Select), Let { + range: SourceRange, bindings: Vec, body: Box, }, diff --git a/src/natu/natu.rs b/src/natu/natu.rs index dcc730e..94ea9b2 100644 --- a/src/natu/natu.rs +++ b/src/natu/natu.rs @@ -56,7 +56,7 @@ impl std::fmt::Display for Report { finding.finding.expected_order.as_ref().expect("let order"); writeln!( formatter, - " let whose first binding starts at line {start}: {}", + " let at line {start}: {}", expected_order.join(", ") )?; } diff --git a/src/natu/natu_test.rs b/src/natu/natu_test.rs index 434d5ba..bc7c81e 100644 --- a/src/natu/natu_test.rs +++ b/src/natu/natu_test.rs @@ -58,8 +58,8 @@ fn natu_reports_out_of_order_let_bindings() -> googletest::Result<()> { "testdata/lint_findings.nix:12 [manual_inherit_from] Use `inherit` for this selected attribute\n", "testdata/lint_findings.nix:13 [unquoted_uri] URI literals should be quoted\n", "testdata/lint_findings.nix [let_order] Let bindings must follow dependency order\n", - " let whose first binding starts at line 2: first, second, nested\n", - " let whose first binding starts at line 5: nestedSecond, nestedFirst\n", + " let at line 1: first, second, nested\n", + " let at line 4: nestedSecond, nestedFirst\n", )) ); Ok(()) diff --git a/src/natu/parser.rs b/src/natu/parser.rs index 419188c..9ece9db 100644 --- a/src/natu/parser.rs +++ b/src/natu/parser.rs @@ -43,6 +43,7 @@ fn convert_expr(expr: rnix::ast::Expr) -> ast::Expr { match expr { rnix::ast::Expr::Ident(ident) => ast::Expr::Ident(ident.into()), rnix::ast::Expr::LetIn(let_in) => ast::Expr::Let { + range: let_in.syntax().text_range().into(), bindings: let_in .attrpath_values() .filter_map(convert_binding_ast) diff --git a/src/natu/rules/let_order.rs b/src/natu/rules/let_order.rs index 658291a..92dc6d6 100644 --- a/src/natu/rules/let_order.rs +++ b/src/natu/rules/let_order.rs @@ -22,7 +22,7 @@ pub fn check(graph: &ScopeGraph) -> Vec { }); (!is_ordered).then(|| Finding { - range: let_scope.bindings[0].range, + range: let_scope.range, expected_order: expected .iter() .filter_map(|declaration| { diff --git a/src/natu/scope_graph.rs b/src/natu/scope_graph.rs index 6795458..e509f6d 100644 --- a/src/natu/scope_graph.rs +++ b/src/natu/scope_graph.rs @@ -26,7 +26,11 @@ impl ScopeGraph { fn build_expr(&mut self, expr: &ast::Expr, scope: NodeId) -> Vec { match expr { ast::Expr::Ident(ident) => vec![self.add_reference(&ident.name, scope)], - ast::Expr::Let { bindings, body } => self.build_let(bindings, body, scope), + ast::Expr::Let { + range, + bindings, + body, + } => self.build_let(*range, bindings, body, scope), ast::Expr::AttrSet(bindings) => bindings .iter() .flat_map(|binding| self.build_expr(&binding.value, scope)) @@ -48,6 +52,7 @@ impl ScopeGraph { fn build_let( &mut self, + range: ast::SourceRange, bindings: &[ast::Binding], body: &ast::Expr, parent: NodeId, @@ -74,6 +79,7 @@ impl ScopeGraph { .chain(entry_references.iter().copied()) .collect(); self.lets.push(LetScope { + range, bindings: graph_bindings, entry_references, }); @@ -181,6 +187,7 @@ struct Edge { #[derive(Debug)] pub(crate) struct LetScope { + pub(crate) range: ast::SourceRange, pub(crate) bindings: Vec, pub(crate) entry_references: Vec, }