fluidattacks-blends-domain 0.6.0

Blends functional core: pure AST graph to syntax graph (no_std)
Documentation
//! Counterpart of `blends/path_search/search/`.

use alloc::vec::Vec;

use crate::path_search::Path;
use crate::syntax::SyntaxGraph;
use crate::{CodeGraph, NodeId};

mod assignment;
mod catch_clause;
mod class_body;
mod declaration_block;
mod file_node;
mod for_each;
mod for_statement;
mod if_statement;
mod method_declaration;
mod method_invocation;
mod try_statement;
mod using_statement;
mod variable_declaration;

pub struct SearchArgs<'a> {
    pub graph: &'a SyntaxGraph,
    pub n_id: NodeId,
    pub symbol: &'a str,
    pub def_only: bool,
    pub all_related: bool,
}

pub type SearchResult = (bool, NodeId);

type Searcher = fn(&SearchArgs<'_>) -> Vec<SearchResult>;

fn searcher_for(label: &str) -> Option<Searcher> {
    match label {
        "Assignment" => Some(assignment::search),
        "CatchClause" => Some(catch_clause::search),
        "ClassBody" => Some(class_body::search),
        "DeclarationBlock" => Some(declaration_block::search),
        "File" => Some(file_node::search),
        "ForEachStatement" => Some(for_each::search),
        "ForStatement" => Some(for_statement::search),
        "If" => Some(if_statement::search),
        "MethodDeclaration" => Some(method_declaration::search),
        "MethodInvocation" => Some(method_invocation::search),
        "TryStatement" => Some(try_statement::search),
        "UsingStatement" => Some(using_statement::search),
        "VariableDeclaration" => Some(variable_declaration::search),
        _ => None,
    }
}

#[must_use]
pub fn search(
    graph: &SyntaxGraph,
    path: &Path,
    symbol: &str,
    def_only: bool,
    all_related: Option<bool>,
) -> Vec<SearchResult> {
    let mut out: Vec<SearchResult> = Vec::new();
    for &n_id in path {
        if let Some(searcher) = graph.label_type(n_id).and_then(searcher_for) {
            out.extend(searcher(&SearchArgs {
                graph,
                n_id,
                symbol,
                def_only,
                all_related: all_related.unwrap_or(false),
            }));
        }
    }
    out
}

#[must_use]
pub fn definition_search(graph: &SyntaxGraph, path: &Path, symbol: &str) -> Option<NodeId> {
    search(graph, path, symbol, true, None)
        .into_iter()
        .next()
        .map(|(_, ref_id)| ref_id)
}

fn collect_until_def(results: Vec<SearchResult>) -> Vec<NodeId> {
    let mut out: Vec<NodeId> = Vec::new();
    for (is_definition, ref_id) in results {
        out.push(ref_id);
        if is_definition {
            break;
        }
    }
    out
}

#[must_use]
pub fn search_until_def(graph: &SyntaxGraph, path: &Path, symbol: &str) -> Vec<NodeId> {
    collect_until_def(search(graph, path, symbol, false, None))
}

#[must_use]
pub fn search_all_related_until_def(graph: &SyntaxGraph, path: &Path, symbol: &str) -> Vec<NodeId> {
    collect_until_def(search(graph, path, symbol, false, Some(true)))
}

#[cfg(test)]
mod tests {
    use super::{definition_search, search, search_all_related_until_def, search_until_def};
    use crate::syntax::{SyntaxGraph, SyntaxNode};
    use crate::NodeId;
    use alloc::string::String;
    use alloc::vec;

    fn compound_then_declaration(graph: &mut SyntaxGraph) {
        graph.add_node(NodeId(3), symbol_lookup("x"));
        graph.add_node(
            NodeId(1),
            SyntaxNode::Assignment {
                variable_id: NodeId(3),
                value_id: None,
                operator: Some(String::from("+=")),
            },
        );
        graph.add_node(NodeId(2), variable_declaration("x"));
    }

    fn variable_declaration(name: &str) -> SyntaxNode {
        SyntaxNode::VariableDeclaration {
            variable: String::from(name),
            variable_type: None,
            value_id: None,
            variable_id: None,
            access_modifier: None,
        }
    }

    fn symbol_lookup(symbol: &str) -> SyntaxNode {
        SyntaxNode::SymbolLookup {
            symbol: String::from(symbol),
            symbol_scope: None,
            value: None,
        }
    }

    #[test]
    fn finds_a_variable_declaration_definition() {
        let mut graph = SyntaxGraph::new();
        graph.add_node(NodeId(1), variable_declaration("x"));
        let path = vec![NodeId(1)];

        assert_eq!(definition_search(&graph, &path, "x"), Some(NodeId(1)));
        assert_eq!(definition_search(&graph, &path, "y"), None);
    }

    #[test]
    fn compound_assignment_is_a_reference_not_a_definition() {
        let mut graph = SyntaxGraph::new();
        graph.add_node(NodeId(2), symbol_lookup("x"));
        graph.add_node(
            NodeId(1),
            SyntaxNode::Assignment {
                variable_id: NodeId(2),
                value_id: None,
                operator: Some(String::from("+=")),
            },
        );
        let path = vec![NodeId(1)];

        assert_eq!(definition_search(&graph, &path, "x"), None);
        assert_eq!(
            search(&graph, &path, "x", false, None),
            [(false, NodeId(1))]
        );
    }

    #[test]
    fn plain_assignment_is_a_definition() {
        let mut graph = SyntaxGraph::new();
        graph.add_node(NodeId(2), symbol_lookup("x"));
        graph.add_node(
            NodeId(1),
            SyntaxNode::Assignment {
                variable_id: NodeId(2),
                value_id: None,
                operator: None,
            },
        );
        let path = vec![NodeId(1)];

        assert_eq!(definition_search(&graph, &path, "x"), Some(NodeId(1)));
    }

    #[test]
    fn search_until_def_yields_references_then_stops_at_the_definition() {
        let mut graph = SyntaxGraph::new();
        compound_then_declaration(&mut graph);
        let path = vec![NodeId(1), NodeId(2)];

        assert_eq!(search_until_def(&graph, &path, "x"), [NodeId(1), NodeId(2)]);
    }

    #[test]
    fn search_all_related_until_def_also_stops_at_the_definition() {
        let mut graph = SyntaxGraph::new();
        compound_then_declaration(&mut graph);
        let path = vec![NodeId(1), NodeId(2)];

        assert_eq!(
            search_all_related_until_def(&graph, &path, "x"),
            [NodeId(1), NodeId(2)]
        );
    }

    #[test]
    fn related_search_sees_a_method_call_argument_only_when_enabled() {
        let mut graph = SyntaxGraph::new();
        graph.add_node(
            NodeId(1),
            SyntaxNode::MethodInvocation {
                expression: String::from("foo"),
                object: None,
                symbol_scope: None,
                expression_id: None,
                arguments_id: Some(NodeId(2)),
                object_id: None,
                block_id: None,
                receiver_type_fqn: None,
            },
        );
        graph.add_node(NodeId(2), SyntaxNode::ArgumentList);
        graph.add_node(NodeId(3), symbol_lookup("x"));
        graph.add_ast_edge(NodeId(2), NodeId(3));
        let path = vec![NodeId(1)];

        assert_eq!(
            search_all_related_until_def(&graph, &path, "x"),
            [NodeId(1)]
        );
        assert!(search_until_def(&graph, &path, "x").is_empty());
    }

    #[test]
    fn file_node_matches_an_import_alias() {
        let mut graph = SyntaxGraph::new();
        graph.add_node(NodeId(1), SyntaxNode::File);
        graph.add_node(
            NodeId(2),
            SyntaxNode::Import {
                expression: Some(String::from("os.path")),
                alias: Some(String::from("p")),
                method_name: None,
                import_type: None,
            },
        );
        graph.add_cfg_edge(NodeId(1), NodeId(2));
        let path = vec![NodeId(1)];

        assert_eq!(definition_search(&graph, &path, "p"), Some(NodeId(2)));
    }
}