use crate::core::entity::{fact_hash_str, EdgeKind, EntityType, RawEntity};
#[derive(Debug, Clone)]
pub struct ScipEntityRef {
pub symbol: String,
pub display_name: String,
pub file: String,
pub start_line: usize,
pub end_line: usize,
pub is_definition: bool,
}
#[derive(Debug, Clone)]
pub struct ScipEdge {
pub from_symbol: String,
pub to_symbol: String,
pub kind: EdgeKind,
}
pub trait CodeEntityIndex: Send + Sync {
fn entities(&self) -> &[RawEntity];
fn edges(&self) -> &[(String, EdgeKind, String)];
}
pub struct ScipIndex {
entities: Vec<RawEntity>,
edges: Vec<(String, EdgeKind, String)>,
}
impl ScipIndex {
pub fn from_scip(path: &std::path::Path) -> anyhow::Result<Self> {
let _bytes = std::fs::read(path)
.map_err(|e| anyhow::anyhow!("failed to read SCIP file {}: {e}", path.display()))?;
anyhow::bail!(
"SCIP protobuf decoding is not yet implemented; use ScipIndex::from_refs for now"
)
}
pub fn from_refs(refs: Vec<ScipEntityRef>, edges: Vec<ScipEdge>) -> Self {
let entities = refs
.iter()
.map(|r| RawEntity {
id: format!("{}:{}:{}", r.file, r.start_line, fact_hash_str(&r.symbol)),
entity_type: if r.is_definition {
EntityType::NamedType
} else {
EntityType::ModulePath
},
text: r.display_name.clone(),
span: (r.start_line, r.end_line),
file: r.file.clone(),
line: r.start_line,
})
.collect();
let edge_tuples = edges
.into_iter()
.map(|e| (e.from_symbol, e.kind, e.to_symbol))
.collect();
Self {
entities,
edges: edge_tuples,
}
}
}
impl CodeEntityIndex for ScipIndex {
fn entities(&self) -> &[RawEntity] {
&self.entities
}
fn edges(&self) -> &[(String, EdgeKind, String)] {
&self.edges
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scip_index_from_refs() {
let refs = vec![ScipEntityRef {
symbol: "rust-analyzer cargo foo/Bar#".into(),
display_name: "Bar".into(),
file: "src/bar.rs".into(),
start_line: 10,
end_line: 20,
is_definition: true,
}];
let idx = ScipIndex::from_refs(refs, vec![]);
assert_eq!(idx.entities().len(), 1);
assert_eq!(idx.entities()[0].text, "Bar");
assert_eq!(idx.edges().len(), 0);
}
#[test]
fn scip_from_missing_file_returns_err() {
let result = ScipIndex::from_scip(std::path::Path::new("/nonexistent/index.scip"));
assert!(result.is_err());
}
#[test]
fn scip_index_classifies_def_vs_ref() {
let refs = vec![
ScipEntityRef {
symbol: "scip-rust cargo crate/Foo#".into(),
display_name: "Foo".into(),
file: "src/foo.rs".into(),
start_line: 1,
end_line: 1,
is_definition: true,
},
ScipEntityRef {
symbol: "scip-rust cargo crate/Foo#".into(),
display_name: "Foo".into(),
file: "src/use_foo.rs".into(),
start_line: 5,
end_line: 5,
is_definition: false,
},
];
let edges = vec![ScipEdge {
from_symbol: "scip-rust cargo crate/use_foo#".into(),
to_symbol: "scip-rust cargo crate/Foo#".into(),
kind: EdgeKind::UsesType,
}];
let idx = ScipIndex::from_refs(refs, edges);
assert_eq!(idx.entities().len(), 2);
assert!(matches!(
idx.entities()[0].entity_type,
EntityType::NamedType
));
assert!(matches!(
idx.entities()[1].entity_type,
EntityType::ModulePath
));
assert_eq!(idx.edges().len(), 1);
assert!(matches!(idx.edges()[0].1, EdgeKind::UsesType));
}
}