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