1use std::{path::PathBuf, time::Instant};
4
5use ide::{
6 AnalysisHost, LineCol, Moniker, MonikerDescriptorKind, MonikerIdentifier, MonikerResult,
7 RootDatabase, StaticIndex, StaticIndexedFile, SymbolInformationKind, TextRange, TokenId,
8 TokenStaticData, VendoredLibrariesConfig,
9};
10use ide_db::line_index;
11use load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace_at};
12use rustc_hash::{FxHashMap, FxHashSet};
13use scip::types::{self as scip_types, SymbolInformation};
14use tracing::error;
15use vfs::FileId;
16
17use crate::{
18 cli::flags,
19 config::ConfigChange,
20 line_index::{LineEndings, LineIndex, PositionEncoding},
21};
22
23impl flags::Scip {
24 pub fn run(self) -> anyhow::Result<()> {
25 eprintln!("Generating SCIP start...");
26 let now = Instant::now();
27
28 let no_progress = &|s| eprintln!("rust-analyzer: Loading {s}");
29 let root =
30 vfs::AbsPathBuf::assert_utf8(std::env::current_dir()?.join(&self.path)).normalize();
31
32 let mut config = crate::config::Config::new(
33 root.clone(),
34 lsp_types::ClientCapabilities::default(),
35 vec![],
36 None,
37 );
38
39 if let Some(p) = self.config_path {
40 let mut file = std::io::BufReader::new(std::fs::File::open(p)?);
41 let json = serde_json::from_reader(&mut file)?;
42 let mut change = ConfigChange::default();
43 change.change_client_config(json);
44
45 let error_sink;
46 (config, error_sink, _) = config.apply_change(change);
47
48 error!(?error_sink, "Config Error(s)");
50 }
51 let load_cargo_config = LoadCargoConfig {
52 load_out_dirs_from_check: true,
53 with_proc_macro_server: ProcMacroServerChoice::Sysroot,
54 prefill_caches: true,
55 num_worker_threads: self.num_threads.unwrap_or_else(num_cpus::get_physical),
56 proc_macro_processes: config.proc_macro_num_processes(),
57 };
58 let cargo_config = config.cargo(None);
59 let (db, vfs, _) = load_workspace_at(
60 root.as_path().as_ref(),
61 &cargo_config,
62 &load_cargo_config,
63 &no_progress,
64 )?;
65 let host = AnalysisHost::with_database(db);
66 let db = host.raw_database();
67 let analysis = host.analysis();
68
69 let vendored_libs_config = if self.exclude_vendored_libraries {
70 VendoredLibrariesConfig::Excluded
71 } else {
72 VendoredLibrariesConfig::Included { workspace_root: &root.clone().into() }
73 };
74
75 let si = StaticIndex::compute(&analysis, vendored_libs_config);
76
77 let metadata = scip_types::Metadata {
78 version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),
79 tool_info: Some(scip_types::ToolInfo {
80 name: "rust-analyzer".to_owned(),
81 version: format!("{}", crate::version::version()),
82 arguments: vec![],
83 special_fields: Default::default(),
84 })
85 .into(),
86 project_root: format!("file://{root}"),
87 text_document_encoding: scip_types::TextEncoding::UTF8.into(),
88 special_fields: Default::default(),
89 };
90
91 let mut documents = Vec::new();
92
93 let mut token_ids_referenced: FxHashSet<TokenId> = FxHashSet::default();
95 let mut token_ids_emitted: FxHashSet<TokenId> = FxHashSet::default();
97 let mut file_ids_emitted: FxHashSet<FileId> = FxHashSet::default();
99
100 let mut nonlocal_symbols_emitted: FxHashSet<String> = FxHashSet::default();
102 let mut duplicate_symbol_errors: Vec<(String, String)> = Vec::new();
104 let mut record_error_if_symbol_already_used =
107 |symbol: String,
108 is_inherent_impl: bool,
109 relative_path: &str,
110 line_index: &LineIndex,
111 text_range: TextRange| {
112 let is_local = symbol.starts_with("local ");
113 if !is_local && !nonlocal_symbols_emitted.insert(symbol.clone()) {
114 if is_inherent_impl {
115 false
119 } else {
120 let source_location =
121 text_range_to_string(relative_path, line_index, text_range);
122 duplicate_symbol_errors.push((source_location, symbol));
123 true
126 }
127 } else {
128 true
129 }
130 };
131
132 let mut symbol_generator = SymbolGenerator::default();
134
135 for StaticIndexedFile { file_id, tokens, .. } in si.files {
136 symbol_generator.clear_document_local_state();
137
138 let Some(relative_path) = get_relative_filepath(&vfs, &root, file_id) else { continue };
139 let line_index = get_line_index(db, file_id);
140
141 let mut occurrences = Vec::new();
142 let mut symbols = Vec::new();
143
144 for (text_range, id) in tokens.into_iter() {
145 let token = si.tokens.get(id).unwrap();
146
147 let Some(TokenSymbols { symbol, enclosing_symbol, is_inherent_impl }) =
148 symbol_generator.token_symbols(id, token)
149 else {
150 continue;
153 };
154
155 let is_defined_in_this_document = match token.definition {
156 Some(def) => def.file_id == file_id,
157 _ => false,
158 };
159 if is_defined_in_this_document {
160 if token_ids_emitted.insert(id) {
161 let should_emit = record_error_if_symbol_already_used(
164 symbol.clone(),
165 is_inherent_impl,
166 relative_path.as_str(),
167 &line_index,
168 text_range,
169 );
170 if should_emit {
171 symbols.push(compute_symbol_info(
172 symbol.clone(),
173 enclosing_symbol,
174 token,
175 ));
176 }
177 }
178 } else {
179 token_ids_referenced.insert(id);
180 }
181
182 let is_definition = match token.definition {
185 Some(def) => def.file_id == file_id && def.range == text_range,
186 _ => false,
187 };
188
189 let mut symbol_roles = Default::default();
190 if is_definition {
191 symbol_roles |= scip_types::SymbolRole::Definition as i32;
192 }
193
194 let enclosing_range = match token.definition_body {
195 Some(def_body) if def_body.file_id == file_id => {
196 text_range_to_scip_range(&line_index, def_body.range)
197 }
198 _ => Vec::new(),
199 };
200
201 occurrences.push(scip_types::Occurrence {
202 range: text_range_to_scip_range(&line_index, text_range),
203 symbol,
204 symbol_roles,
205 override_documentation: Vec::new(),
206 syntax_kind: Default::default(),
207 diagnostics: Vec::new(),
208 special_fields: Default::default(),
209 enclosing_range,
210 });
211 }
212
213 if occurrences.is_empty() {
214 continue;
215 }
216
217 let position_encoding =
218 scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into();
219 documents.push(scip_types::Document {
220 relative_path,
221 language: "rust".to_owned(),
222 occurrences,
223 symbols,
224 text: String::new(),
225 position_encoding,
226 special_fields: Default::default(),
227 });
228 if !file_ids_emitted.insert(file_id) {
229 panic!("Invariant violation: file emitted multiple times.");
230 }
231 }
232
233 let mut external_symbols = Vec::new();
235 for id in token_ids_referenced.difference(&token_ids_emitted) {
236 let id = *id;
237 let token = si.tokens.get(id).unwrap();
238
239 let Some(definition) = token.definition else {
240 break;
241 };
242
243 let file_id = definition.file_id;
244 let Some(relative_path) = get_relative_filepath(&vfs, &root, file_id) else { continue };
245 let line_index = get_line_index(db, file_id);
246 let text_range = definition.range;
247 if file_ids_emitted.contains(&file_id) {
248 tracing::error!(
249 "Bug: definition at {} should have been in an SCIP document but was not.",
250 text_range_to_string(relative_path.as_str(), &line_index, text_range)
251 );
252 continue;
253 }
254
255 let TokenSymbols { symbol, enclosing_symbol, .. } = symbol_generator
256 .token_symbols(id, token)
257 .expect("To have been referenced, the symbol must be in the cache.");
258
259 record_error_if_symbol_already_used(
260 symbol.clone(),
261 false,
262 relative_path.as_str(),
263 &line_index,
264 text_range,
265 );
266 external_symbols.push(compute_symbol_info(symbol.clone(), enclosing_symbol, token));
267 }
268
269 let index = scip_types::Index {
270 metadata: Some(metadata).into(),
271 documents,
272 external_symbols,
273 special_fields: Default::default(),
274 };
275
276 if !duplicate_symbol_errors.is_empty() {
277 eprintln!("{DUPLICATE_SYMBOLS_MESSAGE}");
278 for (source_location, symbol) in duplicate_symbol_errors {
279 eprintln!("{source_location}");
280 eprintln!(" Duplicate symbol: {symbol}");
281 eprintln!();
282 }
283 }
284
285 let out_path = self.output.unwrap_or_else(|| PathBuf::from(r"index.scip"));
286 scip::write_message_to_file(out_path, index)
287 .map_err(|err| anyhow::format_err!("Failed to write scip to file: {}", err))?;
288
289 eprintln!("Generating SCIP finished {:?}", now.elapsed());
290 Ok(())
291 }
292}
293
294const DUPLICATE_SYMBOLS_MESSAGE: &str = "
296Encountered duplicate scip symbols, indicating an internal rust-analyzer bug. These duplicates are
297included in the output, but this causes information lookup to be ambiguous and so information about
298these symbols presented by downstream tools may be incorrect.
299
300Known rust-analyzer bugs that can cause this:
301
302 * Definitions in crate example binaries which have the same symbol as definitions in the library
303 or some other example.
304
305 * Struct/enum/const/static/impl definitions nested in a function do not mention the function name.
306 See #18771.
307
308Duplicate symbols encountered:
309";
310
311fn compute_symbol_info(
312 symbol: String,
313 enclosing_symbol: Option<String>,
314 token: &TokenStaticData,
315) -> SymbolInformation {
316 let documentation = match &token.documentation {
317 Some(doc) => vec![doc.as_str().to_owned()],
318 None => vec![],
319 };
320
321 let position_encoding = scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into();
322 let signature_documentation = token.signature.clone().map(|text| scip_types::Document {
323 relative_path: "".to_owned(),
324 language: "rust".to_owned(),
325 text,
326 position_encoding,
327 ..Default::default()
328 });
329 scip_types::SymbolInformation {
330 symbol,
331 documentation,
332 relationships: Vec::new(),
333 special_fields: Default::default(),
334 kind: symbol_kind(token.kind).into(),
335 display_name: token.display_name.clone().unwrap_or_default(),
336 signature_documentation: signature_documentation.into(),
337 enclosing_symbol: enclosing_symbol.unwrap_or_default(),
338 }
339}
340
341fn get_relative_filepath(
342 vfs: &vfs::Vfs,
343 rootpath: &vfs::AbsPathBuf,
344 file_id: ide::FileId,
345) -> Option<String> {
346 Some(vfs.file_path(file_id).as_path()?.strip_prefix(rootpath)?.as_str().to_owned())
347}
348
349fn get_line_index(db: &RootDatabase, file_id: FileId) -> LineIndex {
350 LineIndex {
351 index: line_index(db, file_id).clone(),
352 encoding: PositionEncoding::Utf8,
353 endings: LineEndings::Unix,
354 }
355}
356
357fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> {
362 let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
363 let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
364
365 if start_line == end_line {
366 vec![start_line as i32, start_col as i32, end_col as i32]
367 } else {
368 vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32]
369 }
370}
371
372fn text_range_to_string(relative_path: &str, line_index: &LineIndex, range: TextRange) -> String {
373 let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());
374 let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());
375
376 format!("{relative_path}:{start_line}:{start_col}-{end_line}:{end_col}")
377}
378
379fn new_descriptor_str(
380 name: &str,
381 suffix: scip_types::descriptor::Suffix,
382) -> scip_types::Descriptor {
383 scip_types::Descriptor {
384 name: name.to_owned(),
385 disambiguator: "".to_owned(),
386 suffix: suffix.into(),
387 special_fields: Default::default(),
388 }
389}
390
391fn symbol_kind(kind: SymbolInformationKind) -> scip_types::symbol_information::Kind {
392 use scip_types::symbol_information::Kind as ScipKind;
393 match kind {
394 SymbolInformationKind::AssociatedType => ScipKind::AssociatedType,
395 SymbolInformationKind::Attribute => ScipKind::Attribute,
396 SymbolInformationKind::Constant => ScipKind::Constant,
397 SymbolInformationKind::Enum => ScipKind::Enum,
398 SymbolInformationKind::EnumMember => ScipKind::EnumMember,
399 SymbolInformationKind::Field => ScipKind::Field,
400 SymbolInformationKind::Function => ScipKind::Function,
401 SymbolInformationKind::Macro => ScipKind::Macro,
402 SymbolInformationKind::Method => ScipKind::Method,
403 SymbolInformationKind::Module => ScipKind::Module,
404 SymbolInformationKind::Parameter => ScipKind::Parameter,
405 SymbolInformationKind::SelfParameter => ScipKind::SelfParameter,
406 SymbolInformationKind::StaticMethod => ScipKind::StaticMethod,
407 SymbolInformationKind::StaticVariable => ScipKind::StaticVariable,
408 SymbolInformationKind::Struct => ScipKind::Struct,
409 SymbolInformationKind::Trait => ScipKind::Trait,
410 SymbolInformationKind::TraitMethod => ScipKind::TraitMethod,
411 SymbolInformationKind::Type => ScipKind::Type,
412 SymbolInformationKind::TypeAlias => ScipKind::TypeAlias,
413 SymbolInformationKind::TypeParameter => ScipKind::TypeParameter,
414 SymbolInformationKind::Union => ScipKind::Union,
415 SymbolInformationKind::Variable => ScipKind::Variable,
416 }
417}
418
419#[derive(Clone)]
420struct TokenSymbols {
421 symbol: String,
422 enclosing_symbol: Option<String>,
424 is_inherent_impl: bool,
427}
428
429#[derive(Default)]
430struct SymbolGenerator {
431 token_to_symbols: FxHashMap<TokenId, Option<TokenSymbols>>,
432 local_count: usize,
433}
434
435impl SymbolGenerator {
436 fn clear_document_local_state(&mut self) {
437 self.local_count = 0;
438 }
439
440 fn token_symbols(&mut self, id: TokenId, token: &TokenStaticData) -> Option<TokenSymbols> {
441 let mut local_count = self.local_count;
442 let token_symbols = self
443 .token_to_symbols
444 .entry(id)
445 .or_insert_with(|| {
446 Some(match token.moniker.as_ref()? {
447 MonikerResult::Moniker(moniker) => TokenSymbols {
448 symbol: scip::symbol::format_symbol(moniker_to_symbol(moniker)),
449 enclosing_symbol: None,
450 is_inherent_impl: match &moniker.identifier.description[..] {
451 [.., descriptor, _] => {
453 descriptor.desc == MonikerDescriptorKind::Type
454 && descriptor.name == "impl"
455 }
456 _ => false,
457 },
458 },
459 MonikerResult::Local { enclosing_moniker } => {
460 let local_symbol = scip::types::Symbol::new_local(local_count);
461 local_count += 1;
462 TokenSymbols {
463 symbol: scip::symbol::format_symbol(local_symbol),
464 enclosing_symbol: enclosing_moniker
465 .as_ref()
466 .map(moniker_to_symbol)
467 .map(scip::symbol::format_symbol),
468 is_inherent_impl: false,
469 }
470 }
471 })
472 })
473 .clone();
474 self.local_count = local_count;
475 token_symbols
476 }
477}
478
479fn moniker_to_symbol(moniker: &Moniker) -> scip_types::Symbol {
480 scip_types::Symbol {
481 scheme: "rust-analyzer".into(),
482 package: Some(scip_types::Package {
483 manager: "cargo".to_owned(),
484 name: moniker.package_information.name.clone(),
485 version: moniker.package_information.version.clone().unwrap_or_else(|| ".".to_owned()),
486 special_fields: Default::default(),
487 })
488 .into(),
489 descriptors: moniker_descriptors(&moniker.identifier),
490 special_fields: Default::default(),
491 }
492}
493
494fn moniker_descriptors(identifier: &MonikerIdentifier) -> Vec<scip_types::Descriptor> {
495 use scip_types::descriptor::Suffix::*;
496 identifier
497 .description
498 .iter()
499 .map(|desc| {
500 new_descriptor_str(
501 &desc.name,
502 match desc.desc {
503 MonikerDescriptorKind::Namespace => Namespace,
504 MonikerDescriptorKind::Type => Type,
505 MonikerDescriptorKind::Term => Term,
506 MonikerDescriptorKind::Method => Method,
507 MonikerDescriptorKind::TypeParameter => TypeParameter,
508 MonikerDescriptorKind::Parameter => Parameter,
509 MonikerDescriptorKind::Macro => Macro,
510 MonikerDescriptorKind::Meta => Meta,
511 },
512 )
513 })
514 .collect()
515}
516
517#[cfg(test)]
518mod test {
519 use super::*;
520 use hir::FileRangeWrapper;
521 use ide::{FilePosition, TextSize};
522 use test_fixture::ChangeFixture;
523 use vfs::VfsPath;
524
525 fn position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (AnalysisHost, FilePosition) {
526 let mut host = AnalysisHost::default();
527 let change_fixture = ChangeFixture::parse(ra_fixture);
528 host.raw_database_mut().apply_change(change_fixture.change);
529 let (file_id, range_or_offset) =
530 change_fixture.file_position.expect("expected a marker ()");
531 let offset = range_or_offset.expect_offset();
532 let position = FilePosition { file_id: file_id.file_id(), offset };
533 (host, position)
534 }
535
536 #[track_caller]
538 fn check_symbol(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: &str) {
539 let (host, position) = position(ra_fixture);
540
541 let analysis = host.analysis();
542 let si = StaticIndex::compute(
543 &analysis,
544 VendoredLibrariesConfig::Included {
545 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
546 },
547 );
548
549 let FilePosition { file_id, offset } = position;
550
551 let mut found_symbol = None;
552 for file in &si.files {
553 if file.file_id != file_id {
554 continue;
555 }
556 for &(range, id) in &file.tokens {
557 if range.start() != TextSize::from(0) && range.contains(offset - TextSize::from(1))
559 {
560 let token = si.tokens.get(id).unwrap();
561 found_symbol = match token.moniker.as_ref() {
562 None => None,
563 Some(MonikerResult::Moniker(moniker)) => {
564 Some(scip::symbol::format_symbol(moniker_to_symbol(moniker)))
565 }
566 Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => {
567 Some(format!(
568 "local enclosed by {}",
569 scip::symbol::format_symbol(moniker_to_symbol(moniker))
570 ))
571 }
572 Some(MonikerResult::Local { enclosing_moniker: None }) => {
573 Some("unenclosed local".to_owned())
574 }
575 };
576 break;
577 }
578 }
579 }
580
581 if expected.is_empty() {
582 assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}");
583 return;
584 }
585
586 assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}");
587 assert_eq!(found_symbol.unwrap(), expected);
588 }
589
590 #[test]
591 fn basic() {
592 check_symbol(
593 r#"
594//- /workspace/lib.rs crate:main deps:foo
595use foo::example_mod::func;
596fn main() {
597 func$0();
598}
599//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
600pub mod example_mod {
601 pub fn func() {}
602}
603"#,
604 "rust-analyzer cargo foo 0.1.0 example_mod/func().",
605 );
606 }
607
608 #[test]
609 fn operator_overload() {
610 check_symbol(
611 r#"
612//- minicore: add
613//- /workspace/lib.rs crate:main
614use core::ops::AddAssign;
615
616struct S;
617
618impl AddAssign for S {
619 fn add_assign(&mut self, _rhs: Self) {}
620}
621
622fn main() {
623 let mut s = S;
624 s +=$0 S;
625}
626"#,
627 "rust-analyzer cargo main . impl#[S][`AddAssign<Self>`]add_assign().",
628 );
629 }
630
631 #[test]
632 fn symbol_for_trait() {
633 check_symbol(
634 r#"
635//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
636pub mod module {
637 pub trait MyTrait {
638 pub fn func$0() {}
639 }
640}
641"#,
642 "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
643 );
644 }
645
646 #[test]
647 fn symbol_for_trait_alias() {
648 check_symbol(
649 r#"
650//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
651#![feature(trait_alias)]
652pub mod module {
653 pub trait MyTrait {}
654 pub trait MyTraitAlias$0 = MyTrait;
655}
656"#,
657 "rust-analyzer cargo foo 0.1.0 module/MyTraitAlias#",
658 );
659 }
660
661 #[test]
662 fn symbol_for_trait_constant() {
663 check_symbol(
664 r#"
665 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
666 pub mod module {
667 pub trait MyTrait {
668 const MY_CONST$0: u8;
669 }
670 }
671 "#,
672 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
673 );
674 }
675
676 #[test]
677 fn symbol_for_trait_type() {
678 check_symbol(
679 r#"
680 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
681 pub mod module {
682 pub trait MyTrait {
683 type MyType$0;
684 }
685 }
686 "#,
687 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#",
688 );
689 }
690
691 #[test]
692 fn symbol_for_trait_impl_function() {
693 check_symbol(
694 r#"
695 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
696 pub mod module {
697 pub trait MyTrait {
698 pub fn func() {}
699 }
700
701 struct MyStruct {}
702
703 impl MyTrait for MyStruct {
704 pub fn func$0() {}
705 }
706 }
707 "#,
708 "rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().",
709 );
710 }
711
712 #[test]
713 fn symbol_for_field() {
714 check_symbol(
715 r#"
716 //- /workspace/lib.rs crate:main deps:foo
717 use foo::St;
718 fn main() {
719 let x = St { a$0: 2 };
720 }
721 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
722 pub struct St {
723 pub a: i32,
724 }
725 "#,
726 "rust-analyzer cargo foo 0.1.0 St#a.",
727 );
728 }
729
730 #[test]
731 fn symbol_for_param() {
732 check_symbol(
733 r#"
734//- /workspace/lib.rs crate:main deps:foo
735use foo::example_mod::func;
736fn main() {
737 func(42);
738}
739//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
740pub mod example_mod {
741 pub fn func(x$0: usize) {}
742}
743"#,
744 "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
745 );
746 }
747
748 #[test]
749 fn symbol_for_closure_param() {
750 check_symbol(
751 r#"
752//- /workspace/lib.rs crate:main deps:foo
753use foo::example_mod::func;
754fn main() {
755 func();
756}
757//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
758pub mod example_mod {
759 pub fn func() {
760 let f = |x$0: usize| {};
761 }
762}
763"#,
764 "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
765 );
766 }
767
768 #[test]
769 fn local_symbol_for_local() {
770 check_symbol(
771 r#"
772 //- /workspace/lib.rs crate:main deps:foo
773 use foo::module::func;
774 fn main() {
775 func();
776 }
777 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
778 pub mod module {
779 pub fn func() {
780 let x$0 = 2;
781 }
782 }
783 "#,
784 "local enclosed by rust-analyzer cargo foo 0.1.0 module/func().",
785 );
786 }
787
788 #[test]
789 fn global_symbol_for_pub_struct() {
790 check_symbol(
791 r#"
792 //- /workspace/lib.rs crate:main
793 mod foo;
794
795 fn main() {
796 let _bar = foo::Bar { i: 0 };
797 }
798 //- /workspace/foo.rs
799 pub struct Bar$0 {
800 pub i: i32,
801 }
802 "#,
803 "rust-analyzer cargo main . foo/Bar#",
804 );
805 }
806
807 #[test]
808 fn global_symbol_for_pub_struct_reference() {
809 check_symbol(
810 r#"
811 //- /workspace/lib.rs crate:main
812 mod foo;
813
814 fn main() {
815 let _bar = foo::Bar$0 { i: 0 };
816 }
817 //- /workspace/foo.rs
818 pub struct Bar {
819 pub i: i32,
820 }
821 "#,
822 "rust-analyzer cargo main . foo/Bar#",
823 );
824 }
825
826 #[test]
827 fn symbol_for_type_alias() {
828 check_symbol(
829 r#"
830 //- /workspace/lib.rs crate:main
831 pub type MyTypeAlias$0 = u8;
832 "#,
833 "rust-analyzer cargo main . MyTypeAlias#",
834 );
835 }
836
837 #[test]
839 fn symbol_for_nested_function() {
840 check_symbol(
841 r#"
842 //- /workspace/lib.rs crate:main
843 pub fn func() {
844 pub fn inner_func$0() {}
845 }
846 "#,
847 "rust-analyzer cargo main . inner_func().",
848 );
851 }
852
853 #[test]
855 fn symbol_for_struct_in_function() {
856 check_symbol(
857 r#"
858 //- /workspace/lib.rs crate:main
859 pub fn func() {
860 struct SomeStruct$0 {}
861 }
862 "#,
863 "rust-analyzer cargo main . SomeStruct#",
864 );
867 }
868
869 #[test]
871 fn symbol_for_const_in_function() {
872 check_symbol(
873 r#"
874 //- /workspace/lib.rs crate:main
875 pub fn func() {
876 const SOME_CONST$0: u32 = 1;
877 }
878 "#,
879 "rust-analyzer cargo main . SOME_CONST.",
880 );
883 }
884
885 #[test]
887 fn symbol_for_static_in_function() {
888 check_symbol(
889 r#"
890 //- /workspace/lib.rs crate:main
891 pub fn func() {
892 static SOME_STATIC$0: u32 = 1;
893 }
894 "#,
895 "rust-analyzer cargo main . SOME_STATIC.",
896 );
899 }
900
901 #[test]
902 fn documentation_matches_doc_comment() {
903 let s = "/// foo\nfn bar() {}";
904
905 let mut host = AnalysisHost::default();
906 let change_fixture = ChangeFixture::parse(s);
907 host.raw_database_mut().apply_change(change_fixture.change);
908
909 let analysis = host.analysis();
910 let si = StaticIndex::compute(
911 &analysis,
912 VendoredLibrariesConfig::Included {
913 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
914 },
915 );
916
917 let file = si.files.first().unwrap();
918 let (_, token_id) = file.tokens.get(1).unwrap(); let token = si.tokens.get(*token_id).unwrap();
920
921 assert_eq!(token.documentation.as_ref().map(|d| d.as_str()), Some("foo"));
922 }
923
924 #[test]
925 fn function_has_enclosing_range() {
926 let s = "fn foo() {}";
927
928 let mut host = AnalysisHost::default();
929 let change_fixture = ChangeFixture::parse(s);
930 host.raw_database_mut().apply_change(change_fixture.change);
931
932 let analysis = host.analysis();
933 let si = StaticIndex::compute(
934 &analysis,
935 VendoredLibrariesConfig::Included {
936 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
937 },
938 );
939
940 let file = si.files.first().unwrap();
941 let (_, token_id) = file.tokens.get(1).unwrap(); let token = si.tokens.get(*token_id).unwrap();
943
944 let expected_range = FileRangeWrapper {
945 file_id: FileId::from_raw(0),
946 range: TextRange::new(0.into(), 11.into()),
947 };
948
949 assert_eq!(token.definition_body, Some(expected_range));
950 }
951}