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