weavatrix-rust 2.1.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use super::super::support::normalized_path;
use std::path::Path;

pub(super) fn push_unique(candidates: &mut Vec<String>, value: String) {
    if !value.is_empty() && !candidates.contains(&value) {
        candidates.push(value);
    }
}

pub(super) fn expand(bases: Vec<String>, extensions: &[&str]) -> Vec<String> {
    let mut result = Vec::new();
    for base in bases {
        if !result.contains(&base) {
            result.push(base.clone());
        }
        // A dot in a module stem is not necessarily a source extension.
        // JavaScript projects commonly use names such as `user.model` or
        // `common.actions` and import them without the final `.js`. Only an
        // extension understood by this language makes the candidate explicit.
        let has_known_extension = Path::new(&base)
            .extension()
            .and_then(|extension| extension.to_str())
            .is_some_and(|candidate| {
                extensions
                    .iter()
                    .any(|extension| candidate.eq_ignore_ascii_case(extension))
            });
        if !has_known_extension {
            for extension in extensions {
                for candidate in [
                    format!("{base}.{extension}"),
                    format!("{base}/mod.{extension}"),
                    format!("{base}/index.{extension}"),
                    format!("{base}/__init__.{extension}"),
                ] {
                    if !result.contains(&candidate) {
                        result.push(candidate);
                    }
                }
            }
        }
    }
    result
}

/// TypeScript source commonly imports the JavaScript path that will exist
/// after compilation. Preserve a real runtime file before trying source forms.
pub(super) fn typescript_runtime_fallbacks(
    candidates: Vec<String>,
    specifier: &str,
) -> Vec<String> {
    let runtime_extension = Path::new(specifier)
        .extension()
        .and_then(|extension| extension.to_str());
    if !matches!(runtime_extension, Some("js" | "jsx")) {
        return candidates;
    }

    let mut result = Vec::new();
    for candidate in candidates {
        push_unique(&mut result, candidate.clone());
        if Path::new(&candidate)
            .extension()
            .and_then(|extension| extension.to_str())
            .is_some_and(|extension| matches!(extension, "js" | "jsx"))
        {
            for extension in ["ts", "tsx", "mts", "cts"] {
                push_unique(
                    &mut result,
                    normalized_path(&Path::new(&candidate).with_extension(extension)),
                );
            }
        }
    }
    result
}