ra_ap_rust_analyzer/cli/
diagnostics.rs

1//! Analyze all modules in a project for diagnostics. Exits with a non-zero
2//! status code if any errors are found.
3
4use project_model::{CargoConfig, RustLibSource};
5use rustc_hash::FxHashSet;
6
7use hir::{db::HirDatabase, sym, Crate, HirFileIdExt, Module};
8use ide::{AnalysisHost, AssistResolveStrategy, Diagnostic, DiagnosticsConfig, Severity};
9use ide_db::{base_db::SourceRootDatabase, LineIndexDatabase};
10use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
11
12use crate::cli::flags;
13
14impl flags::Diagnostics {
15    pub fn run(self) -> anyhow::Result<()> {
16        const STACK_SIZE: usize = 1024 * 1024 * 8;
17
18        let handle = stdx::thread::Builder::new(stdx::thread::ThreadIntent::LatencySensitive)
19            .name("BIG_STACK_THREAD".into())
20            .stack_size(STACK_SIZE)
21            .spawn(|| self.run_())
22            .unwrap();
23
24        handle.join()
25    }
26    fn run_(self) -> anyhow::Result<()> {
27        let cargo_config = CargoConfig {
28            sysroot: Some(RustLibSource::Discover),
29            all_targets: true,
30            ..Default::default()
31        };
32        let with_proc_macro_server = if let Some(p) = &self.proc_macro_srv {
33            let path = vfs::AbsPathBuf::assert_utf8(std::env::current_dir()?.join(p));
34            ProcMacroServerChoice::Explicit(path)
35        } else {
36            ProcMacroServerChoice::Sysroot
37        };
38        let load_cargo_config = LoadCargoConfig {
39            load_out_dirs_from_check: !self.disable_build_scripts,
40            with_proc_macro_server,
41            prefill_caches: false,
42        };
43        let (db, _vfs, _proc_macro) =
44            load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
45        let host = AnalysisHost::with_database(db);
46        let db = host.raw_database();
47        let analysis = host.analysis();
48
49        let mut found_error = false;
50        let mut visited_files = FxHashSet::default();
51
52        let work = all_modules(db).into_iter().filter(|module| {
53            let file_id = module.definition_source_file_id(db).original_file(db);
54            let source_root = db.file_source_root(file_id.into());
55            let source_root = db.source_root(source_root);
56            !source_root.is_library
57        });
58
59        for module in work {
60            let file_id = module.definition_source_file_id(db).original_file(db);
61            if !visited_files.contains(&file_id) {
62                let crate_name =
63                    module.krate().display_name(db).as_deref().unwrap_or(&sym::unknown).to_owned();
64                println!(
65                    "processing crate: {crate_name}, module: {}",
66                    _vfs.file_path(file_id.into())
67                );
68                for diagnostic in analysis
69                    .full_diagnostics(
70                        &DiagnosticsConfig::test_sample(),
71                        AssistResolveStrategy::None,
72                        file_id.into(),
73                    )
74                    .unwrap()
75                {
76                    if matches!(diagnostic.severity, Severity::Error) {
77                        found_error = true;
78                    }
79
80                    let Diagnostic { code, message, range, severity, .. } = diagnostic;
81                    let line_index = db.line_index(range.file_id);
82                    let start = line_index.line_col(range.range.start());
83                    let end = line_index.line_col(range.range.end());
84                    println!("{severity:?} {code:?} from {start:?} to {end:?}: {message}");
85                }
86
87                visited_files.insert(file_id);
88            }
89        }
90
91        println!();
92        println!("diagnostic scan complete");
93
94        if found_error {
95            println!();
96            anyhow::bail!("diagnostic error detected")
97        }
98
99        Ok(())
100    }
101}
102
103fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
104    let mut worklist: Vec<_> =
105        Crate::all(db).into_iter().map(|krate| krate.root_module()).collect();
106    let mut modules = Vec::new();
107
108    while let Some(module) = worklist.pop() {
109        modules.push(module);
110        worklist.extend(module.children(db));
111    }
112
113    modules
114}