From 1d6abab548741126d332ac620a9cbe982f90bec8 Mon Sep 17 00:00:00 2001 From: alexion Date: Thu, 17 Sep 2026 01:13:34 -0400 Subject: [PATCH] chore(xatu): remove project --- Cargo.lock | 8 --- Cargo.toml | 1 - src/xatu/BUILD | 33 ------------ src/xatu/Cargo.toml | 15 ------ src/xatu/analysis.rs | 52 ------------------ src/xatu/diagnostic.rs | 91 ------------------------------- src/xatu/edit.rs | 80 --------------------------- src/xatu/empty_inherit.rs | 37 ------------- src/xatu/file_set.rs | 45 ---------------- src/xatu/lib.rs | 21 -------- src/xatu/lib_test.rs | 111 -------------------------------------- src/xatu/main.rs | 26 --------- src/xatu/rules.rs | 43 --------------- src/xatu/runner.rs | 81 ---------------------------- 14 files changed, 644 deletions(-) delete mode 100644 src/xatu/BUILD delete mode 100644 src/xatu/Cargo.toml delete mode 100644 src/xatu/analysis.rs delete mode 100644 src/xatu/diagnostic.rs delete mode 100644 src/xatu/edit.rs delete mode 100644 src/xatu/empty_inherit.rs delete mode 100644 src/xatu/file_set.rs delete mode 100644 src/xatu/lib.rs delete mode 100644 src/xatu/lib_test.rs delete mode 100644 src/xatu/main.rs delete mode 100644 src/xatu/rules.rs delete mode 100644 src/xatu/runner.rs diff --git a/Cargo.lock b/Cargo.lock index 2a178f4..09bdd12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,14 +299,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "xatu" -version = "0.1.0" -dependencies = [ - "rnix", - "rowan", -] - [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 2945153..de6df26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,6 @@ members = [ "src/ariadne", "src/nix-style-linter", - "src/xatu", "src/machoke", ] resolver = "3" diff --git a/src/xatu/BUILD b/src/xatu/BUILD deleted file mode 100644 index d7b6b4a..0000000 --- a/src/xatu/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") - -rust_library( - name = "xatu_lib", - crate_name = "xatu", - srcs = [ - "analysis.rs", - "diagnostic.rs", - "edit.rs", - "empty_inherit.rs", - "file_set.rs", - "lib.rs", - "rules.rs", - "runner.rs", - ], - deps = [ - "//third_party/crates:rnix", - "//third_party/crates:rowan", - ], -) - -rust_binary( - name = "xatu", - crate_name = "xatu_cli", - srcs = ["main.rs"], - deps = [":xatu_lib"], -) - -rust_test( - name = "lib_test", - srcs = ["lib_test.rs"], - deps = [":xatu_lib"], -) diff --git a/src/xatu/Cargo.toml b/src/xatu/Cargo.toml deleted file mode 100644 index 34444d1..0000000 --- a/src/xatu/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "xatu" -version = "0.1.0" -edition = "2024" - -[lib] -path = "lib.rs" - -[[bin]] -name = "xatu" -path = "main.rs" - -[dependencies] -rnix = "0.14.0" -rowan = "0.16.1" diff --git a/src/xatu/analysis.rs b/src/xatu/analysis.rs deleted file mode 100644 index 1f6bc73..0000000 --- a/src/xatu/analysis.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::path::Path; - -use crate::diagnostic::{parse_error_diagnostics, Diagnostic}; -use crate::edit::Edit; -use crate::rules::{self, RuleContext}; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct AnalysisOutcome { - pub(crate) diagnostics: Vec, - pub(crate) edits: Vec, -} - -pub(crate) fn analyze_source(path: &Path, source: &str) -> AnalysisOutcome { - let parse = rnix::Root::parse(source); - - if !parse.errors().is_empty() { - return AnalysisOutcome { - diagnostics: parse_error_diagnostics(path, source, parse.errors()), - edits: Vec::new(), - }; - } - - let root = parse.tree(); - let context = RuleContext { - source, - root: &root, - }; - let mut findings = Vec::new(); - - for rule in rules::all() { - rule.check(&context, &mut findings); - } - - let mut diagnostics = Vec::with_capacity(findings.len()); - let mut edits = Vec::new(); - - for finding in findings { - diagnostics.push(Diagnostic::at_offset( - path, - source, - finding.rule, - finding.message, - finding.offset, - )); - - if let Some(fix) = finding.fix { - edits.push(fix); - } - } - - AnalysisOutcome { diagnostics, edits } -} diff --git a/src/xatu/diagnostic.rs b/src/xatu/diagnostic.rs deleted file mode 100644 index 1bf4c7c..0000000 --- a/src/xatu/diagnostic.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::fmt; -use std::path::{Path, PathBuf}; - -const PARSE_ERROR_RULE: &str = "parse_error"; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Diagnostic { - pub path: PathBuf, - pub rule: &'static str, - pub message: String, - pub line: usize, - pub column: usize, -} - -impl Diagnostic { - pub(crate) fn at_offset( - path: &Path, - source: &str, - rule: &'static str, - message: String, - offset: usize, - ) -> Self { - let (line, column) = line_column(source, offset); - - Self { - path: path.to_path_buf(), - rule, - message, - line, - column, - } - } -} - -impl fmt::Display for Diagnostic { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - formatter, - "{}:{}:{}: {}: {}", - self.path.display(), - self.line, - self.column, - self.rule, - self.message - ) - } -} - -pub(crate) fn parse_error_diagnostics( - path: &Path, - source: &str, - errors: &[rnix::ParseError], -) -> Vec { - errors - .iter() - .map(|error| { - Diagnostic::at_offset( - path, - source, - PARSE_ERROR_RULE, - error.to_string(), - parse_error_offset(error, source.len()), - ) - }) - .collect() -} - -fn parse_error_offset(error: &rnix::ParseError, end_offset: usize) -> usize { - let range = match error { - rnix::ParseError::Unexpected(range) - | rnix::ParseError::UnexpectedExtra(range) - | rnix::ParseError::UnexpectedWanted(_, range, _) - | rnix::ParseError::UnexpectedDoubleBind(range) - | rnix::ParseError::DuplicatedArgs(range, _) => Some(range), - rnix::ParseError::UnexpectedEOF - | rnix::ParseError::UnexpectedEOFWanted(_) - | rnix::ParseError::RecursionLimitExceeded => None, - _ => None, - }; - - range.map_or(end_offset, |range| usize::from(range.start())) -} - -fn line_column(source: &str, offset: usize) -> (usize, usize) { - let prefix = &source[..offset.min(source.len())]; - let line = prefix.bytes().filter(|byte| *byte == b'\n').count() + 1; - let line_start = prefix.rfind('\n').map_or(0, |index| index + 1); - let column = prefix[line_start..].chars().count() + 1; - - (line, column) -} diff --git a/src/xatu/edit.rs b/src/xatu/edit.rs deleted file mode 100644 index a875a58..0000000 --- a/src/xatu/edit.rs +++ /dev/null @@ -1,80 +0,0 @@ -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct Edit { - start: usize, - end: usize, - replacement: String, -} - -impl Edit { - pub(crate) fn delete_statement(source: &str, start: usize, end: usize) -> Self { - Self { - start: deletion_start(source, start, end), - end: deletion_end(source, start, end), - replacement: String::new(), - } - } -} - -pub(crate) fn apply_edits(source: &str, edits: &[Edit]) -> String { - let mut fixed = source.to_owned(); - let mut edits = non_overlapping_edits(edits); - - for edit in edits.drain(..).rev() { - fixed.replace_range(edit.start..edit.end, &edit.replacement); - } - - fixed -} - -fn non_overlapping_edits(edits: &[Edit]) -> Vec { - let mut sorted = edits.to_vec(); - sorted.sort_by_key(|edit| (edit.start, edit.end)); - - let mut filtered = Vec::new(); - let mut previous_end = 0; - - for edit in sorted { - if edit.start < previous_end { - continue; - } - - previous_end = edit.end; - filtered.push(edit); - } - - filtered -} - -fn deletion_start(source: &str, start: usize, end: usize) -> usize { - let line_start = source[..start].rfind('\n').map_or(0, |index| index + 1); - - if source[line_start..start].trim().is_empty() - && trailing_line_text(source, end).trim().is_empty() - { - line_start - } else { - start - } -} - -fn deletion_end(source: &str, start: usize, end: usize) -> usize { - let line_start = source[..start].rfind('\n').map_or(0, |index| index + 1); - - if source[line_start..start].trim().is_empty() - && trailing_line_text(source, end).trim().is_empty() - { - source[end..] - .find('\n') - .map_or(end, |relative| end + relative + 1) - } else { - end - } -} - -fn trailing_line_text(source: &str, end: usize) -> &str { - let line_end = source[end..] - .find('\n') - .map_or(source.len(), |relative| end + relative); - - &source[end..line_end] -} diff --git a/src/xatu/empty_inherit.rs b/src/xatu/empty_inherit.rs deleted file mode 100644 index f260b43..0000000 --- a/src/xatu/empty_inherit.rs +++ /dev/null @@ -1,37 +0,0 @@ -use rnix::ast; -use rowan::ast::AstNode; - -use crate::edit::Edit; -use crate::rules::{Finding, Rule, RuleContext}; - -pub(crate) static RULE: EmptyInherit = EmptyInherit; - -pub(crate) struct EmptyInherit; - -impl Rule for EmptyInherit { - fn name(&self) -> &'static str { - "empty_inherit" - } - - fn check(&self, context: &RuleContext<'_>, findings: &mut Vec) { - for inherit in context - .root - .syntax() - .descendants() - .filter_map(ast::Inherit::cast) - { - if inherit.attrs().next().is_some() { - continue; - } - - let range = inherit.syntax().text_range(); - let start = usize::from(range.start()); - let end = usize::from(range.end()); - let fix = Edit::delete_statement(context.source, start, end); - - findings.push( - Finding::new(self.name(), "remove empty inherit statement", start).with_fix(fix), - ); - } - } -} diff --git a/src/xatu/file_set.rs b/src/xatu/file_set.rs deleted file mode 100644 index 98053ff..0000000 --- a/src/xatu/file_set.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::ffi::OsStr; -use std::fs; -use std::io; -use std::path::{Path, PathBuf}; - -pub(crate) fn collect_nix_files(path: &Path) -> io::Result> { - let metadata = fs::metadata(path)?; - - if metadata.is_file() { - return Ok(if is_nix_file(path) { - vec![path.to_path_buf()] - } else { - Vec::new() - }); - } - - if !metadata.is_dir() { - return Ok(Vec::new()); - } - - let mut files = Vec::new(); - collect_nix_files_from_dir(path, &mut files)?; - files.sort(); - Ok(files) -} - -fn collect_nix_files_from_dir(path: &Path, files: &mut Vec) -> io::Result<()> { - for entry in fs::read_dir(path)? { - let entry = entry?; - let path = entry.path(); - let metadata = entry.metadata()?; - - if metadata.is_dir() { - collect_nix_files_from_dir(&path, files)?; - } else if metadata.is_file() && is_nix_file(&path) { - files.push(path); - } - } - - Ok(()) -} - -fn is_nix_file(path: &Path) -> bool { - path.extension() == Some(OsStr::new("nix")) -} diff --git a/src/xatu/lib.rs b/src/xatu/lib.rs deleted file mode 100644 index f6260cd..0000000 --- a/src/xatu/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::path::Path; - -mod analysis; -mod diagnostic; -mod edit; -mod empty_inherit; -mod file_set; -mod rules; -mod runner; - -pub use diagnostic::Diagnostic; -pub use runner::{run, RunMode, RunStatus}; - -pub fn diagnostics_for_source(path: &Path, source: &str) -> Vec { - analysis::analyze_source(path, source).diagnostics -} - -pub fn fix_source(source: &str) -> String { - let outcome = analysis::analyze_source(Path::new(""), source); - edit::apply_edits(source, &outcome.edits) -} diff --git a/src/xatu/lib_test.rs b/src/xatu/lib_test.rs deleted file mode 100644 index 58f526d..0000000 --- a/src/xatu/lib_test.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::path::Path; - -use xatu::{Diagnostic, RunMode, RunStatus}; - -#[test] -fn empty_inherit_reports_diagnostic() { - let diagnostics = xatu::diagnostics_for_source(Path::new("input.nix"), "{ inherit; value = 1; }"); - - assert_eq!( - diagnostics, - vec![Diagnostic { - path: Path::new("input.nix").to_path_buf(), - rule: "empty_inherit", - message: "remove empty inherit statement".to_owned(), - line: 1, - column: 3, - }] - ); -} - -#[test] -fn non_empty_inherit_reports_clean() { - let diagnostics = xatu::diagnostics_for_source(Path::new("input.nix"), "{ inherit value; }"); - - assert!(diagnostics.is_empty()); -} - -#[test] -fn fix_removes_standalone_empty_inherit_line() { - let fixed = xatu::fix_source("{\n inherit;\n value = 1;\n}\n"); - - assert_eq!(fixed, "{\n value = 1;\n}\n"); -} - -#[test] -fn fix_removes_inline_empty_inherit_statement() { - let fixed = xatu::fix_source("{ inherit; value = 1; }\n"); - - assert_eq!(fixed, "{ value = 1; }\n"); -} - -#[test] -fn parse_errors_skip_empty_inherit_rule() { - let diagnostics = xatu::diagnostics_for_source(Path::new("input.nix"), "{ inherit;\n"); - - assert_eq!(diagnostics.len(), 1); - assert_eq!(diagnostics[0].rule, "parse_error"); -} - -#[test] -fn run_returns_findings_when_empty_inherit_exists() { - let workspace = TestWorkspace::new(); - let file = workspace.write("input.nix", "{ inherit; }\n"); - let mut stdout = Vec::new(); - let mut stderr = Vec::new(); - - let status = xatu::run(&[file], RunMode::Check, &mut stdout, &mut stderr).unwrap(); - - assert_eq!(status, RunStatus::Findings); - assert!(stderr.is_empty()); - assert!(String::from_utf8(stdout).unwrap().contains("empty_inherit")); -} - -#[test] -fn run_fix_updates_file() { - let workspace = TestWorkspace::new(); - let file = workspace.write("input.nix", "{\n inherit;\n value = 1;\n}\n"); - let mut stdout = Vec::new(); - let mut stderr = Vec::new(); - - let status = xatu::run(&[file.clone()], RunMode::Fix, &mut stdout, &mut stderr).unwrap(); - - assert_eq!(status, RunStatus::Findings); - assert!(stderr.is_empty()); - assert_eq!( - std::fs::read_to_string(file).unwrap(), - "{\n value = 1;\n}\n" - ); -} - -struct TestWorkspace { - root: std::path::PathBuf, -} - -impl TestWorkspace { - fn new() -> Self { - static NEXT_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); - let id = NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let root = std::env::temp_dir().join(format!("xatu-test-{id}")); - - if root.exists() { - std::fs::remove_dir_all(&root).unwrap(); - } - - std::fs::create_dir(&root).unwrap(); - - Self { root } - } - - fn write(&self, name: &str, contents: &str) -> std::path::PathBuf { - let path = self.root.join(name); - std::fs::write(&path, contents).unwrap(); - path - } -} - -impl Drop for TestWorkspace { - fn drop(&mut self) { - std::fs::remove_dir_all(&self.root).unwrap(); - } -} diff --git a/src/xatu/main.rs b/src/xatu/main.rs deleted file mode 100644 index b8fd84e..0000000 --- a/src/xatu/main.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::path::PathBuf; -use std::process::ExitCode; - -fn main() -> ExitCode { - let mut mode = xatu::RunMode::Check; - let mut paths = Vec::new(); - - for argument in std::env::args_os().skip(1) { - if argument == "--fix" { - mode = xatu::RunMode::Fix; - } else { - paths.push(PathBuf::from(argument)); - } - } - - let mut stdout = std::io::stdout().lock(); - let mut stderr = std::io::stderr().lock(); - - match xatu::run(&paths, mode, &mut stdout, &mut stderr) { - Ok(status) => ExitCode::from(status.code()), - Err(error) => { - eprintln!("error: {error}"); - ExitCode::from(2) - } - } -} diff --git a/src/xatu/rules.rs b/src/xatu/rules.rs deleted file mode 100644 index 542f429..0000000 --- a/src/xatu/rules.rs +++ /dev/null @@ -1,43 +0,0 @@ -use rnix::Root; - -use crate::edit::Edit; - -pub(crate) struct RuleContext<'a> { - pub(crate) source: &'a str, - pub(crate) root: &'a Root, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct Finding { - pub(crate) rule: &'static str, - pub(crate) message: String, - pub(crate) offset: usize, - pub(crate) fix: Option, -} - -impl Finding { - pub(crate) fn new(rule: &'static str, message: impl Into, offset: usize) -> Self { - Self { - rule, - message: message.into(), - offset, - fix: None, - } - } - - pub(crate) fn with_fix(mut self, fix: Edit) -> Self { - self.fix = Some(fix); - self - } -} - -pub(crate) trait Rule: Sync { - fn name(&self) -> &'static str; - fn check(&self, context: &RuleContext<'_>, findings: &mut Vec); -} - -static RULES: [&'static dyn Rule; 1] = [&crate::empty_inherit::RULE]; - -pub(crate) fn all() -> &'static [&'static dyn Rule] { - &RULES -} diff --git a/src/xatu/runner.rs b/src/xatu/runner.rs deleted file mode 100644 index 600a31f..0000000 --- a/src/xatu/runner.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::fs; -use std::io::{self, Write}; -use std::path::PathBuf; - -use crate::analysis::analyze_source; -use crate::edit::apply_edits; -use crate::file_set::collect_nix_files; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RunMode { - Check, - Fix, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RunStatus { - Clean, - Findings, -} - -impl RunStatus { - pub fn code(self) -> u8 { - match self { - RunStatus::Clean => 0, - RunStatus::Findings => 1, - } - } -} - -pub fn run( - paths: &[PathBuf], - mode: RunMode, - stdout: &mut impl Write, - stderr: &mut impl Write, -) -> io::Result { - if paths.is_empty() { - writeln!(stderr, "error: provide at least one file or directory")?; - return Ok(RunStatus::Findings); - } - - let mut status = RunStatus::Clean; - - for input in paths { - let files = match collect_nix_files(input) { - Ok(files) => files, - Err(error) => { - writeln!(stderr, "error: {error}")?; - status = RunStatus::Findings; - continue; - } - }; - - for file in files { - let source = match fs::read_to_string(&file) { - Ok(source) => source, - Err(error) => { - writeln!(stderr, "error: {}: {error}", file.display())?; - status = RunStatus::Findings; - continue; - } - }; - - let outcome = analyze_source(&file, &source); - - for diagnostic in &outcome.diagnostics { - writeln!(stdout, "{diagnostic}")?; - } - - if !outcome.diagnostics.is_empty() { - status = RunStatus::Findings; - } - - if mode == RunMode::Fix && !outcome.edits.is_empty() { - let fixed = apply_edits(&source, &outcome.edits); - fs::write(&file, fixed)?; - } - } - } - - Ok(status) -}