fluidattacks-blends-domain 0.2.0

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

use crate::query::match_ast;
use crate::syntax::builders::method_invocation::{build_method_invocation_node, DirectChildren};
use crate::syntax::fields::c_sharp::invocation_expression;
use crate::syntax::readers::common::receiver_fqn::{
    fqn_from_graph_imports, receiver_fqn_from_graph_symbol,
};
use crate::syntax::{SyntaxGraph, SyntaxGraphArgs, SyntaxGraphError, SyntaxNode};
use crate::utilities::text_nodes::node_to_str;
use crate::NodeId;

fn receiver_fqn_from_node(sg: &SyntaxGraph, n_id: Option<NodeId>) -> Option<String> {
    match sg.nodes.get(&n_id?)? {
        SyntaxNode::MemberAccess {
            member,
            expression_id,
            ..
        } => {
            let member = member.trim_start_matches(':');
            let inner = receiver_fqn_from_node(sg, Some(*expression_id))?;
            (!inner.is_empty() && !member.is_empty()).then(|| format!("{inner}.{member}"))
        }
        SyntaxNode::SymbolLookup { symbol, value, .. } => {
            let raw = if symbol.is_empty() {
                value.as_deref().unwrap_or_default()
            } else {
                symbol.as_str()
            };
            let trimmed = raw.trim_start_matches(':');
            if trimmed.is_empty() {
                None
            } else {
                receiver_fqn_from_graph_symbol(sg, trimmed).map(String::from)
            }
        }
        SyntaxNode::ObjectCreation { name, .. } => {
            let name = name.trim_start_matches(':');
            if name.is_empty() {
                None
            } else if name.contains('.') {
                Some(String::from(name))
            } else {
                fqn_from_graph_imports(sg, name).map(String::from)
            }
        }
        SyntaxNode::MethodInvocation {
            receiver_type_fqn, ..
        } => receiver_type_fqn.clone(),
        _ => None,
    }
}

fn member_access_receiver(sg: &SyntaxGraph, invocation_id: NodeId) -> Option<NodeId> {
    let SyntaxNode::MethodInvocation {
        expression_id: Some(expr_id),
        ..
    } = sg.nodes.get(&invocation_id)?
    else {
        return None;
    };
    match sg.nodes.get(expr_id)? {
        SyntaxNode::MemberAccess { expression_id, .. } => Some(*expression_id),
        _ => None,
    }
}

pub fn reader(
    args: &mut SyntaxGraphArgs<'_>,
    n_id: NodeId,
) -> Result<Option<NodeId>, SyntaxGraphError> {
    let graph = args.ast_graph;
    let arguments_id = args.required_field_alt(n_id, invocation_expression::ARGUMENTS)?;
    let expr_id = args.required_field_alt(n_id, invocation_expression::FUNCTION)?;
    let expr = node_to_str(graph, expr_id);
    let include_args = match_ast(graph, arguments_id, &["(", ")"], None).contains_key("__0__");

    let children = DirectChildren {
        expression_id: Some(expr_id),
        arguments_id: include_args.then_some(arguments_id),
        ..DirectChildren::default()
    };
    let invocation_id = build_method_invocation_node(args, n_id, expr, children, None)?;

    if let Some(fqn) = member_access_receiver(args.syntax_graph, invocation_id)
        .and_then(|inner| receiver_fqn_from_node(args.syntax_graph, Some(inner)))
    {
        if let Some(SyntaxNode::MethodInvocation {
            receiver_type_fqn, ..
        }) = args.syntax_graph.nodes.get_mut(&invocation_id)
        {
            *receiver_type_fqn = Some(fqn);
        }
    }

    Ok(Some(invocation_id))
}