use anyhow::{Context, Result};
use tracing::debug;
use crate::db::Store;
use std::sync::Arc;
pub struct EdgeUpdater {
store: Arc<dyn Store + Send + Sync>,
}
impl EdgeUpdater {
pub fn new(store: Arc<dyn Store + Send + Sync>) -> Self {
Self { store }
}
pub async fn update_edges(&self, file_id: i64) -> Result<()> {
use crate::indexer::edges::{self, ChunkWithId};
debug!(file_id = file_id, "Updating edges for file");
self.delete_edges_for_file(file_id).await?;
let (relpath, language, root_path) = match self.store.get_file_edge_context(file_id).await?
{
Some(ctx) => ctx,
None => {
debug!(file_id = file_id, "File not found; skipping edge update");
return Ok(());
}
};
let language = match language {
Some(lang) if matches!(lang.as_str(), "ts" | "tsx" | "js" | "jsx") => lang,
_ => {
debug!(
file_id = file_id,
"No edge extraction for language {:?}", language
);
return Ok(());
}
};
let full_path = std::path::Path::new(&root_path).join(&relpath);
let content = std::fs::read_to_string(&full_path).with_context(|| {
format!(
"Failed to read file: {} (root: {}, relpath: {})",
full_path.display(),
root_path,
relpath
)
})?;
let chunks_with_ids: Vec<ChunkWithId> = self
.store
.get_file_chunks(file_id)
.await?
.into_iter()
.map(|c| ChunkWithId {
id: c.id,
symbol_name: c.symbol_name,
kind: c.kind,
start_line: c.start_line,
end_line: c.end_line,
file_id,
})
.collect();
let edges_to_insert = edges::extract_edges(&content, &language, &chunks_with_ids)?;
for edge in edges_to_insert {
self.store
.insert_chunk_edge(
edge.src_chunk_id,
edge.dst_chunk_id,
edge.edge_type.as_str(),
)
.await?;
}
debug!(file_id = file_id, "Edges updated for file");
Ok(())
}
pub async fn delete_edges_for_file(&self, file_id: i64) -> Result<u64> {
let count = self.store.delete_edges_for_file(file_id).await?;
debug!(
file_id = file_id,
edges_deleted = count,
"Deleted edges for file"
);
Ok(count)
}
}
#[derive(Debug, Clone)]
pub struct Edge {
pub src_chunk_id: i64,
pub dst_chunk_id: i64,
pub edge_type: EdgeType,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EdgeType {
Imports,
Exports,
Calls,
CalledBy,
TestOf,
RouteOf,
}
impl EdgeType {
pub fn as_str(&self) -> &'static str {
match self {
EdgeType::Imports => "imports",
EdgeType::Exports => "exports",
EdgeType::Calls => "calls",
EdgeType::CalledBy => "called_by",
EdgeType::TestOf => "test_of",
EdgeType::RouteOf => "route_of",
}
}
}
#[allow(dead_code)]
async fn compute_edges(
_store: &(dyn Store + Send + Sync),
_chunk_ids: &[i64],
) -> Result<Vec<Edge>> {
Ok(Vec::new())
}
#[allow(dead_code)]
fn is_test_chunk(kind: &str, symbol_name: Option<&str>) -> bool {
if kind.contains("test") {
return true;
}
if let Some(name) = symbol_name {
let lower = name.to_lowercase();
if lower.starts_with("test_") || lower.starts_with("it ") || lower.starts_with("describe ")
{
return true;
}
}
false
}
#[allow(dead_code)]
fn is_route_chunk(kind: &str, symbol_name: Option<&str>) -> bool {
if kind == "func" {
if let Some(name) = symbol_name {
let lower = name.to_lowercase();
if lower.contains("route") || lower.contains("handler") {
return true;
}
}
}
false
}
#[allow(dead_code)]
async fn find_test_targets(
_store: &(dyn Store + Send + Sync),
_test_chunk_id: i64,
_test_symbol_name: Option<&str>,
) -> Result<Vec<Edge>> {
Ok(Vec::new())
}
#[allow(dead_code)]
async fn insert_edges(store: &(dyn Store + Send + Sync), edges: &[Edge]) -> Result<u64> {
if edges.is_empty() {
return Ok(0);
}
let mut count = 0u64;
for edge in edges {
store
.insert_chunk_edge(
edge.src_chunk_id,
edge.dst_chunk_id,
edge.edge_type.as_str(),
)
.await?;
count += 1;
}
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_test_chunk() {
assert!(is_test_chunk("test", None));
assert!(is_test_chunk("func", Some("test_myfunction")));
assert!(is_test_chunk("func", Some("it should work")));
assert!(is_test_chunk("func", Some("describe the feature")));
assert!(!is_test_chunk("func", Some("myFunction")));
assert!(!is_test_chunk("class", Some("MyClass")));
}
#[test]
fn test_is_route_chunk() {
assert!(is_route_chunk("func", Some("handleRoute")));
assert!(is_route_chunk("func", Some("userRouter")));
assert!(!is_route_chunk("func", Some("myFunction")));
assert!(!is_route_chunk("class", Some("RouteHandler")));
}
#[test]
fn test_edge_type_as_str() {
assert_eq!(EdgeType::Imports.as_str(), "imports");
assert_eq!(EdgeType::Exports.as_str(), "exports");
assert_eq!(EdgeType::Calls.as_str(), "calls");
assert_eq!(EdgeType::CalledBy.as_str(), "called_by");
assert_eq!(EdgeType::TestOf.as_str(), "test_of");
assert_eq!(EdgeType::RouteOf.as_str(), "route_of");
}
}