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::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
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::new();
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
420struct SymbolGenerator {
421 token_to_symbols: FxHashMap<TokenId, Option<TokenSymbols>>,
422 local_count: usize,
423}
424
425impl SymbolGenerator {
426 fn new() -> Self {
427 SymbolGenerator { token_to_symbols: FxHashMap::default(), local_count: 0 }
428 }
429
430 fn clear_document_local_state(&mut self) {
431 self.local_count = 0;
432 }
433
434 fn token_symbols(&mut self, id: TokenId, token: &TokenStaticData) -> Option<TokenSymbols> {
435 let mut local_count = self.local_count;
436 let token_symbols = self
437 .token_to_symbols
438 .entry(id)
439 .or_insert_with(|| {
440 Some(match token.moniker.as_ref()? {
441 MonikerResult::Moniker(moniker) => TokenSymbols {
442 symbol: scip::symbol::format_symbol(moniker_to_symbol(moniker)),
443 enclosing_symbol: None,
444 is_inherent_impl: match &moniker.identifier.description[..] {
445 [.., descriptor, _] => {
447 descriptor.desc == MonikerDescriptorKind::Type
448 && descriptor.name == "impl"
449 }
450 _ => false,
451 },
452 },
453 MonikerResult::Local { enclosing_moniker } => {
454 let local_symbol = scip::types::Symbol::new_local(local_count);
455 local_count += 1;
456 TokenSymbols {
457 symbol: scip::symbol::format_symbol(local_symbol),
458 enclosing_symbol: enclosing_moniker
459 .as_ref()
460 .map(moniker_to_symbol)
461 .map(scip::symbol::format_symbol),
462 is_inherent_impl: false,
463 }
464 }
465 })
466 })
467 .clone();
468 self.local_count = local_count;
469 token_symbols
470 }
471}
472
473fn moniker_to_symbol(moniker: &Moniker) -> scip_types::Symbol {
474 scip_types::Symbol {
475 scheme: "rust-analyzer".into(),
476 package: Some(scip_types::Package {
477 manager: "cargo".to_owned(),
478 name: moniker.package_information.name.clone(),
479 version: moniker.package_information.version.clone().unwrap_or_else(|| ".".to_owned()),
480 special_fields: Default::default(),
481 })
482 .into(),
483 descriptors: moniker_descriptors(&moniker.identifier),
484 special_fields: Default::default(),
485 }
486}
487
488fn moniker_descriptors(identifier: &MonikerIdentifier) -> Vec<scip_types::Descriptor> {
489 use scip_types::descriptor::Suffix::*;
490 identifier
491 .description
492 .iter()
493 .map(|desc| {
494 new_descriptor_str(
495 &desc.name,
496 match desc.desc {
497 MonikerDescriptorKind::Namespace => Namespace,
498 MonikerDescriptorKind::Type => Type,
499 MonikerDescriptorKind::Term => Term,
500 MonikerDescriptorKind::Method => Method,
501 MonikerDescriptorKind::TypeParameter => TypeParameter,
502 MonikerDescriptorKind::Parameter => Parameter,
503 MonikerDescriptorKind::Macro => Macro,
504 MonikerDescriptorKind::Meta => Meta,
505 },
506 )
507 })
508 .collect()
509}
510
511#[cfg(test)]
512mod test {
513 use super::*;
514 use ide::{FilePosition, TextSize};
515 use test_fixture::ChangeFixture;
516 use vfs::VfsPath;
517
518 fn position(#[ra_ap_rust_analyzer::rust_fixture] ra_fixture: &str) -> (AnalysisHost, FilePosition) {
519 let mut host = AnalysisHost::default();
520 let change_fixture = ChangeFixture::parse(ra_fixture);
521 host.raw_database_mut().apply_change(change_fixture.change);
522 let (file_id, range_or_offset) =
523 change_fixture.file_position.expect("expected a marker ()");
524 let offset = range_or_offset.expect_offset();
525 (host, FilePosition { file_id: file_id.into(), offset })
526 }
527
528 #[track_caller]
530 fn check_symbol(#[ra_ap_rust_analyzer::rust_fixture] ra_fixture: &str, expected: &str) {
531 let (host, position) = position(ra_fixture);
532
533 let analysis = host.analysis();
534 let si = StaticIndex::compute(
535 &analysis,
536 VendoredLibrariesConfig::Included {
537 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
538 },
539 );
540
541 let FilePosition { file_id, offset } = position;
542
543 let mut found_symbol = None;
544 for file in &si.files {
545 if file.file_id != file_id {
546 continue;
547 }
548 for &(range, id) in &file.tokens {
549 if range.start() != TextSize::from(0) && range.contains(offset - TextSize::from(1))
551 {
552 let token = si.tokens.get(id).unwrap();
553 found_symbol = match token.moniker.as_ref() {
554 None => None,
555 Some(MonikerResult::Moniker(moniker)) => {
556 Some(scip::symbol::format_symbol(moniker_to_symbol(moniker)))
557 }
558 Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => {
559 Some(format!(
560 "local enclosed by {}",
561 scip::symbol::format_symbol(moniker_to_symbol(moniker))
562 ))
563 }
564 Some(MonikerResult::Local { enclosing_moniker: None }) => {
565 Some("unenclosed local".to_owned())
566 }
567 };
568 break;
569 }
570 }
571 }
572
573 if expected.is_empty() {
574 assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}");
575 return;
576 }
577
578 assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}");
579 assert_eq!(found_symbol.unwrap(), expected);
580 }
581
582 #[test]
583 fn basic() {
584 check_symbol(
585 r#"
586//- /workspace/lib.rs crate:main deps:foo
587use foo::example_mod::func;
588fn main() {
589 func$0();
590}
591//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
592pub mod example_mod {
593 pub fn func() {}
594}
595"#,
596 "rust-analyzer cargo foo 0.1.0 example_mod/func().",
597 );
598 }
599
600 #[test]
601 fn symbol_for_trait() {
602 check_symbol(
603 r#"
604//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
605pub mod module {
606 pub trait MyTrait {
607 pub fn func$0() {}
608 }
609}
610"#,
611 "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",
612 );
613 }
614
615 #[test]
616 fn symbol_for_trait_alias() {
617 check_symbol(
618 r#"
619//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
620#![feature(trait_alias)]
621pub mod module {
622 pub trait MyTrait {}
623 pub trait MyTraitAlias$0 = MyTrait;
624}
625"#,
626 "rust-analyzer cargo foo 0.1.0 module/MyTraitAlias#",
627 );
628 }
629
630 #[test]
631 fn symbol_for_trait_constant() {
632 check_symbol(
633 r#"
634 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
635 pub mod module {
636 pub trait MyTrait {
637 const MY_CONST$0: u8;
638 }
639 }
640 "#,
641 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",
642 );
643 }
644
645 #[test]
646 fn symbol_for_trait_type() {
647 check_symbol(
648 r#"
649 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
650 pub mod module {
651 pub trait MyTrait {
652 type MyType$0;
653 }
654 }
655 "#,
656 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#",
657 );
658 }
659
660 #[test]
661 fn symbol_for_trait_impl_function() {
662 check_symbol(
663 r#"
664 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
665 pub mod module {
666 pub trait MyTrait {
667 pub fn func() {}
668 }
669
670 struct MyStruct {}
671
672 impl MyTrait for MyStruct {
673 pub fn func$0() {}
674 }
675 }
676 "#,
677 "rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().",
678 );
679 }
680
681 #[test]
682 fn symbol_for_field() {
683 check_symbol(
684 r#"
685 //- /workspace/lib.rs crate:main deps:foo
686 use foo::St;
687 fn main() {
688 let x = St { a$0: 2 };
689 }
690 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
691 pub struct St {
692 pub a: i32,
693 }
694 "#,
695 "rust-analyzer cargo foo 0.1.0 St#a.",
696 );
697 }
698
699 #[test]
700 fn symbol_for_param() {
701 check_symbol(
702 r#"
703//- /workspace/lib.rs crate:main deps:foo
704use foo::example_mod::func;
705fn main() {
706 func(42);
707}
708//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
709pub mod example_mod {
710 pub fn func(x$0: usize) {}
711}
712"#,
713 "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
714 );
715 }
716
717 #[test]
718 fn symbol_for_closure_param() {
719 check_symbol(
720 r#"
721//- /workspace/lib.rs crate:main deps:foo
722use foo::example_mod::func;
723fn main() {
724 func();
725}
726//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
727pub mod example_mod {
728 pub fn func() {
729 let f = |x$0: usize| {};
730 }
731}
732"#,
733 "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",
734 );
735 }
736
737 #[test]
738 fn local_symbol_for_local() {
739 check_symbol(
740 r#"
741 //- /workspace/lib.rs crate:main deps:foo
742 use foo::module::func;
743 fn main() {
744 func();
745 }
746 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library
747 pub mod module {
748 pub fn func() {
749 let x$0 = 2;
750 }
751 }
752 "#,
753 "local enclosed by rust-analyzer cargo foo 0.1.0 module/func().",
754 );
755 }
756
757 #[test]
758 fn global_symbol_for_pub_struct() {
759 check_symbol(
760 r#"
761 //- /workspace/lib.rs crate:main
762 mod foo;
763
764 fn main() {
765 let _bar = foo::Bar { i: 0 };
766 }
767 //- /workspace/foo.rs
768 pub struct Bar$0 {
769 pub i: i32,
770 }
771 "#,
772 "rust-analyzer cargo main . foo/Bar#",
773 );
774 }
775
776 #[test]
777 fn global_symbol_for_pub_struct_reference() {
778 check_symbol(
779 r#"
780 //- /workspace/lib.rs crate:main
781 mod foo;
782
783 fn main() {
784 let _bar = foo::Bar$0 { i: 0 };
785 }
786 //- /workspace/foo.rs
787 pub struct Bar {
788 pub i: i32,
789 }
790 "#,
791 "rust-analyzer cargo main . foo/Bar#",
792 );
793 }
794
795 #[test]
796 fn symbol_for_type_alias() {
797 check_symbol(
798 r#"
799 //- /workspace/lib.rs crate:main
800 pub type MyTypeAlias$0 = u8;
801 "#,
802 "rust-analyzer cargo main . MyTypeAlias#",
803 );
804 }
805
806 #[test]
808 fn symbol_for_nested_function() {
809 check_symbol(
810 r#"
811 //- /workspace/lib.rs crate:main
812 pub fn func() {
813 pub fn inner_func$0() {}
814 }
815 "#,
816 "rust-analyzer cargo main . inner_func().",
817 );
820 }
821
822 #[test]
824 fn symbol_for_struct_in_function() {
825 check_symbol(
826 r#"
827 //- /workspace/lib.rs crate:main
828 pub fn func() {
829 struct SomeStruct$0 {}
830 }
831 "#,
832 "rust-analyzer cargo main . SomeStruct#",
833 );
836 }
837
838 #[test]
840 fn symbol_for_const_in_function() {
841 check_symbol(
842 r#"
843 //- /workspace/lib.rs crate:main
844 pub fn func() {
845 const SOME_CONST$0: u32 = 1;
846 }
847 "#,
848 "rust-analyzer cargo main . SOME_CONST.",
849 );
852 }
853
854 #[test]
856 fn symbol_for_static_in_function() {
857 check_symbol(
858 r#"
859 //- /workspace/lib.rs crate:main
860 pub fn func() {
861 static SOME_STATIC$0: u32 = 1;
862 }
863 "#,
864 "rust-analyzer cargo main . SOME_STATIC.",
865 );
868 }
869
870 #[test]
871 fn documentation_matches_doc_comment() {
872 let s = "/// foo\nfn bar() {}";
873
874 let mut host = AnalysisHost::default();
875 let change_fixture = ChangeFixture::parse(s);
876 host.raw_database_mut().apply_change(change_fixture.change);
877
878 let analysis = host.analysis();
879 let si = StaticIndex::compute(
880 &analysis,
881 VendoredLibrariesConfig::Included {
882 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),
883 },
884 );
885
886 let file = si.files.first().unwrap();
887 let (_, token_id) = file.tokens.get(1).unwrap(); let token = si.tokens.get(*token_id).unwrap();
889
890 assert_eq!(token.documentation.as_ref().map(|d| d.as_str()), Some("foo"));
891 }
892}