]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / rust-analyzer / src / cli / scip.rs
CommitLineData
f2b60f7d
FG
1//! SCIP generator
2
3use std::{
4 collections::{HashMap, HashSet},
fe692bf9 5 path::PathBuf,
f2b60f7d
FG
6 time::Instant,
7};
8
f2b60f7d 9use ide::{
2b03887a
FG
10 LineCol, MonikerDescriptorKind, StaticIndex, StaticIndexedFile, TextRange, TokenId,
11 TokenStaticData,
f2b60f7d
FG
12};
13use ide_db::LineIndexDatabase;
4b012472 14use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
f2b60f7d 15use scip::types as scip_types;
f2b60f7d 16
add651ee
FG
17use crate::{
18 cli::flags,
19 line_index::{LineEndings, LineIndex, PositionEncoding},
f2b60f7d
FG
20};
21
22impl flags::Scip {
add651ee 23 pub fn run(self) -> anyhow::Result<()> {
f2b60f7d
FG
24 eprintln!("Generating SCIP start...");
25 let now = Instant::now();
f2b60f7d 26
9c376795 27 let no_progress = &|s| (eprintln!("rust-analyzer: Loading {s}"));
f2b60f7d
FG
28 let load_cargo_config = LoadCargoConfig {
29 load_out_dirs_from_check: true,
9ffffee4 30 with_proc_macro_server: ProcMacroServerChoice::Sysroot,
f2b60f7d
FG
31 prefill_caches: true,
32 };
4b012472
FG
33 let root = vfs::AbsPathBuf::assert(std::env::current_dir()?.join(&self.path)).normalize();
34
35 let mut config = crate::config::Config::new(
36 root.clone(),
37 lsp_types::ClientCapabilities::default(),
38 /* workspace_roots = */ vec![],
39 /* is_visual_studio_code = */ false,
40 );
41
42 if let Some(p) = self.config_path {
43 let mut file = std::io::BufReader::new(std::fs::File::open(p)?);
44 let json = serde_json::from_reader(&mut file)?;
45 config.update(json)?;
46 }
47 let cargo_config = config.cargo();
48 let (host, vfs, _) = load_workspace_at(
49 root.as_path().as_ref(),
50 &cargo_config,
51 &load_cargo_config,
52 &no_progress,
53 )?;
f2b60f7d
FG
54 let db = host.raw_database();
55 let analysis = host.analysis();
56
57 let si = StaticIndex::compute(&analysis);
58
487cf647
FG
59 let metadata = scip_types::Metadata {
60 version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),
61 tool_info: Some(scip_types::ToolInfo {
62 name: "rust-analyzer".to_owned(),
781aab86 63 version: format!("{}", crate::version::version()),
487cf647
FG
64 arguments: vec![],
65 special_fields: Default::default(),
f2b60f7d
FG
66 })
67 .into(),
487cf647
FG
68 project_root: format!(
69 "file://{}",
4b012472 70 root.as_os_str()
487cf647 71 .to_str()
add651ee 72 .ok_or(anyhow::format_err!("Unable to normalize project_root path"))?
487cf647
FG
73 ),
74 text_document_encoding: scip_types::TextEncoding::UTF8.into(),
75 special_fields: Default::default(),
f2b60f7d 76 };
487cf647 77 let mut documents = Vec::new();
f2b60f7d
FG
78
79 let mut symbols_emitted: HashSet<TokenId> = HashSet::default();
80 let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new();
81
2b03887a 82 for StaticIndexedFile { file_id, tokens, .. } in si.files {
f2b60f7d
FG
83 let mut local_count = 0;
84 let mut new_local_symbol = || {
85 let new_symbol = scip::types::Symbol::new_local(local_count);
86 local_count += 1;
87
88 new_symbol
89 };
90
4b012472 91 let relative_path = match get_relative_filepath(&vfs, &root, file_id) {
f2b60f7d
FG
92 Some(relative_path) => relative_path,
93 None => continue,
94 };
95
96 let line_index = LineIndex {
97 index: db.line_index(file_id),
487cf647 98 encoding: PositionEncoding::Utf8,
f2b60f7d
FG
99 endings: LineEndings::Unix,
100 };
101
487cf647
FG
102 let mut occurrences = Vec::new();
103 let mut symbols = Vec::new();
f2b60f7d 104
487cf647 105 tokens.into_iter().for_each(|(text_range, id)| {
f2b60f7d
FG
106 let token = si.tokens.get(id).unwrap();
107
487cf647
FG
108 let range = text_range_to_scip_range(&line_index, text_range);
109 let symbol = tokens_to_symbol
2b03887a
FG
110 .entry(id)
111 .or_insert_with(|| {
9c376795 112 let symbol = token_to_symbol(token).unwrap_or_else(&mut new_local_symbol);
2b03887a
FG
113 scip::symbol::format_symbol(symbol)
114 })
115 .clone();
f2b60f7d 116
487cf647
FG
117 let mut symbol_roles = Default::default();
118
f2b60f7d 119 if let Some(def) = token.definition {
487cf647
FG
120 if def.range == text_range {
121 symbol_roles |= scip_types::SymbolRole::Definition as i32;
f2b60f7d
FG
122 }
123
2b03887a 124 if symbols_emitted.insert(id) {
487cf647
FG
125 let documentation = token
126 .hover
127 .as_ref()
128 .map(|hover| hover.markup.as_str())
129 .filter(|it| !it.is_empty())
130 .map(|it| vec![it.to_owned()]);
131 let symbol_info = scip_types::SymbolInformation {
132 symbol: symbol.clone(),
133 documentation: documentation.unwrap_or_default(),
134 relationships: Vec::new(),
135 special_fields: Default::default(),
4b012472
FG
136 kind: Default::default(),
137 display_name: String::new(),
138 signature_documentation: Default::default(),
139 enclosing_symbol: String::new(),
487cf647
FG
140 };
141
142 symbols.push(symbol_info)
f2b60f7d
FG
143 }
144 }
145
487cf647
FG
146 occurrences.push(scip_types::Occurrence {
147 range,
148 symbol,
149 symbol_roles,
150 override_documentation: Vec::new(),
151 syntax_kind: Default::default(),
152 diagnostics: Vec::new(),
153 special_fields: Default::default(),
4b012472 154 enclosing_range: Vec::new(),
487cf647 155 });
f2b60f7d
FG
156 });
157
487cf647 158 if occurrences.is_empty() {
f2b60f7d
FG
159 continue;
160 }
161
487cf647
FG
162 documents.push(scip_types::Document {
163 relative_path,
164 language: "rust".to_string(),
165 occurrences,
166 symbols,
167 special_fields: Default::default(),
4b012472 168 text: String::new(),
487cf647 169 });
f2b60f7d
FG
170 }
171
487cf647
FG
172 let index = scip_types::Index {
173 metadata: Some(metadata).into(),
174 documents,
175 external_symbols: Vec::new(),
176 special_fields: Default::default(),
177 };
178
fe692bf9
FG
179 let out_path = self.output.unwrap_or_else(|| PathBuf::from(r"index.scip"));
180 scip::write_message_to_file(out_path, index)
add651ee 181 .map_err(|err| anyhow::format_err!("Failed to write scip to file: {}", err))?;
f2b60f7d
FG
182
183 eprintln!("Generating SCIP finished {:?}", now.elapsed());
184 Ok(())
185 }
186}
187
188fn get_relative_filepath(
189 vfs: &vfs::Vfs,
190 rootpath: &vfs::AbsPathBuf,
191 file_id: ide::FileId,
192) -> Option<String> {
9c376795 193 Some(vfs.file_path(file_id).as_path()?.strip_prefix(rootpath)?.as_ref().to_str()?.to_string())
f2b60f7d
FG
194}
195
196// SCIP Ranges have a (very large) optimization that ranges if they are on the same line
197// only encode as a vector of [start_line, start_col, end_col].
198//
199// This transforms a line index into the optimized SCIP Range.
200fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> {
201 let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
202 let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
203
204 if start_line == end_line {
205 vec![start_line as i32, start_col as i32, end_col as i32]
206 } else {
207 vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32]
208 }
209}
210
211fn new_descriptor_str(
212 name: &str,
213 suffix: scip_types::descriptor::Suffix,
214) -> scip_types::Descriptor {
215 scip_types::Descriptor {
216 name: name.to_string(),
217 disambiguator: "".to_string(),
218 suffix: suffix.into(),
487cf647 219 special_fields: Default::default(),
f2b60f7d
FG
220 }
221}
222
fe692bf9
FG
223fn new_descriptor(name: &str, suffix: scip_types::descriptor::Suffix) -> scip_types::Descriptor {
224 if name.contains('\'') {
225 new_descriptor_str(&format!("`{name}`"), suffix)
226 } else {
227 new_descriptor_str(&name, suffix)
f2b60f7d 228 }
f2b60f7d
FG
229}
230
231/// Loosely based on `def_to_moniker`
232///
233/// Only returns a Symbol when it's a non-local symbol.
234/// So if the visibility isn't outside of a document, then it will return None
2b03887a 235fn token_to_symbol(token: &TokenStaticData) -> Option<scip_types::Symbol> {
f2b60f7d
FG
236 use scip_types::descriptor::Suffix::*;
237
2b03887a
FG
238 let moniker = token.moniker.as_ref()?;
239
f2b60f7d
FG
240 let package_name = moniker.package_information.name.clone();
241 let version = moniker.package_information.version.clone();
242 let descriptors = moniker
243 .identifier
244 .description
245 .iter()
246 .map(|desc| {
247 new_descriptor(
fe692bf9 248 &desc.name,
f2b60f7d
FG
249 match desc.desc {
250 MonikerDescriptorKind::Namespace => Namespace,
251 MonikerDescriptorKind::Type => Type,
252 MonikerDescriptorKind::Term => Term,
253 MonikerDescriptorKind::Method => Method,
254 MonikerDescriptorKind::TypeParameter => TypeParameter,
255 MonikerDescriptorKind::Parameter => Parameter,
256 MonikerDescriptorKind::Macro => Macro,
257 MonikerDescriptorKind::Meta => Meta,
258 },
259 )
260 })
261 .collect();
262
2b03887a 263 Some(scip_types::Symbol {
f2b60f7d
FG
264 scheme: "rust-analyzer".into(),
265 package: Some(scip_types::Package {
266 manager: "cargo".to_string(),
267 name: package_name,
487cf647
FG
268 version: version.unwrap_or_else(|| ".".to_string()),
269 special_fields: Default::default(),
f2b60f7d
FG
270 })
271 .into(),
272 descriptors,
487cf647 273 special_fields: Default::default(),
2b03887a 274 })
f2b60f7d
FG
275}
276
277#[cfg(test)]
278mod test {
279 use super::*;
2b03887a
FG
280 use ide::{AnalysisHost, FilePosition, StaticIndex, TextSize};
281 use ide_db::base_db::fixture::ChangeFixture;
f2b60f7d 282 use scip::symbol::format_symbol;
f2b60f7d
FG
283
284 fn position(ra_fixture: &str) -> (AnalysisHost, FilePosition) {
285 let mut host = AnalysisHost::default();
286 let change_fixture = ChangeFixture::parse(ra_fixture);
287 host.raw_database_mut().apply_change(change_fixture.change);
288 let (file_id, range_or_offset) =
add651ee 289 change_fixture.file_position.expect("expected a marker ()");
f2b60f7d
FG
290 let offset = range_or_offset.expect_offset();
291 (host, FilePosition { file_id, offset })
292 }
293
294 /// If expected == "", then assert that there are no symbols (this is basically local symbol)
295 #[track_caller]
296 fn check_symbol(ra_fixture: &str, expected: &str) {
297 let (host, position) = position(ra_fixture);
298
2b03887a
FG
299 let analysis = host.analysis();
300 let si = StaticIndex::compute(&analysis);
301
f2b60f7d
FG
302 let FilePosition { file_id, offset } = position;
303
2b03887a
FG
304 let mut found_symbol = None;
305 for file in &si.files {
306 if file.file_id != file_id {
307 continue;
308 }
309 for &(range, id) in &file.tokens {
310 if range.contains(offset - TextSize::from(1)) {
311 let token = si.tokens.get(id).unwrap();
312 found_symbol = token_to_symbol(token);
313 break;
314 }
315 }
316 }
f2b60f7d
FG
317
318 if expected == "" {
9c376795 319 assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}");
f2b60f7d
FG
320 return;
321 }
322
9c376795 323 assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}");
2b03887a
FG
324 let res = found_symbol.unwrap();
325 let formatted = format_symbol(res);
f2b60f7d
FG
326 assert_eq!(formatted, expected);
327 }
328
329 #[test]
330 fn basic() {
331 check_symbol(
332 r#"
333//- /lib.rs crate:main deps:foo
334use foo::example_mod::func;
335fn main() {
336 func$0();
337}
add651ee 338//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
f2b60f7d
FG
339pub mod example_mod {
340 pub fn func() {}
341}
342"#,
343 "rust-analyzer cargo foo 0.1.0 example_mod/func().",
344 );
345 }
346
347 #[test]
348 fn symbol_for_trait() {
349 check_symbol(
350 r#"
add651ee 351//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
f2b60f7d
FG
352pub mod module {
353 pub trait MyTrait {
354 pub fn func$0() {}
355 }
356}
357"#,
358 "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
359 );
360 }
361
362 #[test]
363 fn symbol_for_trait_constant() {
364 check_symbol(
365 r#"
add651ee 366 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
f2b60f7d
FG
367 pub mod module {
368 pub trait MyTrait {
369 const MY_CONST$0: u8;
370 }
371 }
372 "#,
373 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
374 );
375 }
376
377 #[test]
378 fn symbol_for_trait_type() {
379 check_symbol(
380 r#"
add651ee 381 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
f2b60f7d
FG
382 pub mod module {
383 pub trait MyTrait {
384 type MyType$0;
385 }
386 }
387 "#,
388 // "foo::module::MyTrait::MyType",
389 "rust-analyzer cargo foo 0.1.0 module/MyTrait#[MyType]",
390 );
391 }
392
393 #[test]
394 fn symbol_for_trait_impl_function() {
395 check_symbol(
396 r#"
add651ee 397 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
f2b60f7d
FG
398 pub mod module {
399 pub trait MyTrait {
400 pub fn func() {}
401 }
402
403 struct MyStruct {}
404
405 impl MyTrait for MyStruct {
406 pub fn func$0() {}
407 }
408 }
409 "#,
410 // "foo::module::MyStruct::MyTrait::func",
411 "rust-analyzer cargo foo 0.1.0 module/MyStruct#MyTrait#func().",
412 );
413 }
414
415 #[test]
416 fn symbol_for_field() {
417 check_symbol(
418 r#"
419 //- /lib.rs crate:main deps:foo
420 use foo::St;
421 fn main() {
422 let x = St { a$0: 2 };
423 }
add651ee 424 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
f2b60f7d
FG
425 pub struct St {
426 pub a: i32,
427 }
428 "#,
429 "rust-analyzer cargo foo 0.1.0 St#a.",
430 );
431 }
432
add651ee
FG
433 #[test]
434 fn symbol_for_param() {
435 check_symbol(
436 r#"
437//- /lib.rs crate:main deps:foo
438use foo::example_mod::func;
439fn main() {
440 func(42);
441}
442//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
443pub mod example_mod {
444 pub fn func(x$0: usize) {}
445}
446"#,
447 "rust-analyzer cargo foo 0.1.0 example_mod/func().(x)",
448 );
449 }
450
451 #[test]
452 fn symbol_for_closure_param() {
453 check_symbol(
454 r#"
455//- /lib.rs crate:main deps:foo
456use foo::example_mod::func;
457fn main() {
458 func();
459}
460//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
461pub mod example_mod {
462 pub fn func() {
463 let f = |x$0: usize| {};
464 }
465}
466"#,
467 "rust-analyzer cargo foo 0.1.0 example_mod/func().(x)",
468 );
469 }
470
f2b60f7d
FG
471 #[test]
472 fn local_symbol_for_local() {
473 check_symbol(
474 r#"
475 //- /lib.rs crate:main deps:foo
476 use foo::module::func;
477 fn main() {
478 func();
479 }
add651ee 480 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
f2b60f7d
FG
481 pub mod module {
482 pub fn func() {
483 let x$0 = 2;
484 }
485 }
486 "#,
487 "",
488 );
489 }
487cf647
FG
490
491 #[test]
492 fn global_symbol_for_pub_struct() {
493 check_symbol(
494 r#"
495 //- /lib.rs crate:main
496 mod foo;
497
498 fn main() {
499 let _bar = foo::Bar { i: 0 };
500 }
501 //- /foo.rs
502 pub struct Bar$0 {
503 pub i: i32,
504 }
505 "#,
506 "rust-analyzer cargo main . foo/Bar#",
507 );
508 }
509
510 #[test]
511 fn global_symbol_for_pub_struct_reference() {
512 check_symbol(
513 r#"
514 //- /lib.rs crate:main
515 mod foo;
516
517 fn main() {
518 let _bar = foo::Bar$0 { i: 0 };
519 }
520 //- /foo.rs
521 pub struct Bar {
522 pub i: i32,
523 }
524 "#,
525 "rust-analyzer cargo main . foo/Bar#",
526 );
527 }
f2b60f7d 528}