relay-knowledge 1.1.17

Graph-database-based knowledge graph project.
Documentation
use std::ops::Range;

use crate::{
    code::{SnapshotBuild, stable_id},
    domain::{
        CodeFrameworkEdgeRecord, CodeFrameworkNodeRecord, FrameworkEdgeKind, FrameworkKind,
        FrameworkNodeKind, RepositoryCodeRange,
    },
};

use super::FrameworkFileInput;

pub(super) fn framework_node(
    build: &SnapshotBuild,
    input: &FrameworkFileInput<'_>,
    framework: FrameworkKind,
    kind: FrameworkNodeKind,
    name: &str,
    detail: Option<String>,
    byte_range: Range<usize>,
) -> CodeFrameworkNodeRecord {
    let byte_start = byte_range.start;
    let byte_end = byte_range.end;
    let byte_end = byte_end.max(byte_start).min(input.content.len());
    let line_range = line_range(input.content, byte_start, byte_end);
    let symbol_snapshot_id = input
        .symbols
        .iter()
        .find(|symbol| symbol.name == name)
        .map(|symbol| symbol.symbol_snapshot_id.clone());
    let node_id = stable_id(
        "framework-node",
        [
            build.repository_id.as_str(),
            build.source_scope.as_str(),
            input.path,
            framework.as_str(),
            kind.as_str(),
            name,
            &byte_start.to_string(),
        ],
    );
    CodeFrameworkNodeRecord {
        repository_id: build.repository_id.clone(),
        source_scope: build.source_scope.clone(),
        node_id,
        file_id: input.file_id.to_owned(),
        path: input.path.to_owned(),
        framework,
        kind,
        name: name.to_owned(),
        detail,
        symbol_snapshot_id,
        byte_range: RepositoryCodeRange {
            start: bounded_u32(byte_start),
            end: bounded_u32(byte_end),
        },
        line_range,
    }
}

pub(super) fn framework_edge(
    build: &SnapshotBuild,
    input: &FrameworkFileInput<'_>,
    framework: FrameworkKind,
    kind: FrameworkEdgeKind,
    source_node_id: &str,
    target: (Option<String>, Option<String>),
    byte_range: Range<usize>,
) -> CodeFrameworkEdgeRecord {
    let (target_node_id, target_hint) = target;
    let byte_start = byte_range.start;
    let byte_end = byte_range.end;
    let byte_end = byte_end.max(byte_start).min(input.content.len());
    let resolution_state = if target_node_id.is_some() {
        "resolved"
    } else {
        "unresolved"
    };
    let confidence_basis_points = if target_node_id.is_some() {
        10_000
    } else {
        6_000
    };
    let edge_id = stable_id(
        "framework-edge",
        [
            build.repository_id.as_str(),
            build.source_scope.as_str(),
            input.path,
            framework.as_str(),
            kind.as_str(),
            source_node_id,
            target_hint.as_deref().unwrap_or_default(),
            &byte_start.to_string(),
        ],
    );
    CodeFrameworkEdgeRecord {
        repository_id: build.repository_id.clone(),
        source_scope: build.source_scope.clone(),
        edge_id,
        file_id: input.file_id.to_owned(),
        path: input.path.to_owned(),
        framework,
        kind,
        source_node_id: source_node_id.to_owned(),
        target_node_id,
        target_hint,
        resolution_state: resolution_state.to_owned(),
        confidence_basis_points,
        confidence_tier: if confidence_basis_points == 10_000 {
            "exact"
        } else {
            "extracted"
        }
        .to_owned(),
        byte_range: RepositoryCodeRange {
            start: bounded_u32(byte_start),
            end: bounded_u32(byte_end),
        },
        line_range: line_range(input.content, byte_start, byte_end),
    }
}

pub(super) fn line_range(content: &str, byte_start: usize, byte_end: usize) -> RepositoryCodeRange {
    let start = content
        .as_bytes()
        .get(..byte_start.min(content.len()))
        .unwrap_or_default()
        .iter()
        .filter(|byte| **byte == b'\n')
        .count()
        + 1;
    let end = start
        + content
            .as_bytes()
            .get(byte_start.min(content.len())..byte_end.min(content.len()))
            .unwrap_or_default()
            .iter()
            .filter(|byte| **byte == b'\n')
            .count();
    RepositoryCodeRange {
        start: bounded_u32(start),
        end: bounded_u32(end),
    }
}

fn bounded_u32(value: usize) -> u32 {
    u32::try_from(value).unwrap_or(u32::MAX)
}

pub(super) fn balanced_region(
    content: &str,
    open: usize,
    opening: u8,
    closing: u8,
) -> Option<usize> {
    let bytes = content.as_bytes();
    if bytes.get(open) != Some(&opening) {
        return None;
    }
    let mut depth = 0usize;
    let mut quote = None;
    let mut escaped = false;
    for (offset, byte) in bytes.iter().copied().enumerate().skip(open) {
        if let Some(active_quote) = quote {
            if escaped {
                escaped = false;
            } else if byte == b'\\' {
                escaped = true;
            } else if byte == active_quote {
                quote = None;
            }
            continue;
        }
        if matches!(byte, b'\'' | b'"' | b'`') {
            quote = Some(byte);
        } else if byte == opening {
            depth += 1;
        } else if byte == closing {
            depth = depth.checked_sub(1)?;
            if depth == 0 {
                return Some(offset + 1);
            }
        }
    }
    None
}

pub(super) fn quoted_property(content: &str, property: &str) -> Option<(String, usize, usize)> {
    let mut search_start = 0usize;
    while let Some(relative_start) = content.get(search_start..)?.find(property) {
        let property_start = search_start + relative_start;
        let property_end = property_start + property.len();
        let boundary_before = property_start == 0
            || content
                .as_bytes()
                .get(property_start - 1)
                .is_some_and(|byte| {
                    !byte.is_ascii_alphanumeric() && *byte != b'_' && *byte != b'$'
                });
        let mut cursor = property_end;
        while content
            .as_bytes()
            .get(cursor)
            .is_some_and(u8::is_ascii_whitespace)
        {
            cursor += 1;
        }
        if boundary_before && content.as_bytes().get(cursor) == Some(&b':') {
            cursor += 1;
            while content
                .as_bytes()
                .get(cursor)
                .is_some_and(u8::is_ascii_whitespace)
            {
                cursor += 1;
            }
            let quote = *content.as_bytes().get(cursor)?;
            if matches!(quote, b'\'' | b'"' | b'`') {
                let value_start = cursor;
                cursor += 1;
                let mut escaped = false;
                while let Some(byte) = content.as_bytes().get(cursor).copied() {
                    if escaped {
                        escaped = false;
                    } else if byte == b'\\' {
                        escaped = true;
                    } else if byte == quote {
                        return Some((
                            content.get(value_start + 1..cursor)?.to_owned(),
                            value_start,
                            cursor + 1,
                        ));
                    }
                    cursor += 1;
                }
                return None;
            }
        }
        search_start = property_end;
    }
    None
}

pub(super) fn identifiers(expression: &str) -> impl Iterator<Item = (usize, &str)> {
    let mut cursor = 0usize;
    std::iter::from_fn(move || {
        while cursor < expression.len()
            && !expression.as_bytes()[cursor].is_ascii_alphabetic()
            && expression.as_bytes()[cursor] != b'_'
            && expression.as_bytes()[cursor] != b'$'
        {
            cursor += 1;
        }
        let start = cursor;
        while cursor < expression.len()
            && (expression.as_bytes()[cursor].is_ascii_alphanumeric()
                || matches!(expression.as_bytes()[cursor], b'_' | b'$'))
        {
            cursor += 1;
        }
        (start < cursor).then(|| (start, &expression[start..cursor]))
    })
}

pub(super) fn expression_identifier(name: &str) -> bool {
    !matches!(
        name,
        "as" | "const"
            | "else"
            | "false"
            | "for"
            | "if"
            | "in"
            | "let"
            | "null"
            | "of"
            | "return"
            | "this"
            | "track"
            | "true"
            | "undefined"
    ) && !name.starts_with('$')
}

pub(super) fn relative_module_path(source_path: &str, target: &str) -> String {
    let mut parts = source_path.split('/').collect::<Vec<_>>();
    parts.pop();
    for segment in target.split('/') {
        match segment {
            "." | "" => {}
            ".." => {
                parts.pop();
            }
            segment => parts.push(segment),
        }
    }
    parts.join("/")
}