fluidattacks-blends-domain 0.44.3

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

use crate::stack::build::scopes::is_statement;
use crate::stack::build::StackBuilder;
use crate::stack::{Definition, ScopeType, StackNode, StackScope, SymbolPop};
use crate::syntax::{SyntaxGraph, SyntaxNode};
use crate::{CodeGraph, Language, NodeId};

#[derive(Clone, Copy)]
pub(super) struct Members {
    pub(super) gate: NodeId,
    scope: NodeId,
}

impl StackBuilder {
    pub(super) fn add_members(&mut self, class: NodeId) {
        let scope = self.add(StackNode::Scope(StackScope {
            scope_type: Some(ScopeType::Class),
        }));
        let gate = self.add(StackNode::PopSymbol(SymbolPop {
            symbol: String::from("."),
            precedence: 0,
            definition: None,
            import: None,
        }));
        self.graph.add_edge(gate, scope, 0);
        self.members.insert(class, Members { gate, scope });
    }

    pub(super) fn add_member(
        &mut self,
        language: Language,
        class: NodeId,
        name: &str,
        member: NodeId,
    ) {
        if let Some(members) = self.members.get(&class).copied() {
            self.define(members.scope, name, member);
        }
        if !matches!(language, Language::JavaScript | Language::TypeScript) {
            self.define(class, name, member);
        }
    }

    pub(super) fn add_receiver(
        &mut self,
        syntax: &SyntaxGraph,
        language: Language,
        class: NodeId,
        method: NodeId,
    ) {
        let Some(members) = self.members.get(&class).copied() else {
            return;
        };
        let receiver = match language {
            Language::Python => python_receiver(syntax, method).map(|(id, name)| {
                self.graph.add_node(id, receiver_definition(name));
                id
            }),
            Language::JavaScript | Language::TypeScript
                if matches!(
                    syntax.nodes.get(&method),
                    Some(SyntaxNode::MethodDeclaration { .. })
                ) =>
            {
                Some(self.add(receiver_definition("this")))
            }
            Language::Java | Language::CSharp => match syntax.nodes.get(&method) {
                Some(SyntaxNode::MethodDeclaration {
                    access_modifiers, ..
                }) if !access_modifiers
                    .iter()
                    .flat_map(|words| words.split_whitespace())
                    .any(|word| word == "static") =>
                {
                    Some(self.add(receiver_definition("this")))
                }
                _ => None,
            },
            _ => None,
        };
        if let Some(receiver) = receiver {
            self.graph.add_edge(method, receiver, 1);
            self.graph.add_edge(receiver, members.gate, 0);
        }
    }
}

fn receiver_definition(name: &str) -> StackNode {
    StackNode::PopSymbol(SymbolPop {
        symbol: name.into(),
        precedence: 1,
        definition: Some(Definition::default()),
        import: None,
    })
}

fn python_receiver(syntax: &SyntaxGraph, method: NodeId) -> Option<(NodeId, &str)> {
    let Some(SyntaxNode::MethodDeclaration {
        parameters_id: Some(parameters),
        ..
    }) = syntax.nodes.get(&method)
    else {
        return None;
    };
    if !is_statement(syntax, method) || is_static(syntax, method) {
        return None;
    }
    let first = syntax.ast_children(*parameters).into_iter().next()?;
    let node = syntax.nodes.get(&first)?;
    Some((first, node.symbol().or_else(|| node.variable())?))
}

fn is_static(syntax: &SyntaxGraph, method: NodeId) -> bool {
    syntax
        .ast_children(method)
        .into_iter()
        .filter(|child| matches!(syntax.nodes.get(child), Some(SyntaxNode::Modifiers)))
        .flat_map(|modifiers| syntax.ast_children(modifiers))
        .any(|decorator| {
            syntax.nodes.get(&decorator).and_then(SyntaxNode::symbol) == Some("staticmethod")
        })
}