gobby-code 1.3.2

Fast Rust CLI for Gobby's code index — AST-aware search, symbol navigation, and dependency graph
Documentation
use crate::models::ImportRelation;

use super::super::context::{ExternalRootBinding, ExtractedImports, ImportResolutionContext};
use super::super::helpers::{
    collapse_whitespace, dart_import_alias, dart_local_import_target, elixir_alias_as,
    extract_quoted_string, is_elixir_alias_path,
};
use super::super::predicates::is_external_dart_uri;

pub(crate) fn parse_swift_import_statement(
    text: &str,
    rel_path: &str,
    import_context: &ImportResolutionContext,
    extracted: &mut ExtractedImports,
) {
    let normalized = text.trim();
    let Some(rest) = normalized.strip_prefix("import ") else {
        return;
    };

    let mut tokens = rest.split_whitespace();
    let mut module_token = tokens.next().unwrap_or_default();
    if matches!(
        module_token,
        "class" | "struct" | "enum" | "protocol" | "func" | "typealias" | "var" | "let"
    ) {
        module_token = tokens.next().unwrap_or_default();
    }
    if module_token.is_empty() {
        return;
    }
    let module = module_token.split('.').next().unwrap_or_default();
    extracted.imports.push(ImportRelation {
        file_path: rel_path.to_string(),
        module_name: rest.to_string(),
    });
    if module.is_empty()
        || matches!(
            module,
            "class" | "struct" | "enum" | "protocol" | "func" | "typealias" | "var" | "let"
        )
        || import_context.swift_local_modules.contains(module)
    {
        return;
    }

    extracted.bindings.external_roots.insert(
        module.to_string(),
        ExternalRootBinding {
            module: module.to_string(),
            module_from_qualifier: false,
        },
    );
}

pub(crate) fn parse_ruby_import_statement(
    text: &str,
    rel_path: &str,
    import_context: &ImportResolutionContext,
    extracted: &mut ExtractedImports,
) {
    let normalized = text.trim();
    let Some(method) = normalized.split_whitespace().next() else {
        return;
    };

    let literal = extract_quoted_string(normalized);
    extracted.imports.push(ImportRelation {
        file_path: rel_path.to_string(),
        module_name: literal.clone().unwrap_or_else(|| normalized.to_string()),
    });

    if method != "require" {
        return;
    }
    let Some(required) = literal else {
        return;
    };
    let Some(root) = import_context.ruby_require_root(&required) else {
        return;
    };
    if import_context.ruby_local_constant_roots.contains(root) {
        return;
    }
    extracted.bindings.external_roots.insert(
        root.to_string(),
        ExternalRootBinding {
            module: required,
            module_from_qualifier: false,
        },
    );
}

pub(crate) fn parse_dart_import_statement(
    text: &str,
    rel_path: &str,
    import_context: &ImportResolutionContext,
    extracted: &mut ExtractedImports,
) {
    let normalized = collapse_whitespace(text);
    if !normalized.starts_with("import ") {
        return;
    }
    let Some(uri) = extract_quoted_string(&normalized) else {
        return;
    };

    extracted.imports.push(ImportRelation {
        file_path: rel_path.to_string(),
        module_name: uri.clone(),
    });

    let alias = dart_import_alias(&normalized);

    if is_external_dart_uri(&uri, import_context) {
        match alias {
            Some(alias) => {
                extracted.bindings.member.insert(alias, uri);
            }
            None => extracted.bindings.bare_wildcard_modules.push(uri),
        }
        return;
    }

    // Local import (self-package `package:<self>/…` or relative URI). Resolve the
    // URI to the project-relative file it names. Dart brings a file's public
    // top-level symbols into scope as a whole, so the imported name is the symbol
    // name; record the target file as a candidate and let the post-write DB pass
    // narrow it to a real id (or degrade to unresolved).
    let Some(target_file) = dart_local_import_target(
        &uri,
        rel_path,
        import_context.dart_self_package_name.as_deref(),
    ) else {
        return;
    };

    match alias {
        // `import '…' as p;` only exposes symbols through the prefix `p.name()`;
        // a bare `name()` does not see them, so the file feeds the member channel
        // (`p.name()` resolves `name` against it).
        Some(alias) => extracted
            .bindings
            .local_member
            .entry(alias)
            .or_default()
            .push(target_file),
        // An unaliased local import exposes the file's top-level symbols to bare
        // calls in the importing file.
        None => extracted.bindings.dart_local_import_files.push(target_file),
    }
}

pub(crate) fn parse_elixir_import_statement(
    text: &str,
    rel_path: &str,
    import_context: &ImportResolutionContext,
    extracted: &mut ExtractedImports,
) {
    let normalized = collapse_whitespace(text);
    let Some((keyword, rest)) = normalized.split_once(' ') else {
        return;
    };
    if !matches!(keyword, "alias" | "import" | "require") {
        return;
    }
    let target = rest.split([',', ' ']).next().unwrap_or_default().trim();
    extracted.imports.push(ImportRelation {
        file_path: rel_path.to_string(),
        module_name: if target.is_empty() {
            normalized.clone()
        } else {
            target.to_string()
        },
    });

    if !is_elixir_alias_path(target) {
        return;
    }
    let Some(root) = target.split('.').next() else {
        return;
    };
    if import_context.elixir_local_module_roots.contains(root) {
        // Local module reference. Local modules shadow same-named dependencies,
        // so this branch takes precedence over the external root map below. Bind
        // the alias/import to the module's declaring file(s) for the post-write
        // DB pass; `require` adds no call surface beyond the fully-qualified
        // path, which resolves directly against `elixir_module_files`.
        let candidate_files = import_context.elixir_module_files(target);
        if candidate_files.is_empty() {
            return;
        }
        match keyword {
            "alias" => {
                let alias = elixir_alias_as(&normalized)
                    .unwrap_or_else(|| target.rsplit('.').next().unwrap_or(target).to_string());
                extracted
                    .bindings
                    .local_member
                    .insert(alias, candidate_files);
            }
            "import" => {
                extracted
                    .bindings
                    .elixir_local_import_files
                    .extend(candidate_files);
            }
            _ => {}
        }
        return;
    }
    let Some(module) = import_context.elixir_external_root_module(root) else {
        return;
    };

    if keyword == "alias" {
        let alias = elixir_alias_as(&normalized)
            .unwrap_or_else(|| target.rsplit('.').next().unwrap_or(target).to_string());
        extracted.bindings.member.insert(alias, target.to_string());
    } else if keyword == "import" {
        extracted
            .bindings
            .bare_wildcard_modules
            .push(target.to_string());
    }
    extracted.bindings.external_roots.insert(
        root.to_string(),
        ExternalRootBinding {
            module: module.to_string(),
            module_from_qualifier: true,
        },
    );
}