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, false);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 continue;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 false,548 );549550 let FilePosition { file_id, offset } = position;551552 let mut found_symbol = None;553 for file in &si.files {554 if file.file_id != file_id {555 continue;556 }557 for &(range, id) in &file.tokens {558 // check if cursor is within token, ignoring token for the module defined by the file (whose range is the whole file)559 if range.start() != TextSize::from(0) && range.contains(offset - TextSize::from(1))560 {561 let token = si.tokens.get(id).unwrap();562 found_symbol = match token.moniker.as_ref() {563 None => None,564 Some(MonikerResult::Moniker(moniker)) => {565 Some(scip::symbol::format_symbol(moniker_to_symbol(moniker)))566 }567 Some(MonikerResult::Local { enclosing_moniker: Some(moniker) }) => {568 Some(format!(569 "local enclosed by {}",570 scip::symbol::format_symbol(moniker_to_symbol(moniker))571 ))572 }573 Some(MonikerResult::Local { enclosing_moniker: None }) => {574 Some("unenclosed local".to_owned())575 }576 };577 break;578 }579 }580 }581582 if expected.is_empty() {583 assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}");584 return;585 }586587 assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}");588 assert_eq!(found_symbol.unwrap(), expected);589 }590591 #[test]592 fn basic() {593 check_symbol(594 r#"595//- /workspace/lib.rs crate:main deps:foo596use foo::example_mod::func;597fn main() {598 func$0();599}600//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library601pub mod example_mod {602 pub fn func() {}603}604"#,605 "rust-analyzer cargo foo 0.1.0 example_mod/func().",606 );607 }608609 #[test]610 fn operator_overload() {611 check_symbol(612 r#"613//- minicore: add614//- /workspace/lib.rs crate:main615use core::ops::AddAssign;616617struct S;618619impl AddAssign for S {620 fn add_assign(&mut self, _rhs: Self) {}621}622623fn main() {624 let mut s = S;625 s +=$0 S;626}627"#,628 "rust-analyzer cargo main . impl#[S][`AddAssign<Self>`]add_assign().",629 );630 }631632 #[test]633 fn symbol_for_trait() {634 check_symbol(635 r#"636//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library637pub mod module {638 pub trait MyTrait {639 pub fn func$0() {}640 }641}642"#,643 "rust-analyzer cargo foo 0.1.0 module/MyTrait#func().",644 );645 }646647 #[test]648 fn symbol_for_trait_alias() {649 check_symbol(650 r#"651//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library652#![feature(trait_alias)]653pub mod module {654 pub trait MyTrait {}655 pub trait MyTraitAlias$0 = MyTrait;656}657"#,658 "rust-analyzer cargo foo 0.1.0 module/MyTraitAlias#",659 );660 }661662 #[test]663 fn symbol_for_trait_constant() {664 check_symbol(665 r#"666 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library667 pub mod module {668 pub trait MyTrait {669 const MY_CONST$0: u8;670 }671 }672 "#,673 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MY_CONST.",674 );675 }676677 #[test]678 fn symbol_for_trait_type() {679 check_symbol(680 r#"681 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library682 pub mod module {683 pub trait MyTrait {684 type MyType$0;685 }686 }687 "#,688 "rust-analyzer cargo foo 0.1.0 module/MyTrait#MyType#",689 );690 }691692 #[test]693 fn symbol_for_trait_impl_function() {694 check_symbol(695 r#"696 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library697 pub mod module {698 pub trait MyTrait {699 pub fn func() {}700 }701702 struct MyStruct {}703704 impl MyTrait for MyStruct {705 pub fn func$0() {}706 }707 }708 "#,709 "rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().",710 );711 }712713 #[test]714 fn symbol_for_field() {715 check_symbol(716 r#"717 //- /workspace/lib.rs crate:main deps:foo718 use foo::St;719 fn main() {720 let x = St { a$0: 2 };721 }722 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library723 pub struct St {724 pub a: i32,725 }726 "#,727 "rust-analyzer cargo foo 0.1.0 St#a.",728 );729 }730731 #[test]732 fn symbol_for_param() {733 check_symbol(734 r#"735//- /workspace/lib.rs crate:main deps:foo736use foo::example_mod::func;737fn main() {738 func(42);739}740//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library741pub mod example_mod {742 pub fn func(x$0: usize) {}743}744"#,745 "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",746 );747 }748749 #[test]750 fn symbol_for_closure_param() {751 check_symbol(752 r#"753//- /workspace/lib.rs crate:main deps:foo754use foo::example_mod::func;755fn main() {756 func();757}758//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library759pub mod example_mod {760 pub fn func() {761 let f = |x$0: usize| {};762 }763}764"#,765 "local enclosed by rust-analyzer cargo foo 0.1.0 example_mod/func().",766 );767 }768769 #[test]770 fn local_symbol_for_local() {771 check_symbol(772 r#"773 //- /workspace/lib.rs crate:main deps:foo774 use foo::module::func;775 fn main() {776 func();777 }778 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library779 pub mod module {780 pub fn func() {781 let x$0 = 2;782 }783 }784 "#,785 "local enclosed by rust-analyzer cargo foo 0.1.0 module/func().",786 );787 }788789 #[test]790 fn global_symbol_for_pub_struct() {791 check_symbol(792 r#"793 //- /workspace/lib.rs crate:main794 mod foo;795796 fn main() {797 let _bar = foo::Bar { i: 0 };798 }799 //- /workspace/foo.rs800 pub struct Bar$0 {801 pub i: i32,802 }803 "#,804 "rust-analyzer cargo main . foo/Bar#",805 );806 }807808 #[test]809 fn global_symbol_for_pub_struct_reference() {810 check_symbol(811 r#"812 //- /workspace/lib.rs crate:main813 mod foo;814815 fn main() {816 let _bar = foo::Bar$0 { i: 0 };817 }818 //- /workspace/foo.rs819 pub struct Bar {820 pub i: i32,821 }822 "#,823 "rust-analyzer cargo main . foo/Bar#",824 );825 }826827 #[test]828 fn symbol_for_type_alias() {829 check_symbol(830 r#"831 //- /workspace/lib.rs crate:main832 pub type MyTypeAlias$0 = u8;833 "#,834 "rust-analyzer cargo main . MyTypeAlias#",835 );836 }837838 // FIXME: This test represents current misbehavior.839 #[test]840 fn symbol_for_nested_function() {841 check_symbol(842 r#"843 //- /workspace/lib.rs crate:main844 pub fn func() {845 pub fn inner_func$0() {}846 }847 "#,848 "rust-analyzer cargo main . inner_func().",849 // FIXME: This should be a local:850 // "local enclosed by rust-analyzer cargo main . func().",851 );852 }853854 // FIXME: This test represents current misbehavior.855 #[test]856 fn symbol_for_struct_in_function() {857 check_symbol(858 r#"859 //- /workspace/lib.rs crate:main860 pub fn func() {861 struct SomeStruct$0 {}862 }863 "#,864 "rust-analyzer cargo main . SomeStruct#",865 // FIXME: This should be a local:866 // "local enclosed by rust-analyzer cargo main . func().",867 );868 }869870 // FIXME: This test represents current misbehavior.871 #[test]872 fn symbol_for_const_in_function() {873 check_symbol(874 r#"875 //- /workspace/lib.rs crate:main876 pub fn func() {877 const SOME_CONST$0: u32 = 1;878 }879 "#,880 "rust-analyzer cargo main . SOME_CONST.",881 // FIXME: This should be a local:882 // "local enclosed by rust-analyzer cargo main . func().",883 );884 }885886 // FIXME: This test represents current misbehavior.887 #[test]888 fn symbol_for_static_in_function() {889 check_symbol(890 r#"891 //- /workspace/lib.rs crate:main892 pub fn func() {893 static SOME_STATIC$0: u32 = 1;894 }895 "#,896 "rust-analyzer cargo main . SOME_STATIC.",897 // FIXME: This should be a local:898 // "local enclosed by rust-analyzer cargo main . func().",899 );900 }901902 #[test]903 fn documentation_matches_doc_comment() {904 let s = "/// foo\nfn bar() {}";905906 let mut host = AnalysisHost::default();907 let change_fixture = ChangeFixture::parse(s);908 host.raw_database_mut().apply_change(change_fixture.change);909910 let analysis = host.analysis();911 let si = StaticIndex::compute(912 &analysis,913 VendoredLibrariesConfig::Included {914 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),915 },916 false,917 );918919 let file = si.files.first().unwrap();920 let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `bar`921 let token = si.tokens.get(*token_id).unwrap();922923 assert_eq!(token.documentation.as_ref().map(|d| d.as_str()), Some("foo"));924 }925926 #[test]927 fn function_has_enclosing_range() {928 let s = "fn foo() {}";929930 let mut host = AnalysisHost::default();931 let change_fixture = ChangeFixture::parse(s);932 host.raw_database_mut().apply_change(change_fixture.change);933934 let analysis = host.analysis();935 let si = StaticIndex::compute(936 &analysis,937 VendoredLibrariesConfig::Included {938 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),939 },940 false,941 );942943 let file = si.files.first().unwrap();944 let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `foo`945 let token = si.tokens.get(*token_id).unwrap();946947 let expected_range = FileRangeWrapper {948 file_id: FileId::from_raw(0),949 range: TextRange::new(0.into(), 11.into()),950 };951952 assert_eq!(token.definition_body, Some(expected_range));953 }954955 #[test]956 fn function_enclosing_range_trivia() {957 let s = "fn first() {}\n// belongs to first\n/// second docs\nfn second() {}";958959 let mut host = AnalysisHost::default();960 let change_fixture = ChangeFixture::parse(s);961 host.raw_database_mut().apply_change(change_fixture.change);962963 let analysis = host.analysis();964 let si = StaticIndex::compute(965 &analysis,966 VendoredLibrariesConfig::Included {967 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),968 },969 false,970 );971972 let file = si.files.first().unwrap();973 let token = file974 .tokens975 .iter()976 .filter_map(|(_, token_id)| si.tokens.get(*token_id))977 .find(|token| token.display_name.as_deref() == Some("second"))978 .unwrap();979980 let definition_body = token.definition_body.unwrap();981 assert_eq!(982 definition_body.range.start(),983 TextSize::new(s.find("/// second docs").unwrap() as u32)984 );985 assert_eq!(definition_body.range.end(), TextSize::of(s));986 }987988 #[test]989 fn const_enclosing_range_trivia() {990 let s = "const FOO_ONE: i32 = 123; // one\nconst FOO_TWO: i32 = 123; // two";991992 let mut host = AnalysisHost::default();993 let change_fixture = ChangeFixture::parse(s);994 host.raw_database_mut().apply_change(change_fixture.change);995996 let analysis = host.analysis();997 let si = StaticIndex::compute(998 &analysis,999 VendoredLibrariesConfig::Included {1000 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),1001 },1002 false,1003 );10041005 let file = si.files.first().unwrap();1006 let token = file1007 .tokens1008 .iter()1009 .filter_map(|(_, token_id)| si.tokens.get(*token_id))1010 .find(|token| token.display_name.as_deref() == Some("FOO_TWO"))1011 .unwrap();10121013 let definition_body = token.definition_body.unwrap();1014 assert_eq!(1015 definition_body.range.start(),1016 TextSize::new(s.find("const FOO_TWO").unwrap() as u32)1017 );1018 assert_eq!(definition_body.range.end(), TextSize::new(s.find(" // two").unwrap() as u32));1019 }1020}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.
Generated by searchcode.com · powered by code_get_file + code_get_findings