fluidattacks_blends_domain/query/
paths.rs1use 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}