aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-04-15 00:35:50 +0100
committerGitHub <[email protected]>2020-04-15 00:35:50 +0100
commitb495e56b0d3f7a72494d002a69440563e8394574 (patch)
treee3d061a29bfdff9ac145d5e94b7f6ffc27080398 /crates
parentc82e7696e6f86cc0843c5aab9f09b5d6dd0d4bac (diff)
parent6be972770e75db5ec9f9ae2504786a9d7e957e4a (diff)
Merge #3971
3971: add diagnostics subcommand to rust-analyzer CLI r=JoshMcguigan a=JoshMcguigan This PR adds a `diagnostics` subcommand to the rust-analyzer CLI. The intent is to detect all diagnostics on a workspace. It returns a non-zero status code if any error diagnostics are detected. Ideally I'd like to run this in CI against the rust analyzer project as a guard against false positives. ``` $ cargo run --release --bin rust-analyzer -- diagnostics . ``` Questions for reviewers: 1. Is this the proper way to get all diagnostics for a workspace? It seems there are at least a few ways this can be done, and I'm not sure if this is the most appropriate mechanism to do this. 2. It currently prints out the relative file path as it is collecting diagnostics, but it doesn't print the crate name. Since the file name is relative to the crate there can be repeated names, so it would be nice to print some identifier for the crate as well, but it wasn't clear to me how best to accomplish this. Co-authored-by: Josh Mcguigan <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_hir/src/code_model.rs6
-rw-r--r--crates/rust-analyzer/src/bin/args.rs39
-rw-r--r--crates/rust-analyzer/src/bin/main.rs4
-rw-r--r--crates/rust-analyzer/src/cli.rs8
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs74
5 files changed, 127 insertions, 4 deletions
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index 9baebf643..3801fce23 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -25,7 +25,7 @@ use hir_ty::{
25 autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy, 25 autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy,
26 Canonical, InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor, 26 Canonical, InEnvironment, Substs, TraitEnvironment, Ty, TyDefId, TypeCtor,
27}; 27};
28use ra_db::{CrateId, Edition, FileId}; 28use ra_db::{CrateId, CrateName, Edition, FileId};
29use ra_prof::profile; 29use ra_prof::profile;
30use ra_syntax::{ 30use ra_syntax::{
31 ast::{self, AttrsOwner, NameOwner}, 31 ast::{self, AttrsOwner, NameOwner},
@@ -91,6 +91,10 @@ impl Crate {
91 db.crate_graph()[self.id].edition 91 db.crate_graph()[self.id].edition
92 } 92 }
93 93
94 pub fn display_name(self, db: &dyn HirDatabase) -> Option<CrateName> {
95 db.crate_graph()[self.id].display_name.as_ref().cloned()
96 }
97
94 pub fn all(db: &dyn HirDatabase) -> Vec<Crate> { 98 pub fn all(db: &dyn HirDatabase) -> Vec<Crate> {
95 db.crate_graph().iter().map(|id| Crate { id }).collect() 99 db.crate_graph().iter().map(|id| Crate { id }).collect()
96 } 100 }
diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs
index 3cf394bb4..f5981588a 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 {
35 what: BenchWhat, 35 what: BenchWhat,
36 load_output_dirs: bool, 36 load_output_dirs: bool,
37 }, 37 },
38 Diagnostics {
39 path: PathBuf,
40 load_output_dirs: bool,
41 /// Include files which are not modules. In rust-analyzer
42 /// this would include the parser test files.
43 all: bool,
44 },
38 RunServer, 45 RunServer,
39 Version, 46 Version,
40} 47}
@@ -209,6 +216,38 @@ ARGS:
209 let load_output_dirs = matches.contains("--load-output-dirs"); 216 let load_output_dirs = matches.contains("--load-output-dirs");
210 Command::Bench { path, what, load_output_dirs } 217 Command::Bench { path, what, load_output_dirs }
211 } 218 }
219 "diagnostics" => {
220 if matches.contains(["-h", "--help"]) {
221 eprintln!(
222 "\
223ra-cli-diagnostics
224
225USAGE:
226 rust-analyzer diagnostics [FLAGS] [PATH]
227
228FLAGS:
229 -h, --help Prints help information
230 --load-output-dirs Load OUT_DIR values by running `cargo check` before analysis
231 --all Include all files rather than only modules
232
233ARGS:
234 <PATH>"
235 );
236 return Ok(Err(HelpPrinted));
237 }
238
239 let load_output_dirs = matches.contains("--load-output-dirs");
240 let all = matches.contains("--all");
241 let path = {
242 let mut trailing = matches.free()?;
243 if trailing.len() != 1 {
244 bail!("Invalid flags");
245 }
246 trailing.pop().unwrap().into()
247 };
248
249 Command::Diagnostics { path, load_output_dirs, all }
250 }
212 _ => { 251 _ => {
213 eprintln!( 252 eprintln!(
214 "\ 253 "\
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 608f4f67b..7cfc44f01 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -39,6 +39,10 @@ fn main() -> Result<()> {
39 cli::analysis_bench(args.verbosity, path.as_ref(), what, load_output_dirs)? 39 cli::analysis_bench(args.verbosity, path.as_ref(), what, load_output_dirs)?
40 } 40 }
41 41
42 args::Command::Diagnostics { path, load_output_dirs, all } => {
43 cli::diagnostics(path.as_ref(), load_output_dirs, all)?
44 }
45
42 args::Command::RunServer => run_server()?, 46 args::Command::RunServer => run_server()?,
43 args::Command::Version => println!("rust-analyzer {}", env!("REV")), 47 args::Command::Version => println!("rust-analyzer {}", env!("REV")),
44 } 48 }
diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs
index c9738d101..a865a7c7e 100644
--- a/crates/rust-analyzer/src/cli.rs
+++ b/crates/rust-analyzer/src/cli.rs
@@ -3,6 +3,7 @@
3mod load_cargo; 3mod load_cargo;
4mod analysis_stats; 4mod analysis_stats;
5mod analysis_bench; 5mod analysis_bench;
6mod diagnostics;
6mod progress_report; 7mod progress_report;
7 8
8use std::io::Read; 9use std::io::Read;
@@ -12,6 +13,10 @@ use ra_ide::{file_structure, Analysis};
12use ra_prof::profile; 13use ra_prof::profile;
13use ra_syntax::{AstNode, SourceFile}; 14use ra_syntax::{AstNode, SourceFile};
14 15
16pub use analysis_bench::{analysis_bench, BenchWhat, Position};
17pub use analysis_stats::analysis_stats;
18pub use diagnostics::diagnostics;
19
15#[derive(Clone, Copy)] 20#[derive(Clone, Copy)]
16pub enum Verbosity { 21pub enum Verbosity {
17 Spammy, 22 Spammy,
@@ -60,9 +65,6 @@ pub fn highlight(rainbow: bool) -> Result<()> {
60 Ok(()) 65 Ok(())
61} 66}
62 67
63pub use analysis_bench::{analysis_bench, BenchWhat, Position};
64pub use analysis_stats::analysis_stats;
65
66fn file() -> Result<SourceFile> { 68fn file() -> Result<SourceFile> {
67 let text = read_stdin()?; 69 let text = read_stdin()?;
68 Ok(SourceFile::parse(&text).tree()) 70 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 000000000..92664b415
--- /dev/null
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -0,0 +1,74 @@
1//! Analyze all modules in a project for diagnostics. Exits with a non-zero status
2//! code if any errors are found.
3
4use anyhow::anyhow;
5use ra_db::SourceDatabaseExt;
6use ra_ide::Severity;
7use std::{collections::HashSet, path::Path};
8
9use crate::cli::{load_cargo::load_cargo, Result};
10use hir::Semantics;
11
12pub fn diagnostics(path: &Path, load_output_dirs: bool, all: bool) -> Result<()> {
13 let (host, roots) = load_cargo(path, load_output_dirs)?;
14 let db = host.raw_database();
15 let analysis = host.analysis();
16 let semantics = Semantics::new(db);
17 let members = roots
18 .into_iter()
19 .filter_map(|(source_root_id, project_root)| {
20 // filter out dependencies
21 if project_root.is_member() {
22 Some(source_root_id)
23 } else {
24 None
25 }
26 })
27 .collect::<HashSet<_>>();
28
29 let mut found_error = false;
30 let mut visited_files = HashSet::new();
31 for source_root_id in members {
32 for file_id in db.source_root(source_root_id).walk() {
33 // Filter out files which are not actually modules (unless `--all` flag is
34 // passed). In the rust-analyzer repository this filters out the parser test files.
35 if semantics.to_module_def(file_id).is_some() || all {
36 if !visited_files.contains(&file_id) {
37 let crate_name = if let Some(module) = semantics.to_module_def(file_id) {
38 if let Some(name) = module.krate().display_name(db) {
39 format!("{}", name)
40 } else {
41 String::from("unknown")
42 }
43 } else {
44 String::from("unknown")
45 };
46 println!(
47 "processing crate: {}, module: {}",
48 crate_name,
49 db.file_relative_path(file_id)
50 );
51 for diagnostic in analysis.diagnostics(file_id).unwrap() {
52 if matches!(diagnostic.severity, Severity::Error) {
53 found_error = true;
54 }
55
56 println!("{:?}", diagnostic);
57 }
58
59 visited_files.insert(file_id);
60 }
61 }
62 }
63 }
64
65 println!();
66 println!("diagnostic scan complete");
67
68 if found_error {
69 println!();
70 Err(anyhow!("diagnostic error detected"))
71 } else {
72 Ok(())
73 }
74}