From f62c73a97286a49c228cd13c32ed37340afd2c1d Mon Sep 17 00:00:00 2001 From: Josh Mcguigan Date: Mon, 13 Apr 2020 05:44:35 -0700 Subject: [PATCH] add diagnostics subcommand to rust-analyzer CLI --- crates/rust-analyzer/src/bin/args.rs | 34 ++++++++++ crates/rust-analyzer/src/bin/main.rs | 4 ++ crates/rust-analyzer/src/cli.rs | 8 ++- crates/rust-analyzer/src/cli/diagnostics.rs | 74 +++++++++++++++++++++ 4 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 crates/rust-analyzer/src/cli/diagnostics.rs diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs index 3cf394bb41f..25367df45bb 100644 --- a/crates/rust-analyzer/src/bin/args.rs +++ b/crates/rust-analyzer/src/bin/args.rs @@ -35,6 +35,10 @@ pub(crate) enum Command { what: BenchWhat, load_output_dirs: bool, }, + Diagnostics { + path: PathBuf, + load_output_dirs: bool, + }, RunServer, Version, } @@ -209,6 +213,36 @@ pub(crate) fn parse() -> Result> { let load_output_dirs = matches.contains("--load-output-dirs"); Command::Bench { path, what, load_output_dirs } } + "diagnostics" => { + if matches.contains(["-h", "--help"]) { + eprintln!( + "\ +ra-cli-diagnostics + +USAGE: + rust-analyzer diagnostics [FLAGS] [PATH] + +FLAGS: + -h, --help Prints help information + --load-output-dirs Load OUT_DIR values by running `cargo check` before analysis + +ARGS: + " + ); + return Ok(Err(HelpPrinted)); + } + + let load_output_dirs = matches.contains("--load-output-dirs"); + let path = { + let mut trailing = matches.free()?; + if trailing.len() != 1 { + bail!("Invalid flags"); + } + trailing.pop().unwrap().into() + }; + + Command::Diagnostics { path, load_output_dirs } + } _ => { eprintln!( "\ diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index 608f4f67b2c..4edd617ee35 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -39,6 +39,10 @@ fn main() -> Result<()> { cli::analysis_bench(args.verbosity, path.as_ref(), what, load_output_dirs)? } + args::Command::Diagnostics { path, load_output_dirs } => { + cli::diagnostics(path.as_ref(), load_output_dirs)? + } + args::Command::RunServer => run_server()?, args::Command::Version => println!("rust-analyzer {}", env!("REV")), } diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs index c9738d1010b..a865a7c7e23 100644 --- a/crates/rust-analyzer/src/cli.rs +++ b/crates/rust-analyzer/src/cli.rs @@ -3,6 +3,7 @@ mod load_cargo; mod analysis_stats; mod analysis_bench; +mod diagnostics; mod progress_report; use std::io::Read; @@ -12,6 +13,10 @@ use ra_prof::profile; use ra_syntax::{AstNode, SourceFile}; +pub use analysis_bench::{analysis_bench, BenchWhat, Position}; +pub use analysis_stats::analysis_stats; +pub use diagnostics::diagnostics; + #[derive(Clone, Copy)] pub enum Verbosity { Spammy, @@ -60,9 +65,6 @@ pub fn highlight(rainbow: bool) -> Result<()> { Ok(()) } -pub use analysis_bench::{analysis_bench, BenchWhat, Position}; -pub use analysis_stats::analysis_stats; - fn file() -> Result { let text = read_stdin()?; Ok(SourceFile::parse(&text).tree()) diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs new file mode 100644 index 00000000000..f5aab89a24b --- /dev/null +++ b/crates/rust-analyzer/src/cli/diagnostics.rs @@ -0,0 +1,74 @@ +//! Analyze all files in project for diagnostics. Exits with a non-zero status +//! code if any errors are found. + +use anyhow::anyhow; +use ra_db::{SourceDatabaseExt, SourceRootId}; +use ra_ide::{Analysis, Severity}; +use std::{collections::HashSet, path::Path}; + +use crate::cli::{load_cargo::load_cargo, Result}; +use hir::{db::HirDatabase, Crate, Module}; + +pub fn diagnostics(path: &Path, load_output_dirs: bool) -> Result<()> { + let (host, roots) = load_cargo(path, load_output_dirs)?; + let db = host.raw_database(); + let analysis = host.analysis(); + let members = roots + .into_iter() + .filter_map( + |(source_root_id, project_root)| { + if project_root.is_member() { + Some(source_root_id) + } else { + None + } + }, + ) + .collect::>(); + + let mut found_error = false; + let mut visited_modules = HashSet::new(); + for krate in Crate::all(db) { + let module = krate.root_module(db).expect("crate without root module"); + check_module(module, db, &mut visited_modules, &members, &analysis, &mut found_error); + } + + println!(); + println!("diagnostic scan complete"); + + if found_error { + println!(); + Err(anyhow!("diagnostic error detected")) + } else { + Ok(()) + } +} + +fn check_module( + module: Module, + db: &(impl HirDatabase + SourceDatabaseExt), + visited_modules: &mut HashSet, + members: &HashSet, + analysis: &Analysis, + found_error: &mut bool, +) { + let file_id = module.definition_source(db).file_id.original_file(db); + if !visited_modules.contains(&module) { + if members.contains(&db.file_source_root(file_id)) { + println!("processing: {}", db.file_relative_path(file_id)); + for diagnostic in analysis.diagnostics(file_id).unwrap() { + if matches!(diagnostic.severity, Severity::Error) { + *found_error = true; + } + + println!("{:?}", diagnostic); + } + } + + visited_modules.insert(module); + + for child_module in module.children(db) { + check_module(child_module, db, visited_modules, members, analysis, found_error); + } + } +}