use anyhow::Result;
use serde::Serialize;
use tsift_graph as graph;
use tsift_index::index;
use tsift_search::tagpath_adapter;
use crate::{
EdgeSide, annotate_community_members_with_context, community_tagpath_cache_part_for_loaded,
file_communities_from_callers, resolve_tagpath_handle_for_callee_edge,
};
#[derive(Debug, Default, Clone)]
pub struct TagpathAnnotationDiagnostic {
pub stale: bool,
pub reason: Option<String>,
pub loaded: bool,
pub ambiguous_members: Vec<CommunityMemberAmbiguityDiagnostic>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommunityMemberAmbiguityDiagnostic {
pub community_id: usize,
pub name: String,
pub candidate_count: usize,
pub tagpath_candidate_count: usize,
pub evidence: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub chosen_file: Option<String>,
}
#[allow(clippy::too_many_arguments)]
#[allow(dead_code)]
#[derive(Debug, Default, Clone, Copy)]
pub struct TagpathSearchOpts {
pub no_tagpath: bool,
pub strict: bool,
}
pub fn annotate_hits_with_tagpath(
hits: &mut [index::SymbolHit],
root: &std::path::Path,
opts: &TagpathSearchOpts,
) -> Result<TagpathAnnotationDiagnostic> {
if opts.no_tagpath {
return Ok(TagpathAnnotationDiagnostic::default());
}
match tagpath_adapter::try_load(root) {
tagpath_adapter::LoadResult::Loaded(adapter) => {
for hit in hits.iter_mut() {
let abs = if std::path::Path::new(&hit.file).is_absolute() {
std::path::PathBuf::from(&hit.file)
} else {
root.join(&hit.file)
};
if let Some(handle) = adapter.handle_for_member(&abs, &hit.name) {
hit.tagpath_handle = Some(handle);
}
}
Ok(TagpathAnnotationDiagnostic {
stale: false,
reason: None,
loaded: true,
ambiguous_members: Vec::new(),
})
}
tagpath_adapter::LoadResult::Stale { reason, .. } => {
if opts.strict {
anyhow::bail!(
"tagpath index is stale (reason={reason}); rerun `tagpath index --update` or drop --tagpath-strict"
);
}
Ok(TagpathAnnotationDiagnostic {
stale: true,
reason: Some(reason),
loaded: false,
ambiguous_members: Vec::new(),
})
}
tagpath_adapter::LoadResult::Missing => Ok(TagpathAnnotationDiagnostic::default()),
}
}
pub fn annotate_stored_symbols_with_tagpath(
symbols: &mut [index::StoredSymbol],
root: &std::path::Path,
opts: &TagpathSearchOpts,
) -> Result<TagpathAnnotationDiagnostic> {
if opts.no_tagpath {
return Ok(TagpathAnnotationDiagnostic::default());
}
match tagpath_adapter::try_load(root) {
tagpath_adapter::LoadResult::Loaded(adapter) => {
for sym in symbols.iter_mut() {
let abs = if std::path::Path::new(&sym.file).is_absolute() {
std::path::PathBuf::from(&sym.file)
} else {
root.join(&sym.file)
};
if let Some(handle) = adapter.handle_for_member(&abs, &sym.name) {
sym.tagpath_handle = Some(handle);
}
}
Ok(TagpathAnnotationDiagnostic {
stale: false,
reason: None,
loaded: true,
ambiguous_members: Vec::new(),
})
}
tagpath_adapter::LoadResult::Stale { reason, .. } => {
if opts.strict {
anyhow::bail!(
"tagpath index is stale (reason={reason}); rerun `tagpath index --update` or drop --tagpath-strict"
);
}
Ok(TagpathAnnotationDiagnostic {
stale: true,
reason: Some(reason),
loaded: false,
ambiguous_members: Vec::new(),
})
}
tagpath_adapter::LoadResult::Missing => Ok(TagpathAnnotationDiagnostic::default()),
}
}
pub fn annotate_stored_edges_with_tagpath(
edges: &mut [index::StoredEdge],
db: &index::IndexDb,
root: &std::path::Path,
scope: Option<&str>,
side: EdgeSide,
opts: &TagpathSearchOpts,
) -> Result<TagpathAnnotationDiagnostic> {
if opts.no_tagpath {
return Ok(TagpathAnnotationDiagnostic::default());
}
match tagpath_adapter::try_load(root) {
tagpath_adapter::LoadResult::Loaded(adapter) => {
match side {
EdgeSide::Caller => {
for edge in edges.iter_mut() {
let abs = if std::path::Path::new(&edge.caller_file).is_absolute() {
std::path::PathBuf::from(&edge.caller_file)
} else {
root.join(&edge.caller_file)
};
if let Some(handle) = adapter.handle_for_member(&abs, &edge.caller_name) {
edge.tagpath_handle = Some(handle);
}
}
}
EdgeSide::Callee => {
let tagpath = community_tagpath_cache_part_for_loaded(&adapter);
let communities_by_file =
file_communities_from_callers(db, root, scope, &tagpath)
.unwrap_or_default();
for edge in edges.iter_mut() {
if let Some(handle) = resolve_tagpath_handle_for_callee_edge(
edge,
db,
root,
&adapter,
&communities_by_file,
) {
edge.tagpath_handle = Some(handle);
}
}
}
}
Ok(TagpathAnnotationDiagnostic {
stale: false,
reason: None,
loaded: true,
ambiguous_members: Vec::new(),
})
}
tagpath_adapter::LoadResult::Stale { reason, .. } => {
if opts.strict {
anyhow::bail!(
"tagpath index is stale (reason={reason}); rerun `tagpath index --update` or drop --tagpath-strict"
);
}
Ok(TagpathAnnotationDiagnostic {
stale: true,
reason: Some(reason),
loaded: false,
ambiguous_members: Vec::new(),
})
}
tagpath_adapter::LoadResult::Missing => Ok(TagpathAnnotationDiagnostic::default()),
}
}
pub fn annotate_communities_with_tagpath(
communities: &mut [graph::Community],
db: &index::IndexDb,
root: &std::path::Path,
opts: &TagpathSearchOpts,
) -> Result<Option<TagpathAnnotationDiagnostic>> {
if opts.no_tagpath {
annotate_community_members_with_context(communities, db, root, None)?;
return Ok(None);
}
match tagpath_adapter::try_load(root) {
tagpath_adapter::LoadResult::Loaded(adapter) => {
let ambiguous_members =
annotate_community_members_with_context(communities, db, root, Some(&adapter))?;
Ok(Some(TagpathAnnotationDiagnostic {
stale: false,
reason: None,
loaded: true,
ambiguous_members,
}))
}
tagpath_adapter::LoadResult::Stale { reason, .. } => {
if opts.strict {
anyhow::bail!(
"tagpath index is stale (reason={reason}); rerun `tagpath index --update` or drop --tagpath-strict"
);
}
let ambiguous_members =
annotate_community_members_with_context(communities, db, root, None)?;
Ok(Some(TagpathAnnotationDiagnostic {
stale: true,
reason: Some(reason),
loaded: false,
ambiguous_members,
}))
}
tagpath_adapter::LoadResult::Missing => {
annotate_community_members_with_context(communities, db, root, None)?;
Ok(None)
}
}
}
pub fn annotate_path_nodes_with_tagpath(
nodes: &mut [graph::PathNode],
db: &index::IndexDb,
root: &std::path::Path,
opts: &TagpathSearchOpts,
) -> Result<TagpathAnnotationDiagnostic> {
if opts.no_tagpath {
return Ok(TagpathAnnotationDiagnostic::default());
}
match tagpath_adapter::try_load(root) {
tagpath_adapter::LoadResult::Loaded(adapter) => {
for node in nodes.iter_mut() {
let syms = match db.symbol_info(&node.name) {
Ok(v) => v,
Err(_) => continue,
};
let Some(sym) = syms.into_iter().next() else {
continue;
};
let abs = if std::path::Path::new(&sym.file).is_absolute() {
std::path::PathBuf::from(&sym.file)
} else {
root.join(&sym.file)
};
if let Some(handle) = adapter.handle_for_member(&abs, &node.name) {
node.tagpath_handle = Some(handle);
}
}
Ok(TagpathAnnotationDiagnostic {
stale: false,
reason: None,
loaded: true,
ambiguous_members: Vec::new(),
})
}
tagpath_adapter::LoadResult::Stale { reason, .. } => {
if opts.strict {
anyhow::bail!(
"tagpath index is stale (reason={reason}); rerun `tagpath index --update` or drop --tagpath-strict"
);
}
Ok(TagpathAnnotationDiagnostic {
stale: true,
reason: Some(reason),
loaded: false,
ambiguous_members: Vec::new(),
})
}
tagpath_adapter::LoadResult::Missing => Ok(TagpathAnnotationDiagnostic::default()),
}
}