relay-knowledge 1.1.14

Graph-database-based knowledge graph project.
Documentation
use crate::domain::CodeParseStatus;

use super::{
    chunks::{add_file_chunk, chunks_for_symbols},
    dependencies::{collect_dependencies, dependency_manifest_is_facts_only},
    imports::collect_imports,
    manual::collect_manual_nodes,
    records::records_from_captures,
    syntax::{extract_tag_captures_safely, parse_tree_safely},
    text::{count_lines, validate_text_content},
};

use crate::code::{
    CodeIndexError, SnapshotBuild, config_files, generated_detection, languages::detect_language,
    stable_content_hash, stable_id,
};

mod contracts;
mod feature_flag_projection;
mod parse_status;
mod route_projection;
mod text_only;

pub(in crate::code::parser) use contracts::{
    FileParseContext, FileParseOutput, ReferenceDedupKey, SyntaxFileInput,
};

pub(in crate::code) fn parse_indexed_file(
    build: &mut SnapshotBuild,
    path: &str,
    bytes: &[u8],
) -> Result<(), CodeIndexError> {
    let blob_hash = stable_content_hash(bytes);
    let file_id = stable_id(
        "file",
        [&build.repository_id, &build.source_scope, path, &blob_hash],
    );
    let language = detect_language(path);
    let line_count = count_lines(bytes);
    let is_generated = generated_detection::is_generated_file(path, bytes);
    let (parse_status, degraded_reason, content) = validate_text_content(path, bytes, language)?;

    let Some(content) = content else {
        parse_status::record_file_status(
            build,
            parse_status::FileStatusInput {
                path,
                file_id: &file_id,
                language_id: language.map_or("unknown", |spec| spec.id),
                blob_hash: &blob_hash,
                byte_len: bytes.len(),
                line_count,
                parse_status,
                is_generated,
                degraded_reason,
            },
        );
        return Ok(());
    };
    let Some(language) = language else {
        parse_status::record_file_status(
            build,
            parse_status::FileStatusInput {
                path,
                file_id: &file_id,
                language_id: "unknown",
                blob_hash: &blob_hash,
                byte_len: bytes.len(),
                line_count,
                parse_status,
                is_generated,
                degraded_reason,
            },
        );
        if dependency_manifest_is_facts_only(path) {
            record_dependencies(build, path, &file_id, &content)?;
            return Ok(());
        }
        add_file_chunk(build, path, &file_id, "unknown", &content)?;
        record_dependencies(build, path, &file_id, &content)?;
        feature_flag_projection::record_feature_flags(
            build, path, &file_id, "unknown", &content, None,
        )?;
        return Ok(());
    };
    if parse_status == CodeParseStatus::TextOnly {
        parse_status::record_file_status(
            build,
            parse_status::FileStatusInput {
                path,
                file_id: &file_id,
                language_id: language.id,
                blob_hash: &blob_hash,
                byte_len: bytes.len(),
                line_count,
                parse_status,
                is_generated,
                degraded_reason,
            },
        );
        text_only::record_topic_symbols(build, path, &file_id, language.id, bytes)?;
        add_file_chunk(build, path, &file_id, language.id, &content)?;
        record_dependencies(build, path, &file_id, &content)?;
        feature_flag_projection::record_feature_flags(
            build,
            path,
            &file_id,
            language.id,
            &content,
            None,
        )?;
        route_projection::record_routes(build, path, &file_id, language.id, &content);
        return Ok(());
    }

    parse_syntax_file(
        build,
        SyntaxFileInput {
            path,
            file_id: &file_id,
            language,
            blob_hash: &blob_hash,
            byte_len: bytes.len(),
            line_count,
            is_generated,
            content: &content,
        },
    )
}

pub(in crate::code::parser) fn parse_syntax_file(
    build: &mut SnapshotBuild,
    input: SyntaxFileInput<'_>,
) -> Result<(), CodeIndexError> {
    let parsed = match parse_tree_safely(input.language, input.content) {
        Ok(parsed) => parsed,
        Err(error) => {
            return record_syntax_failure_fallback(build, &input, "parse", &error);
        }
    };
    let root = parsed.root_node();
    let captures = if input.language.id == "c" {
        Vec::new()
    } else {
        match extract_tag_captures_safely(input.language, root, input.content) {
            Ok(captures) => captures,
            Err(error) => {
                return record_syntax_failure_fallback(build, &input, "query", &error);
            }
        }
    };
    let context = FileParseContext {
        build,
        path: input.path,
        file_id: input.file_id,
        language_id: input.language.id,
        content: input.content,
    };
    let mut output = FileParseOutput::new();
    let (config_definitions, config_references) =
        config_files::structured_facts(input.path, input.language.id, input.content);
    records_from_captures(&context, captures, &mut output)?;
    collect_manual_nodes(
        &context,
        root,
        &config_definitions,
        &config_references,
        &mut output,
    )?;
    let imports = collect_imports(
        build,
        input.path,
        input.file_id,
        input.language.id,
        input.content,
        root,
    )?;
    let chunks = chunks_for_symbols(
        build,
        input.path,
        input.file_id,
        input.language.id,
        input.content,
        &output.symbols,
    )?;
    let (parse_status, degraded_reason) = parse_status::syntax_parse_status(
        input.language.id,
        root,
        input.content,
        &output,
        &imports,
    );
    parse_status::record_file_status(
        build,
        parse_status::FileStatusInput {
            path: input.path,
            file_id: input.file_id,
            language_id: input.language.id,
            blob_hash: input.blob_hash,
            byte_len: input.byte_len,
            line_count: input.line_count,
            parse_status,
            is_generated: input.is_generated,
            degraded_reason,
        },
    );

    build.symbols.extend(output.symbols);
    build.references.extend(output.references);
    build.imports.extend(imports);
    record_dependencies(build, input.path, input.file_id, input.content)?;
    feature_flag_projection::record_feature_flags(
        build,
        input.path,
        input.file_id,
        input.language.id,
        input.content,
        Some(&config_definitions),
    )?;
    build.chunks.extend(chunks);
    route_projection::record_routes(
        build,
        input.path,
        input.file_id,
        input.language.id,
        input.content,
    );

    Ok(())
}

fn record_syntax_failure_fallback(
    build: &mut SnapshotBuild,
    input: &SyntaxFileInput<'_>,
    stage: &str,
    error: &CodeIndexError,
) -> Result<(), CodeIndexError> {
    parse_status::record_tree_sitter_failure(build, input, stage, error);
    add_file_chunk(
        build,
        input.path,
        input.file_id,
        input.language.id,
        input.content,
    )?;
    record_dependencies(build, input.path, input.file_id, input.content)?;
    feature_flag_projection::record_feature_flags(
        build,
        input.path,
        input.file_id,
        input.language.id,
        input.content,
        None,
    )?;
    route_projection::record_routes(
        build,
        input.path,
        input.file_id,
        input.language.id,
        input.content,
    );
    Ok(())
}

fn record_dependencies(
    build: &mut SnapshotBuild,
    path: &str,
    file_id: &str,
    content: &str,
) -> Result<(), CodeIndexError> {
    let records = collect_dependencies(build, path, file_id, content)?;
    build.dependencies.extend(records);
    Ok(())
}