mod ast; mod file_store; mod parser; mod rules; pub use file_store::{FileId, FileStore, SourceFile}; pub use rules::{Diagnostic, Finding}; #[derive(Debug)] pub struct Report { pub files: FileStore, pub diagnostics: Vec, } impl Report { pub fn is_clean(&self) -> bool { self.diagnostics.is_empty() } } impl std::fmt::Display for Report { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for diagnostic in &self.diagnostics { let file = self.files.get(diagnostic.file_id); writeln!(formatter, "{}:{diagnostic}", file.path.display())?; } Ok(()) } } /// Contains parser output without lint diagnostics. #[derive(Debug, serde::Serialize)] pub struct DebugReport { files: Vec, } impl DebugReport { pub fn has_parse_errors(&self) -> bool { self.files.iter().any(|file| !file.parsed.errors.is_empty()) } } #[derive(Debug, serde::Serialize)] struct DebugFile { path: std::path::PathBuf, parsed: ast::ParsedFile, } /// Collects parser output without running lint rules. pub fn debug(mut files: Vec) -> DebugReport { files.sort_by(|left, right| left.path.cmp(&right.path)); files.dedup_by(|left, right| left.path == right.path); let mut store = FileStore::default(); let files = files .into_iter() .map(|file| { let file_id = store.add(file); let file = store.get(file_id); DebugFile { path: file.path.clone(), parsed: parser::parse_file(file_id, &file.source), } }) .collect(); DebugReport { files } } /// Analyzes source files and returns the diagnostics Xatu finds. pub fn future_sight(files: Vec) -> status::Result { let mut store = FileStore::default(); let mut diagnostics = Vec::new(); for file in files { let file_id = store.add(file); let file = store.get(file_id); let parsed = parser::parse_file(file_id, &file.source); if !parsed.errors.is_empty() { return Err(status::Status::failed_precondition(format!( "failed to parse {}", file.path.display() ))); } diagnostics.extend(rules::run_rules(&parsed)); } Ok(Report { files: store, diagnostics, }) }