src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs RUST 1,021 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, 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}

Code quality findings 28

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
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: '.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 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 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
.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 definition_body = token.definition_body.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
TextSize::new(s.find("const FOO_TWO").unwrap() as u32)
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
assert_eq!(definition_body.range.end(), TextSize::new(s.find(" // two").unwrap() as u32));
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.

Generated by searchcode.com · powered by code_get_file + code_get_findings