relay-knowledge 1.0.7

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

use super::imports::{
    ImportContext, ImportResolution, ModuleFileResolution, normalize_join, parent_dir,
    parse_quoted_specifier,
};

pub(super) fn resolve_import(
    import: &CodeImportRecord,
    context: &ImportContext<'_>,
) -> Option<(ImportResolution, Option<String>)> {
    let request = CppImportRequest::parse(&import.path, &import.module)?;

    Some(match request {
        CppImportRequest::Include {
            candidates,
            allow_source_root_match,
        } => match context.resolve_first_module_file(&candidates, allow_source_root_match) {
            ModuleFileResolution::Resolved(target_hint) => {
                (ImportResolution::Resolved, Some(target_hint))
            }
            ModuleFileResolution::Ambiguous => (ImportResolution::Ambiguous, None),
            ModuleFileResolution::Unresolved => (ImportResolution::Unresolved, None),
        },
        CppImportRequest::UsingSymbol { namespace, name } => {
            let resolution = if namespace.is_empty() {
                context.resolve_name(&name)
            } else {
                context.resolve_name_in_namespace(&namespace, &name)
            };
            (resolution, None)
        }
        CppImportRequest::UsingNamespace { namespace } => {
            let resolution = if context.namespace_exists(&namespace) {
                ImportResolution::Resolved
            } else {
                ImportResolution::Unresolved
            };
            (resolution, None)
        }
    })
}

enum CppImportRequest {
    Include {
        candidates: Vec<String>,
        allow_source_root_match: bool,
    },
    UsingSymbol {
        namespace: String,
        name: String,
    },
    UsingNamespace {
        namespace: String,
    },
}

impl CppImportRequest {
    fn parse(import_path: &str, statement: &str) -> Option<Self> {
        let statement = statement.trim().trim_end_matches(';').trim();
        if statement.starts_with("#include") {
            return parse_include(import_path, statement);
        }
        if let Some(namespace) = statement.strip_prefix("using namespace ") {
            let namespace = namespace.trim().replace("::", ".");
            if namespace.is_empty() {
                return None;
            }
            return Some(Self::UsingNamespace { namespace });
        }
        if let Some(name) = statement.strip_prefix("using ") {
            if name.contains('=') {
                return None;
            }
            let (namespace, name) = name.rsplit_once("::").unwrap_or(("", name));
            let name = name.trim();
            if name.is_empty() {
                return None;
            }
            return Some(Self::UsingSymbol {
                namespace: namespace.trim().to_owned(),
                name: name.to_owned(),
            });
        }

        None
    }
}

fn parse_include(import_path: &str, statement: &str) -> Option<CppImportRequest> {
    let (target, quoted) = if let Some(target) = parse_quoted_specifier(statement) {
        (target, true)
    } else {
        (parse_angle_specifier(statement)?, false)
    };
    let mut candidates = Vec::new();
    if quoted {
        if let Some(relative) = normalize_join(parent_dir(import_path), target) {
            candidates.push(relative);
        }
    }
    push_candidate(&mut candidates, target.to_owned());
    if !target.starts_with("include/") {
        push_candidate(&mut candidates, format!("include/{target}"));
    }

    Some(CppImportRequest::Include {
        candidates,
        allow_source_root_match: quoted,
    })
}

fn push_candidate(candidates: &mut Vec<String>, candidate: String) {
    if !candidates.contains(&candidate) {
        candidates.push(candidate);
    }
}

fn parse_angle_specifier(statement: &str) -> Option<&str> {
    let start = statement.find('<')?;
    let rest = &statement[start + 1..];
    let end = rest.find('>')?;

    Some(&rest[..end])
}