1use std::collections::HashMap;
8use std::collections::HashSet;
9
10use react_compiler_ast::expressions::*;
11use react_compiler_ast::patterns::*;
12use react_compiler_ast::scope::*;
13use react_compiler_ast::statements::FunctionDeclaration;
14use react_compiler_ast::visitor::AstWalker;
15use react_compiler_ast::visitor::Visitor;
16use react_compiler_diagnostics::CompilerError;
17use react_compiler_diagnostics::CompilerErrorDetail;
18use react_compiler_diagnostics::ErrorCategory;
19use react_compiler_diagnostics::Position;
20use react_compiler_diagnostics::SourceLocation;
21use react_compiler_hir::environment::Environment;
22
23use crate::FunctionNode;
24
25#[derive(Default)]
26struct BindingInfo {
27 reassigned: bool,
28 reassigned_by_inner_fn: bool,
29 referenced_by_inner_fn: bool,
30}
31
32struct ContextIdentifierVisitor<'a> {
33 scope_info: &'a ScopeInfo,
34 env: &'a mut Environment,
35 function_stack: Vec<ScopeId>,
38 binding_info: HashMap<BindingId, BindingInfo>,
39 error: Option<CompilerError>,
40}
41
42impl<'a> ContextIdentifierVisitor<'a> {
43 fn push_function_scope(&mut self, _start: Option<u32>, node_id: Option<u32>) {
44 let scope = self.scope_info.resolve_scope_for_node(node_id);
45 if let Some(scope) = scope {
46 self.function_stack.push(scope);
47 }
48 }
49
50 fn pop_function_scope(&mut self, _start: Option<u32>, node_id: Option<u32>) {
51 let has_scope = self.scope_info.resolve_scope_for_node(node_id);
52 if has_scope.is_some() {
53 self.function_stack.pop();
54 }
55 }
56
57 fn check_captured_reference(&mut self, _start: Option<u32>, node_id: Option<u32>) {
58 let binding_id = match self.scope_info.resolve_reference_id_for_node(node_id) {
59 Some(id) => id,
60 None => return,
61 };
62 let &fn_scope = match self.function_stack.last() {
63 Some(s) => s,
64 None => return,
65 };
66 let binding = &self.scope_info.bindings[binding_id.0 as usize];
67 if is_captured_by_function(self.scope_info, binding.scope, fn_scope) {
68 let info = self.binding_info.entry(binding_id).or_default();
69 info.referenced_by_inner_fn = true;
70 }
71 }
72
73 fn handle_reassignment_identifier(&mut self, name: &str, current_scope: ScopeId) {
74 if let Some(binding_id) = self.scope_info.get_binding(current_scope, name) {
75 let info = self.binding_info.entry(binding_id).or_default();
76 info.reassigned = true;
77 if let Some(&fn_scope) = self.function_stack.last() {
78 let binding = &self.scope_info.bindings[binding_id.0 as usize];
79 if is_captured_by_function(self.scope_info, binding.scope, fn_scope) {
80 info.reassigned_by_inner_fn = true;
81 }
82 }
83 }
84 }
85}
86
87impl<'ast> Visitor<'ast> for ContextIdentifierVisitor<'_> {
88 fn enter_function_declaration(&mut self, node: &'ast FunctionDeclaration, _: &[ScopeId]) {
89 self.push_function_scope(node.base.start, node.base.node_id);
90 }
91 fn leave_function_declaration(&mut self, node: &'ast FunctionDeclaration, _: &[ScopeId]) {
92 self.pop_function_scope(node.base.start, node.base.node_id);
93 }
94 fn enter_function_expression(&mut self, node: &'ast FunctionExpression, _: &[ScopeId]) {
95 self.push_function_scope(node.base.start, node.base.node_id);
96 }
97 fn leave_function_expression(&mut self, node: &'ast FunctionExpression, _: &[ScopeId]) {
98 self.pop_function_scope(node.base.start, node.base.node_id);
99 }
100 fn enter_arrow_function_expression(
101 &mut self,
102 node: &'ast ArrowFunctionExpression,
103 _: &[ScopeId],
104 ) {
105 self.push_function_scope(node.base.start, node.base.node_id);
106 }
107 fn leave_arrow_function_expression(
108 &mut self,
109 node: &'ast ArrowFunctionExpression,
110 _: &[ScopeId],
111 ) {
112 self.pop_function_scope(node.base.start, node.base.node_id);
113 }
114 fn enter_object_method(&mut self, node: &'ast ObjectMethod, _: &[ScopeId]) {
115 self.push_function_scope(node.base.start, node.base.node_id);
116 }
117 fn leave_object_method(&mut self, node: &'ast ObjectMethod, _: &[ScopeId]) {
118 self.pop_function_scope(node.base.start, node.base.node_id);
119 }
120
121 fn enter_identifier(&mut self, node: &'ast Identifier, _scope_stack: &[ScopeId]) {
122 self.check_captured_reference(node.base.start, node.base.node_id);
123 }
124
125 fn enter_jsx_identifier(
126 &mut self,
127 node: &'ast react_compiler_ast::jsx::JSXIdentifier,
128 _scope_stack: &[ScopeId],
129 ) {
130 self.check_captured_reference(node.base.start, node.base.node_id);
131 }
132
133 fn enter_assignment_expression(
134 &mut self,
135 node: &'ast AssignmentExpression,
136 scope_stack: &[ScopeId],
137 ) {
138 let current_scope = scope_stack
139 .last()
140 .copied()
141 .unwrap_or(self.scope_info.program_scope);
142 if self.error.is_none() {
143 if let Err(error) = walk_lval_for_reassignment(self, &node.left, current_scope) {
144 self.error = Some(error);
145 }
146 }
147 }
148
149 fn enter_update_expression(&mut self, node: &'ast UpdateExpression, scope_stack: &[ScopeId]) {
150 if let Expression::Identifier(ident) = node.argument.as_ref() {
151 let current_scope = scope_stack
152 .last()
153 .copied()
154 .unwrap_or(self.scope_info.program_scope);
155 self.handle_reassignment_identifier(&ident.name, current_scope);
156 }
157 }
158}
159
160fn walk_lval_for_reassignment(
162 visitor: &mut ContextIdentifierVisitor<'_>,
163 pattern: &PatternLike,
164 current_scope: ScopeId,
165) -> Result<(), CompilerError> {
166 match pattern {
167 PatternLike::Identifier(ident) => {
168 visitor.handle_reassignment_identifier(&ident.name, current_scope);
169 }
170 PatternLike::ArrayPattern(pat) => {
171 for element in &pat.elements {
172 if let Some(el) = element {
173 walk_lval_for_reassignment(visitor, el, current_scope)?;
174 }
175 }
176 }
177 PatternLike::ObjectPattern(pat) => {
178 for prop in &pat.properties {
179 match prop {
180 ObjectPatternProperty::ObjectProperty(p) => {
181 walk_lval_for_reassignment(visitor, &p.value, current_scope)?;
182 }
183 ObjectPatternProperty::RestElement(p) => {
184 walk_lval_for_reassignment(visitor, &p.argument, current_scope)?;
185 }
186 }
187 }
188 }
189 PatternLike::AssignmentPattern(pat) => {
190 walk_lval_for_reassignment(visitor, &pat.left, current_scope)?;
191 }
192 PatternLike::RestElement(pat) => {
193 walk_lval_for_reassignment(visitor, &pat.argument, current_scope)?;
194 }
195 PatternLike::MemberExpression(_) => {
196 }
198 PatternLike::TSAsExpression(node) => {
199 record_unsupported_lval(visitor.env, "TSAsExpression", convert_opt_loc(&node.base.loc))?;
200 }
201 PatternLike::TSSatisfiesExpression(node) => {
202 record_unsupported_lval(
203 visitor.env,
204 "TSSatisfiesExpression",
205 convert_opt_loc(&node.base.loc),
206 )?;
207 }
208 PatternLike::TSNonNullExpression(node) => {
209 record_unsupported_lval(
210 visitor.env,
211 "TSNonNullExpression",
212 convert_opt_loc(&node.base.loc),
213 )?;
214 }
215 PatternLike::TSTypeAssertion(node) => {
216 record_unsupported_lval(
217 visitor.env,
218 "TSTypeAssertion",
219 convert_opt_loc(&node.base.loc),
220 )?;
221 }
222 PatternLike::TypeCastExpression(node) => {
223 record_unsupported_lval(
224 visitor.env,
225 "TypeCastExpression",
226 convert_opt_loc(&node.base.loc),
227 )?;
228 }
229 }
230 Ok(())
231}
232
233fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation {
234 SourceLocation {
235 start: Position {
236 line: loc.start.line,
237 column: loc.start.column,
238 index: loc.start.index,
239 },
240 end: Position {
241 line: loc.end.line,
242 column: loc.end.column,
243 index: loc.end.index,
244 },
245 }
246}
247
248fn convert_opt_loc(
249 loc: &Option<react_compiler_ast::common::SourceLocation>,
250) -> Option<SourceLocation> {
251 loc.as_ref().map(convert_loc)
252}
253
254fn record_unsupported_lval(
261 env: &mut Environment,
262 type_name: &str,
263 loc: Option<SourceLocation>,
264) -> Result<(), CompilerError> {
265 let _ = env;
266 let mut err = CompilerError::new();
267 err.push_error_detail(CompilerErrorDetail {
268 category: ErrorCategory::Todo,
269 reason: format!(
270 "[FindContextIdentifiers] Cannot handle Object destructuring assignment target {type_name}"
271 ),
272 description: None,
273 loc,
274 suggestions: None,
275 });
276 Err(err)
277}
278
279fn is_captured_by_function(
282 scope_info: &ScopeInfo,
283 binding_scope: ScopeId,
284 function_scope: ScopeId,
285) -> bool {
286 let fn_parent = match scope_info.scopes[function_scope.0 as usize].parent {
287 Some(p) => p,
288 None => return false,
289 };
290 if binding_scope == fn_parent {
291 return true;
292 }
293 let mut current = scope_info.scopes[fn_parent.0 as usize].parent;
295 while let Some(scope_id) = current {
296 if scope_id == binding_scope {
297 return true;
298 }
299 current = scope_info.scopes[scope_id.0 as usize].parent;
300 }
301 false
302}
303
304fn build_declaration_node_ids(scope_info: &ScopeInfo) -> HashSet<(BindingId, u32)> {
313 let mut result = HashSet::new();
314 for (&ref_nid, &binding_id) in &scope_info.ref_node_id_to_binding {
315 let binding = &scope_info.bindings[binding_id.0 as usize];
316 if binding.declaration_node_id == Some(ref_nid) {
317 result.insert((binding_id, ref_nid));
318 }
319 }
320 result
321}
322
323pub fn find_context_identifiers(
333 func: &FunctionNode<'_>,
334 scope_info: &ScopeInfo,
335 env: &mut Environment,
336 identifier_locs: &crate::identifier_loc_index::IdentifierLocIndex,
337) -> Result<HashSet<BindingId>, CompilerError> {
338 let func_scope = scope_info
339 .resolve_scope_for_node(func.node_id())
340 .unwrap_or(scope_info.program_scope);
341
342 let mut visitor = ContextIdentifierVisitor {
343 scope_info,
344 env,
345 function_stack: Vec::new(),
346 binding_info: HashMap::new(),
347 error: None,
348 };
349 let mut walker = AstWalker::with_initial_scope(scope_info, func_scope);
350
351 match func {
353 FunctionNode::FunctionDeclaration(d) => {
354 for param in &d.params {
355 walker.walk_pattern(&mut visitor, param);
356 }
357 walker.walk_block_statement(&mut visitor, &d.body);
358 }
359 FunctionNode::FunctionExpression(e) => {
360 for param in &e.params {
361 walker.walk_pattern(&mut visitor, param);
362 }
363 walker.walk_block_statement(&mut visitor, &e.body);
364 }
365 FunctionNode::ArrowFunctionExpression(a) => {
366 for param in &a.params {
367 walker.walk_pattern(&mut visitor, param);
368 }
369 match a.body.as_ref() {
370 ArrowFunctionBody::BlockStatement(block) => {
371 walker.walk_block_statement(&mut visitor, block);
372 }
373 ArrowFunctionBody::Expression(expr) => {
374 walker.walk_expression(&mut visitor, expr);
375 }
376 }
377 }
378 }
379
380 if let Some(error) = visitor.error {
381 return Err(error);
382 }
383
384 let declaration_node_ids = build_declaration_node_ids(scope_info);
396 for (&ref_nid, &binding_id) in &scope_info.ref_node_id_to_binding {
397 let info = match visitor.binding_info.get(&binding_id) {
398 Some(info) if info.reassigned && !info.referenced_by_inner_fn => info,
399 _ => continue,
400 };
401 let _ = info;
402 if declaration_node_ids.contains(&(binding_id, ref_nid)) {
403 continue;
404 }
405 let ref_pos = match identifier_locs.get(&ref_nid) {
407 Some(entry) => entry.start,
408 None => continue,
409 };
410 let binding = &scope_info.bindings[binding_id.0 as usize];
411 for (&scope_start, &scope_id) in &scope_info.node_to_scope {
413 if scope_start <= ref_pos {
414 if let Some(&scope_end) = scope_info.node_to_scope_end.get(&scope_start) {
415 if ref_pos < scope_end
416 && matches!(
417 scope_info.scopes[scope_id.0 as usize].kind,
418 ScopeKind::Function
419 )
420 && is_captured_by_function(scope_info, binding.scope, scope_id)
421 {
422 visitor
423 .binding_info
424 .get_mut(&binding_id)
425 .unwrap()
426 .referenced_by_inner_fn = true;
427 break;
428 }
429 }
430 }
431 }
432 }
433
434 Ok(visitor
436 .binding_info
437 .into_iter()
438 .filter(|(_, info)| {
439 info.reassigned_by_inner_fn || (info.reassigned && info.referenced_by_inner_fn)
440 })
441 .map(|(id, _)| id)
442 .collect())
443}