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 * Struct/enum/const/static/impl definitions nested in a function do not mention the function name.303 See #18771.304305Duplicate symbols encountered:306";307308fn compute_symbol_info(309 symbol: String,310 enclosing_symbol: Option<String>,311 token: &TokenStaticData,312) -> SymbolInformation {313 let documentation = match &token.documentation {314 Some(doc) => vec![doc.as_str().to_owned()],315 None => vec![],316 };317318 let position_encoding = scip_types::PositionEncoding::UTF8CodeUnitOffsetFromLineStart.into();319 let signature_documentation = token.signature.clone().map(|text| scip_types::Document {320 relative_path: "".to_owned(),321 language: "rust".to_owned(),322 text,323 position_encoding,324 ..Default::default()325 });326 scip_types::SymbolInformation {327 symbol,328 documentation,329 relationships: Vec::new(),330 special_fields: Default::default(),331 kind: symbol_kind(token.kind).into(),332 display_name: token.display_name.clone().unwrap_or_default(),333 signature_documentation: signature_documentation.into(),334 enclosing_symbol: enclosing_symbol.unwrap_or_default(),335 }336}337338fn get_relative_filepath(339 vfs: &vfs::Vfs,340 rootpath: &vfs::AbsPathBuf,341 file_id: ide::FileId,342) -> Option<String> {343 Some(vfs.file_path(file_id).as_path()?.strip_prefix(rootpath)?.as_str().to_owned())344}345346fn get_line_index(db: &RootDatabase, file_id: FileId) -> LineIndex {347 LineIndex {348 index: line_index(db, file_id).clone(),349 encoding: PositionEncoding::Utf8,350 endings: LineEndings::Unix,351 }352}353354// SCIP Ranges have a (very large) optimization that ranges if they are on the same line355// only encode as a vector of [start_line, start_col, end_col].356//357// This transforms a line index into the optimized SCIP Range.358fn text_range_to_scip_range(line_index: &LineIndex, range: TextRange) -> Vec<i32> {359 let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());360 let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());361362 if start_line == end_line {363 vec![start_line as i32, start_col as i32, end_col as i32]364 } else {365 vec![start_line as i32, start_col as i32, end_line as i32, end_col as i32]366 }367}368369fn text_range_to_string(relative_path: &str, line_index: &LineIndex, range: TextRange) -> String {370 let LineCol { line: start_line, col: start_col } = line_index.index.line_col(range.start());371 let LineCol { line: end_line, col: end_col } = line_index.index.line_col(range.end());372373 format!("{relative_path}:{start_line}:{start_col}-{end_line}:{end_col}")374}375376fn new_descriptor_str(377 name: &str,378 suffix: scip_types::descriptor::Suffix,379) -> scip_types::Descriptor {380 scip_types::Descriptor {381 name: name.to_owned(),382 disambiguator: "".to_owned(),383 suffix: suffix.into(),384 special_fields: Default::default(),385 }386}387388fn symbol_kind(kind: SymbolInformationKind) -> scip_types::symbol_information::Kind {389 use scip_types::symbol_information::Kind as ScipKind;390 match kind {391 SymbolInformationKind::AssociatedType => ScipKind::AssociatedType,392 SymbolInformationKind::Attribute => ScipKind::Attribute,393 SymbolInformationKind::Constant => ScipKind::Constant,394 SymbolInformationKind::Enum => ScipKind::Enum,395 SymbolInformationKind::EnumMember => ScipKind::EnumMember,396 SymbolInformationKind::Field => ScipKind::Field,397 SymbolInformationKind::Function => ScipKind::Function,398 SymbolInformationKind::Macro => ScipKind::Macro,399 SymbolInformationKind::Method => ScipKind::Method,400 SymbolInformationKind::Module => ScipKind::Module,401 SymbolInformationKind::Parameter => ScipKind::Parameter,402 SymbolInformationKind::SelfParameter => ScipKind::SelfParameter,403 SymbolInformationKind::StaticMethod => ScipKind::StaticMethod,404 SymbolInformationKind::StaticVariable => ScipKind::StaticVariable,405 SymbolInformationKind::Struct => ScipKind::Struct,406 SymbolInformationKind::Trait => ScipKind::Trait,407 SymbolInformationKind::TraitMethod => ScipKind::TraitMethod,408 SymbolInformationKind::Type => ScipKind::Type,409 SymbolInformationKind::TypeAlias => ScipKind::TypeAlias,410 SymbolInformationKind::TypeParameter => ScipKind::TypeParameter,411 SymbolInformationKind::Union => ScipKind::Union,412 SymbolInformationKind::Variable => ScipKind::Variable,413 }414}415416#[derive(Clone)]417struct TokenSymbols {418 symbol: String,419 /// Definition that contains this one. Only set when `symbol` is local.420 enclosing_symbol: Option<String>,421 /// True if this symbol is for an inherent impl. This is used to only emit `SymbolInformation`422 /// for a struct's first inherent impl, since their symbol names are not disambiguated.423 is_inherent_impl: bool,424}425426#[derive(Default)]427struct SymbolGenerator {428 token_to_symbols: FxHashMap<TokenId, Option<TokenSymbols>>,429 local_count: usize,430}431432impl SymbolGenerator {433 fn clear_document_local_state(&mut self) {434 self.local_count = 0;435 }436437 fn token_symbols(&mut self, id: TokenId, token: &TokenStaticData) -> Option<TokenSymbols> {438 let mut local_count = self.local_count;439 let token_symbols = self440 .token_to_symbols441 .entry(id)442 .or_insert_with(|| {443 Some(match token.moniker.as_ref()? {444 MonikerResult::Moniker(moniker) => TokenSymbols {445 symbol: scip::symbol::format_symbol(moniker_to_symbol(moniker)),446 enclosing_symbol: None,447 is_inherent_impl: match &moniker.identifier.description[..] {448 // inherent impls are represented as impl#[SelfType]449 [.., descriptor, _] => {450 descriptor.desc == MonikerDescriptorKind::Type451 && descriptor.name == "impl"452 }453 _ => false,454 },455 },456 MonikerResult::Local { enclosing_moniker } => {457 let local_symbol = scip::types::Symbol::new_local(local_count);458 local_count += 1;459 TokenSymbols {460 symbol: scip::symbol::format_symbol(local_symbol),461 enclosing_symbol: enclosing_moniker462 .as_ref()463 .map(moniker_to_symbol)464 .map(scip::symbol::format_symbol),465 is_inherent_impl: false,466 }467 }468 })469 })470 .clone();471 self.local_count = local_count;472 token_symbols473 }474}475476fn moniker_to_symbol(moniker: &Moniker) -> scip_types::Symbol {477 scip_types::Symbol {478 scheme: "rust-analyzer".into(),479 package: Some(scip_types::Package {480 manager: "cargo".to_owned(),481 name: moniker.package_information.name.clone(),482 version: moniker.package_information.version.clone().unwrap_or_else(|| ".".to_owned()),483 special_fields: Default::default(),484 })485 .into(),486 descriptors: moniker_descriptors(&moniker.identifier),487 special_fields: Default::default(),488 }489}490491fn moniker_descriptors(identifier: &MonikerIdentifier) -> Vec<scip_types::Descriptor> {492 use scip_types::descriptor::Suffix::*;493 identifier494 .description495 .iter()496 .map(|desc| {497 new_descriptor_str(498 &desc.name,499 match desc.desc {500 MonikerDescriptorKind::Namespace => Namespace,501 MonikerDescriptorKind::Type => Type,502 MonikerDescriptorKind::Term => Term,503 MonikerDescriptorKind::Method => Method,504 MonikerDescriptorKind::TypeParameter => TypeParameter,505 MonikerDescriptorKind::Parameter => Parameter,506 MonikerDescriptorKind::Macro => Macro,507 MonikerDescriptorKind::Meta => Meta,508 },509 )510 })511 .collect()512}513514#[cfg(test)]515mod test {516 use super::*;517 use hir::FileRangeWrapper;518 use ide::{FilePosition, TextSize};519 use test_fixture::ChangeFixture;520 use vfs::VfsPath;521522 fn position(#[rust_analyzer::rust_fixture] ra_fixture: &str) -> (AnalysisHost, FilePosition) {523 let mut host = AnalysisHost::default();524 let change_fixture = ChangeFixture::parse(ra_fixture);525 host.raw_database_mut().apply_change(change_fixture.change);526 let (file_id, range_or_offset) =527 change_fixture.file_position.expect("expected a marker ()");528 let offset = range_or_offset.expect_offset();529 let position = FilePosition { file_id: file_id.file_id(), offset };530 (host, position)531 }532533 /// If expected == "", then assert that there are no symbols (this is basically local symbol)534 #[track_caller]535 fn check_symbol(#[rust_analyzer::rust_fixture] ra_fixture: &str, expected: &str) {536 let (host, position) = position(ra_fixture);537538 let analysis = host.analysis();539 let si = StaticIndex::compute(540 &analysis,541 VendoredLibrariesConfig::Included {542 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),543 },544 false,545 );546547 let FilePosition { file_id, offset } = position;548549 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 // check if cursor is within token, ignoring token for the module defined by the file (whose range is the whole file)556 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 }578579 if expected.is_empty() {580 assert!(found_symbol.is_none(), "must have no symbols {found_symbol:?}");581 return;582 }583584 assert!(found_symbol.is_some(), "must have one symbol {found_symbol:?}");585 assert_eq!(found_symbol.unwrap(), expected);586 }587588 #[test]589 fn basic() {590 check_symbol(591 r#"592//- /workspace/lib.rs crate:main deps:foo593use foo::example_mod::func;594fn main() {595 func$0();596}597//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library598pub mod example_mod {599 pub fn func() {}600}601"#,602 "rust-analyzer cargo foo 0.1.0 lib/example_mod/func().",603 );604 }605606 #[test]607 fn operator_overload() {608 check_symbol(609 r#"610//- minicore: add611//- /workspace/lib.rs crate:main612use core::ops::AddAssign;613614struct S;615616impl AddAssign for S {617 fn add_assign(&mut self, _rhs: Self) {}618}619620fn main() {621 let mut s = S;622 s +=$0 S;623}624"#,625 "rust-analyzer cargo main . lib/impl#[S][`AddAssign<Self>`]add_assign().",626 );627 }628629 #[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 library634pub mod module {635 pub trait MyTrait {636 pub fn func$0() {}637 }638}639"#,640 "rust-analyzer cargo foo 0.1.0 lib/module/MyTrait#func().",641 );642 }643644 #[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 library649#![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 lib/module/MyTraitAlias#",656 );657 }658659 #[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 library664 pub mod module {665 pub trait MyTrait {666 const MY_CONST$0: u8;667 }668 }669 "#,670 "rust-analyzer cargo foo 0.1.0 lib/module/MyTrait#MY_CONST.",671 );672 }673674 #[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 library679 pub mod module {680 pub trait MyTrait {681 type MyType$0;682 }683 }684 "#,685 "rust-analyzer cargo foo 0.1.0 lib/module/MyTrait#MyType#",686 );687 }688689 #[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 library694 pub mod module {695 pub trait MyTrait {696 pub fn func() {}697 }698699 struct MyStruct {}700701 impl MyTrait for MyStruct {702 pub fn func$0() {}703 }704 }705 "#,706 "rust-analyzer cargo foo 0.1.0 lib/module/impl#[MyStruct][MyTrait]func().",707 );708 }709710 #[test]711 fn symbol_for_field() {712 check_symbol(713 r#"714 //- /workspace/lib.rs crate:main deps:foo715 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 library720 pub struct St {721 pub a: i32,722 }723 "#,724 "rust-analyzer cargo foo 0.1.0 lib/St#a.",725 );726 }727728 #[test]729 fn symbol_for_param() {730 check_symbol(731 r#"732//- /workspace/lib.rs crate:main deps:foo733use foo::example_mod::func;734fn main() {735 func(42);736}737//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library738pub mod example_mod {739 pub fn func(x$0: usize) {}740}741"#,742 "local enclosed by rust-analyzer cargo foo 0.1.0 lib/example_mod/func().",743 );744 }745746 #[test]747 fn symbol_for_closure_param() {748 check_symbol(749 r#"750//- /workspace/lib.rs crate:main deps:foo751use foo::example_mod::func;752fn main() {753 func();754}755//- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library756pub 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 lib/example_mod/func().",763 );764 }765766 #[test]767 fn local_symbol_for_local() {768 check_symbol(769 r#"770 //- /workspace/lib.rs crate:main deps:foo771 use foo::module::func;772 fn main() {773 func();774 }775 //- /foo/lib.rs crate:foo@0.1.0,https://a.b/foo.git library776 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 lib/module/func().",783 );784 }785786 #[test]787 fn global_symbol_for_pub_struct() {788 check_symbol(789 r#"790 //- /workspace/lib.rs crate:main791 mod foo;792793 fn main() {794 let _bar = foo::Bar { i: 0 };795 }796 //- /workspace/foo.rs797 pub struct Bar$0 {798 pub i: i32,799 }800 "#,801 "rust-analyzer cargo main . lib/foo/Bar#",802 );803 }804805 #[test]806 fn global_symbol_for_pub_struct_reference() {807 check_symbol(808 r#"809 //- /workspace/lib.rs crate:main810 mod foo;811812 fn main() {813 let _bar = foo::Bar$0 { i: 0 };814 }815 //- /workspace/foo.rs816 pub struct Bar {817 pub i: i32,818 }819 "#,820 "rust-analyzer cargo main . lib/foo/Bar#",821 );822 }823824 #[test]825 fn symbol_for_type_alias() {826 check_symbol(827 r#"828 //- /workspace/lib.rs crate:main829 pub type MyTypeAlias$0 = u8;830 "#,831 "rust-analyzer cargo main . lib/MyTypeAlias#",832 );833 }834835 #[test]836 fn proc_macro_target() {837 check_symbol(838 r#"839 //- /workspace/proc-macro.rs crate:proc-macro:name840 struct S$0;841 "#,842 "rust-analyzer cargo name . proc-macro/S#",843 );844 }845846 #[test]847 fn bin_target() {848 check_symbol(849 r#"850 //- /workspace/main.rs crate:bin:main851 struct S$0;852 "#,853 "rust-analyzer cargo main . bin/main/S#",854 );855 }856857 #[test]858 fn example_target() {859 check_symbol(860 r#"861 //- /workspace/example.rs crate:example:name862 struct S$0;863 "#,864 "rust-analyzer cargo name . example/name/S#",865 );866 }867868 #[test]869 fn test_target() {870 check_symbol(871 r#"872 //- /workspace/test.rs crate:test:name873 struct S$0;874 "#,875 "rust-analyzer cargo name . test/name/S#",876 );877 }878879 #[test]880 fn bench_target() {881 check_symbol(882 r#"883 //- /workspace/bench.rs crate:bench:name884 struct S$0;885 "#,886 "rust-analyzer cargo name . bench/name/S#",887 );888 }889890 #[test]891 fn custom_build_target() {892 check_symbol(893 r#"894 //- /workspace/build.rs crate:custom-build:build895 struct S$0;896 "#,897 "rust-analyzer cargo build . custom-build/S#",898 );899 }900901 #[test]902 fn other_target() {903 check_symbol(904 r#"905 //- /workspace/other.rs crate:other:other906 struct S$0;907 "#,908 "rust-analyzer cargo other . other/S#",909 );910 }911912 // FIXME: This test represents current misbehavior.913 #[test]914 fn symbol_for_nested_function() {915 check_symbol(916 r#"917 //- /workspace/lib.rs crate:main918 pub fn func() {919 pub fn inner_func$0() {}920 }921 "#,922 "rust-analyzer cargo main . lib/inner_func().",923 // FIXME: This should be a local:924 // "local enclosed by rust-analyzer cargo main . func().",925 );926 }927928 // FIXME: This test represents current misbehavior.929 #[test]930 fn symbol_for_struct_in_function() {931 check_symbol(932 r#"933 //- /workspace/lib.rs crate:main934 pub fn func() {935 struct SomeStruct$0 {}936 }937 "#,938 "rust-analyzer cargo main . lib/SomeStruct#",939 // FIXME: This should be a local:940 // "local enclosed by rust-analyzer cargo main . func().",941 );942 }943944 // FIXME: This test represents current misbehavior.945 #[test]946 fn symbol_for_const_in_function() {947 check_symbol(948 r#"949 //- /workspace/lib.rs crate:main950 pub fn func() {951 const SOME_CONST$0: u32 = 1;952 }953 "#,954 "rust-analyzer cargo main . lib/SOME_CONST.",955 // FIXME: This should be a local:956 // "local enclosed by rust-analyzer cargo main . func().",957 );958 }959960 // FIXME: This test represents current misbehavior.961 #[test]962 fn symbol_for_static_in_function() {963 check_symbol(964 r#"965 //- /workspace/lib.rs crate:main966 pub fn func() {967 static SOME_STATIC$0: u32 = 1;968 }969 "#,970 "rust-analyzer cargo main . lib/SOME_STATIC.",971 // FIXME: This should be a local:972 // "local enclosed by rust-analyzer cargo main . func().",973 );974 }975976 #[test]977 fn documentation_matches_doc_comment() {978 let s = "/// foo\nfn bar() {}";979980 let mut host = AnalysisHost::default();981 let change_fixture = ChangeFixture::parse(s);982 host.raw_database_mut().apply_change(change_fixture.change);983984 let analysis = host.analysis();985 let si = StaticIndex::compute(986 &analysis,987 VendoredLibrariesConfig::Included {988 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),989 },990 false,991 );992993 let file = si.files.first().unwrap();994 let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `bar`995 let token = si.tokens.get(*token_id).unwrap();996997 assert_eq!(token.documentation.as_ref().map(|d| d.as_str()), Some("foo"));998 }9991000 #[test]1001 fn function_has_enclosing_range() {1002 let s = "fn foo() {}";10031004 let mut host = AnalysisHost::default();1005 let change_fixture = ChangeFixture::parse(s);1006 host.raw_database_mut().apply_change(change_fixture.change);10071008 let analysis = host.analysis();1009 let si = StaticIndex::compute(1010 &analysis,1011 VendoredLibrariesConfig::Included {1012 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),1013 },1014 false,1015 );10161017 let file = si.files.first().unwrap();1018 let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `foo`1019 let token = si.tokens.get(*token_id).unwrap();10201021 let expected_range = FileRangeWrapper {1022 file_id: FileId::from_raw(0),1023 range: TextRange::new(0.into(), 11.into()),1024 };10251026 assert_eq!(token.definition_body, Some(expected_range));1027 }10281029 #[test]1030 fn function_enclosing_range_trivia() {1031 let s = "fn first() {}\n// belongs to first\n/// second docs\nfn second() {}";10321033 let mut host = AnalysisHost::default();1034 let change_fixture = ChangeFixture::parse(s);1035 host.raw_database_mut().apply_change(change_fixture.change);10361037 let analysis = host.analysis();1038 let si = StaticIndex::compute(1039 &analysis,1040 VendoredLibrariesConfig::Included {1041 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),1042 },1043 false,1044 );10451046 let file = si.files.first().unwrap();1047 let token = file1048 .tokens1049 .iter()1050 .filter_map(|(_, token_id)| si.tokens.get(*token_id))1051 .find(|token| token.display_name.as_deref() == Some("second"))1052 .unwrap();10531054 let definition_body = token.definition_body.unwrap();1055 assert_eq!(1056 definition_body.range.start(),1057 TextSize::new(s.find("/// second docs").unwrap() as u32)1058 );1059 assert_eq!(definition_body.range.end(), TextSize::of(s));1060 }10611062 #[test]1063 fn const_enclosing_range_trivia() {1064 let s = "const FOO_ONE: i32 = 123; // one\nconst FOO_TWO: i32 = 123; // two";10651066 let mut host = AnalysisHost::default();1067 let change_fixture = ChangeFixture::parse(s);1068 host.raw_database_mut().apply_change(change_fixture.change);10691070 let analysis = host.analysis();1071 let si = StaticIndex::compute(1072 &analysis,1073 VendoredLibrariesConfig::Included {1074 workspace_root: &VfsPath::new_virtual_path("/workspace".to_owned()),1075 },1076 false,1077 );10781079 let file = si.files.first().unwrap();1080 let token = file1081 .tokens1082 .iter()1083 .filter_map(|(_, token_id)| si.tokens.get(*token_id))1084 .find(|token| token.display_name.as_deref() == Some("FOO_TWO"))1085 .unwrap();10861087 let definition_body = token.definition_body.unwrap();1088 assert_eq!(1089 definition_body.range.start(),1090 TextSize::new(s.find("const FOO_TWO").unwrap() as u32)1091 );1092 assert_eq!(definition_body.range.end(), TextSize::new(s.find(" // two").unwrap() as u32));1093 }1094}
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