src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs RUST 952 lines View on github.com → Search inside
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}

Code quality findings 26

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.
warning correctness unwrap-usage
let token = si.tokens.get(id).unwrap();
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.
warning correctness unwrap-usage
let token = si.tokens.get(id).unwrap();
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("To have been referenced, the symbol must be in the cache.");
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
// only encode as a vector of [start_line, start_col, end_col].
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
is_inherent_impl: match &moniker.identifier.description[..] {
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
change_fixture.file_position.expect("expected a marker ()");
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.
warning correctness unwrap-usage
let token = si.tokens.get(id).unwrap();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
"rust-analyzer cargo main . impl#[S][`AddAssign<Self>`]add_assign().",
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
"rust-analyzer cargo foo 0.1.0 module/impl#[MyStruct][MyTrait]func().",
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.
warning correctness unwrap-usage
let file = si.files.first().unwrap();
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.
warning correctness unwrap-usage
let (_, token_id) = file.tokens.get(1).unwrap(); // first token is file module, second is `foo`
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.
warning correctness unwrap-usage
let token = si.tokens.get(*token_id).unwrap();
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
eprintln!("Generating SCIP start...");
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
let no_progress = &|s| eprintln!("rust-analyzer: Loading {s}");
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let is_defined_in_this_document = match token.definition {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let is_definition = match token.definition {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let enclosing_range = match token.definition_body {
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
eprintln!("{DUPLICATE_SYMBOLS_MESSAGE}");
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
eprintln!("{source_location}");
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
eprintln!(" Duplicate symbol: {symbol}");
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
eprintln!();
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
eprintln!("Generating SCIP finished {:?}", now.elapsed());
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
Some(match token.moniker.as_ref()? {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
is_inherent_impl: match &moniker.identifier.description[..] {
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use scip_types::descriptor::Suffix::*;
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use super::*;

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.