weavatrix-rust 2.16.0

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use crate::engine::RepositoryState;
use crate::operations::optional_str;
use blazingly_json::{Value, json};
use weavatrix_graph::NodeIndex;

use super::path;
use super::position::{self, Query};
use super::report::{
    node_json, optional_query, page_json, page_limit, query_json, scip_definition, sources,
    span_key,
};
use super::scip;

pub(super) fn find_references(state: &RepositoryState, args: &Value) -> Result<Value, String> {
    let loaded = scip::load(state, optional_str(args, "scip_path")?)?;
    let (offset, max) = page_limit(args)?;
    let Some(subject) = reference_subject(state, args, loaded.as_ref())? else {
        return scip_only_or_unresolved(state, args, loaded.as_ref());
    };
    let mut rows = graph_references(state, subject.index)?;
    if let Some(symbol) = subject.scip_symbol.as_deref()
        && let Some(index) = loaded.as_ref()
    {
        merge_scip_references(&mut rows, index, symbol);
    } else if let (Some(index), Some(query)) = (loaded.as_ref(), subject.query.as_ref())
        && let Some(symbol) = index.symbol_at(query)
    {
        merge_scip_references(&mut rows, index, symbol);
    }
    rows.sort_by_key(value_span_key);
    rows.dedup_by(|left, right| value_span_key(left) == value_span_key(right));
    let total = rows.len();
    let page = rows.into_iter().skip(offset).take(max).collect::<Vec<_>>();
    let returned = page.len();
    Ok(json!({
        "state": "RESOLVED",
        "query": subject.query.as_ref().map(query_json),
        "definition": node_json(state.node(subject.index)?),
        "references": page,
        "page": page_json(offset, returned, total),
        "precision": if loaded.is_some() { "parser_plus_graph_plus_scip" } else { "parser_plus_graph" },
        "semantic_precision": "BOUNDED_STATIC",
        "sources": sources(loaded.as_ref())
    }))
}

pub(super) fn unresolved(query: &Query, loaded: Option<&scip::Loaded>, reason: &str) -> Value {
    json!({
        "state": "UNRESOLVED",
        "query": query_json(query),
        "occurrence": Value::Null,
        "definition": Value::Null,
        "precision": "parser_plus_graph",
        "semantic_precision": "BOUNDED_STATIC",
        "sources": sources(loaded),
        "reason": reason
    })
}

struct ReferenceSubject {
    index: NodeIndex,
    query: Option<Query>,
    scip_symbol: Option<String>,
}

fn reference_subject(
    state: &RepositoryState,
    args: &Value,
    loaded: Option<&scip::Loaded>,
) -> Result<Option<ReferenceSubject>, String> {
    if let Some(query) = optional_query(args)? {
        if let Some(hit) = position::resolve(state, &query) {
            return Ok(Some(ReferenceSubject {
                index: hit.index,
                scip_symbol: loaded.and_then(|index| index.symbol_at(&query).map(str::to_owned)),
                query: Some(query),
            }));
        }
        if let Some(hit) = loaded.and_then(|index| index.at_position(&query))
            && let Some(span) = hit.definition.as_ref()
        {
            let at = Query {
                path: path::normalize(span.file.as_str()),
                line: span.start.line,
                column: span.start.column,
            };
            if let Some(mapped) = position::resolve(state, &at) {
                return Ok(Some(ReferenceSubject {
                    index: mapped.index,
                    query: Some(query),
                    scip_symbol: Some(hit.symbol),
                }));
            }
        }
        return Ok(None);
    }
    let label = optional_str(args, "label")?
        .ok_or_else(|| "find_references requires label or path, line, and column".to_owned())?;
    Ok(Some(ReferenceSubject {
        index: state.resolve_node(label)?,
        query: None,
        scip_symbol: None,
    }))
}

fn graph_references(state: &RepositoryState, index: NodeIndex) -> Result<Vec<Value>, String> {
    let node = state.node(index)?;
    let mut rows = Vec::new();
    if let Some(span) = node.span.as_ref() {
        rows.push(json!({
            "role": "definition",
            "span": span,
            "extractor": "weavatrix-graph",
            "relation": Value::Null,
            "node": node_json(node)
        }));
    }
    for edge in position::incoming_references(state, index) {
        if let Some(span) = edge.provenance.span.as_ref() {
            rows.push(json!({
                "role": "reference",
                "span": span,
                "extractor": edge.provenance.extractor,
                "relation": edge.kind,
                "node": state.graph().node(edge.source.as_str()).map(node_json)
            }));
        }
    }
    Ok(rows)
}

fn merge_scip_references(rows: &mut Vec<Value>, index: &scip::Loaded, symbol: &str) {
    let known = rows
        .iter()
        .filter_map(|row| row.get("span").and_then(span_from_value))
        .collect::<Vec<_>>();
    for span in index.references(symbol) {
        if known
            .iter()
            .any(|existing| span_key(existing) == span_key(&span))
        {
            continue;
        }
        rows.push(json!({
            "role": "reference",
            "span": span,
            "extractor": "scip",
            "relation": Value::Null,
            "node": Value::Null
        }));
    }
}

fn value_span_key(row: &Value) -> (String, u32, u32) {
    row.get("span")
        .and_then(span_from_value)
        .map(|span| span_key(&span))
        .unwrap_or_default()
}

fn span_from_value(value: &Value) -> Option<weavatrix_graph::SourceSpan> {
    let file = value.get("file")?.as_str()?;
    let start = value.get("start")?;
    let end = value.get("end")?;
    Some(weavatrix_graph::SourceSpan::new(
        file,
        weavatrix_graph::SourcePosition::new(
            u32::try_from(start.get("line")?.as_u64()?).ok()?,
            u32::try_from(start.get("column")?.as_u64()?).ok()?,
        ),
        weavatrix_graph::SourcePosition::new(
            u32::try_from(end.get("line")?.as_u64()?).ok()?,
            u32::try_from(end.get("column")?.as_u64()?).ok()?,
        ),
    ))
}

fn scip_only_or_unresolved(
    state: &RepositoryState,
    args: &Value,
    loaded: Option<&scip::Loaded>,
) -> Result<Value, String> {
    let query = optional_query(args)?;
    if let (Some(query), Some(index)) = (query.as_ref(), loaded)
        && let Some(hit) = index.at_position(query)
    {
        let (offset, max) = page_limit(args)?;
        let mut rows = Vec::new();
        merge_scip_references(&mut rows, index, &hit.symbol);
        rows.sort_by_key(value_span_key);
        let total = rows.len();
        let page = rows.into_iter().skip(offset).take(max).collect::<Vec<_>>();
        let returned = page.len();
        return Ok(json!({
            "state": "RESOLVED",
            "query": query_json(query),
            "definition": scip_definition(state, &hit),
            "references": page,
            "page": page_json(offset, returned, total),
            "precision": "scip",
            "semantic_precision": "BOUNDED_STATIC",
            "sources": sources(loaded)
        }));
    }
    Ok(json!({
        "state": "UNRESOLVED",
        "query": query.as_ref().map(query_json),
        "definition": Value::Null,
        "references": [],
        "page": page_json(0, 0, 0),
        "precision": "parser_plus_graph",
        "semantic_precision": "BOUNDED_STATIC",
        "sources": sources(loaded),
        "reason": "no occurrence at this position"
    }))
}