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