use crate::graph::types::{BindingTarget, FileFacts};
use crate::symbol::{Package, SymbolId};
pub fn enrich_file_facts(facts: &mut FileFacts, package: &Package) {
for sym in &mut facts.symbols {
sym.id = sym.id.with_package(package.clone());
}
for binding in &mut facts.bindings {
if let BindingTarget::Def(id) = &binding.target {
binding.target = BindingTarget::Def(id.with_package(package.clone()));
}
}
for export in &mut facts.ffi_exports {
export.symbol = export.symbol.with_package(package.clone());
}
for reference in &mut facts.references {
rewrite_source_module(&mut reference.source_module, package);
}
}
fn rewrite_source_module(source_module: &mut Option<String>, package: &Package) {
let Some(existing) = source_module else {
return;
};
let Ok(id) = SymbolId::from_scip_string(existing) else {
return;
};
if id.language().is_some() {
*existing = id.with_package(package.clone()).to_scip_string();
}
}
pub fn enrich(facts: &mut FileFacts, package: &Package) {
enrich_file_facts(facts, package);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::types::{
Binding, BindingKind, BindingTarget, ByteSpan, FfiAbi, FfiExport, FileFacts, Occurrence,
RefRole, Reference, Symbol, SymbolKind, Visibility,
};
use crate::symbol::{Descriptor, SymbolId};
fn make_symbol(id: SymbolId) -> Symbol {
Symbol {
id,
name: "foo".into(),
kind: SymbolKind::Function,
visibility: Visibility::Public,
entry_points: Vec::new(),
file: "src/lib.rs".into(),
line: 1,
span: ByteSpan { start: 0, end: 10 },
signature: "fn foo()".into(),
}
}
#[test]
fn with_package_on_global_stamps_package() {
let id = SymbolId::global("rust", vec![Descriptor::Term("foo".into())]);
assert_eq!(id.to_scip_string(), "codegraph . . . foo.");
let pkg = Package {
manager: "cargo".into(),
name: "mylib".into(),
version: "1.0.0".into(),
};
let enriched = id.with_package(pkg);
assert_eq!(
enriched.to_scip_string(),
"codegraph cargo mylib 1.0.0 foo."
);
}
#[test]
fn with_package_on_local_is_unchanged() {
let id = SymbolId::local("src/main.rs", "x0");
let pkg = Package {
manager: "cargo".into(),
name: "mylib".into(),
version: "1.0.0".into(),
};
let after = id.with_package(pkg);
assert_eq!(after.to_scip_string(), "local x0");
assert_eq!(after, id);
}
#[test]
fn enrich_restamps_symbols_bindings_ffi_exports() {
let pkg = Package {
manager: "cargo".into(),
name: "mylib".into(),
version: "1.0.0".into(),
};
let expected_scip = "codegraph cargo mylib 1.0.0 foo.";
let sym_id = SymbolId::global("rust", vec![Descriptor::Term("foo".into())]);
let export_id = SymbolId::global("rust", vec![Descriptor::Term("foo".into())]);
let def_id = SymbolId::global("rust", vec![Descriptor::Term("foo".into())]);
let mut facts = FileFacts {
file: "src/lib.rs".into(),
lang: "rust".into(),
symbols: vec![make_symbol(sym_id.clone())],
references: vec![
Reference {
name: "dep".into(),
occ: Occurrence {
file: "src/lib.rs".into(),
line: 1,
col: 0,
byte: 0,
},
role: RefRole::Import,
source_module: Some(sym_id.to_scip_string()),
from_path: Some("external::dep".into()),
is_reexport: false,
imported_name: None,
qualifier: Some("external".into()),
scope: None,
type_ref_ctx: None,
cross_artifact: false,
self_receiver: false,
},
Reference {
name: "local".into(),
occ: Occurrence {
file: "src/lib.rs".into(),
line: 1,
col: 1,
byte: 1,
},
role: RefRole::Import,
source_module: Some(SymbolId::local("src/lib.rs", "module").to_scip_string()),
from_path: Some("keep/me".into()),
is_reexport: false,
imported_name: None,
qualifier: None,
scope: None,
type_ref_ctx: None,
cross_artifact: false,
self_receiver: false,
},
Reference {
name: "opaque".into(),
occ: Occurrence {
file: "src/lib.rs".into(),
line: 1,
col: 2,
byte: 2,
},
role: RefRole::Import,
source_module: Some("not a SCIP id".into()),
from_path: Some("still/external".into()),
is_reexport: false,
imported_name: None,
qualifier: None,
scope: None,
type_ref_ctx: None,
cross_artifact: false,
self_receiver: false,
},
],
scopes: vec![],
bindings: vec![
Binding {
scope: 0,
name: "foo".into(),
intro: 0,
kind: BindingKind::Definition,
target: BindingTarget::Def(def_id),
type_name: None,
},
Binding {
scope: 0,
name: "bar".into(),
intro: 5,
kind: BindingKind::Local,
target: BindingTarget::Local,
type_name: None,
},
],
ffi_exports: vec![FfiExport {
symbol: export_id,
abi: FfiAbi::C,
export_name: "foo".into(),
}],
};
enrich(&mut facts, &pkg);
assert_eq!(facts.symbols[0].id.to_scip_string(), expected_scip);
assert!(
matches!(&facts.bindings[0].target, BindingTarget::Def(id) if id.to_scip_string() == expected_scip)
);
assert_eq!(facts.bindings[1].target, BindingTarget::Local);
assert_eq!(facts.ffi_exports[0].symbol.to_scip_string(), expected_scip);
assert_eq!(
facts.references[0].source_module.as_deref(),
Some(expected_scip)
);
assert_eq!(
facts.references[0].from_path.as_deref(),
Some("external::dep")
);
assert_eq!(facts.references[0].qualifier.as_deref(), Some("external"));
assert_eq!(
facts.references[1].source_module.as_deref(),
Some("local module")
);
assert_eq!(facts.references[1].from_path.as_deref(), Some("keep/me"));
assert_eq!(
facts.references[2].source_module.as_deref(),
Some("not a SCIP id")
);
assert_eq!(
facts.references[2].from_path.as_deref(),
Some("still/external")
);
}
#[test]
fn per_file_enrichment_supports_mixed_packages_without_graph_context() {
let global = SymbolId::global("rust", vec![Descriptor::Term("f".into())]);
let local = SymbolId::local("src/a.rs", "local");
let mut first = FileFacts {
file: "src/a.rs".into(),
lang: "rust".into(),
symbols: vec![make_symbol(global.clone()), make_symbol(local.clone())],
references: vec![],
scopes: vec![],
bindings: vec![],
ffi_exports: vec![],
};
let mut second = FileFacts {
file: "src/b.rs".into(),
lang: "rust".into(),
symbols: vec![make_symbol(global)],
references: vec![],
scopes: vec![],
bindings: vec![],
ffi_exports: vec![],
};
enrich_file_facts(
&mut first,
&Package {
manager: "cargo".into(),
name: "one".into(),
version: "1".into(),
},
);
enrich_file_facts(
&mut second,
&Package {
manager: "npm".into(),
name: "two".into(),
version: "2".into(),
},
);
assert!(first.symbols[0].id.to_scip_string().contains("cargo one 1"));
assert!(second.symbols[0].id.to_scip_string().contains("npm two 2"));
assert_eq!(first.symbols[1].id, local);
}
}