From 081a704a8119936759bf3122179c6c1917a1504b Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Fri, 1 Mar 2024 11:56:23 -0800 Subject: [PATCH 1/3] Enable `nil diagnostics` for multiple files --- crates/nil/src/main.rs | 90 +++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/crates/nil/src/main.rs b/crates/nil/src/main.rs index eb52b45..f23110e 100644 --- a/crates/nil/src/main.rs +++ b/crates/nil/src/main.rs @@ -1,7 +1,13 @@ use anyhow::{Context, Result}; use argh::FromArgs; +use codespan_reporting::diagnostic::Severity; use codespan_reporting::term::termcolor::WriteColor; -use ide::{AnalysisHost, Severity}; +use ide::AnalysisHost; +use ide::Change; +use ide::FileId; +use ide::FileSet; +use ide::SourceRoot; +use ide::VfsPath; use std::io::IsTerminal; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -39,15 +45,15 @@ enum Subcommand { #[derive(Debug, FromArgs)] #[argh(subcommand, name = "diagnostics")] -/// Check and print diagnostics for a file. +/// Check and print diagnostics for Nix files. /// Exit with non-zero code if there are any diagnostics. (`1` for errors, `2` if only warnings) /// WARNING: The output format is for human and should not be relied on. struct DiagnosticsArgs { - /// nix file to check, or read from stdin for `-`. + /// nix files to check, or read from stdin for `-`. /// NB. You need `--` before `-` for paths starting with `-`, /// to disambiguous it from flags. #[argh(positional)] - path: PathBuf, + paths: Vec, } #[derive(Debug, FromArgs)] @@ -111,32 +117,12 @@ fn main() { } fn main_diagnostics(args: DiagnosticsArgs) { - use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; - - let ret = (|| -> Result> { - let path = &*args.path; - - let src = if path.as_os_str() == "-" { - io::read_to_string(io::stdin().lock()).context("Failed to read from stdin")? - } else { - fs::read_to_string(path).context("Failed to read file")? - }; - - let (analysis, file) = AnalysisHost::new_single_file(&src); - let diags = analysis - .snapshot() - .diagnostics(file) - .expect("No cancellation"); + let ret = diagnostics_for_files(args.paths); - let mut writer = StandardStream::stdout(ColorChoice::Auto); - emit_diagnostics(path, &src, &mut writer, &mut diags.iter().cloned())?; - - Ok(diags.iter().map(|diag| diag.severity()).max()) - })(); match ret { Ok(None) => process::exit(0), Ok(Some(max_severity)) => { - if max_severity > Severity::Warning { + if max_severity > ide::Severity::Warning { process::exit(1) } else { process::exit(0) @@ -149,6 +135,56 @@ fn main_diagnostics(args: DiagnosticsArgs) { } } +fn diagnostics_for_files(paths: Vec) -> Result> { + use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; + if paths.is_empty() { + unreachable!(); + } + + let mut sources = Vec::new(); + let mut file_set = FileSet::default(); + let mut change = Change::default(); + for (id, path) in paths.iter().enumerate() { + let file = FileId(id as u32); + + let src = if path.as_os_str() == "-" { + io::read_to_string(io::stdin().lock()).context("Failed to read from stdin")? + } else { + fs::read_to_string(path).context("Failed to read file")? + }; + + let src: Arc = src.into(); + sources.push((file, path, src.clone())); + + change.change_file(file, src); + file_set.insert(file, VfsPath::new(path)); + } + + change.set_roots(vec![SourceRoot::new_local(file_set, None)]); + + let mut analysis = AnalysisHost::new(); + analysis.apply_change(change); + + let snapshot = analysis.snapshot(); + + let mut writer = StandardStream::stdout(ColorChoice::Auto); + let mut max_severity = None; + for (id, path, src) in sources { + let diagnostics = snapshot.diagnostics(id).expect("No cancellation"); + emit_diagnostics(path, &src, &mut writer, &mut diagnostics.iter().cloned())?; + + max_severity = std::cmp::max( + max_severity, + diagnostics + .iter() + .map(|diagnostic| diagnostic.severity()) + .max(), + ); + } + + Ok(max_severity) +} + fn main_parse(args: ParseArgs) { use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; @@ -255,7 +291,7 @@ fn emit_diagnostics( writer: &mut dyn WriteColor, diags: &mut dyn Iterator, ) -> Result<()> { - use codespan_reporting::diagnostic::{Diagnostic, Label, Severity}; + use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::files::SimpleFiles; use codespan_reporting::term; From 96d47c8d8faf9c9fc19bab7dc5c88eba35b6bfba Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Fri, 1 Mar 2024 12:16:05 -0800 Subject: [PATCH 2/3] Error if no files are given --- crates/nil/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/nil/src/main.rs b/crates/nil/src/main.rs index f23110e..3c0a7b8 100644 --- a/crates/nil/src/main.rs +++ b/crates/nil/src/main.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use anyhow::{Context, Result}; use argh::FromArgs; use codespan_reporting::diagnostic::Severity; @@ -138,7 +139,7 @@ fn main_diagnostics(args: DiagnosticsArgs) { fn diagnostics_for_files(paths: Vec) -> Result> { use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; if paths.is_empty() { - unreachable!(); + return Err(anyhow!("No files given")); } let mut sources = Vec::new(); From 55ccc80b2543b94dbcace749fc0418ea99e6dd12 Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Fri, 1 Mar 2024 12:38:17 -0800 Subject: [PATCH 3/3] Discover all `.nix` files when running diagnostics --- Cargo.lock | 40 +++++++++++++++++++++++++++++ crates/nil/Cargo.toml | 1 + crates/nil/src/main.rs | 58 +++++++++++++++++++++++++++++++----------- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9147240..fe8f689 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,16 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "builtin" version = "0.0.0" @@ -436,6 +446,19 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + [[package]] name = "half" version = "1.8.2" @@ -507,6 +530,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.3", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -702,6 +741,7 @@ dependencies = [ "async-lsp", "codespan-reporting", "ide", + "ignore", "log", "lsp-types", "macro_rules_attribute", diff --git a/crates/nil/Cargo.toml b/crates/nil/Cargo.toml index 64a231f..200bfe7 100644 --- a/crates/nil/Cargo.toml +++ b/crates/nil/Cargo.toml @@ -11,6 +11,7 @@ argh = "0.1.10" async-lsp = { version = "0.2.0", features = ["tokio"] } codespan-reporting = "0.11.1" ide = { path = "../ide" } +ignore = "0.4.22" log = "0.4.17" lsp-types = "0.95.0" macro_rules_attribute = "0.2.0" diff --git a/crates/nil/src/main.rs b/crates/nil/src/main.rs index 3c0a7b8..8547ce4 100644 --- a/crates/nil/src/main.rs +++ b/crates/nil/src/main.rs @@ -1,4 +1,3 @@ -use anyhow::anyhow; use anyhow::{Context, Result}; use argh::FromArgs; use codespan_reporting::diagnostic::Severity; @@ -9,6 +8,8 @@ use ide::FileId; use ide::FileSet; use ide::SourceRoot; use ide::VfsPath; +use ignore::types::TypesBuilder; +use ignore::WalkBuilder; use std::io::IsTerminal; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -83,6 +84,8 @@ fn main() { return; } + setup_logger(); + if let Some(subcommand) = args.subcommand { return match subcommand { Subcommand::Diagnostics(args) => main_diagnostics(args), @@ -91,8 +94,6 @@ fn main() { }; } - setup_logger(); - if !args.stdio && (io::stdin().is_terminal() || io::stdout().is_terminal()) { // TODO: Make this a hard error. eprintln!( @@ -136,29 +137,56 @@ fn main_diagnostics(args: DiagnosticsArgs) { } } -fn diagnostics_for_files(paths: Vec) -> Result> { +fn diagnostics_for_files(mut roots: Vec) -> Result> { use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; - if paths.is_empty() { - return Err(anyhow!("No files given")); + let mut walk = if roots.is_empty() { + WalkBuilder::new(".") + } else { + WalkBuilder::new(roots.pop().expect("non_empty vec has an item")) + }; + + walk.types({ + let mut builder = TypesBuilder::new(); + builder + .add("nix", "*.nix") + .expect("parsing glob '*.nix' will never fail"); + builder.select("nix"); + builder.build().expect("building types will never fail") + }); + + for root in roots { + walk.add(root); } + let walk = walk.build(); + let mut sources = Vec::new(); let mut file_set = FileSet::default(); let mut change = Change::default(); - for (id, path) in paths.iter().enumerate() { - let file = FileId(id as u32); + for (id, entry) in walk.enumerate() { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + tracing::error!("{err}"); + continue; + } + }; - let src = if path.as_os_str() == "-" { + let file = FileId(id as u32); + let src = if entry.is_stdin() { io::read_to_string(io::stdin().lock()).context("Failed to read from stdin")? } else { - fs::read_to_string(path).context("Failed to read file")? + if entry.path().is_dir() { + continue; + } + fs::read_to_string(entry.path()).context("Failed to read file")? }; + let path = entry.into_path(); let src: Arc = src.into(); - sources.push((file, path, src.clone())); - - change.change_file(file, src); - file_set.insert(file, VfsPath::new(path)); + change.change_file(file, src.clone()); + file_set.insert(file, VfsPath::new(&path)); + sources.push((file, path, src)); } change.set_roots(vec![SourceRoot::new_local(file_set, None)]); @@ -172,7 +200,7 @@ fn diagnostics_for_files(paths: Vec) -> Result> { let mut max_severity = None; for (id, path, src) in sources { let diagnostics = snapshot.diagnostics(id).expect("No cancellation"); - emit_diagnostics(path, &src, &mut writer, &mut diagnostics.iter().cloned())?; + emit_diagnostics(&path, &src, &mut writer, &mut diagnostics.iter().cloned())?; max_severity = std::cmp::max( max_severity,