main
rs 1,121 lines 40.1 KB
Raw
1 use std::path::PathBuf;
2
3 use react_compiler_ast::declarations::*;
4 use react_compiler_ast::expressions::*;
5 use react_compiler_ast::jsx::*;
6 use react_compiler_ast::patterns::*;
7 use react_compiler_ast::scope::ScopeInfo;
8 use react_compiler_ast::statements::*;
9
10 fn get_fixture_json_dir() -> PathBuf {
11 if let Ok(dir) = std::env::var("FIXTURE_JSON_DIR") {
12 return PathBuf::from(dir);
13 }
14 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
15 }
16
17 /// Recursively sort all keys in a JSON value for order-independent comparison.
18 fn normalize_json(value: &serde_json::Value) -> serde_json::Value {
19 match value {
20 serde_json::Value::Object(map) => {
21 let mut sorted: Vec<(String, serde_json::Value)> = map
22 .iter()
23 .map(|(k, v)| (k.clone(), normalize_json(v)))
24 .collect();
25 sorted.sort_by(|a, b| a.0.cmp(&b.0));
26 serde_json::Value::Object(sorted.into_iter().collect())
27 }
28 serde_json::Value::Array(arr) => {
29 serde_json::Value::Array(arr.iter().map(normalize_json).collect())
30 }
31 serde_json::Value::Number(n) => {
32 if let Some(f) = n.as_f64() {
33 if f.fract() == 0.0 && f.is_finite() && f.abs() < (i64::MAX as f64) {
34 serde_json::Value::Number(serde_json::Number::from(f as i64))
35 } else {
36 value.clone()
37 }
38 } else {
39 value.clone()
40 }
41 }
42 other => other.clone(),
43 }
44 }
45
46 fn compute_diff(original: &str, round_tripped: &str) -> String {
47 use similar::ChangeTag;
48 use similar::TextDiff;
49 let diff = TextDiff::from_lines(original, round_tripped);
50 let mut output = String::new();
51 let mut lines_written = 0;
52 const MAX_DIFF_LINES: usize = 50;
53 for change in diff.iter_all_changes() {
54 if lines_written >= MAX_DIFF_LINES {
55 output.push_str("... (diff truncated)\n");
56 break;
57 }
58 let sign = match change.tag() {
59 ChangeTag::Delete => "-",
60 ChangeTag::Insert => "+",
61 ChangeTag::Equal => continue,
62 };
63 output.push_str(&format!("{sign} {change}"));
64 lines_written += 1;
65 }
66 output
67 }
68
69 #[test]
70 fn scope_info_round_trip() {
71 let json_dir = get_fixture_json_dir();
72 let mut failures: Vec<(String, String)> = Vec::new();
73 let mut total = 0;
74 let mut passed = 0;
75 let mut skipped = 0;
76
77 for entry in walkdir::WalkDir::new(&json_dir)
78 .into_iter()
79 .filter_map(|e| e.ok())
80 .filter(|e| {
81 e.path().extension().is_some_and(|ext| ext == "json")
82 && !e.path().to_string_lossy().contains(".scope.")
83 && !e.path().to_string_lossy().contains(".renamed.")
84 })
85 {
86 let ast_path_str = entry.path().to_string_lossy().to_string();
87 let scope_path_str = ast_path_str.replace(".json", ".scope.json");
88 let scope_path = std::path::Path::new(&scope_path_str);
89
90 if !scope_path.exists() {
91 skipped += 1;
92 continue;
93 }
94
95 let fixture_name = entry
96 .path()
97 .strip_prefix(&json_dir)
98 .unwrap()
99 .display()
100 .to_string();
101 total += 1;
102
103 let scope_json = std::fs::read_to_string(scope_path).unwrap();
104
105 let scope_info: react_compiler_ast::scope::ScopeInfo =
106 match serde_json::from_str(&scope_json) {
107 Ok(info) => info,
108 Err(e) => {
109 failures.push((fixture_name, format!("Scope deserialization error: {e}")));
110 continue;
111 }
112 };
113
114 let round_tripped = serde_json::to_string_pretty(&scope_info).unwrap();
115 let original_value: serde_json::Value = serde_json::from_str(&scope_json).unwrap();
116 let round_tripped_value: serde_json::Value = serde_json::from_str(&round_tripped).unwrap();
117
118 let original_normalized = normalize_json(&original_value);
119 let round_tripped_normalized = normalize_json(&round_tripped_value);
120
121 if original_normalized != round_tripped_normalized {
122 let orig_str = serde_json::to_string_pretty(&original_normalized).unwrap();
123 let rt_str = serde_json::to_string_pretty(&round_tripped_normalized).unwrap();
124 let diff = compute_diff(&orig_str, &rt_str);
125 failures.push((fixture_name, format!("Round-trip mismatch:\n{diff}")));
126 continue;
127 }
128
129 let mut consistency_error = None;
130
131 for binding in &scope_info.bindings {
132 if binding.scope.0 as usize >= scope_info.scopes.len() {
133 consistency_error = Some(format!(
134 "Binding {} has scope {} but only {} scopes exist",
135 binding.name,
136 binding.scope.0,
137 scope_info.scopes.len()
138 ));
139 break;
140 }
141 }
142
143 if consistency_error.is_none() {
144 for scope in &scope_info.scopes {
145 for (name, &bid) in &scope.bindings {
146 if bid.0 as usize >= scope_info.bindings.len() {
147 consistency_error = Some(format!(
148 "Scope {} has binding '{}' with id {} but only {} bindings exist",
149 scope.id.0,
150 name,
151 bid.0,
152 scope_info.bindings.len()
153 ));
154 break;
155 }
156 }
157 if consistency_error.is_some() {
158 break;
159 }
160 if let Some(parent) = scope.parent {
161 if parent.0 as usize >= scope_info.scopes.len() {
162 consistency_error = Some(format!(
163 "Scope {} has parent {} but only {} scopes exist",
164 scope.id.0,
165 parent.0,
166 scope_info.scopes.len()
167 ));
168 break;
169 }
170 }
171 }
172 }
173
174 if consistency_error.is_none() {
175 for (&_offset, &bid) in &scope_info.reference_to_binding {
176 if bid.0 as usize >= scope_info.bindings.len() {
177 consistency_error = Some(format!(
178 "reference_to_binding has binding id {} but only {} bindings exist",
179 bid.0,
180 scope_info.bindings.len()
181 ));
182 break;
183 }
184 }
185 }
186
187 if consistency_error.is_none() {
188 for (&_offset, &sid) in &scope_info.node_to_scope {
189 if sid.0 as usize >= scope_info.scopes.len() {
190 consistency_error = Some(format!(
191 "node_to_scope has scope id {} but only {} scopes exist",
192 sid.0,
193 scope_info.scopes.len()
194 ));
195 break;
196 }
197 }
198 }
199
200 // Validate node-ID maps
201 if consistency_error.is_none() {
202 for (&_nid, &bid) in &scope_info.ref_node_id_to_binding {
203 if bid.0 as usize >= scope_info.bindings.len() {
204 consistency_error = Some(format!(
205 "ref_node_id_to_binding has binding id {} but only {} bindings exist",
206 bid.0,
207 scope_info.bindings.len()
208 ));
209 break;
210 }
211 }
212 }
213
214 if consistency_error.is_none() {
215 for (&_nid, &sid) in &scope_info.node_id_to_scope {
216 if sid.0 as usize >= scope_info.scopes.len() {
217 consistency_error = Some(format!(
218 "node_id_to_scope has scope id {} but only {} scopes exist",
219 sid.0,
220 scope_info.scopes.len()
221 ));
222 break;
223 }
224 }
225 }
226
227 if let Some(err) = consistency_error {
228 failures.push((fixture_name, format!("Consistency error: {err}")));
229 continue;
230 }
231
232 passed += 1;
233 }
234
235 println!(
236 "\n{passed}/{total} fixtures passed scope info round-trip ({skipped} skipped - no scope.json)"
237 );
238
239 if !failures.is_empty() {
240 let show_count = failures.len().min(5);
241 let mut msg = format!(
242 "\n{} of {total} fixtures failed scope info test (showing first {show_count}):\n\n",
243 failures.len()
244 );
245 for (name, err) in failures.iter().take(show_count) {
246 msg.push_str(&format!("--- {name} ---\n{err}\n\n"));
247 }
248 if failures.len() > show_count {
249 msg.push_str(&format!(
250 "... and {} more failures\n",
251 failures.len() - show_count
252 ));
253 }
254 panic!("{msg}");
255 }
256 }
257
258 // ============================================================================
259 // Typed AST traversal for identifier renaming
260 // ============================================================================
261
262 /// Rename an Identifier if it has a binding in ref_node_id_to_binding.
263 /// Uses the declaring scope from the binding table — no scope stack needed.
264 fn rename_id(id: &mut Identifier, si: &ScopeInfo) {
265 if let Some(nid) = id.base.node_id {
266 if let Some(bid) = si.resolve_reference_by_node_id(nid) {
267 let scope = si.bindings[bid.0 as usize].scope.0;
268 id.name = format!("{}_{}", id.name, format_args!("{scope}_{}", bid.0));
269 }
270 }
271 visit_json_opt(&mut id.type_annotation, si);
272 if let Some(decorators) = &mut id.decorators {
273 visit_json_vec(decorators, si);
274 }
275 }
276
277 /// Fallback walker for serde_json::Value fields (class bodies, type annotations, decorators, etc.)
278 fn visit_json(val: &mut serde_json::Value, si: &ScopeInfo) {
279 match val {
280 serde_json::Value::Object(map) => {
281 if map.get("type").and_then(|v| v.as_str()) == Some("Identifier") {
282 if let Some(nid) = map.get("_nodeId").and_then(|v| v.as_u64()) {
283 if let Some(bid) = si.resolve_reference_by_node_id(nid as u32) {
284 let scope = si.bindings[bid.0 as usize].scope.0;
285 if let Some(name) = map
286 .get("name")
287 .and_then(|v| v.as_str())
288 .map(|s| s.to_string())
289 {
290 map.insert(
291 "name".to_string(),
292 serde_json::Value::String(format!("{name}_{scope}_{}", bid.0)),
293 );
294 }
295 }
296 }
297 }
298 let keys: Vec<String> = map.keys().cloned().collect();
299 for key in keys {
300 if let Some(child) = map.get_mut(&key) {
301 visit_json(child, si);
302 }
303 }
304 }
305 serde_json::Value::Array(arr) => {
306 for item in arr.iter_mut() {
307 visit_json(item, si);
308 }
309 }
310 _ => {}
311 }
312 }
313
314 fn visit_raw(val: &mut react_compiler_ast::common::RawNode, si: &ScopeInfo) {
315 let mut v = val.parse_value();
316 visit_json(&mut v, si);
317 *val = react_compiler_ast::common::RawNode::from_value(&v);
318 }
319
320 fn visit_json_vec(vals: &mut [react_compiler_ast::common::RawNode], si: &ScopeInfo) {
321 for val in vals.iter_mut() {
322 visit_raw(val, si);
323 }
324 }
325
326 fn visit_json_opt(val: &mut Option<react_compiler_ast::common::RawNode>, si: &ScopeInfo) {
327 if let Some(v) = val {
328 visit_raw(v, si);
329 }
330 }
331
332 fn rename_identifiers(file: &mut react_compiler_ast::File, si: &ScopeInfo) {
333 visit_program(&mut file.program, si);
334 }
335
336 fn visit_program(prog: &mut react_compiler_ast::Program, si: &ScopeInfo) {
337 for stmt in &mut prog.body {
338 visit_stmt(stmt, si);
339 }
340 }
341
342 fn visit_block(block: &mut BlockStatement, si: &ScopeInfo) {
343 for stmt in &mut block.body {
344 visit_stmt(stmt, si);
345 }
346 }
347
348 fn visit_stmt(stmt: &mut Statement, si: &ScopeInfo) {
349 match stmt {
350 Statement::BlockStatement(s) => visit_block(s, si),
351 Statement::ReturnStatement(s) => {
352 if let Some(arg) = &mut s.argument {
353 visit_expr(arg, si);
354 }
355 }
356 Statement::ExpressionStatement(s) => visit_expr(&mut s.expression, si),
357 Statement::IfStatement(s) => {
358 visit_expr(&mut s.test, si);
359 visit_stmt(&mut s.consequent, si);
360 if let Some(alt) = &mut s.alternate {
361 visit_stmt(alt, si);
362 }
363 }
364 Statement::ForStatement(s) => {
365 if let Some(init) = &mut s.init {
366 match init.as_mut() {
367 ForInit::VariableDeclaration(d) => visit_var_decl(d, si),
368 ForInit::Expression(e) => visit_expr(e, si),
369 }
370 }
371 if let Some(test) = &mut s.test {
372 visit_expr(test, si);
373 }
374 if let Some(update) = &mut s.update {
375 visit_expr(update, si);
376 }
377 visit_stmt(&mut s.body, si);
378 }
379 Statement::WhileStatement(s) => {
380 visit_expr(&mut s.test, si);
381 visit_stmt(&mut s.body, si);
382 }
383 Statement::DoWhileStatement(s) => {
384 visit_stmt(&mut s.body, si);
385 visit_expr(&mut s.test, si);
386 }
387 Statement::ForInStatement(s) => {
388 visit_for_left(&mut s.left, si);
389 visit_expr(&mut s.right, si);
390 visit_stmt(&mut s.body, si);
391 }
392 Statement::ForOfStatement(s) => {
393 visit_for_left(&mut s.left, si);
394 visit_expr(&mut s.right, si);
395 visit_stmt(&mut s.body, si);
396 }
397 Statement::SwitchStatement(s) => {
398 visit_expr(&mut s.discriminant, si);
399 for case in &mut s.cases {
400 if let Some(test) = &mut case.test {
401 visit_expr(test, si);
402 }
403 for child in &mut case.consequent {
404 visit_stmt(child, si);
405 }
406 }
407 }
408 Statement::ThrowStatement(s) => visit_expr(&mut s.argument, si),
409 Statement::TryStatement(s) => {
410 visit_block(&mut s.block, si);
411 if let Some(handler) = &mut s.handler {
412 if let Some(param) = &mut handler.param {
413 visit_pat(param, si);
414 }
415 visit_block(&mut handler.body, si);
416 }
417 if let Some(fin) = &mut s.finalizer {
418 visit_block(fin, si);
419 }
420 }
421 Statement::LabeledStatement(s) => visit_stmt(&mut s.body, si),
422 Statement::WithStatement(s) => {
423 visit_expr(&mut s.object, si);
424 visit_stmt(&mut s.body, si);
425 }
426 Statement::VariableDeclaration(d) => visit_var_decl(d, si),
427 Statement::FunctionDeclaration(f) => visit_func_decl(f, si),
428 Statement::ClassDeclaration(c) => visit_class_decl(c, si),
429 Statement::ImportDeclaration(d) => visit_import_decl(d, si),
430 Statement::ExportNamedDeclaration(d) => visit_export_named(d, si),
431 Statement::ExportDefaultDeclaration(d) => visit_export_default(d, si),
432 Statement::TSTypeAliasDeclaration(d) => {
433 rename_id(&mut d.id, si);
434 visit_raw(&mut d.type_annotation, si);
435 visit_json_opt(&mut d.type_parameters, si);
436 }
437 Statement::TSInterfaceDeclaration(d) => {
438 rename_id(&mut d.id, si);
439 visit_raw(&mut d.body, si);
440 visit_json_opt(&mut d.type_parameters, si);
441 if let Some(ext) = &mut d.extends {
442 visit_json_vec(ext, si);
443 }
444 }
445 Statement::TSEnumDeclaration(d) => {
446 rename_id(&mut d.id, si);
447 visit_json_vec(&mut d.members, si);
448 }
449 Statement::TSModuleDeclaration(d) => {
450 visit_raw(&mut d.id, si);
451 visit_raw(&mut d.body, si);
452 }
453 Statement::TSDeclareFunction(d) => {
454 if let Some(id) = &mut d.id {
455 rename_id(id, si);
456 }
457 visit_json_vec(&mut d.params, si);
458 visit_json_opt(&mut d.return_type, si);
459 visit_json_opt(&mut d.type_parameters, si);
460 }
461 Statement::TypeAlias(d) => {
462 rename_id(&mut d.id, si);
463 visit_raw(&mut d.right, si);
464 visit_json_opt(&mut d.type_parameters, si);
465 }
466 Statement::OpaqueType(d) => {
467 rename_id(&mut d.id, si);
468 if let Some(st) = &mut d.supertype {
469 visit_raw(st, si);
470 }
471 visit_raw(&mut d.impltype, si);
472 visit_json_opt(&mut d.type_parameters, si);
473 }
474 Statement::InterfaceDeclaration(d) => {
475 rename_id(&mut d.id, si);
476 visit_raw(&mut d.body, si);
477 visit_json_opt(&mut d.type_parameters, si);
478 if let Some(ext) = &mut d.extends {
479 visit_json_vec(ext, si);
480 }
481 }
482 Statement::DeclareVariable(d) => rename_id(&mut d.id, si),
483 Statement::DeclareFunction(d) => {
484 rename_id(&mut d.id, si);
485 if let Some(pred) = &mut d.predicate {
486 visit_raw(pred, si);
487 }
488 }
489 Statement::DeclareClass(d) => {
490 rename_id(&mut d.id, si);
491 visit_raw(&mut d.body, si);
492 visit_json_opt(&mut d.type_parameters, si);
493 if let Some(ext) = &mut d.extends {
494 visit_json_vec(ext, si);
495 }
496 }
497 Statement::DeclareModule(d) => {
498 visit_raw(&mut d.id, si);
499 visit_raw(&mut d.body, si);
500 }
501 Statement::DeclareModuleExports(d) => visit_raw(&mut d.type_annotation, si),
502 Statement::DeclareExportDeclaration(d) => {
503 if let Some(decl) = &mut d.declaration {
504 visit_raw(decl, si);
505 }
506 if let Some(specs) = &mut d.specifiers {
507 visit_json_vec(specs, si);
508 }
509 }
510 Statement::DeclareInterface(d) => {
511 rename_id(&mut d.id, si);
512 visit_raw(&mut d.body, si);
513 visit_json_opt(&mut d.type_parameters, si);
514 if let Some(ext) = &mut d.extends {
515 visit_json_vec(ext, si);
516 }
517 }
518 Statement::DeclareTypeAlias(d) => {
519 rename_id(&mut d.id, si);
520 visit_raw(&mut d.right, si);
521 visit_json_opt(&mut d.type_parameters, si);
522 }
523 Statement::DeclareOpaqueType(d) => {
524 rename_id(&mut d.id, si);
525 if let Some(st) = &mut d.supertype {
526 visit_raw(st, si);
527 }
528 if let Some(impl_) = &mut d.impltype {
529 visit_raw(impl_, si);
530 }
531 visit_json_opt(&mut d.type_parameters, si);
532 }
533 Statement::EnumDeclaration(d) => {
534 rename_id(&mut d.id, si);
535 visit_raw(&mut d.body, si);
536 }
537 Statement::Unknown(d) => {
538 d.with_raw_mut(|raw| visit_raw(raw, si))
539 .expect("identifier rename preserves the node `type`");
540 }
541 Statement::BreakStatement(_)
542 | Statement::ContinueStatement(_)
543 | Statement::EmptyStatement(_)
544 | Statement::DebuggerStatement(_)
545 | Statement::ExportAllDeclaration(_)
546 | Statement::DeclareExportAllDeclaration(_) => {}
547 }
548 }
549
550 fn visit_expr(expr: &mut Expression, si: &ScopeInfo) {
551 match expr {
552 Expression::Identifier(id) => rename_id(id, si),
553 Expression::CallExpression(e) => {
554 visit_expr(&mut e.callee, si);
555 for arg in &mut e.arguments {
556 visit_expr(arg, si);
557 }
558 visit_json_opt(&mut e.type_parameters, si);
559 visit_json_opt(&mut e.type_arguments, si);
560 }
561 Expression::MemberExpression(e) => {
562 visit_expr(&mut e.object, si);
563 visit_expr(&mut e.property, si);
564 }
565 Expression::OptionalCallExpression(e) => {
566 visit_expr(&mut e.callee, si);
567 for arg in &mut e.arguments {
568 visit_expr(arg, si);
569 }
570 visit_json_opt(&mut e.type_parameters, si);
571 visit_json_opt(&mut e.type_arguments, si);
572 }
573 Expression::OptionalMemberExpression(e) => {
574 visit_expr(&mut e.object, si);
575 visit_expr(&mut e.property, si);
576 }
577 Expression::BinaryExpression(e) => {
578 visit_expr(&mut e.left, si);
579 visit_expr(&mut e.right, si);
580 }
581 Expression::LogicalExpression(e) => {
582 visit_expr(&mut e.left, si);
583 visit_expr(&mut e.right, si);
584 }
585 Expression::UnaryExpression(e) => visit_expr(&mut e.argument, si),
586 Expression::UpdateExpression(e) => visit_expr(&mut e.argument, si),
587 Expression::ConditionalExpression(e) => {
588 visit_expr(&mut e.test, si);
589 visit_expr(&mut e.consequent, si);
590 visit_expr(&mut e.alternate, si);
591 }
592 Expression::AssignmentExpression(e) => {
593 visit_pat(&mut e.left, si);
594 visit_expr(&mut e.right, si);
595 }
596 Expression::SequenceExpression(e) => {
597 for child in &mut e.expressions {
598 visit_expr(child, si);
599 }
600 }
601 Expression::ArrowFunctionExpression(e) => {
602 if let Some(id) = &mut e.id {
603 rename_id(id, si);
604 }
605 for param in &mut e.params {
606 visit_pat(param, si);
607 }
608 match e.body.as_mut() {
609 ArrowFunctionBody::BlockStatement(block) => visit_block(block, si),
610 ArrowFunctionBody::Expression(expr) => visit_expr(expr, si),
611 }
612 visit_json_opt(&mut e.return_type, si);
613 visit_json_opt(&mut e.type_parameters, si);
614 visit_json_opt(&mut e.predicate, si);
615 }
616 Expression::FunctionExpression(e) => {
617 if let Some(id) = &mut e.id {
618 rename_id(id, si);
619 }
620 for param in &mut e.params {
621 visit_pat(param, si);
622 }
623 visit_block(&mut e.body, si);
624 visit_json_opt(&mut e.return_type, si);
625 visit_json_opt(&mut e.type_parameters, si);
626 }
627 Expression::ObjectExpression(e) => {
628 for prop in &mut e.properties {
629 match prop {
630 ObjectExpressionProperty::ObjectProperty(p) => {
631 visit_expr(&mut p.key, si);
632 visit_expr(&mut p.value, si);
633 }
634 ObjectExpressionProperty::ObjectMethod(m) => {
635 visit_expr(&mut m.key, si);
636 for param in &mut m.params {
637 visit_pat(param, si);
638 }
639 visit_block(&mut m.body, si);
640 visit_json_opt(&mut m.return_type, si);
641 visit_json_opt(&mut m.type_parameters, si);
642 }
643 ObjectExpressionProperty::SpreadElement(s) => visit_expr(&mut s.argument, si),
644 }
645 }
646 }
647 Expression::ArrayExpression(e) => {
648 for elem in &mut e.elements {
649 if let Some(el) = elem {
650 visit_expr(el, si);
651 }
652 }
653 }
654 Expression::NewExpression(e) => {
655 visit_expr(&mut e.callee, si);
656 for arg in &mut e.arguments {
657 visit_expr(arg, si);
658 }
659 visit_json_opt(&mut e.type_parameters, si);
660 visit_json_opt(&mut e.type_arguments, si);
661 }
662 Expression::TemplateLiteral(e) => {
663 for child in &mut e.expressions {
664 visit_expr(child, si);
665 }
666 }
667 Expression::TaggedTemplateExpression(e) => {
668 visit_expr(&mut e.tag, si);
669 for child in &mut e.quasi.expressions {
670 visit_expr(child, si);
671 }
672 visit_json_opt(&mut e.type_parameters, si);
673 }
674 Expression::AwaitExpression(e) => visit_expr(&mut e.argument, si),
675 Expression::YieldExpression(e) => {
676 if let Some(arg) = &mut e.argument {
677 visit_expr(arg, si);
678 }
679 }
680 Expression::SpreadElement(e) => visit_expr(&mut e.argument, si),
681 Expression::MetaProperty(e) => {
682 rename_id(&mut e.meta, si);
683 rename_id(&mut e.property, si);
684 }
685 Expression::ClassExpression(e) => {
686 if let Some(id) = &mut e.id {
687 rename_id(id, si);
688 }
689 if let Some(sc) = &mut e.super_class {
690 visit_expr(sc, si);
691 }
692 visit_json_vec(&mut e.body.body, si);
693 if let Some(dec) = &mut e.decorators {
694 visit_json_vec(dec, si);
695 }
696 visit_json_opt(&mut e.super_type_parameters, si);
697 visit_json_opt(&mut e.type_parameters, si);
698 if let Some(imp) = &mut e.implements {
699 visit_json_vec(imp, si);
700 }
701 }
702 Expression::PrivateName(e) => rename_id(&mut e.id, si),
703 Expression::ParenthesizedExpression(e) => visit_expr(&mut e.expression, si),
704 Expression::AssignmentPattern(p) => {
705 visit_pat(&mut p.left, si);
706 visit_expr(&mut p.right, si);
707 }
708 Expression::TSAsExpression(e) => {
709 visit_expr(&mut e.expression, si);
710 visit_raw(&mut e.type_annotation, si);
711 }
712 Expression::TSSatisfiesExpression(e) => {
713 visit_expr(&mut e.expression, si);
714 visit_raw(&mut e.type_annotation, si);
715 }
716 Expression::TSNonNullExpression(e) => visit_expr(&mut e.expression, si),
717 Expression::TSTypeAssertion(e) => {
718 visit_expr(&mut e.expression, si);
719 visit_raw(&mut e.type_annotation, si);
720 }
721 Expression::TSInstantiationExpression(e) => {
722 visit_expr(&mut e.expression, si);
723 visit_raw(&mut e.type_parameters, si);
724 }
725 Expression::TypeCastExpression(e) => {
726 visit_expr(&mut e.expression, si);
727 visit_raw(&mut e.type_annotation, si);
728 }
729 Expression::JSXElement(e) => visit_jsx_element(e, si),
730 Expression::JSXFragment(f) => {
731 for child in &mut f.children {
732 visit_jsx_child(child, si);
733 }
734 }
735 Expression::StringLiteral(_)
736 | Expression::NumericLiteral(_)
737 | Expression::BooleanLiteral(_)
738 | Expression::NullLiteral(_)
739 | Expression::BigIntLiteral(_)
740 | Expression::RegExpLiteral(_)
741 | Expression::Super(_)
742 | Expression::Import(_)
743 | Expression::ThisExpression(_) => {}
744 }
745 }
746
747 fn visit_pat(pat: &mut PatternLike, si: &ScopeInfo) {
748 match pat {
749 PatternLike::Identifier(id) => rename_id(id, si),
750 PatternLike::ObjectPattern(op) => {
751 for prop in &mut op.properties {
752 match prop {
753 ObjectPatternProperty::ObjectProperty(pp) => {
754 visit_expr(&mut pp.key, si);
755 visit_pat(&mut pp.value, si);
756 }
757 ObjectPatternProperty::RestElement(r) => {
758 visit_pat(&mut r.argument, si);
759 visit_json_opt(&mut r.type_annotation, si);
760 }
761 }
762 }
763 visit_json_opt(&mut op.type_annotation, si);
764 }
765 PatternLike::ArrayPattern(ap) => {
766 for elem in &mut ap.elements {
767 if let Some(el) = elem {
768 visit_pat(el, si);
769 }
770 }
771 visit_json_opt(&mut ap.type_annotation, si);
772 }
773 PatternLike::AssignmentPattern(ap) => {
774 visit_pat(&mut ap.left, si);
775 visit_expr(&mut ap.right, si);
776 visit_json_opt(&mut ap.type_annotation, si);
777 }
778 PatternLike::RestElement(re) => {
779 visit_pat(&mut re.argument, si);
780 visit_json_opt(&mut re.type_annotation, si);
781 }
782 PatternLike::MemberExpression(e) => {
783 visit_expr(&mut e.object, si);
784 visit_expr(&mut e.property, si);
785 }
786 PatternLike::TSAsExpression(e) => {
787 visit_expr(&mut e.expression, si);
788 visit_raw(&mut e.type_annotation, si);
789 }
790 PatternLike::TSSatisfiesExpression(e) => {
791 visit_expr(&mut e.expression, si);
792 visit_raw(&mut e.type_annotation, si);
793 }
794 PatternLike::TSNonNullExpression(e) => {
795 visit_expr(&mut e.expression, si);
796 }
797 PatternLike::TSTypeAssertion(e) => {
798 visit_expr(&mut e.expression, si);
799 visit_raw(&mut e.type_annotation, si);
800 }
801 PatternLike::TypeCastExpression(e) => {
802 visit_expr(&mut e.expression, si);
803 visit_raw(&mut e.type_annotation, si);
804 }
805 }
806 }
807
808 fn visit_for_left(left: &mut Box<ForInOfLeft>, si: &ScopeInfo) {
809 match left.as_mut() {
810 ForInOfLeft::VariableDeclaration(d) => visit_var_decl(d, si),
811 ForInOfLeft::Pattern(p) => visit_pat(p, si),
812 }
813 }
814
815 fn visit_var_decl(d: &mut VariableDeclaration, si: &ScopeInfo) {
816 for decl in &mut d.declarations {
817 visit_pat(&mut decl.id, si);
818 if let Some(init) = &mut decl.init {
819 visit_expr(init, si);
820 }
821 }
822 }
823
824 fn visit_func_decl(f: &mut FunctionDeclaration, si: &ScopeInfo) {
825 if let Some(id) = &mut f.id {
826 rename_id(id, si);
827 }
828 for param in &mut f.params {
829 visit_pat(param, si);
830 }
831 visit_block(&mut f.body, si);
832 visit_json_opt(&mut f.return_type, si);
833 visit_json_opt(&mut f.type_parameters, si);
834 visit_json_opt(&mut f.predicate, si);
835 }
836
837 fn visit_class_decl(c: &mut ClassDeclaration, si: &ScopeInfo) {
838 if let Some(id) = &mut c.id {
839 rename_id(id, si);
840 }
841 if let Some(sc) = &mut c.super_class {
842 visit_expr(sc, si);
843 }
844 visit_json_vec(&mut c.body.body, si);
845 if let Some(dec) = &mut c.decorators {
846 visit_json_vec(dec, si);
847 }
848 visit_json_opt(&mut c.super_type_parameters, si);
849 visit_json_opt(&mut c.type_parameters, si);
850 if let Some(imp) = &mut c.implements {
851 visit_json_vec(imp, si);
852 }
853 }
854
855 fn visit_import_decl(d: &mut ImportDeclaration, si: &ScopeInfo) {
856 for spec in &mut d.specifiers {
857 match spec {
858 ImportSpecifier::ImportSpecifier(s) => {
859 rename_id(&mut s.local, si);
860 visit_module_export_name(&mut s.imported, si);
861 }
862 ImportSpecifier::ImportDefaultSpecifier(s) => rename_id(&mut s.local, si),
863 ImportSpecifier::ImportNamespaceSpecifier(s) => rename_id(&mut s.local, si),
864 }
865 }
866 }
867
868 fn visit_export_named(d: &mut ExportNamedDeclaration, si: &ScopeInfo) {
869 if let Some(decl) = &mut d.declaration {
870 visit_declaration(decl, si);
871 }
872 for spec in &mut d.specifiers {
873 match spec {
874 ExportSpecifier::ExportSpecifier(s) => {
875 visit_module_export_name(&mut s.local, si);
876 visit_module_export_name(&mut s.exported, si);
877 }
878 ExportSpecifier::ExportDefaultSpecifier(s) => rename_id(&mut s.exported, si),
879 ExportSpecifier::ExportNamespaceSpecifier(s) => {
880 visit_module_export_name(&mut s.exported, si);
881 }
882 }
883 }
884 }
885
886 fn visit_export_default(d: &mut ExportDefaultDeclaration, si: &ScopeInfo) {
887 match d.declaration.as_mut() {
888 ExportDefaultDecl::FunctionDeclaration(f) => visit_func_decl(f, si),
889 ExportDefaultDecl::ClassDeclaration(c) => visit_class_decl(c, si),
890 ExportDefaultDecl::EnumDeclaration(_) => {} // Flow enums are opaque
891 ExportDefaultDecl::Expression(e) => visit_expr(e, si),
892 }
893 }
894
895 fn visit_declaration(d: &mut Declaration, si: &ScopeInfo) {
896 match d {
897 Declaration::FunctionDeclaration(f) => visit_func_decl(f, si),
898 Declaration::ClassDeclaration(c) => visit_class_decl(c, si),
899 Declaration::VariableDeclaration(v) => visit_var_decl(v, si),
900 Declaration::TSTypeAliasDeclaration(d) => {
901 rename_id(&mut d.id, si);
902 visit_raw(&mut d.type_annotation, si);
903 visit_json_opt(&mut d.type_parameters, si);
904 }
905 Declaration::TSInterfaceDeclaration(d) => {
906 rename_id(&mut d.id, si);
907 visit_raw(&mut d.body, si);
908 visit_json_opt(&mut d.type_parameters, si);
909 if let Some(ext) = &mut d.extends {
910 visit_json_vec(ext, si);
911 }
912 }
913 Declaration::TSEnumDeclaration(d) => {
914 rename_id(&mut d.id, si);
915 visit_json_vec(&mut d.members, si);
916 }
917 Declaration::TSModuleDeclaration(d) => {
918 visit_raw(&mut d.id, si);
919 visit_raw(&mut d.body, si);
920 }
921 Declaration::TSDeclareFunction(d) => {
922 if let Some(id) = &mut d.id {
923 rename_id(id, si);
924 }
925 visit_json_vec(&mut d.params, si);
926 visit_json_opt(&mut d.return_type, si);
927 visit_json_opt(&mut d.type_parameters, si);
928 }
929 Declaration::TypeAlias(d) => {
930 rename_id(&mut d.id, si);
931 visit_raw(&mut d.right, si);
932 visit_json_opt(&mut d.type_parameters, si);
933 }
934 Declaration::OpaqueType(d) => {
935 rename_id(&mut d.id, si);
936 if let Some(st) = &mut d.supertype {
937 visit_raw(st, si);
938 }
939 visit_raw(&mut d.impltype, si);
940 visit_json_opt(&mut d.type_parameters, si);
941 }
942 Declaration::InterfaceDeclaration(d) => {
943 rename_id(&mut d.id, si);
944 visit_raw(&mut d.body, si);
945 visit_json_opt(&mut d.type_parameters, si);
946 if let Some(ext) = &mut d.extends {
947 visit_json_vec(ext, si);
948 }
949 }
950 Declaration::EnumDeclaration(d) => {
951 rename_id(&mut d.id, si);
952 visit_raw(&mut d.body, si);
953 }
954 }
955 }
956
957 fn visit_module_export_name(n: &mut ModuleExportName, si: &ScopeInfo) {
958 match n {
959 ModuleExportName::Identifier(id) => rename_id(id, si),
960 ModuleExportName::StringLiteral(_) => {}
961 }
962 }
963
964 fn visit_jsx_element(el: &mut JSXElement, si: &ScopeInfo) {
965 for attr in &mut el.opening_element.attributes {
966 match attr {
967 JSXAttributeItem::JSXAttribute(a) => {
968 if let Some(val) = &mut a.value {
969 match val {
970 JSXAttributeValue::JSXExpressionContainer(c) => {
971 visit_jsx_expr(&mut c.expression, si);
972 }
973 JSXAttributeValue::JSXElement(e) => visit_jsx_element(e, si),
974 JSXAttributeValue::JSXFragment(f) => {
975 for child in &mut f.children {
976 visit_jsx_child(child, si);
977 }
978 }
979 JSXAttributeValue::StringLiteral(_) => {}
980 }
981 }
982 }
983 JSXAttributeItem::JSXSpreadAttribute(s) => visit_expr(&mut s.argument, si),
984 }
985 }
986 visit_json_opt(&mut el.opening_element.type_parameters, si);
987 for child in &mut el.children {
988 visit_jsx_child(child, si);
989 }
990 }
991
992 fn visit_jsx_child(child: &mut JSXChild, si: &ScopeInfo) {
993 match child {
994 JSXChild::JSXElement(e) => visit_jsx_element(e, si),
995 JSXChild::JSXFragment(f) => {
996 for child in &mut f.children {
997 visit_jsx_child(child, si);
998 }
999 }
1000 JSXChild::JSXExpressionContainer(c) => visit_jsx_expr(&mut c.expression, si),
1001 JSXChild::JSXSpreadChild(s) => visit_expr(&mut s.expression, si),
1002 JSXChild::JSXText(_) => {}
1003 }
1004 }
1005
1006 fn visit_jsx_expr(expr: &mut JSXExpressionContainerExpr, si: &ScopeInfo) {
1007 match expr {
1008 JSXExpressionContainerExpr::Expression(e) => visit_expr(e, si),
1009 JSXExpressionContainerExpr::JSXEmptyExpression(_) => {}
1010 }
1011 }
1012
1013 #[test]
1014 fn scope_resolution_rename() {
1015 let json_dir = get_fixture_json_dir();
1016 let mut failures: Vec<(String, String)> = Vec::new();
1017 let mut total = 0;
1018 let mut passed = 0;
1019 let mut skipped = 0;
1020
1021 let known_failures: &[&str] = &[
1022 "lone-surrogate-string-values",
1023 "component-in-object-method-body.flow",
1024 "error.todo-hoist-type-alias-before-declaration",
1025 "error.todo-round2_severity_diff",
1026 "error.todo-update-expression-context-variable-via-type-annotation",
1027 ];
1028
1029 for entry in walkdir::WalkDir::new(&json_dir)
1030 .into_iter()
1031 .filter_map(|e| e.ok())
1032 .filter(|e| {
1033 e.path().extension().is_some_and(|ext| ext == "json")
1034 && !e.path().to_string_lossy().contains(".scope.")
1035 && !e.path().to_string_lossy().contains(".renamed.")
1036 })
1037 {
1038 let ast_path_str = entry.path().to_string_lossy().to_string();
1039 let scope_path_str = ast_path_str.replace(".json", ".scope.json");
1040 let renamed_path_str = ast_path_str.replace(".json", ".renamed.json");
1041 let scope_path = std::path::Path::new(&scope_path_str);
1042 let renamed_path = std::path::Path::new(&renamed_path_str);
1043
1044 if !scope_path.exists() || !renamed_path.exists() {
1045 skipped += 1;
1046 continue;
1047 }
1048
1049 let fixture_name = entry
1050 .path()
1051 .strip_prefix(&json_dir)
1052 .unwrap()
1053 .display()
1054 .to_string();
1055
1056 if known_failures.iter().any(|kf| fixture_name.contains(kf)) {
1057 continue;
1058 }
1059
1060 total += 1;
1061
1062 let ast_json = std::fs::read_to_string(entry.path()).unwrap();
1063 let scope_json = std::fs::read_to_string(scope_path).unwrap();
1064 let babel_renamed_json = std::fs::read_to_string(renamed_path).unwrap();
1065
1066 let scope_info: react_compiler_ast::scope::ScopeInfo =
1067 match serde_json::from_str(&scope_json) {
1068 Ok(info) => info,
1069 Err(e) => {
1070 failures.push((fixture_name, format!("Scope deserialization error: {e}")));
1071 continue;
1072 }
1073 };
1074
1075 // Deserialize into typed AST, rename using scope info, re-serialize
1076 let mut file: react_compiler_ast::File = match serde_json::from_str(&ast_json) {
1077 Ok(f) => f,
1078 Err(e) => {
1079 failures.push((fixture_name, format!("AST deserialization error: {e}")));
1080 continue;
1081 }
1082 };
1083 rename_identifiers(&mut file, &scope_info);
1084 let rust_renamed = serde_json::to_value(&file).unwrap();
1085
1086 let babel_renamed_value: serde_json::Value =
1087 serde_json::from_str(&babel_renamed_json).unwrap();
1088
1089 let rust_normalized = normalize_json(&rust_renamed);
1090 let babel_normalized = normalize_json(&babel_renamed_value);
1091
1092 if rust_normalized != babel_normalized {
1093 let rust_str = serde_json::to_string_pretty(&rust_normalized).unwrap();
1094 let babel_str = serde_json::to_string_pretty(&babel_normalized).unwrap();
1095 let diff = compute_diff(&babel_str, &rust_str);
1096 failures.push((fixture_name, format!("Rename mismatch:\n{diff}")));
1097 } else {
1098 passed += 1;
1099 }
1100 }
1101
1102 println!("\n{passed}/{total} fixtures passed scope resolution rename ({skipped} skipped)");
1103
1104 if !failures.is_empty() {
1105 let show_count = failures.len().min(5);
1106 let mut msg = format!(
1107 "\n{} of {total} fixtures failed scope resolution rename (showing first {show_count}):\n\n",
1108 failures.len()
1109 );
1110 for (name, err) in failures.iter().take(show_count) {
1111 msg.push_str(&format!("--- {name} ---\n{err}\n\n"));
1112 }
1113 if failures.len() > show_count {
1114 msg.push_str(&format!(
1115 "... and {} more failures\n",
1116 failures.len() - show_count
1117 ));
1118 }
1119 panic!("{msg}");
1120 }
1121 }