diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 9baebf6435..3801fce23a 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -25,7 +25,7 @@ use hir_ty::{ autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy, Canonical, InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor, }; -use ra_db::{CrateId, Edition, FileId}; +use ra_db::{CrateId, CrateName, Edition, FileId}; use ra_prof::profile; use ra_syntax::{ ast::{self, AttrsOwner, NameOwner}, @@ -91,6 +91,10 @@ impl Crate { db.crate_graph()[self.id].edition } + pub fn display_name(self, db: &dyn HirDatabase) -> Option { + db.crate_graph()[self.id].display_name.as_ref().cloned() + } + pub fn all(db: &dyn HirDatabase) -> Vec { db.crate_graph().iter().map(|id| Crate { id }).collect() } diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs index 3cf394bb41..f5981588ab 100644 --- a/crates/rust-analyzer/src/bin/args.rs +++ b/crates/rust-analyzer/src/bin/args.rs @@ -35,6 +35,13 @@ pub(crate) enum Command { what: BenchWhat, load_output_dirs: bool, }, + Diagnostics { + path: PathBuf, + load_output_dirs: bool, + /// Include files which are not modules. In rust-analyzer + /// this would include the parser test files. + all: bool, + }, RunServer, Version, } @@ -209,6 +216,38 @@ ARGS: 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 + --all Include all files rather than only modules + +ARGS: + " + ); + return Ok(Err(HelpPrinted)); + } + + let load_output_dirs = matches.contains("--load-output-dirs"); + let all = matches.contains("--all"); + let path = { + let mut trailing = matches.free()?; + if trailing.len() != 1 { + bail!("Invalid flags"); + } + trailing.pop().unwrap().into() + }; + + Command::Diagnostics { path, load_output_dirs, all } + } _ => { eprintln!( "\ diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index 608f4f67b2..7cfc44f01f 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, all } => { + cli::diagnostics(path.as_ref(), load_output_dirs, all)? + } + 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 c9738d1010..a865a7c7e2 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_ide::{file_structure, Analysis}; 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 0000000000..92664b415b --- /dev/null +++ b/crates/rust-analyzer/src/cli/diagnostics.rs @@ -0,0 +1,74 @@ +//! Analyze all modules in a project for diagnostics. Exits with a non-zero status +//! code if any errors are found. + +use anyhow::anyhow; +use ra_db::SourceDatabaseExt; +use ra_ide::Severity; +use std::{collections::HashSet, path::Path}; + +use crate::cli::{load_cargo::load_cargo, Result}; +use hir::Semantics; + +pub fn diagnostics(path: &Path, load_output_dirs: bool, all: bool) -> Result<()> { + let (host, roots) = load_cargo(path, load_output_dirs)?; + let db = host.raw_database(); + let analysis = host.analysis(); + let semantics = Semantics::new(db); + let members = roots + .into_iter() + .filter_map(|(source_root_id, project_root)| { + // filter out dependencies + if project_root.is_member() { + Some(source_root_id) + } else { + None + } + }) + .collect::>(); + + let mut found_error = false; + let mut visited_files = HashSet::new(); + for source_root_id in members { + for file_id in db.source_root(source_root_id).walk() { + // Filter out files which are not actually modules (unless `--all` flag is + // passed). In the rust-analyzer repository this filters out the parser test files. + if semantics.to_module_def(file_id).is_some() || all { + if !visited_files.contains(&file_id) { + let crate_name = if let Some(module) = semantics.to_module_def(file_id) { + if let Some(name) = module.krate().display_name(db) { + format!("{}", name) + } else { + String::from("unknown") + } + } else { + String::from("unknown") + }; + println!( + "processing crate: {}, module: {}", + crate_name, + 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_files.insert(file_id); + } + } + } + } + + println!(); + println!("diagnostic scan complete"); + + if found_error { + println!(); + Err(anyhow!("diagnostic error detected")) + } else { + Ok(()) + } +}