ra_ap_rust_analyzer/cli/
unresolved_references.rs

1//! Reports references in code that the IDE layer cannot resolve.
2use hir::{db::HirDatabase, sym, AnyDiagnostic, Crate, HirFileIdExt as _, Module, Semantics};
3use ide::{AnalysisHost, RootDatabase, TextRange};
4use ide_db::{
5    base_db::{SourceDatabase, SourceRootDatabase},
6    defs::NameRefClass,
7    EditionedFileId, FxHashSet, LineIndexDatabase as _,
8};
9use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
10use parser::SyntaxKind;
11use syntax::{ast, AstNode, WalkEvent};
12use vfs::FileId;
13
14use crate::cli::flags;
15
16impl flags::UnresolvedReferences {
17    pub fn run(self) -> anyhow::Result<()> {
18        const STACK_SIZE: usize = 1024 * 1024 * 8;
19
20        let handle = stdx::thread::Builder::new(stdx::thread::ThreadIntent::LatencySensitive)
21            .name("BIG_STACK_THREAD".into())
22            .stack_size(STACK_SIZE)
23            .spawn(|| self.run_())
24            .unwrap();
25
26        handle.join()
27    }
28
29    fn run_(self) -> anyhow::Result<()> {
30        let root =
31            vfs::AbsPathBuf::assert_utf8(std::env::current_dir()?.join(&self.path)).normalize();
32        let config = crate::config::Config::new(
33            root.clone(),
34            lsp_types::ClientCapabilities::default(),
35            vec![],
36            None,
37        );
38        let cargo_config = config.cargo(None);
39        let with_proc_macro_server = if let Some(p) = &self.proc_macro_srv {
40            let path = vfs::AbsPathBuf::assert_utf8(std::env::current_dir()?.join(p));
41            ProcMacroServerChoice::Explicit(path)
42        } else {
43            ProcMacroServerChoice::Sysroot
44        };
45        let load_cargo_config = LoadCargoConfig {
46            load_out_dirs_from_check: !self.disable_build_scripts,
47            with_proc_macro_server,
48            prefill_caches: false,
49        };
50        let (db, vfs, _proc_macro) =
51            load_workspace_at(&self.path, &cargo_config, &load_cargo_config, &|_| {})?;
52        let host = AnalysisHost::with_database(db);
53        let db = host.raw_database();
54        let sema = Semantics::new(db);
55
56        let mut visited_files = FxHashSet::default();
57
58        let work = all_modules(db).into_iter().filter(|module| {
59            let file_id = module.definition_source_file_id(db).original_file(db);
60            let source_root = db.file_source_root(file_id.into());
61            let source_root = db.source_root(source_root);
62            !source_root.is_library
63        });
64
65        for module in work {
66            let file_id = module.definition_source_file_id(db).original_file(db);
67            if !visited_files.contains(&file_id) {
68                let crate_name =
69                    module.krate().display_name(db).as_deref().unwrap_or(&sym::unknown).to_owned();
70                let file_path = vfs.file_path(file_id.into());
71                eprintln!("processing crate: {crate_name}, module: {file_path}",);
72
73                let line_index = db.line_index(file_id.into());
74                let file_text = db.file_text(file_id.into());
75
76                for range in find_unresolved_references(db, &sema, file_id.into(), &module) {
77                    let line_col = line_index.line_col(range.start());
78                    let line = line_col.line + 1;
79                    let col = line_col.col + 1;
80                    let text = &file_text[range];
81                    println!("{file_path}:{line}:{col}: {text}");
82                }
83
84                visited_files.insert(file_id);
85            }
86        }
87
88        eprintln!();
89        eprintln!("scan complete");
90
91        Ok(())
92    }
93}
94
95fn all_modules(db: &dyn HirDatabase) -> Vec<Module> {
96    let mut worklist: Vec<_> =
97        Crate::all(db).into_iter().map(|krate| krate.root_module()).collect();
98    let mut modules = Vec::new();
99
100    while let Some(module) = worklist.pop() {
101        modules.push(module);
102        worklist.extend(module.children(db));
103    }
104
105    modules
106}
107
108fn find_unresolved_references(
109    db: &RootDatabase,
110    sema: &Semantics<'_, RootDatabase>,
111    file_id: FileId,
112    module: &Module,
113) -> Vec<TextRange> {
114    let mut unresolved_references = all_unresolved_references(sema, file_id);
115
116    // remove unresolved references which are within inactive code
117    let mut diagnostics = Vec::new();
118    module.diagnostics(db, &mut diagnostics, false);
119    for diagnostic in diagnostics {
120        let AnyDiagnostic::InactiveCode(inactive_code) = diagnostic else {
121            continue;
122        };
123
124        let node = inactive_code.node;
125        let range = node.map(|it| it.text_range()).original_node_file_range_rooted(db);
126
127        if range.file_id != file_id {
128            continue;
129        }
130
131        unresolved_references.retain(|r| !range.range.contains_range(*r));
132    }
133
134    unresolved_references
135}
136
137fn all_unresolved_references(
138    sema: &Semantics<'_, RootDatabase>,
139    file_id: FileId,
140) -> Vec<TextRange> {
141    let file_id = sema
142        .attach_first_edition(file_id)
143        .unwrap_or_else(|| EditionedFileId::current_edition(file_id));
144    let file = sema.parse(file_id);
145    let root = file.syntax();
146
147    let mut unresolved_references = Vec::new();
148    for event in root.preorder() {
149        let WalkEvent::Enter(syntax) = event else {
150            continue;
151        };
152        let Some(name_ref) = ast::NameRef::cast(syntax) else {
153            continue;
154        };
155        let Some(descended_name_ref) = name_ref.syntax().first_token().and_then(|tok| {
156            sema.descend_into_macros_single_exact(tok).parent().and_then(ast::NameRef::cast)
157        }) else {
158            continue;
159        };
160
161        // if we can classify the name_ref, it's not unresolved
162        if NameRefClass::classify(sema, &descended_name_ref).is_some() {
163            continue;
164        }
165
166        // if we couldn't classify it, but it's in an attr, ignore it. See #10935
167        if descended_name_ref.syntax().ancestors().any(|it| it.kind() == SyntaxKind::ATTR) {
168            continue;
169        }
170
171        // otherwise, it's unresolved
172        unresolved_references.push(name_ref.syntax().text_range());
173    }
174    unresolved_references
175}