fluidattacks-blends-domain 0.37.0

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

use crate::stack::imports::{
    module_binding, script_module, symbol_binding, BindingReach, ImportSite,
};
use crate::syntax::{SyntaxGraph, SyntaxNode};
use crate::{CodeGraph, NodeId};

pub fn require_sites(
    syntax: &SyntaxGraph,
    bound_names: &BTreeMap<NodeId, (NodeId, &str)>,
) -> Vec<ImportSite> {
    bound_names
        .iter()
        .flat_map(|(node, (scope, symbol))| declaration_sites(syntax, *node, *scope, symbol))
        .collect()
}

fn declaration_sites(
    syntax: &SyntaxGraph,
    node: NodeId,
    scope: NodeId,
    symbol: &str,
) -> Vec<ImportSite> {
    let Some(SyntaxNode::VariableDeclaration {
        value_id: Some(value),
        variable_id,
        ..
    }) = syntax.nodes.get(&node)
    else {
        return Vec::new();
    };
    let Some((module, relative_level)) = required_module(syntax, *value) else {
        return Vec::new();
    };
    let site = |bound, name: &str, binding| ImportSite {
        node: bound,
        scope,
        symbol: String::from(name),
        binding,
        reach: BindingReach::Scope,
    };
    match variable_id.and_then(|pattern| syntax.nodes.get(&pattern).map(|kind| (pattern, kind))) {
        Some((pattern, SyntaxNode::Object { .. })) => syntax
            .ast_children(pattern)
            .into_iter()
            .filter_map(|member| {
                let (bound, local, imported) = pattern_member(syntax, member)?;
                Some(site(
                    bound,
                    local,
                    symbol_binding(&module, relative_level, imported, local),
                ))
            })
            .collect(),
        None if syntax.ast_children(node).contains(value) => {
            Vec::from([site(node, symbol, module_binding(&module, relative_level))])
        }
        _ => Vec::new(),
    }
}

fn pattern_member(syntax: &SyntaxGraph, member: NodeId) -> Option<(NodeId, &str, &str)> {
    match syntax.nodes.get(&member)? {
        SyntaxNode::VariableDeclaration { variable, .. } => Some((member, variable, variable)),
        SyntaxNode::Pair { key_id, value_id } => {
            let imported = looked_up_symbol(syntax, *key_id)?;
            let local = looked_up_symbol(syntax, *value_id)?;
            Some((*value_id, local, imported))
        }
        _ => None,
    }
}

fn looked_up_symbol(syntax: &SyntaxGraph, node: NodeId) -> Option<&str> {
    match syntax.nodes.get(&node)? {
        SyntaxNode::SymbolLookup { symbol, .. } => Some(symbol),
        _ => None,
    }
}

fn required_module(syntax: &SyntaxGraph, value: NodeId) -> Option<(String, u32)> {
    let SyntaxNode::MethodInvocation {
        expression,
        object: None,
        arguments_id: Some(arguments),
        ..
    } = syntax.nodes.get(&value)?
    else {
        return None;
    };
    if expression != "require" {
        return None;
    }
    match syntax.nodes.get(syntax.ast_children(*arguments).first()?)? {
        SyntaxNode::Literal {
            value: specifier,
            value_type,
        } if value_type == "string" => Some(script_module(specifier)),
        _ => None,
    }
}