relay-knowledge 1.1.9

Graph-database-based knowledge graph project.
Documentation
use std::collections::BTreeSet;

use crate::{
    code::{SourceGrepMatch, simple_source_identifier, source_line_defines_identity},
    domain::{CodeRetrievalHit, CodeRetrievalLayer, CodeRetrievalRequest},
};

use super::source_surface::hit_has_complete_source_surface;

pub(super) fn hybrid_source_surface_fallback(
    request: &CodeRetrievalRequest,
    results: &[CodeRetrievalHit],
) -> Option<(String, Vec<String>)> {
    let query_terms = identifier_terms(&request.query);
    if query_terms.len() < 2 {
        return None;
    }
    let mut best: Option<(String, usize, usize)> = None;
    for hit in results {
        if !hit_allows_source_refresh(hit) {
            continue;
        }
        let identity = hit_identity(hit)?;
        let identity_terms = identifier_terms(&identity);
        if identity_terms.len() >= 2
            && identity_terms.len() < query_terms.len()
            && identity_terms.iter().all(|term| query_terms.contains(term))
        {
            let term_count = identity_terms.len();
            let identity_len = identity.len();
            if best.as_ref().is_none_or(|(_, best_terms, best_len)| {
                (term_count, identity_len) > (*best_terms, *best_len)
            }) {
                best = Some((identity, term_count, identity_len));
            }
        }
    }

    let (identity, _, _) = best?;
    let paths = incomplete_hybrid_source_surface_paths(results, &identity);
    (!paths.is_empty()).then_some((identity, paths))
}

pub(super) fn hybrid_exact_path_source_fallback(
    request: &CodeRetrievalRequest,
    results: &[CodeRetrievalHit],
) -> Option<(String, Vec<String>)> {
    let query = exact_path_hybrid_source_query(&request.query, results)?;
    let paths = request
        .repository
        .path_filters
        .iter()
        .filter(|path| exact_file_filter(path))
        .map(|path| normalize_filter_path(path).to_owned())
        .collect::<Vec<_>>();

    (!paths.is_empty()).then_some((query, paths))
}

pub(super) fn hit_allows_source_refresh(hit: &CodeRetrievalHit) -> bool {
    hit.retrieval_layers.contains(&CodeRetrievalLayer::Symbol)
        || hit
            .retrieval_layers
            .contains(&CodeRetrievalLayer::Definition)
        || hit
            .retrieval_layers
            .contains(&CodeRetrievalLayer::CallGraph)
}

pub(super) fn hit_source_line_is_better(
    hit: &CodeRetrievalHit,
    matched: &SourceGrepMatch,
    query: &str,
) -> bool {
    if source_type_declaration_line_matches_query(&matched.excerpt, query)
        && !source_type_declaration_line_matches_query(&hit.excerpt, query)
    {
        return true;
    }
    if matched.excerpt.contains(query) && !hit.excerpt.contains(query) {
        return true;
    }
    matched.excerpt.len() > hit.excerpt.len()
        || (matched.excerpt.contains("export ") && !hit.excerpt.contains("export "))
}

pub(super) fn exact_path_hybrid_source_line_score(
    request: &CodeRetrievalRequest,
    paths: &[String],
    matched: &SourceGrepMatch,
    lowest_score: Option<f64>,
) -> Option<f64> {
    if !paths.iter().any(|path| exact_file_filter(path))
        || line_query_term_match_count(&matched.excerpt, &request.query) == 0
    {
        return None;
    }

    let assignment_bonus = if source_line_has_assignment_surface(&matched.excerpt) {
        0.35
    } else {
        0.0
    };
    Some(lowest_score.unwrap_or(1.0) + 0.75 + assignment_bonus)
}

pub(super) fn source_type_declaration_line_matches_query(line: &str, query: &str) -> bool {
    query
        .split(|character: char| !source_identifier_char(character))
        .filter(|term| term.len() >= 3 && simple_source_identifier(term))
        .any(|term| source_type_declaration_line_defines_identity(line, term))
}

fn source_type_declaration_line_defines_identity(line: &str, identity: &str) -> bool {
    let line = line.trim();
    if !source_line_defines_identity(line, identity) {
        return false;
    }

    source_identifier_ranges(line, identity).any(|(start, _)| {
        line.get(..start)
            .is_some_and(declaration_keyword_before_identity)
    })
}

fn declaration_keyword_before_identity(before: &str) -> bool {
    before
        .split(|character: char| !source_identifier_char(character))
        .any(|token| {
            matches!(
                token,
                "class"
                    | "struct"
                    | "enum"
                    | "interface"
                    | "trait"
                    | "type"
                    | "typealias"
                    | "protocol"
            )
        })
}

fn line_query_term_match_count(line: &str, query: &str) -> usize {
    let line = line.to_ascii_lowercase();
    query
        .split(|character: char| !source_identifier_char(character))
        .filter(|term| term.len() >= 3 && simple_source_identifier(term))
        .filter(|term| line.contains(&term.to_ascii_lowercase()))
        .count()
}

fn source_line_has_assignment_surface(line: &str) -> bool {
    line.contains('=') || line.contains("=>") || line.contains(":=")
}

fn exact_path_hybrid_source_query(query: &str, results: &[CodeRetrievalHit]) -> Option<String> {
    let terms = ordered_identifier_terms(query);
    if terms.is_empty() {
        return None;
    }
    let primary = terms
        .iter()
        .enumerate()
        .filter_map(|(index, term)| {
            let score = results
                .iter()
                .map(|hit| {
                    code_surface_term_score(&hit.excerpt, term)
                        + hit
                            .canonical_symbol_id
                            .as_deref()
                            .map_or(0, |symbol_id| code_surface_term_score(symbol_id, term))
                })
                .sum::<usize>();
            (score > 0).then_some((score, index, term))
        })
        .max_by_key(|(score, index, _)| (*score, *index))
        .map(|(_, _, term)| term.clone())
        .unwrap_or_else(|| terms[0].clone());
    let mut query_terms = vec![primary.clone()];
    if let Some(support) = exact_path_identity_support_term(&terms, results, &primary) {
        query_terms.push(support);
    }

    Some(query_terms.join(" "))
}

fn exact_path_identity_support_term(
    terms: &[String],
    results: &[CodeRetrievalHit],
    primary: &str,
) -> Option<String> {
    terms
        .iter()
        .enumerate()
        .filter(|(_, term)| term.as_str() != primary)
        .filter_map(|(index, term)| {
            let score = incomplete_identity_support_score(results, term);
            (score > 0).then_some((score, index, term))
        })
        .max_by_key(|(score, index, _)| (*score, *index))
        .map(|(_, _, term)| term.clone())
}

fn incomplete_identity_support_score(results: &[CodeRetrievalHit], term: &str) -> usize {
    let term = term.to_ascii_lowercase();
    results
        .iter()
        .filter(|hit| hit_allows_source_refresh(hit))
        .filter(|hit| !hit.excerpt.to_ascii_lowercase().contains(&term))
        .filter(|hit| {
            hit.canonical_symbol_id
                .as_deref()
                .is_some_and(|identity| identifier_terms(identity).contains(&term))
        })
        .count()
}

fn ordered_identifier_terms(query: &str) -> Vec<String> {
    let mut terms = Vec::new();
    for term in query
        .split(|character: char| !source_identifier_char(character))
        .filter(|term| term.len() >= 3 && simple_source_identifier(term))
    {
        let term = term.to_owned();
        if !terms.contains(&term) {
            terms.push(term);
        }
    }

    terms
}

fn code_surface_term_score(text: &str, term: &str) -> usize {
    let text = text.to_ascii_lowercase();
    let term = term.to_ascii_lowercase();
    text.match_indices(&term)
        .filter(|(start, _)| identifier_match_has_boundary(&text, &term, *start))
        .map(|(start, _)| 1 + usize::from(term_has_code_separator_before(&text, start)))
        .sum()
}

fn identifier_match_has_boundary(text: &str, term: &str, start: usize) -> bool {
    let end = start + term.len();
    text.get(..start).is_some_and(|prefix| {
        prefix
            .chars()
            .next_back()
            .is_none_or(|character| !source_identifier_char(character))
    }) && text.get(end..).is_some_and(|suffix| {
        suffix
            .chars()
            .next()
            .is_none_or(|character| !source_identifier_char(character))
    })
}

fn term_has_code_separator_before(text: &str, start: usize) -> bool {
    text.get(..start)
        .and_then(|prefix| prefix.chars().next_back())
        .is_some_and(|character| matches!(character, '_' | '.' | ':' | '>' | '-'))
}

fn incomplete_hybrid_source_surface_paths(
    results: &[CodeRetrievalHit],
    identity: &str,
) -> Vec<String> {
    let mut paths = Vec::new();
    for hit in results {
        let own_identity = hit_identity(hit);
        if hit_allows_source_refresh(hit)
            && !hit_has_complete_source_surface(hit, identity)
            && own_identity.as_deref() != Some(identity)
            && !own_identity.as_deref().is_some_and(|own_identity| {
                hit_has_complete_source_surface(hit, own_identity)
                    && !canonical_symbol_has_parent_identity(hit, identity)
            })
        {
            push_candidate_path(&mut paths, &hit.path);
        }
    }
    paths
}

fn exact_file_filter(path: &str) -> bool {
    let path = normalize_filter_path(path);
    !path.is_empty()
        && path
            .rsplit('/')
            .next()
            .is_some_and(|name| name.contains('.'))
        && !path.ends_with('/')
}

fn canonical_symbol_has_parent_identity(hit: &CodeRetrievalHit, identity: &str) -> bool {
    hit.canonical_symbol_id.as_deref().is_some_and(|symbol_id| {
        let mut parts = symbol_id
            .split(|character: char| !source_identifier_char(character))
            .filter(|part| !part.is_empty());
        parts.any(|part| part == identity) && parts.next().is_some()
    })
}

fn hit_identity(hit: &CodeRetrievalHit) -> Option<String> {
    hit.canonical_symbol_id
        .as_deref()
        .and_then(|symbol_id| {
            symbol_id
                .rsplit(|character: char| !source_identifier_char(character))
                .find(|term| !term.is_empty())
        })
        .or_else(|| {
            hit.excerpt
                .split(|character: char| !source_identifier_char(character))
                .find(|term| simple_source_identifier(term))
        })
        .map(str::to_owned)
}

fn identifier_terms(value: &str) -> BTreeSet<String> {
    let mut terms = BTreeSet::new();
    for token in value.split(|character: char| !source_identifier_char(character)) {
        if token.is_empty() {
            continue;
        }
        for term in split_identifier_token(token) {
            if term.len() > 1 {
                terms.insert(term.to_ascii_lowercase());
            }
        }
    }

    terms
}

fn split_identifier_token(token: &str) -> Vec<&str> {
    let mut terms = Vec::new();
    let mut start = 0usize;
    let mut previous_lowercase = false;
    for (index, character) in token.char_indices() {
        let boundary = index > start
            && (character == '_' || (character.is_ascii_uppercase() && previous_lowercase));
        if boundary {
            terms.push(token[start..index].trim_matches('_'));
            start = index + usize::from(character == '_');
        }
        previous_lowercase = character.is_ascii_lowercase() || character.is_ascii_digit();
    }
    if start < token.len() {
        terms.push(token[start..].trim_matches('_'));
    }

    terms.into_iter().filter(|term| !term.is_empty()).collect()
}

fn push_candidate_path(paths: &mut Vec<String>, path: &str) {
    let normalized = normalize_filter_path(path);
    if !normalized.is_empty() && !paths.iter().any(|existing| existing == normalized) {
        paths.push(normalized.to_owned());
    }
}

fn normalize_filter_path(path: &str) -> &str {
    let mut path = path.trim_end_matches(['/', '\\']);
    while let Some(stripped) = path.strip_prefix("./") {
        path = stripped;
    }

    path
}

fn source_identifier_ranges<'a>(
    line: &'a str,
    identity: &'a str,
) -> impl Iterator<Item = (usize, usize)> + 'a {
    line.match_indices(identity).filter_map(|(start, _)| {
        let end = start + identity.len();
        let has_start_boundary = line.get(..start).is_some_and(|prefix| {
            prefix
                .chars()
                .next_back()
                .is_none_or(|character| !source_identifier_char(character))
        });
        let has_end_boundary = line.get(end..).is_some_and(|suffix| {
            suffix
                .chars()
                .next()
                .is_none_or(|character| !source_identifier_char(character))
        });

        (has_start_boundary && has_end_boundary).then_some((start, end))
    })
}

fn source_identifier_char(character: char) -> bool {
    character.is_ascii_alphanumeric() || character == '_'
}