fix(natu): report let locations

This commit is contained in:
2026-09-26 23:19:23 -04:00
parent 832bfe589c
commit dc514a2348
6 changed files with 14 additions and 5 deletions

View File

@@ -49,6 +49,7 @@ pub enum Expr {
Ident(Ident), Ident(Ident),
Select(Select), Select(Select),
Let { Let {
range: SourceRange,
bindings: Vec<Binding>, bindings: Vec<Binding>,
body: Box<Expr>, body: Box<Expr>,
}, },

View File

@@ -56,7 +56,7 @@ impl std::fmt::Display for Report {
finding.finding.expected_order.as_ref().expect("let order"); finding.finding.expected_order.as_ref().expect("let order");
writeln!( writeln!(
formatter, formatter,
" let whose first binding starts at line {start}: {}", " let at line {start}: {}",
expected_order.join(", ") expected_order.join(", ")
)?; )?;
} }

View File

@@ -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: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:13 [unquoted_uri] URI literals should be quoted\n",
"testdata/lint_findings.nix [let_order] Let bindings must follow dependency order\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 at line 1: first, second, nested\n",
" let whose first binding starts at line 5: nestedSecond, nestedFirst\n", " let at line 4: nestedSecond, nestedFirst\n",
)) ))
); );
Ok(()) Ok(())

View File

@@ -43,6 +43,7 @@ fn convert_expr(expr: rnix::ast::Expr) -> ast::Expr {
match expr { match expr {
rnix::ast::Expr::Ident(ident) => ast::Expr::Ident(ident.into()), rnix::ast::Expr::Ident(ident) => ast::Expr::Ident(ident.into()),
rnix::ast::Expr::LetIn(let_in) => ast::Expr::Let { rnix::ast::Expr::LetIn(let_in) => ast::Expr::Let {
range: let_in.syntax().text_range().into(),
bindings: let_in bindings: let_in
.attrpath_values() .attrpath_values()
.filter_map(convert_binding_ast) .filter_map(convert_binding_ast)

View File

@@ -22,7 +22,7 @@ pub fn check(graph: &ScopeGraph) -> Vec<Finding> {
}); });
(!is_ordered).then(|| Finding { (!is_ordered).then(|| Finding {
range: let_scope.bindings[0].range, range: let_scope.range,
expected_order: expected expected_order: expected
.iter() .iter()
.filter_map(|declaration| { .filter_map(|declaration| {

View File

@@ -26,7 +26,11 @@ impl ScopeGraph {
fn build_expr(&mut self, expr: &ast::Expr, scope: NodeId) -> Vec<NodeId> { fn build_expr(&mut self, expr: &ast::Expr, scope: NodeId) -> Vec<NodeId> {
match expr { match expr {
ast::Expr::Ident(ident) => vec![self.add_reference(&ident.name, scope)], 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 ast::Expr::AttrSet(bindings) => bindings
.iter() .iter()
.flat_map(|binding| self.build_expr(&binding.value, scope)) .flat_map(|binding| self.build_expr(&binding.value, scope))
@@ -48,6 +52,7 @@ impl ScopeGraph {
fn build_let( fn build_let(
&mut self, &mut self,
range: ast::SourceRange,
bindings: &[ast::Binding], bindings: &[ast::Binding],
body: &ast::Expr, body: &ast::Expr,
parent: NodeId, parent: NodeId,
@@ -74,6 +79,7 @@ impl ScopeGraph {
.chain(entry_references.iter().copied()) .chain(entry_references.iter().copied())
.collect(); .collect();
self.lets.push(LetScope { self.lets.push(LetScope {
range,
bindings: graph_bindings, bindings: graph_bindings,
entry_references, entry_references,
}); });
@@ -181,6 +187,7 @@ struct Edge {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct LetScope { pub(crate) struct LetScope {
pub(crate) range: ast::SourceRange,
pub(crate) bindings: Vec<Binding>, pub(crate) bindings: Vec<Binding>,
pub(crate) entry_references: Vec<NodeId>, pub(crate) entry_references: Vec<NodeId>,
} }