fluidattacks_blends_domain/syntax/
graph.rs1use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use crate::syntax::{SyntaxEdge, SyntaxKind, SyntaxNode};
8use crate::{Ast, Cfg, CodeGraph, NodeId};
9
10#[derive(Clone, PartialEq, Eq, Debug)]
11pub struct SanitizationEvent {
12 pub node_id: NodeId,
13 pub kind: String,
14 pub subpath: Option<NodeId>,
15}
16
17#[derive(Clone, Copy, PartialEq, Eq, Debug)]
18pub enum UsageRole {
19 Receiver,
20 Argument,
21 MemberWrite,
22}
23
24#[derive(Clone, PartialEq, Eq, Debug)]
25pub struct UsageEvent {
26 pub node_id: NodeId,
27 pub role: UsageRole,
28 pub arg_index: Option<i64>,
29 pub subpath: Option<NodeId>,
30}
31
32#[derive(Clone, PartialEq, Eq, Debug, Default)]
33pub struct SyntaxGraph {
34 pub nodes: BTreeMap<NodeId, SyntaxNode>,
35 pub edges: BTreeMap<NodeId, BTreeMap<NodeId, SyntaxEdge>>,
36 pub symbol_index: BTreeMap<String, BTreeMap<NodeId, Vec<NodeId>>>,
37 pub scope_parent: BTreeMap<NodeId, NodeId>,
38 pub sanitization_index: BTreeMap<String, BTreeMap<NodeId, Vec<SanitizationEvent>>>,
39 pub usage_index: BTreeMap<String, BTreeMap<NodeId, Vec<UsageEvent>>>,
40 pub subpath_index: BTreeMap<NodeId, Option<NodeId>>,
41 pub nodes_by_type: BTreeMap<SyntaxKind, Vec<NodeId>>,
42}
43
44impl SyntaxGraph {
45 #[must_use]
46 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn add_node(&mut self, id: NodeId, node: SyntaxNode) {
51 self.nodes_by_type
52 .entry(SyntaxKind::from(&node))
53 .or_default()
54 .push(id);
55 self.nodes.insert(id, node);
56 }
57
58 pub fn register_sanitization(&mut self, symbol: &str, scope: NodeId, event: SanitizationEvent) {
59 self.sanitization_index
60 .entry(String::from(symbol))
61 .or_default()
62 .entry(scope)
63 .or_default()
64 .push(event);
65 }
66
67 pub fn register_usage(&mut self, symbol: &str, scope: NodeId, event: UsageEvent) {
68 self.usage_index
69 .entry(String::from(symbol))
70 .or_default()
71 .entry(scope)
72 .or_default()
73 .push(event);
74 }
75
76 pub fn add_ast_edge(&mut self, from: NodeId, to: NodeId) {
77 self.edges
78 .entry(from)
79 .or_default()
80 .entry(to)
81 .or_default()
82 .ast = Some(Ast);
83 }
84
85 pub fn add_cfg_edge(&mut self, from: NodeId, to: NodeId) {
86 self.edges
87 .entry(from)
88 .or_default()
89 .entry(to)
90 .or_default()
91 .cfg = Some(Cfg);
92 }
93
94 pub fn finalize_symbol_index(&mut self) {
95 for scope_definitions in self.symbol_index.values_mut() {
96 for definition_list in scope_definitions.values_mut() {
97 definition_list.reverse();
98 }
99 }
100 }
101
102 pub fn remove_edge(&mut self, from: NodeId, to: NodeId) {
103 if let Some(adjacent) = self.edges.get_mut(&from) {
104 adjacent.remove(&to);
105 if adjacent.is_empty() {
106 self.edges.remove(&from);
107 }
108 }
109 }
110}
111
112impl CodeGraph for SyntaxGraph {
113 fn children(&self, n_id: NodeId) -> Vec<NodeId> {
114 self.edges
115 .get(&n_id)
116 .map(|adjacent| adjacent.keys().copied().collect())
117 .unwrap_or_default()
118 }
119
120 fn ast_children(&self, n_id: NodeId) -> Vec<NodeId> {
121 self.edges
122 .get(&n_id)
123 .map(|adjacent| {
124 adjacent
125 .iter()
126 .filter(|(_, edge)| edge.ast.is_some())
127 .map(|(to, _)| *to)
128 .collect()
129 })
130 .unwrap_or_default()
131 }
132
133 fn parents(&self, n_id: NodeId) -> Vec<NodeId> {
134 self.edges
135 .iter()
136 .filter(|(_, adjacent)| adjacent.contains_key(&n_id))
137 .map(|(from, _)| *from)
138 .collect()
139 }
140
141 fn ast_parents(&self, n_id: NodeId) -> Vec<NodeId> {
142 self.edges
143 .iter()
144 .filter(|(_, adjacent)| adjacent.get(&n_id).is_some_and(|edge| edge.ast.is_some()))
145 .map(|(from, _)| *from)
146 .collect()
147 }
148
149 fn cfg_parents(&self, n_id: NodeId) -> Vec<NodeId> {
150 self.edges
151 .iter()
152 .filter(|(_, adjacent)| adjacent.get(&n_id).is_some_and(|edge| edge.cfg.is_some()))
153 .map(|(from, _)| *from)
154 .collect()
155 }
156
157 fn cfg_children(&self, n_id: NodeId) -> Vec<NodeId> {
158 self.edges
159 .get(&n_id)
160 .map(|adjacent| {
161 adjacent
162 .iter()
163 .filter(|(_, edge)| edge.cfg.is_some())
164 .map(|(to, _)| *to)
165 .collect()
166 })
167 .unwrap_or_default()
168 }
169
170 fn label_type(&self, n_id: NodeId) -> Option<&str> {
171 self.nodes.get(&n_id).map(SyntaxNode::label_type)
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::SyntaxGraph;
178 use crate::syntax::{SyntaxEdge, SyntaxKind, SyntaxNode};
179 use crate::{Ast, Cfg, CodeGraph, NodeId};
180 use alloc::borrow::ToOwned;
181
182 #[test]
183 fn stores_nodes_and_edges_by_id() {
184 let mut graph = SyntaxGraph::new();
185 graph.add_node(
186 NodeId(1),
187 SyntaxNode::MissingNode {
188 node_type: "stream".to_owned(),
189 },
190 );
191 graph.add_ast_edge(NodeId(1), NodeId(2));
192
193 assert_eq!(
194 graph.nodes.get(&NodeId(1)),
195 Some(&SyntaxNode::MissingNode {
196 node_type: "stream".to_owned()
197 })
198 );
199 assert_eq!(
200 graph
201 .edges
202 .get(&NodeId(1))
203 .and_then(|adjacent| adjacent.get(&NodeId(2))),
204 Some(&SyntaxEdge {
205 ast: Some(Ast),
206 cfg: None
207 })
208 );
209 }
210
211 #[test]
212 fn add_node_indexes_ids_by_syntax_type_in_insertion_order() {
213 let mut graph = SyntaxGraph::new();
214 graph.add_node(NodeId(9), SyntaxNode::Break);
215 graph.add_node(NodeId(1), SyntaxNode::File);
216 graph.add_node(NodeId(2), SyntaxNode::Break);
217
218 assert_eq!(
219 graph.nodes_by_type.get(&SyntaxKind::Break),
220 Some(&alloc::vec![NodeId(9), NodeId(2)])
221 );
222 assert_eq!(
223 graph.nodes_by_type.get(&SyntaxKind::File),
224 Some(&alloc::vec![NodeId(1)])
225 );
226 assert_eq!(graph.nodes_by_type.get(&SyntaxKind::If), None);
227 }
228
229 #[test]
230 fn ast_edge_merges_into_existing_edge() {
231 let mut graph = SyntaxGraph::new();
232 graph.add_ast_edge(NodeId(1), NodeId(2));
233 graph.add_ast_edge(NodeId(1), NodeId(2));
234
235 assert_eq!(
236 graph
237 .edges
238 .get(&NodeId(1))
239 .map(alloc::collections::BTreeMap::len),
240 Some(1)
241 );
242 }
243
244 #[test]
245 fn ast_children_skip_edges_without_the_ast_mark() {
246 let mut graph = SyntaxGraph::new();
247 graph.add_ast_edge(NodeId(1), NodeId(2));
248 graph.edges.entry(NodeId(1)).or_default().insert(
249 NodeId(3),
250 SyntaxEdge {
251 ast: None,
252 cfg: None,
253 },
254 );
255
256 assert_eq!(graph.ast_children(NodeId(1)), [NodeId(2)]);
257 }
258
259 #[test]
260 fn cfg_edge_merges_onto_the_ast_edge() {
261 let mut graph = SyntaxGraph::new();
262 graph.add_ast_edge(NodeId(1), NodeId(2));
263 graph.add_cfg_edge(NodeId(1), NodeId(2));
264
265 assert_eq!(
266 graph
267 .edges
268 .get(&NodeId(1))
269 .and_then(|adjacent| adjacent.get(&NodeId(2))),
270 Some(&SyntaxEdge {
271 ast: Some(Ast),
272 cfg: Some(Cfg)
273 })
274 );
275 }
276
277 #[test]
278 fn remove_edge_drops_the_edge_and_prunes_the_empty_entry() {
279 let mut graph = SyntaxGraph::new();
280 graph.add_ast_edge(NodeId(1), NodeId(2));
281 graph.add_ast_edge(NodeId(1), NodeId(3));
282 graph.add_cfg_edge(NodeId(1), NodeId(2));
283
284 graph.remove_edge(NodeId(1), NodeId(2));
285
286 assert_eq!(graph.children(NodeId(1)), [NodeId(3)]);
287 assert_eq!(graph.parents(NodeId(2)), []);
288
289 graph.remove_edge(NodeId(1), NodeId(3));
290
291 assert!(!graph.edges.contains_key(&NodeId(1)));
292 }
293
294 #[test]
295 fn finalize_symbol_index_puts_the_most_recent_definition_first() {
296 let mut graph = SyntaxGraph::new();
297 graph
298 .symbol_index
299 .entry("x".to_owned())
300 .or_default()
301 .entry(NodeId(1))
302 .or_default()
303 .extend([NodeId(2), NodeId(5), NodeId(9)]);
304
305 graph.finalize_symbol_index();
306
307 assert_eq!(
308 graph
309 .symbol_index
310 .get("x")
311 .and_then(|scopes| scopes.get(&NodeId(1))),
312 Some(&alloc::vec![NodeId(9), NodeId(5), NodeId(2)])
313 );
314 }
315
316 #[test]
317 fn remove_edge_ignores_a_missing_edge() {
318 let mut graph = SyntaxGraph::new();
319 graph.add_ast_edge(NodeId(1), NodeId(2));
320
321 graph.remove_edge(NodeId(7), NodeId(8));
322 graph.remove_edge(NodeId(1), NodeId(9));
323
324 assert_eq!(graph.children(NodeId(1)), [NodeId(2)]);
325 }
326
327 #[test]
328 fn cfg_children_skip_edges_without_the_cfg_mark() {
329 let mut graph = SyntaxGraph::new();
330 graph.add_ast_edge(NodeId(1), NodeId(2));
331 graph.add_ast_edge(NodeId(1), NodeId(3));
332 graph.add_cfg_edge(NodeId(1), NodeId(3));
333
334 assert_eq!(graph.cfg_children(NodeId(1)), [NodeId(3)]);
335 assert_eq!(graph.cfg_children(NodeId(9)), []);
336 }
337}