fluidattacks-blends-domain 0.35.0

Blends functional core: pure AST graph to syntax graph (no_std)
Documentation
use alloc::string::String;
use alloc::vec::Vec;

const FILE_EXTENSIONS: [&str; 8] = ["dart", "rb", "py", "js", "jsx", "ts", "tsx", "php"];

fn is_file_path(expression: &str) -> bool {
    expression.contains('/') || expression.matches('.').count() == 1
}

fn file_extension(name: &str) -> Option<&str> {
    let base_name = name
        .rsplit_once('/')
        .map_or(name, |(_, base_name)| base_name);
    let (stem, extension) = base_name.rsplit_once('.')?;
    stem.contains(|c| c != '.').then_some(extension)
}

fn strip_file_extension(name: &str) -> &str {
    if !is_file_path(name) {
        return name;
    }
    file_extension(name)
        .filter(|extension| {
            FILE_EXTENSIONS
                .iter()
                .any(|known| extension.eq_ignore_ascii_case(known))
        })
        .and_then(|extension| name.strip_suffix(extension)?.strip_suffix('.'))
        .unwrap_or(name)
}

#[must_use]
pub fn normalize_module_path(expression: &str) -> String {
    let segments: Vec<&str> = strip_file_extension(expression)
        .split(['.', '/', '\\'])
        .filter(|segment| !segment.is_empty() && *segment != "*")
        .collect();
    if segments.is_empty() {
        String::from(expression)
    } else {
        segments.join(".")
    }
}

#[cfg(test)]
mod tests {
    use super::normalize_module_path;

    #[test]
    fn keeps_a_dotted_module_name() {
        assert_eq!(normalize_module_path("a.b.c"), "a.b.c");
    }

    #[test]
    fn keeps_an_unknown_extension_on_a_two_segment_name() {
        assert_eq!(normalize_module_path("os.path"), "os.path");
    }

    #[test]
    fn strips_a_known_extension_ignoring_its_case() {
        assert_eq!(normalize_module_path("util.PY"), "util");
    }

    #[test]
    fn keeps_a_known_extension_when_the_name_has_two_dots() {
        assert_eq!(normalize_module_path("a.b.py"), "a.b.py");
    }

    #[test]
    fn turns_a_file_path_into_a_dotted_module() {
        assert_eq!(normalize_module_path("./lib/util.js"), "lib.util");
        assert_eq!(normalize_module_path("lib\\util"), "lib.util");
    }

    #[test]
    fn strips_only_the_last_extension_of_a_file_path() {
        assert_eq!(normalize_module_path("lib/types.d.ts"), "lib.types.d");
    }

    #[test]
    fn keeps_a_dotfile_name_as_its_only_segment() {
        assert_eq!(normalize_module_path("lib/.py"), "lib.py");
    }

    #[test]
    fn drops_wildcard_segments() {
        assert_eq!(normalize_module_path("x.*"), "x");
    }

    #[test]
    fn returns_the_expression_when_no_segment_survives() {
        assert_eq!(normalize_module_path("..."), "...");
        assert_eq!(normalize_module_path("*"), "*");
        assert_eq!(normalize_module_path(""), "");
    }
}