fluidattacks-blends-domain 0.32.0

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

use crate::stack::module_path::normalize_module_path;
use crate::stack::ImportBinding;
use crate::syntax::{SyntaxGraph, SyntaxNode};
use crate::{Language, NodeId};

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ImportSite {
    pub node: NodeId,
    pub scope: NodeId,
    pub symbol: String,
    pub binding: ImportBinding,
}

#[must_use]
pub fn import_sites(syntax: &SyntaxGraph, language: Language) -> Vec<ImportSite> {
    if language != Language::Python {
        return Vec::new();
    }
    let mut sites: Vec<ImportSite> = syntax
        .symbol_index
        .iter()
        .flat_map(|(symbol, scopes)| {
            scopes.iter().flat_map(move |(scope, nodes)| {
                nodes
                    .iter()
                    .filter_map(move |node| python_site(syntax, *node, *scope, symbol))
            })
        })
        .collect();
    sites.sort_by_key(|site| site.node);
    sites
}

fn python_site(
    syntax: &SyntaxGraph,
    node: NodeId,
    scope: NodeId,
    symbol: &str,
) -> Option<ImportSite> {
    let Some(SyntaxNode::Import {
        expression: Some(expression),
        alias,
        module_name: None,
        ..
    }) = syntax.nodes.get(&node)
    else {
        return None;
    };
    Some(ImportSite {
        node,
        scope,
        symbol: String::from(symbol),
        binding: ImportBinding {
            module_stem: normalize_module_path(expression),
            source_symbol: alias.as_ref().map(|_| last_segment(expression)),
            relative_level: 0,
            binds_module: false,
        },
    })
}

fn last_segment(expression: &str) -> String {
    String::from(expression.rsplit('.').next().unwrap_or(expression))
}