fluidattacks-blends-domain 0.3.0

Blends functional core: pure AST graph to syntax graph (no_std)
Documentation
use alloc::borrow::ToOwned;
use alloc::collections::BTreeMap;
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::java::{method_invocation, object_creation_expression};
use crate::syntax::node::FileInstanceData;
use crate::syntax::readers::common::receiver_fqn::receiver_fqn_from_graph_symbol;
use crate::syntax::{SyntaxGraphArgs, SyntaxGraphError, SyntaxNode};
use crate::utilities::text_nodes::node_to_str;
use crate::{CodeGraph, NodeId};

fn enclosing_class_fqn(args: &SyntaxGraphArgs<'_>) -> Option<String> {
    if args.metadata.class_path.is_empty() {
        return None;
    }
    let class_name = args.metadata.class_path.join(".");
    match args.syntax_graph.nodes.get(&NodeId(0)) {
        Some(SyntaxNode::Metadata {
            package: Some(package),
            ..
        }) => Some(format!("{package}.{class_name}")),
        _ => Some(class_name),
    }
}

fn fqn_from_imports(imports: &[String], symbol: &str) -> Option<String> {
    imports
        .iter()
        .find(|import| !import.ends_with(".*") && import.rsplit('.').next() == Some(symbol))
        .cloned()
}

fn fqn_from_instances(
    instances: &BTreeMap<String, BTreeMap<String, FileInstanceData>>,
    class_path: &[String],
    symbol: &str,
) -> Option<String> {
    let entry = instances.get(class_path.last()?)?.get(symbol)?;
    if entry.source_type != "package" || entry.source.is_empty() || entry.object.is_empty() {
        return None;
    }
    Some(format!("{}.{}", entry.source, entry.object))
}

fn receiver_fqn_from_symbol(args: &SyntaxGraphArgs<'_>, symbol: &str) -> Option<String> {
    match args.syntax_graph.nodes.get(&NodeId(0)) {
        Some(SyntaxNode::Metadata {
            imports, instances, ..
        }) => fqn_from_imports(imports, symbol)
            .or_else(|| fqn_from_instances(instances, &args.metadata.class_path, symbol)),
        _ => receiver_fqn_from_graph_symbol(args.syntax_graph, symbol).map(String::from),
    }
}

fn receiver_fqn_from_object(args: &SyntaxGraphArgs<'_>, object_id: NodeId) -> Option<String> {
    match args.syntax_graph.nodes.get(&object_id)? {
        SyntaxNode::This { .. } => enclosing_class_fqn(args),
        SyntaxNode::SymbolLookup { symbol, value, .. } => {
            let raw = if symbol.is_empty() {
                value.as_deref().filter(|value| !value.is_empty())
            } else {
                Some(symbol.as_str())
            };
            let raw = raw.map(ToOwned::to_owned)?;
            receiver_fqn_from_symbol(args, &raw)
        }
        SyntaxNode::ObjectCreation { name, .. } => {
            if name.is_empty() {
                None
            } else if name.contains('.') {
                Some(name.clone())
            } else {
                let name = name.clone();
                receiver_fqn_from_symbol(args, &name)
            }
        }
        SyntaxNode::MethodInvocation {
            receiver_type_fqn, ..
        } => receiver_type_fqn.clone().filter(|value| !value.is_empty()),
        _ => None,
    }
}

pub fn reader(
    args: &mut SyntaxGraphArgs<'_>,
    n_id: NodeId,
) -> Result<Option<NodeId>, SyntaxGraphError> {
    let expression_id = args.required_field_alt(n_id, method_invocation::NAME)?;
    let expression = node_to_str(args.ast_graph, expression_id);

    let raw_arguments_id = args.required_field_alt(n_id, method_invocation::ARGUMENTS)?;
    let arguments_id = match_ast(args.ast_graph, raw_arguments_id, &["(", ")"], None)
        .contains_key("__0__")
        .then_some(raw_arguments_id);

    let object_id = args.optional_field_alt(n_id, method_invocation::OBJECT);
    let mut object_name = None;
    if let Some(object_id) = object_id {
        if args.ast_graph.label_type(object_id) == Some("object_creation_expression") {
            let type_id = args.required_field_alt(object_id, object_creation_expression::TYPE)?;
            object_name = Some(node_to_str(args.ast_graph, type_id));
        }
    }

    let children = DirectChildren {
        expression_id: Some(expression_id),
        arguments_id,
        object_id,
        block_id: None,
    };
    let built_id = build_method_invocation_node(args, n_id, expression, children, object_name)?;

    let receiver_fqn = object_id.map_or_else(
        || enclosing_class_fqn(args),
        |object_id| receiver_fqn_from_object(args, object_id),
    );
    if let Some(receiver_fqn) = receiver_fqn.filter(|value| !value.is_empty()) {
        if let Some(SyntaxNode::MethodInvocation {
            receiver_type_fqn, ..
        }) = args.syntax_graph.nodes.get_mut(&built_id)
        {
            *receiver_type_fqn = Some(receiver_fqn);
        }
    }

    Ok(Some(built_id))
}