1use rustc_hash::FxHashMap;
2
3use react_compiler_utils::FxIndexMap;
4use serde::Serialize;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
8pub struct ScopeId(pub u32);
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
12pub struct BindingId(pub u32);
13
14#[derive(Debug, Clone, Serialize)]
15#[serde(rename_all = "camelCase")]
16pub struct ScopeData {
17 pub id: ScopeId,
18 pub parent: Option<ScopeId>,
19 pub kind: ScopeKind,
20 pub bindings: FxHashMap<String, BindingId>,
23}
24
25#[derive(Debug, Clone, Serialize)]
26#[serde(rename_all = "lowercase")]
27pub enum ScopeKind {
28 Program,
29 Function,
30 Block,
31 #[serde(rename = "for")]
32 For,
33 Class,
34 Switch,
35 Catch,
36}
37
38#[derive(Debug, Clone, Serialize)]
39#[serde(rename_all = "camelCase")]
40pub struct BindingData {
41 pub id: BindingId,
42 pub name: String,
43 pub kind: BindingKind,
44 pub scope: ScopeId,
46 pub declaration_type: String,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
53 pub declaration_start: Option<u32>,
54 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub declaration_node_id: Option<u32>,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub import: Option<ImportBindingData>,
62}
63
64#[derive(Debug, Clone, Serialize)]
65#[serde(rename_all = "lowercase")]
66pub enum BindingKind {
67 Var,
68 Let,
69 Const,
70 Param,
71 Module,
73 Hoisted,
75 Local,
77 Unknown,
79}
80
81#[derive(Debug, Clone, Serialize)]
82pub struct ImportBindingData {
83 pub source: String,
85 pub kind: ImportBindingKind,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub imported: Option<String>,
90}
91
92#[derive(Debug, Clone, Serialize)]
93#[serde(rename_all = "lowercase")]
94pub enum ImportBindingKind {
95 Default,
96 Named,
97 Namespace,
98}
99
100#[derive(Debug, Clone, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct ScopeInfo {
105 pub scopes: Vec<ScopeData>,
107 pub bindings: Vec<BindingData>,
109
110 pub node_to_scope: FxHashMap<u32, ScopeId>,
116
117 #[serde(default, skip_serializing_if = "FxHashMap::is_empty")]
120 pub node_to_scope_end: FxHashMap<u32, u32>,
121
122 #[serde(default)]
126 pub reference_to_binding: FxIndexMap<u32, BindingId>,
127
128 #[serde(
132 default,
133 skip_serializing_if = "FxIndexMap::is_empty",
134 rename = "refNodeIdToBinding"
135 )]
136 pub ref_node_id_to_binding: FxIndexMap<u32, BindingId>,
137
138 #[serde(
140 default,
141 skip_serializing_if = "FxHashMap::is_empty",
142 rename = "nodeIdToScope"
143 )]
144 pub node_id_to_scope: FxHashMap<u32, ScopeId>,
145
146 pub program_scope: ScopeId,
148}
149
150impl ScopeInfo {
151 pub fn get_binding(&self, scope_id: ScopeId, name: &str) -> Option<BindingId> {
154 let mut current = Some(scope_id);
155 while let Some(id) = current {
156 let scope = &self.scopes[id.0 as usize];
157 if let Some(&binding_id) = scope.bindings.get(name) {
158 return Some(binding_id);
159 }
160 current = scope.parent;
161 }
162 None
163 }
164
165 pub fn resolve_scope_by_node_id(&self, node_id: u32) -> Option<ScopeId> {
167 self.node_id_to_scope.get(&node_id).copied()
168 }
169
170 pub fn resolve_scope_for_node(&self, node_id: Option<u32>) -> Option<ScopeId> {
177 let nid = node_id?;
178 self.node_id_to_scope.get(&nid).copied()
179 }
180
181 pub fn resolve_reference_by_node_id(&self, node_id: u32) -> Option<BindingId> {
184 self.ref_node_id_to_binding.get(&node_id).copied()
185 }
186
187 pub fn resolve_reference_id_for_node(&self, node_id: Option<u32>) -> Option<BindingId> {
191 let nid = node_id?;
192 self.ref_node_id_to_binding.get(&nid).copied()
193 }
194
195 pub fn resolve_reference_for_node(&self, node_id: Option<u32>) -> Option<&BindingData> {
199 self.resolve_reference_id_for_node(node_id)
200 .map(|id| &self.bindings[id.0 as usize])
201 }
202
203 pub fn find_binding_in_descendants(
205 &self,
206 name: &str,
207 ancestor: ScopeId,
208 ) -> Option<&BindingData> {
209 let mut descendants = rustc_hash::FxHashSet::default();
210 descendants.insert(ancestor);
211 let mut changed = true;
212 while changed {
213 changed = false;
214 for (i, scope) in self.scopes.iter().enumerate() {
215 let sid = ScopeId(i as u32);
216 if let Some(parent) = scope.parent {
217 if descendants.contains(&parent) && !descendants.contains(&sid) {
218 descendants.insert(sid);
219 changed = true;
220 }
221 }
222 }
223 }
224 for sid in &descendants {
225 let scope = &self.scopes[sid.0 as usize];
226 if let Some(id) = scope.bindings.get(name) {
227 return Some(&self.bindings[id.0 as usize]);
228 }
229 }
230 None
231 }
232
233 pub fn find_binding_id_in_descendants(
236 &self,
237 name: &str,
238 ancestor: ScopeId,
239 ) -> Option<(BindingId, &BindingData)> {
240 let mut descendants = rustc_hash::FxHashSet::default();
241 descendants.insert(ancestor);
242 let mut changed = true;
243 while changed {
244 changed = false;
245 for (i, scope) in self.scopes.iter().enumerate() {
246 let sid = ScopeId(i as u32);
247 if let Some(parent) = scope.parent {
248 if descendants.contains(&parent) && !descendants.contains(&sid) {
249 descendants.insert(sid);
250 changed = true;
251 }
252 }
253 }
254 }
255 for sid in &descendants {
256 let scope = &self.scopes[sid.0 as usize];
257 if let Some(&id) = scope.bindings.get(name) {
258 return Some((id, &self.bindings[id.0 as usize]));
259 }
260 }
261 None
262 }
263
264 pub fn scope_bindings(&self, scope_id: ScopeId) -> impl Iterator<Item = &BindingData> {
266 self.scopes[scope_id.0 as usize]
267 .bindings
268 .values()
269 .map(|id| &self.bindings[id.0 as usize])
270 }
271
272 pub fn scope_bindings_with_children(
278 &self,
279 scope_id: ScopeId,
280 ) -> impl Iterator<Item = &BindingData> {
281 let mut binding_ids: Vec<BindingId> = Vec::new();
282 for &id in self.scopes[scope_id.0 as usize].bindings.values() {
284 binding_ids.push(id);
285 }
286 for scope in self.scopes.iter() {
288 if scope.parent == Some(scope_id) && matches!(scope.kind, ScopeKind::Block) {
289 for &id in scope.bindings.values() {
290 binding_ids.push(id);
291 }
292 }
293 }
294 binding_ids
295 .into_iter()
296 .map(|id| &self.bindings[id.0 as usize])
297 }
298
299 pub fn find_block_scope_by_bindings(
303 &self,
304 names: &[&str],
305 ancestor: ScopeId,
306 is_claimed: impl Fn(ScopeId) -> bool,
307 ) -> Option<ScopeId> {
308 let mut descendants = rustc_hash::FxHashSet::default();
309 descendants.insert(ancestor);
310 let mut changed = true;
311 while changed {
312 changed = false;
313 for (i, scope) in self.scopes.iter().enumerate() {
314 let sid = ScopeId(i as u32);
315 if let Some(parent) = scope.parent {
316 if descendants.contains(&parent) && !descendants.contains(&sid) {
317 descendants.insert(sid);
318 changed = true;
319 }
320 }
321 }
322 }
323 for sid in &descendants {
324 let scope = &self.scopes[sid.0 as usize];
325 if matches!(scope.kind, ScopeKind::Function) {
326 continue;
327 }
328 if is_claimed(*sid) {
329 continue;
330 }
331 let all_match = names.iter().all(|name| scope.bindings.contains_key(*name));
332 if all_match {
333 return Some(*sid);
334 }
335 }
336 None
337 }
338}