Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
let token = si.tokens.get(id).unwrap();
1//! SCIP generator23use std::{path::PathBuf, time::Instant};45use ide::{6 AnalysisHost, LineCol, Moniker, MonikerDescriptorKind, MonikerIdentifier, MonikerResult,7 RootDatabase, StaticIndex, StaticIndexedFile, SymbolInformationKind, TextRange, TokenId,8 TokenStaticData, VendoredLibrariesConfig,9};10use ide_db::line_index;11use load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace_at};12use rustc_hash::{FxHashMap, FxHashSet};13use scip::types::{self as scip_types, SymbolInformation};14use tracing::error;15use vfs::FileId;1617use crate::{18 cli::flags,19 config::ConfigChange,20 line_index::{LineEndings, LineIndex, PositionEncoding},21};2223impl flags::Scip {24 pub fn run(self) -> anyhow::Result<()> {25 eprintln!("Generating SCIP start...");26 let now = Instant::now();2728 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();3132 let mut config = crate::config::Config::new(33 root.clone(),34 lsp_types::ClientCapabilities::default(),35 vec![],36 None,37 );3839 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);4445 let error_sink;46 (config, error_sink, _) = config.apply_change(change);4748 // FIXME @alibektas : What happens to errors without logging?49 error!(?error_sink, "Config Error(s)");50 }51 let load_cargo_config = LoadCargoConfig {52 load_out_dirs_from_check: true,53 with_proc_macro_server: ProcMacroServerChoice::Sysroot,54 prefill_caches: true,55 num_worker_threads: self.num_threads.unwrap_or_else(num_cpus::get_physical),56 proc_macro_processes: config.proc_macro_num_processes(),57 };58 let cargo_config = config.cargo(None);59 let (db, vfs, _) = load_workspace_at(60 root.as_path().as_ref(),61 &cargo_config,62 &load_cargo_config,63 &no_progress,64 )?;65 let host = AnalysisHost::with_database(db);66 let db = host.raw_database();67 let analysis = host.analysis();6869 let vendored_libs_config = if self.exclude_vendored_libraries {70 VendoredLibrariesConfig::Excluded71 } else {72 VendoredLibrariesConfig::Included { workspace_root: &root.clone().into() }73 };7475 let si = StaticIndex::compute(&analysis, vendored_libs_config);7677 let metadata = scip_types::Metadata {78 version: scip_types::ProtocolVersion::UnspecifiedProtocolVersion.into(),79 tool_info: Some(scip_types::ToolInfo {80 name: "rust-analyzer".to_owned(),81 version: format!("{}", crate::version::version()),82 arguments: vec![],83 special_fields: Default::default(),84 })85 .into(),86 project_root: format!("file://{root}"),87 text_document_encoding: scip_types::TextEncoding::UTF8.into(),88 special_fields: Default::default(),89 };9091 let mut documents = Vec::new();9293 // All TokenIds where an Occurrence has been emitted that references a symbol.94 let mut token_ids_referenced: FxHashSet<TokenId> = FxHashSet::default();95 // All TokenIds where the SymbolInformation has been written to the document.96 let mut token_ids_emitted: FxHashSet<TokenId> = FxHashSet::default();97 // All FileIds emitted as documents.98 let mut file_ids_emitted: FxHashSet<FileId> = FxHashSet::default();99100 // All non-local symbols encountered, for detecting duplicate symbol errors.101 let mut nonlocal_symbols_emitted: FxHashSet<String> = FxHashSet::default();102 // List of (source_location, symbol) for duplicate symbol errors to report.103 let mut duplicate_symbol_errors: Vec<(String, String)> = Vec::new();104 // This is called after definitions have been deduplicated by token_ids_emitted. The purpose105 // is to detect reuse of symbol names because this causes ambiguity about their meaning.106 let mut record_error_if_symbol_already_used =107 |symbol: String,108 is_inherent_impl: bool,109 relative_path: &str,110 line_index: &LineIndex,111 text_range: TextRange| {112 let is_local = symbol.starts_with("local ");113 if !is_local && !nonlocal_symbols_emitted.insert(symbol.clone()) {114 if is_inherent_impl {115 // FIXME: See #18772. Duplicate SymbolInformation for inherent impls is116 // omitted. It would be preferable to emit them with numbers with117 // disambiguation, but this is more complex to implement.118 false119 } else {120 let source_location =121 text_range_to_string(relative_path, line_index, text_range);122 duplicate_symbol_errors.push((source_location, symbol));123 // Keep duplicate SymbolInformation. This behavior is preferred over124 // omitting so that the issue might be visible within downstream tools.125 true126 }127 } else {128 true129 }130 };131132 // Generates symbols from token monikers.133 let mut symbol_generator = SymbolGenerator::default();134135 for StaticIndexedFile { file_id, tokens, .. } in si.files {136 symbol_generator.clear_document_local_state();137138 let Some(relative_path) = get_relative_filepath(&vfs, &root, file_id) else { continue };139 let line_index = get_line_index(db, file_id);140141 let mut occurrences = Vec::new();142 let mut symbols = Vec::new();143144 for (text_range, id) in tokens.into_iter() {145 let token = si.tokens.get(id).unwrap();146147 let Some(TokenSymbols { symbol, enclosing_symbol, is_inherent_impl }) =148 symbol_generator.token_symbols(id, token)149 else {150 // token did not have a moniker, so there is no reasonable occurrence to emit151 // see ide::moniker::def_to_moniker152 continue;153 };154155 let is_defined_in_this_document = match token.definition {156 Some(def) => def.file_id == file_id,157 _ => false,158 };159 if is_defined_in_this_document {160 if token_ids_emitted.insert(id) {161 // token_ids_emitted does deduplication. This checks that this results162 // in unique emitted symbols, as otherwise references are ambiguous.163 let should_emit = record_error_if_symbol_already_used(164 symbol.clone(),165 is_inherent_impl,166 relative_path.as_str(),167 &line_index,168 text_range,169 );170 if should_emit {171 symbols.push(compute_symbol_info(172 symbol.clone(),173 enclosing_symbol,174 token,175 ));176 }177 }178 } else {179 token_ids_referenced.insert(id);180 }181182 // If the range of the def and the range of the token are the same, this must be the definition.183 // they also must be in the same file. See https://github.com/rust-lang/rust-analyzer/pull/17988184 let is_definition = match token.definition {185 Some(def) => def.file_id == file_id && def.range == text_range,186 _ => false,187 };188189 let mut symbol_roles = Default::default();190 if is_definition {191 symbol_roles |= scip_types::SymbolRole::Definition as i32;192 }193194 let enclosing_range = match token.definition_body {195 Some(def_body) if def_body.file_id == file_id => {196 text_range_to_scip_range(&line_index, def_body.range)197 }198 _ => Vec::new(),199 };200201 occurrences.push(scip_types::Occurrence {202 range: text_range_to_scip_range(&line_index, text_range),203 symbol,204 symbol_roles,205 override_documentation: Vec::new(),206 syntax_kind: Default::default(),207 diagnostics: Vec::new(),208 special_fields: Default::default(),209 enclosing_range,210 });211 }212213 if occurrences.is_empty() {214 continue;215 }216217 let position_encoding =218 scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into();219 documents.push(scip_types::Document {220 relative_path,221 language: "rust".to_owned(),222 occurrences,223 symbols,224 text: String::new(),225 position_encoding,226 special_fields: Default::default(),227 });228 if !file_ids_emitted.insert(file_id) {229 panic!("Invariant violation: file emitted multiple times.");230 }231 }232233 // Collect all symbols referenced by the files but not defined within them.234 let mut external_symbols = Vec::new();235 for id in token_ids_referenced.difference(&token_ids_emitted) {236 let id = *id;237 let token = si.tokens.get(id).unwrap();238239 let Some(definition) = token.definition else {240 break;241 };242243 let file_id = definition.file_id;244 let Some(relative_path) = get_relative_filepath(&vfs, &root, file_id) else { continue };245 let line_index = get_line_index(db, file_id);246 let text_range = definition.range;247 if file_ids_emitted.contains(&file_id) {248 tracing::error!(249 "Bug: definition at {} should have been in an SCIP document but was not.",250 text_range_to_string(relative_path.as_str(), &line_index, text_range)251 );252 continue;253 }254255 let TokenSymbols { symbol, enclosing_symbol, .. } = symbol_generator256 .token_symbols(id, token)257 .expect("To have been referenced, the symbol must be in the cache.");258259 record_error_if_symbol_already_used(260 symbol.clone(),261 false,262 relative_path.as_str(),263 &line_index,264 text_range,265 );266 external_symbols.push(compute_symbol_info(symbol.clone(), enclosing_symbol, token));267 }268269 let index = scip_types::Index {270 metadata: Some(metadata).into(),271 documents,272 external_symbols,273 special_fields: Default::default(),274 };275276 if !duplicate_symbol_errors.is_empty() {277 eprintln!("{DUPLICATE_SYMBOLS_MESSAGE}");278 for (source_location, symbol) in duplicate_symbol_errors {279 eprintln!("{source_location}");280 eprintln!(" Duplicate symbol: {symbol}");281 eprintln!();282 }283 }284285 let out_path = self.output.unwrap_or_else(|| PathBuf::from(r"index.scip"));286 scip::write_message_to_file(out_path, index)287 .map_err(|err| anyhow::format_err!("Failed to write scip to file: {}", err))?;288289 eprintln!("Generating SCIP finished {:?}", now.elapsed());290 Ok(())291 }292}293294// FIXME: Known buggy cases are described here.295const DUPLICATE_SYMBOLS_MESSAGE: &str = "296Encountered duplicate scip symbols, indicating an internal rust-analyzer bug. These duplicates are297included in the output, but this causes information lookup to be ambiguous and so information about298these symbols presented by downstream tools may be incorrect.299300Known rust-analyzer bugs that can cause this:301302 * Definitions in crate example binaries which have the same symbol as definitions in the library303 or some other example.304305 * Struct/enum/const/static/impl definitions nested in a function do not mention the function name.306 See #18771.307308Duplicate symbols encountered:309";310311fn compute_symbol_info(312 symbol: String,313 enclosing_symbol: Option<String>,314 token: &TokenStaticData,315) -> SymbolInformation {316 let documentation = match &token.documentation {317 Some(doc) => vec![doc.as_str().to_owned()],318 None => vec![],319 };320321 let position_encoding = scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into();322 let signature_documentation = token.signature.clone().map(|text| scip_types::Document {323 relative_path: "".to_owned(),324 language: "rust".to_owned(),325 text,326 position_encoding,327 ..Default::default()328 });329 scip_types::SymbolInformation {330 symbol,331 documentation,332 relationships: Vec::new(),333 special_fields: Default::default(),334 kind: symbol_kind(token.kind).into(),335 display_name: token.display_name.clone().unwrap_or_default(),336 signature_documentation: signature_documentation.into(),337 enclosing_symbol: enclosing_symbol.unwrap_or_default(),338 }339}340341fn get_relative_filepath(342 vfs: &vfs::Vfs,343 rootpath: &vfs::AbsPathBuf,344 file_id: ide::FileId,345) -> Option<String> {346 Some(vfs.file_path(file_id).as_path()?.strip_prefix(rootpath)?.as_str().to_owned())347}348349fn get_line_index(db: &RootDatabase, file_id: FileId) -> LineIndex {350 LineIndex {351 index: line_index(db, file_id).clone(),352 encoding: PositionEncoding::Utf8,353 endings: LineEndings::Unix,354 }355}356357// SCIP Ranges have a (very large) optimization that ranges if they are on the same line358// only encode as a vector of [start_line, start_col, end_col].359//360// This transforms a line index into the optimized SCIP Range.361fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> {362 let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());363 let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());364365 if start_line == end_line {366 vec![start_line as i32, start_col as i32, end_col as i32]367 } else {368 vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32]369 }370}371372fn text_range_to_string(relative_path: &str, line_index: &LineIndex, range: TextRange) -> String {373 let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());374 let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());375376 format!("{relative_path}:{start_line}:{start_col}-{end_line}:{end_col}")377}378379fn new_descriptor_str(380 name: &str,381 suffix: scip_types::descriptor::Suffix,382) -> scip_types::Descriptor {383 scip_types::Descriptor {384 name: name.to_owned(),385 disambiguator: "".to_owned(),386 suffix: suffix.into(),387 special_fields: Default::default(),388 }389}390391fn symbol_kind(kind: SymbolInformationKind) -> scip_types::symbol_information::Kind {392 use scip_types::symbol_information::Kind as ScipKind;393 match kind {394 SymbolInformationKind::AssociatedType => ScipKind::AssociatedType,395 SymbolInformationKind::Attribute => ScipKind::Attribute,396 SymbolInformationKind::Constant => ScipKind::Constant,397 SymbolInformationKind::Enum => ScipKind::Enum,398 SymbolInformationKind::EnumMember => ScipKind::EnumMember,399 SymbolInformationKind::Field => ScipKind::Field,400 SymbolInformationKind::Function => ScipKind::Function,401 SymbolInformationKind::Macro => ScipKind::Macro,402 SymbolInformationKind::Method => ScipKind::Method,403 SymbolInformationKind::Module => ScipKind::Module,404 SymbolInformationKind::Parameter => ScipKind::Parameter,405 SymbolInformationKind::SelfParameter => ScipKind::SelfParameter,406 SymbolInformationKind::StaticMethod => ScipKind::StaticMethod,407 SymbolInformationKind::StaticVariable => ScipKind::StaticVariable,408 SymbolInformationKind::Struct => ScipKind::Struct,409 SymbolInformationKind::Trait => ScipKind::Trait,410 SymbolInformationKind::TraitMethod => ScipKind::TraitMethod,411 SymbolInformationKind::Type => ScipKind::Type,412 SymbolInformationKind::TypeAlias => ScipKind::TypeAlias,413 SymbolInformationKind::TypeParameter => ScipKind::TypeParameter,414 SymbolInformationKind::Union => ScipKind::Union,415 SymbolInformationKind::Variable => ScipKind::Variable,416 }417}418419#[derive(Clone)]420struct TokenSymbols {421 symbol: String,422 /// Definition that contains this one. Only set when `symbol` is local.423 enclosing_symbol: Option<String>,424 /// True if this symbol is for an inherent impl. This is used to only emit `SymbolInformation`425 /// for a struct's first inherent impl, since their symbol names are not disambiguated.426 is_inherent_impl: bool,427}428429#[derive(Default)]430struct SymbolGenerator {431 token_to_symbols: FxHashMap<TokenId, Option<TokenSymbols>>,432 local_count: usize,433}434435impl SymbolGenerator {436 fn clear_document_local_state(&mut self) {437 self.local_count = 0;438 }439440 fn token_symbols(&mut self, id: TokenId, token: &TokenStaticData) -> Option<TokenSymbols> {441 let mut local_count = self.local_count;442 let token_symbols = self443 .token_to_symbols444 .entry(id)445 .or_insert_with(|| {446 Some(match token.moniker.as_ref()? {447 MonikerResult::Moniker(moniker) => TokenSymbols {448 symbol: scip::symbol::format_symbol(moniker_to_symbol(moniker)),449 enclosing_symbol: None,450 is_inherent_impl: match &moniker.identifier.description[..] {451 // inherent impls are represented as impl#[SelfType]452 [.., descriptor, _] => {453 descriptor.desc == MonikerDescriptorKind::Type454 && descriptor.name == "impl"455 }456 _ => false,457 },458 },459 MonikerResult::Local { enclosing_moniker } => {460 let local_symbol = scip::types::Symbol::new_local(local_count);461 local_count += 1;462 TokenSymbols {463 symbol: scip::symbol::format_symbol(local_symbol),464 enclosing_symbol: enclosing_moniker465 .as_ref()466 .map(moniker_to_symbol)467 .map(scip::symbol::format_symbol),468 is_inherent_impl: false,469 }470 }471 })472 })473 .clone();474 self.local_count = local_count;475 token_symbols476 }477}478479fn moniker_to_symbol(moniker: &Moniker) -> scip_types::Symbol {480 scip_types::Symbol {481 scheme: "rust-analyzer".into(),482 package: Some(scip_types::Package {483 manager: "cargo".to_owned(),484 name: moniker.package_information.name.clone(),485 version: moniker.package_information.version.clone().unwrap_or_else(|| ".".to_owned()),486 special_fields: Default::default(),487 })488 .into(),489 descriptors: moniker_descriptors(&moniker.identifier),490 special_fields: Default::default(),491 }492}493494fn moniker_descriptors(identifier: &MonikerIdentifier) -> Vec<scip_types::Descriptor> {495 use scip_types::descriptor::Suffix::*;496 identifier497 .description498 .iter()499 .map(|desc| {500 new_descriptor_str(501 &desc.name,502 match desc.desc {503 MonikerDescriptorKind::Namespace => Namespace,504 MonikerDescriptorKind::Type => Type,505 MonikerDescriptorKind::Term => Term,506 MonikerDescriptorKind::Method => Method,507 MonikerDescriptorKind::TypeParameter => TypeParameter,508 MonikerDescriptorKind::Parameter => Parameter,509 MonikerDescriptorKind::Macro => Macro,510 MonikerDescriptorKind::Meta => Meta,511 },512 )513 })514 .collect()515}516517#[cfg(test)]518mod test {519 use super::*;520 use hir::FileRangeWrapper;521 use ide::{FilePosition, TextSize};522 use test_fixture::ChangeFixture;523 use vfs::VfsPath;524525 fn position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (AnalysisHost, FilePosition) {526 let mut host = AnalysisHost::default();527 let change_fixture = ChangeFixture::parse(ra_fixture);528 host.raw_database_mut().apply_change(change_fixture.change);529 let (file_id, range_or_offset) =530 change_fixture.file_position.expect("expected a marker ()");531 let offset = range_or_offset.expect_offset();532 let position = FilePosition { file_id: file_id.file_id(), offset };533 (host, position)534 }535536 /// If expected == "", then assert that there are no symbols (this is basically local symbol)537 #[track_caller]538 fn check_symbol(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: &str) {539 let (host, position) = position(ra_fixture);540541 let analysis = host.analysis();542 let si = StaticIndex::compute(543 &analysis,544 VendoredLibrariesConfig::Included {545 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),546 },547 );548549 let FilePosition { file_id, offset } = position;550551 let mut found_symbol = None;552 for file in &si.files {553 if file.file_id != file_id {554 continue;555 }556 for &(range, id) in &file.tokens {557 // check if cursor is within token, ignoring token for the module defined by the file (whose range is the whole file)558 if range.start() != TextSize::from(0) && range.contains(offset - TextSize::from(1))559 {560 let token = si.tokens.get(id).unwrap();561 found_symbol = match token.moniker.as_ref() {562 None => None,563 Some(MonikerResult::Moniker(moniker)) => {564 Some(scip::symbol::format_symbol(moniker_to_symbol(moniker)))565 }566 Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => {567 Some(format!(568 "local enclosed by {}",569 scip::symbol::format_symbol(moniker_to_symbol(moniker))570 ))571 }572 Some(MonikerResult::Local { enclosing_moniker: None }) => {573 Some("unenclosed local".to_owned())574 }575 };576 break;577 }578 }579 }580581 if expected.is_empty() {582 assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}");583 return;584 }585586 assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}");587 assert_eq!(found_symbol.unwrap(), expected);588 }589590 #[test]591 fn basic() {592 check_symbol(593 r#"594//- /workspace/lib.rs crate:main deps:foo595use foo::example_mod::func;596fn main() {597 func$0();598}599//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library600pub mod example_mod {601 pub fn func() {}602}603"#,604 "rust-analyzer cargo foo 0.1.0 example_mod/func().",605 );606 }607608 #[test]609 fn operator_overload() {610 check_symbol(611 r#"612//- minicore: add613//- /workspace/lib.rs crate:main614use core::ops::AddAssign;615616struct S;617618impl AddAssign for S {619 fn add_assign(&mut self, _rhs: Self) {}620}621622fn main() {623 let mut s = S;624 s +=$0 S;625}626"#,627 "rust-analyzer cargo main . impl#[S][`AddAssign<Self>`]add_assign().",628 );629 }630631 #[test]632 fn symbol_for_trait() {633 check_symbol(634 r#"635//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library636pub mod module {637 pub trait MyTrait {638 pub fn func$0() {}639 }640}641"#,642 "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",643 );644 }645646 #[test]647 fn symbol_for_trait_alias() {648 check_symbol(649 r#"650//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library651#![feature(trait_alias)]652pub mod module {653 pub trait MyTrait {}654 pub trait MyTraitAlias$0 = MyTrait;655}656"#,657 "rust-analyzer cargo foo 0.1.0 module/MyTraitAlias#",658 );659 }660661 #[test]662 fn symbol_for_trait_constant() {663 check_symbol(664 r#"665 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library666 pub mod module {667 pub trait MyTrait {668 const MY_CONST$0: u8;669 }670 }671 "#,672 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",673 );674 }675676 #[test]677 fn symbol_for_trait_type() {678 check_symbol(679 r#"680 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library681 pub mod module {682 pub trait MyTrait {683 type MyType$0;684 }685 }686 "#,687 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#",688 );689 }690691 #[test]692 fn symbol_for_trait_impl_function() {693 check_symbol(694 r#"695 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library696 pub mod module {697 pub trait MyTrait {698 pub fn func() {}699 }700701 struct MyStruct {}702703 impl MyTrait for MyStruct {704 pub fn func$0() {}705 }706 }707 "#,708 "rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().",709 );710 }711712 #[test]713 fn symbol_for_field() {714 check_symbol(715 r#"716 //- /workspace/lib.rs crate:main deps:foo717 use foo::St;718 fn main() {719 let x = St { a$0: 2 };720 }721 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library722 pub struct St {723 pub a: i32,724 }725 "#,726 "rust-analyzer cargo foo 0.1.0 St#a.",727 );728 }729730 #[test]731 fn symbol_for_param() {732 check_symbol(733 r#"734//- /workspace/lib.rs crate:main deps:foo735use foo::example_mod::func;736fn main() {737 func(42);738}739//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library740pub mod example_mod {741 pub fn func(x$0: usize) {}742}743"#,744 "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",745 );746 }747748 #[test]749 fn symbol_for_closure_param() {750 check_symbol(751 r#"752//- /workspace/lib.rs crate:main deps:foo753use foo::example_mod::func;754fn main() {755 func();756}757//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library758pub mod example_mod {759 pub fn func() {760 let f = |x$0: usize| {};761 }762}763"#,764 "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",765 );766 }767768 #[test]769 fn local_symbol_for_local() {770 check_symbol(771 r#"772 //- /workspace/lib.rs crate:main deps:foo773 use foo::module::func;774 fn main() {775 func();776 }777 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library778 pub mod module {779 pub fn func() {780 let x$0 = 2;781 }782 }783 "#,784 "local enclosed by rust-analyzer cargo foo 0.1.0 module/func().",785 );786 }787788 #[test]789 fn global_symbol_for_pub_struct() {790 check_symbol(791 r#"792 //- /workspace/lib.rs crate:main793 mod foo;794795 fn main() {796 let _bar = foo::Bar { i: 0 };797 }798 //- /workspace/foo.rs799 pub struct Bar$0 {800 pub i: i32,801 }802 "#,803 "rust-analyzer cargo main . foo/Bar#",804 );805 }806807 #[test]808 fn global_symbol_for_pub_struct_reference() {809 check_symbol(810 r#"811 //- /workspace/lib.rs crate:main812 mod foo;813814 fn main() {815 let _bar = foo::Bar$0 { i: 0 };816 }817 //- /workspace/foo.rs818 pub struct Bar {819 pub i: i32,820 }821 "#,822 "rust-analyzer cargo main . foo/Bar#",823 );824 }825826 #[test]827 fn symbol_for_type_alias() {828 check_symbol(829 r#"830 //- /workspace/lib.rs crate:main831 pub type MyTypeAlias$0 = u8;832 "#,833 "rust-analyzer cargo main . MyTypeAlias#",834 );835 }836837 // FIXME: This test represents current misbehavior.838 #[test]839 fn symbol_for_nested_function() {840 check_symbol(841 r#"842 //- /workspace/lib.rs crate:main843 pub fn func() {844 pub fn inner_func$0() {}845 }846 "#,847 "rust-analyzer cargo main . inner_func().",848 // FIXME: This should be a local:849 // "local enclosed by rust-analyzer cargo main . func().",850 );851 }852853 // FIXME: This test represents current misbehavior.854 #[test]855 fn symbol_for_struct_in_function() {856 check_symbol(857 r#"858 //- /workspace/lib.rs crate:main859 pub fn func() {860 struct SomeStruct$0 {}861 }862 "#,863 "rust-analyzer cargo main . SomeStruct#",864 // FIXME: This should be a local:865 // "local enclosed by rust-analyzer cargo main . func().",866 );867 }868869 // FIXME: This test represents current misbehavior.870 #[test]871 fn symbol_for_const_in_function() {872 check_symbol(873 r#"874 //- /workspace/lib.rs crate:main875 pub fn func() {876 const SOME_CONST$0: u32 = 1;877 }878 "#,879 "rust-analyzer cargo main . SOME_CONST.",880 // FIXME: This should be a local:881 // "local enclosed by rust-analyzer cargo main . func().",882 );883 }884885 // FIXME: This test represents current misbehavior.886 #[test]887 fn symbol_for_static_in_function() {888 check_symbol(889 r#"890 //- /workspace/lib.rs crate:main891 pub fn func() {892 static SOME_STATIC$0: u32 = 1;893 }894 "#,895 "rust-analyzer cargo main . SOME_STATIC.",896 // FIXME: This should be a local:897 // "local enclosed by rust-analyzer cargo main . func().",898 );899 }900901 #[test]902 fn documentation_matches_doc_comment() {903 let s = "/// foo\nfn bar() {}";904905 let mut host = AnalysisHost::default();906 let change_fixture = ChangeFixture::parse(s);907 host.raw_database_mut().apply_change(change_fixture.change);908909 let analysis = host.analysis();910 let si = StaticIndex::compute(911 &analysis,912 VendoredLibrariesConfig::Included {913 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),914 },915 );916917 let file = si.files.first().unwrap();918 let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `bar`919 let token = si.tokens.get(*token_id).unwrap();920921 assert_eq!(token.documentation.as_ref().map(|d| d.as_str()), Some("foo"));922 }923924 #[test]925 fn function_has_enclosing_range() {926 let s = "fn foo() {}";927928 let mut host = AnalysisHost::default();929 let change_fixture = ChangeFixture::parse(s);930 host.raw_database_mut().apply_change(change_fixture.change);931932 let analysis = host.analysis();933 let si = StaticIndex::compute(934 &analysis,935 VendoredLibrariesConfig::Included {936 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),937 },938 );939940 let file = si.files.first().unwrap();941 let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `foo`942 let token = si.tokens.get(*token_id).unwrap();943944 let expected_range = FileRangeWrapper {945 file_id: FileId::from_raw(0),946 range: TextRange::new(0.into(), 11.into()),947 };948949 assert_eq!(token.definition_body, Some(expected_range));950 }951}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.