fluidattacks-blends-domain 0.41.0

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

use crate::stack::build::StackBuilder;
use crate::stack::{
    import_sites, BindingReach, Definition, ImportSite, StackNode, SymbolPop, SymbolPush,
};
use crate::syntax::SyntaxGraph;
use crate::{Language, NodeId};

impl StackBuilder {
    pub(super) fn add_imports(
        &mut self,
        syntax: &SyntaxGraph,
        language: Language,
        root: NodeId,
        path: &str,
    ) {
        let directories = package_directories(path);
        for site in import_sites(syntax, language) {
            if site.reach == BindingReach::Scope
                && matches!(self.graph.nodes.get(&site.scope), Some(StackNode::Scope(_)))
            {
                self.add_import(&site, language, root, &directories);
            }
        }
    }

    fn add_import(
        &mut self,
        site: &ImportSite,
        language: Language,
        root: NodeId,
        directories: &[&str],
    ) {
        let binding = &site.binding;
        let module = imported_module(
            binding.module_stem.as_str(),
            binding.relative_level,
            language,
            directories,
        );
        let imported = (!binding.binds_module).then(|| {
            binding
                .source_symbol
                .as_deref()
                .unwrap_or(site.symbol.as_str())
        });
        let pushes: Option<Vec<&str>> = module.map(|module| {
            imported
                .filter(|symbol| *symbol != "*")
                .into_iter()
                .chain(qualified(&module).into_iter().rev())
                .collect()
        });
        if site.symbol == "*" {
            let Some([first, rest @ ..]) = pushes.as_deref() else {
                return;
            };
            self.graph.add_node(site.node, relay(first));
            self.graph.add_edge(site.scope, site.node, 0);
            self.add_pushes(site.node, rest, root);
            return;
        }
        self.graph.add_node(
            site.node,
            StackNode::PopSymbol(SymbolPop {
                symbol: site.symbol.clone(),
                precedence: 1,
                definition: Some(Definition {
                    is_private: language != Language::Python,
                    visibility_guard: None,
                }),
                import: Some(binding.clone()),
            }),
        );
        self.graph.add_edge(site.scope, site.node, 1);
        if let Some(pushes) = pushes {
            self.add_pushes(site.node, &pushes, root);
        }
    }

    fn add_pushes(&mut self, from: NodeId, symbols: &[&str], root: NodeId) {
        let last = symbols.iter().fold(from, |previous, symbol| {
            let push = self.add(relay(symbol));
            self.graph.add_edge(previous, push, 0);
            push
        });
        self.graph.add_edge(last, root, 0);
    }
}

fn relay(symbol: &str) -> StackNode {
    StackNode::PushSymbol(SymbolPush {
        symbol: symbol.into(),
        precedence: 0,
        is_reference: false,
    })
}

fn package_directories(path: &str) -> Vec<&str> {
    path.rsplit_once('/')
        .map_or("", |(directories, _)| directories)
        .split(['/', '.'])
        .filter(|segment| !segment.is_empty())
        .collect()
}

fn imported_module<'a>(
    stem: &'a str,
    relative_level: u32,
    language: Language,
    directories: &[&'a str],
) -> Option<Vec<&'a str>> {
    let package = if relative_level == 0 {
        &[][..]
    } else {
        let climbed = usize::try_from(relative_level.saturating_sub(1)).ok()?;
        let kept = directories.len().checked_sub(climbed)?;
        if language == Language::Python && kept == 0 {
            return None;
        }
        directories.get(..kept)?
    };
    Some(
        package
            .iter()
            .copied()
            .chain(stem.split('.').filter(|segment| !segment.is_empty()))
            .collect(),
    )
}

fn qualified<'a>(segments: &[&'a str]) -> Vec<&'a str> {
    segments
        .iter()
        .enumerate()
        .flat_map(|(index, segment)| (index > 0).then_some(".").into_iter().chain([*segment]))
        .collect()
}