fluidattacks_blends_domain/stack/
build.rs1mod functions;
2mod imports;
3mod lookups;
4mod module_chain;
5
6use alloc::string::String;
7
8use crate::stack::build::module_chain::module_segments;
9use crate::stack::{ScopeType, StackGraph, StackNode, StackScope, SymbolPop};
10use crate::syntax::{SyntaxGraph, SyntaxKind};
11use crate::{CodeGraph, Language, NodeId};
12
13#[must_use]
18pub fn build_stack_graph(syntax: &SyntaxGraph, language: Language, path: &str) -> StackGraph {
19 let mut builder = StackBuilder::after(syntax);
20 let root = builder.add(StackNode::Root);
21 let file = syntax
22 .nodes_by_type
23 .get(&SyntaxKind::File)
24 .into_iter()
25 .flatten()
26 .copied()
27 .find(|file| syntax.ast_parents(*file).is_empty());
28 if let Some(file) = file {
29 builder.graph.add_node(
30 file,
31 StackNode::Scope(StackScope {
32 scope_type: Some(ScopeType::File),
33 }),
34 );
35 builder.add_module_chain(root, file, &module_segments(language, path));
36 }
37 builder.add_function_scopes(syntax);
38 builder.add_lookups(syntax, language);
39 builder.add_imports(syntax, language, root, path);
40 builder.graph
41}
42
43struct StackBuilder {
44 graph: StackGraph,
45 next_id: u64,
46}
47
48impl StackBuilder {
49 fn after(syntax: &SyntaxGraph) -> Self {
50 let last_id = syntax.nodes.last_key_value().map_or(0, |(id, _)| id.0);
51 Self {
52 graph: StackGraph::new(),
53 next_id: last_id.saturating_add(1),
54 }
55 }
56
57 fn add(&mut self, node: StackNode) -> NodeId {
58 let id = NodeId(self.next_id);
59 self.next_id = self.next_id.saturating_add(1);
60 self.graph.add_node(id, node);
61 id
62 }
63
64 fn add_module_chain(&mut self, root: NodeId, file: NodeId, segments: &[&str]) {
65 if segments.is_empty() {
66 return;
67 }
68 let symbols = segments
69 .iter()
70 .enumerate()
71 .flat_map(|(index, segment)| (index > 0).then_some(".").into_iter().chain([*segment]));
72 let last = symbols.fold(root, |previous, symbol| {
73 let pop = self.add(StackNode::PopSymbol(SymbolPop {
74 symbol: String::from(symbol),
75 precedence: 0,
76 definition: None,
77 import: None,
78 }));
79 self.graph.add_edge(previous, pop, 0);
80 pop
81 });
82 self.graph.add_edge(last, file, 0);
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::build_stack_graph;
89 use crate::stack::{ScopeType, StackGraph, StackNode, StackScope};
90 use crate::syntax::{SyntaxGraph, SyntaxNode};
91 use crate::{Language, NodeId};
92 use alloc::vec::Vec;
93
94 const FILE: NodeId = NodeId(1);
95
96 fn file_syntax() -> SyntaxGraph {
97 let mut syntax = SyntaxGraph::new();
98 syntax.add_node(FILE, SyntaxNode::File);
99 syntax.add_node(NodeId(7), SyntaxNode::ExecutionBlock);
100 syntax
101 }
102
103 const ROOT: NodeId = NodeId(8);
104
105 fn path_from_root(graph: &StackGraph) -> (Vec<&str>, Option<NodeId>) {
106 let mut symbols = Vec::new();
107 let mut current = ROOT;
108 while let [(next, _)] = graph.successors(current).as_slice() {
109 match graph.nodes.get(next) {
110 Some(StackNode::PopSymbol(pop)) => symbols.push(pop.symbol.as_str()),
111 _ => return (symbols, Some(*next)),
112 }
113 current = *next;
114 }
115 (symbols, None)
116 }
117
118 #[test]
119 fn the_file_node_becomes_a_file_scope() {
120 let graph = build_stack_graph(&file_syntax(), Language::Python, "pkg/mod.py");
121
122 assert_eq!(
123 graph.nodes.get(&FILE),
124 Some(&StackNode::Scope(StackScope {
125 scope_type: Some(ScopeType::File),
126 }))
127 );
128 }
129
130 #[test]
131 fn the_root_reaches_the_file_scope_through_its_module_path() {
132 let graph = build_stack_graph(&file_syntax(), Language::Python, "pkg/sub/mod.py");
133
134 assert_eq!(
135 path_from_root(&graph),
136 (Vec::from(["pkg", ".", "sub", ".", "mod"]), Some(FILE))
137 );
138 }
139
140 #[test]
141 fn a_file_without_a_module_name_is_not_reachable_from_the_root() {
142 let graph = build_stack_graph(&file_syntax(), Language::Python, "__init__.py");
143
144 assert_eq!(path_from_root(&graph), (Vec::new(), None));
145 }
146
147 #[test]
148 fn a_nested_file_node_is_not_a_file_scope() {
149 let mut syntax = file_syntax();
150 syntax.add_node(NodeId(2), SyntaxNode::File);
151 syntax.add_ast_edge(FILE, NodeId(2));
152 let graph = build_stack_graph(&syntax, Language::Python, "pkg/mod.py");
153
154 assert_eq!(graph.nodes.get(&NodeId(2)), None);
155 assert_eq!(
156 path_from_root(&graph),
157 (Vec::from(["pkg", ".", "mod"]), Some(FILE))
158 );
159 }
160
161 #[test]
162 fn module_path_nodes_pop_symbols_without_defining_them() {
163 let graph = build_stack_graph(&file_syntax(), Language::JavaScript, "lib/x.js");
164
165 assert!(graph.nodes.values().all(|node| !node.is_definition()));
166 }
167
168 #[test]
169 fn the_root_and_the_module_path_get_ids_after_every_syntax_id() {
170 let graph = build_stack_graph(&file_syntax(), Language::Python, "pkg/mod.py");
171
172 assert_eq!(graph.nodes.get(&ROOT), Some(&StackNode::Root));
173 assert_eq!(
174 graph.nodes.keys().copied().collect::<Vec<_>>(),
175 [FILE, ROOT, NodeId(9), NodeId(10), NodeId(11)]
176 );
177 }
178}