mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 13:33:31 +00:00
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 <joshmcg88@gmail.com>
This commit is contained in:
commit
b495e56b0d
5 changed files with 127 additions and 4 deletions
|
@ -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<CrateName> {
|
||||
db.crate_graph()[self.id].display_name.as_ref().cloned()
|
||||
}
|
||||
|
||||
pub fn all(db: &dyn HirDatabase) -> Vec<Crate> {
|
||||
db.crate_graph().iter().map(|id| Crate { id }).collect()
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
<PATH>"
|
||||
);
|
||||
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!(
|
||||
"\
|
||||
|
|
|
@ -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")),
|
||||
}
|
||||
|
|
|
@ -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<SourceFile> {
|
||||
let text = read_stdin()?;
|
||||
Ok(SourceFile::parse(&text).tree())
|
||||
|
|
74
crates/rust-analyzer/src/cli/diagnostics.rs
Normal file
74
crates/rust-analyzer/src/cli/diagnostics.rs
Normal file
|
@ -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::<HashSet<_>>();
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue