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