Skip to main content

fluidattacks_blends_domain/query/
paths.rs

1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::vec::Vec;
3
4use crate::query::adj_ast;
5use crate::syntax::{SyntaxGraph, SyntaxKind};
6use crate::{CodeGraph, NodeId};
7
8#[must_use]
9pub const fn nodes_by_type(graph: &SyntaxGraph) -> &BTreeMap<SyntaxKind, Vec<NodeId>> {
10    &graph.nodes_by_type
11}
12
13#[must_use]
14pub fn get_nodes_by_path<G: CodeGraph>(graph: &G, n_id: NodeId, path: &[&str]) -> BTreeSet<NodeId> {
15    let Some((first, rest)) = path.split_first() else {
16        return BTreeSet::new();
17    };
18    let matches = adj_ast(graph, n_id, Some(1), &[*first]);
19    if rest.is_empty() {
20        matches.into_iter().collect()
21    } else {
22        matches
23            .into_iter()
24            .flat_map(|child| get_nodes_by_path(graph, child, rest))
25            .collect()
26    }
27}
28
29#[must_use]
30pub fn get_node_by_path<G: CodeGraph>(graph: &G, n_id: NodeId, path: &[&str]) -> Option<NodeId> {
31    get_nodes_by_path(graph, n_id, path).into_iter().next()
32}
33
34#[cfg(test)]
35mod tests {
36    use super::{get_node_by_path, get_nodes_by_path, nodes_by_type};
37    use crate::ast::{AstGraph, AstNode};
38    use crate::syntax::{SyntaxGraph, SyntaxKind, SyntaxNode};
39    use crate::NodeId;
40    use alloc::borrow::ToOwned;
41    use alloc::collections::BTreeSet;
42    use alloc::vec;
43
44    fn sample() -> AstGraph {
45        let mut ast = AstGraph::new();
46        ast.add_node(
47            NodeId(1),
48            AstNode::new(1, 1, "class_declaration".to_owned()),
49        );
50        ast.add_node(NodeId(2), AstNode::new(1, 1, "base_list".to_owned()));
51        ast.add_node(NodeId(3), AstNode::new(1, 1, "identifier".to_owned()));
52        ast.add_node(NodeId(4), AstNode::new(1, 1, "modifier".to_owned()));
53        ast.add_node(NodeId(5), AstNode::new(1, 2, "identifier".to_owned()));
54        ast.add_edge(NodeId(1), NodeId(2), 0);
55        ast.add_edge(NodeId(1), NodeId(4), 1);
56        ast.add_edge(NodeId(2), NodeId(3), 0);
57        ast.add_edge(NodeId(2), NodeId(5), 1);
58        ast
59    }
60
61    #[test]
62    fn nodes_by_type_exposes_the_index_maintained_by_add_node() {
63        let mut graph = SyntaxGraph::new();
64        graph.add_node(NodeId(9), SyntaxNode::Break);
65        graph.add_node(NodeId(1), SyntaxNode::File);
66        graph.add_node(NodeId(2), SyntaxNode::Break);
67
68        assert_eq!(
69            nodes_by_type(&graph).get(&SyntaxKind::Break),
70            Some(&vec![NodeId(9), NodeId(2)])
71        );
72        assert_eq!(
73            nodes_by_type(&graph).get(&SyntaxKind::File),
74            Some(&vec![NodeId(1)])
75        );
76        assert_eq!(nodes_by_type(&graph).get(&SyntaxKind::If), None);
77        assert!(nodes_by_type(&SyntaxGraph::new()).is_empty());
78    }
79
80    #[test]
81    fn nodes_by_path_collect_every_match_of_the_last_step() {
82        let ast = sample();
83        assert_eq!(
84            get_nodes_by_path(&ast, NodeId(1), &["base_list", "identifier"]),
85            BTreeSet::from([NodeId(3), NodeId(5)])
86        );
87        assert_eq!(
88            get_nodes_by_path(&ast, NodeId(1), &["base_list"]),
89            BTreeSet::from([NodeId(2)])
90        );
91    }
92
93    #[test]
94    fn nodes_by_path_are_empty_when_the_path_breaks_or_is_empty() {
95        let ast = sample();
96        assert_eq!(
97            get_nodes_by_path(&ast, NodeId(1), &["base_list", "block"]),
98            BTreeSet::new()
99        );
100        assert_eq!(get_nodes_by_path(&ast, NodeId(1), &[]), BTreeSet::new());
101    }
102
103    #[test]
104    fn follows_a_multi_step_label_path() {
105        let ast = sample();
106        assert_eq!(
107            get_node_by_path(&ast, NodeId(1), &["base_list", "identifier"]),
108            Some(NodeId(3))
109        );
110    }
111
112    #[test]
113    fn returns_the_direct_child_for_a_single_step_path() {
114        let ast = sample();
115        assert_eq!(
116            get_node_by_path(&ast, NodeId(1), &["base_list"]),
117            Some(NodeId(2))
118        );
119    }
120
121    #[test]
122    fn is_none_when_the_path_breaks_or_is_empty() {
123        let ast = sample();
124        assert_eq!(
125            get_node_by_path(&ast, NodeId(1), &["base_list", "block"]),
126            None
127        );
128        assert_eq!(get_node_by_path(&ast, NodeId(1), &["missing"]), None);
129        assert_eq!(get_node_by_path(&ast, NodeId(1), &[]), None);
130    }
131}