From 7a54789dd277315819f0c064cfb1ff76baa80b02 Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 26 Sep 2026 20:02:19 -0400 Subject: [PATCH] feat(xatu): identify ordered bindings --- src/xatu/rules.rs | 18 ++++++++++++++---- src/xatu/rules/let_order.rs | 12 ++++++++++-- src/xatu/scope_graph.rs | 2 ++ src/xatu/xatu.rs | 11 ++++++----- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/xatu/rules.rs b/src/xatu/rules.rs index 589f580..558e302 100644 --- a/src/xatu/rules.rs +++ b/src/xatu/rules.rs @@ -21,15 +21,22 @@ pub struct Finding { pub rule_id: &'static str, pub range: ast::SourceRange, pub message: &'static str, + pub binding: Option, } impl std::fmt::Display for Diagnostic { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( formatter, - "{}: {}", + "[{}] {}", self.finding.rule_id, self.finding.message - ) + )?; + + if let Some(binding) = &self.finding.binding { + write!(formatter, ": {binding}")?; + } + + Ok(()) } } @@ -68,10 +75,11 @@ pub fn run_rules(file: &ast::ParsedFile, graph: &ScopeGraph) -> Vec .flat_map(|token| [run_token_rule::(token)]) .flatten(); - let let_order_diagnostics = let_order::check(graph).into_iter().map(|range| Finding { + let let_order_diagnostics = let_order::check(graph).into_iter().map(|finding| Finding { rule_id: "let_order", - range, + range: finding.range, message: "Let bindings must follow dependency order", + binding: Some(finding.binding), }); node_diagnostics @@ -95,6 +103,7 @@ where rule_id: R::ID, range: node.range, message, + binding: None, }) } @@ -109,5 +118,6 @@ where rule_id: R::ID, range: token.range, message, + binding: None, }) } diff --git a/src/xatu/rules/let_order.rs b/src/xatu/rules/let_order.rs index 1ea72ac..7d01ebf 100644 --- a/src/xatu/rules/let_order.rs +++ b/src/xatu/rules/let_order.rs @@ -3,7 +3,12 @@ use crate::{ scope_graph::{LetScope, NodeId, ScopeGraph}, }; -pub fn check(graph: &ScopeGraph) -> Vec { +pub struct Finding { + pub range: ast::SourceRange, + pub binding: String, +} + +pub fn check(graph: &ScopeGraph) -> Vec { graph .let_scopes() .iter() @@ -24,7 +29,10 @@ pub fn check(graph: &ScopeGraph) -> Vec { .iter() .find(|(declaration, _)| *declaration == binding.declaration) .filter(|(_, expected_position)| position != *expected_position) - .map(|_| binding.range) + .map(|_| Finding { + range: binding.range, + binding: binding.name.clone(), + }) }) }) .collect() diff --git a/src/xatu/scope_graph.rs b/src/xatu/scope_graph.rs index f9e1817..6795458 100644 --- a/src/xatu/scope_graph.rs +++ b/src/xatu/scope_graph.rs @@ -57,6 +57,7 @@ impl ScopeGraph { .iter() .map(|binding| Binding { declaration: self.add_declaration(&binding.key.name, scope), + name: binding.key.name.clone(), references: Vec::new(), range: binding.range, }) @@ -187,6 +188,7 @@ pub(crate) struct LetScope { #[derive(Debug)] pub(crate) struct Binding { pub(crate) declaration: NodeId, + pub(crate) name: String, pub(crate) references: Vec, pub(crate) range: ast::SourceRange, } diff --git a/src/xatu/xatu.rs b/src/xatu/xatu.rs index 79f7e85..f718d89 100644 --- a/src/xatu/xatu.rs +++ b/src/xatu/xatu.rs @@ -26,11 +26,12 @@ impl std::fmt::Display for Report { for diagnostic in &self.diagnostics { let file = self.files.get(diagnostic.file_id); let (start, end) = line_range(&file.source, diagnostic.finding.range); - writeln!( - formatter, - "{}:{start}..{end}:{diagnostic}", - file.path.display() - )?; + let location = if start == end { + start.to_string() + } else { + format!("{start}-{end}") + }; + writeln!(formatter, "{}:{location} {diagnostic}", file.path.display())?; } Ok(())