CLI for diagnostics

This commit is contained in:
oxalica 2023-01-28 23:48:56 +08:00
parent f1c2dbc857
commit dfd91e3b7e
4 changed files with 149 additions and 1 deletions

35
Cargo.lock generated
View file

@ -64,6 +64,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "countme"
version = "3.0.1"
@ -291,6 +301,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"argh",
"codespan-reporting",
"crossbeam-channel",
"ide",
"indexmap",
@ -650,6 +661,15 @@ dependencies = [
"rowan",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "text-size"
version = "1.1.0"
@ -768,6 +788,12 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "url"
version = "2.3.1"
@ -802,6 +828,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View file

@ -14,7 +14,10 @@ mod syntax_highlighting;
use crate::base::SourceDatabaseStorage;
use crate::def::DefDatabaseStorage;
use crate::ty::TyDatabaseStorage;
use crate::{Change, Diagnostic, FileId, FilePos, FileRange, WorkspaceEdit};
use crate::{
Change, Diagnostic, FileId, FilePos, FileRange, FileSet, SourceRoot, VfsPath, WorkspaceEdit,
};
use nix_interop::DEFAULT_IMPORT_FILE;
use salsa::{Database, Durability, ParallelDatabase};
use smol_str::SmolStr;
use std::fmt;
@ -73,6 +76,21 @@ impl AnalysisHost {
Self::default()
}
pub fn new_single_file(src: &str) -> (Self, FileId) {
let mut change = Change::default();
let file = FileId(0);
change.change_file(file, src.into());
let mut file_set = FileSet::default();
file_set.insert(
file,
VfsPath::new(format!("/{DEFAULT_IMPORT_FILE}")).unwrap(),
);
change.set_roots(vec![SourceRoot::new_local(file_set, Some(file))]);
let mut this = Self::new();
this.apply_change(change);
(this, file)
}
pub fn snapshot(&self) -> Analysis {
Analysis {
db: self.db.snapshot(),

View file

@ -8,6 +8,7 @@ rust-version = "1.66"
[dependencies]
anyhow = "1.0.68"
argh = "0.1.10"
codespan-reporting = "0.11.1"
crossbeam-channel = "0.5.6"
ide = { path = "../ide" }
indexmap = "1.9.1"

View file

@ -1,8 +1,11 @@
use anyhow::Context;
use argh::FromArgs;
use ide::AnalysisHost;
use lsp_server::Connection;
use std::path::PathBuf;
use std::sync::Arc;
use std::{env, fs, io, process};
use text_size::TextRange;
use tracing_subscriber::fmt::writer::BoxMakeWriter;
use tracing_subscriber::EnvFilter;
@ -17,6 +20,27 @@ struct Args {
/// print the version and exit
#[argh(switch)]
version: bool,
#[argh(subcommand)]
subcommand: Option<Subcommand>,
}
#[derive(Debug, FromArgs)]
#[argh(subcommand)]
enum Subcommand {
Diagnostics(DiagnosticsArgs),
}
#[derive(Debug, FromArgs)]
#[argh(subcommand, name = "diagnostics")]
/// Check and print diagnostics for a file.
/// Exit with non-zero code if there are any diagnostics.
/// WARNING: The output format is for human and should not be relied on.
struct DiagnosticsArgs {
/// nix file to check, or read from stdin for `-`.
/// NB. You need `--` before `-` for paths starting with `-`,
/// to disambiguous it from flags.
#[argh(positional)]
path: PathBuf,
}
fn main() {
@ -31,6 +55,12 @@ fn main() {
return;
}
if let Some(subcommand) = args.subcommand {
return match subcommand {
Subcommand::Diagnostics(args) => main_diagnostics(args),
};
}
setup_logger();
let (conn, io_threads) = Connection::stdio();
@ -44,6 +74,70 @@ fn main() {
}
}
fn main_diagnostics(args: DiagnosticsArgs) {
use codespan_reporting::diagnostic::{Diagnostic, Label, Severity};
use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
let ret = (|| -> anyhow::Result<bool> {
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");
if diags.is_empty() {
return Ok(true);
}
let mut files = SimpleFiles::new();
let cr_file = files.add(path.display().to_string(), src);
let writer = StandardStream::stdout(ColorChoice::Auto);
let config = codespan_reporting::term::Config::default();
for diag in diags {
let severity = match diag.severity() {
ide::Severity::IncompleteSyntax | ide::Severity::Error => Severity::Error,
ide::Severity::Warning => Severity::Warning,
};
let to_range = |range: TextRange| usize::from(range.start())..usize::from(range.end());
let labels = std::iter::once(Label::primary(cr_file, to_range(diag.range)))
.chain(diag.notes.iter().map(|(frange, note)| {
assert_eq!(frange.file_id, file, "Diagnostics are local");
Label::secondary(cr_file, to_range(frange.range)).with_message(note)
}))
.collect();
let diag = Diagnostic::new(severity)
.with_code(diag.code())
.with_message(diag.message())
.with_labels(labels);
term::emit(&mut writer.lock(), &config, &files, &diag)?;
}
Ok(false)
})();
match ret {
Ok(true) => {}
Ok(false) => process::exit(1),
Err(err) => {
eprintln!("{err:#}");
process::exit(1);
}
}
}
fn setup_logger() {
let file = env::var_os(LOG_PATH_ENV).and_then(|path| {
let path = PathBuf::from(path);