Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
.unwrap()
1use std::path::PathBuf;23use react_compiler_ast::declarations::*;4use react_compiler_ast::expressions::*;5use react_compiler_ast::jsx::*;6use react_compiler_ast::patterns::*;7use react_compiler_ast::scope::ScopeInfo;8use react_compiler_ast::statements::*;910fn 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}1617/// Recursively sort all keys in a JSON value for order-independent comparison.18fn 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)> = map22 .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}4546fn 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 output67}6869#[test]70fn 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;7677 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);8990 if !scope_path.exists() {91 skipped += 1;92 continue;93 }9495 let fixture_name = entry96 .path()97 .strip_prefix(&json_dir)98 .unwrap()99 .display()100 .to_string();101 total += 1;102103 let scope_json = std::fs::read_to_string(scope_path).unwrap();104105 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 };113114 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();117118 let original_normalized = normalize_json(&original_value);119 let round_tripped_normalized = normalize_json(&round_tripped_value);120121 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 }128129 let mut consistency_error = None;130131 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 }142143 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 }173174 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 }186187 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 }199200 // Validate node-ID maps201 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 }213214 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 }226227 if let Some(err) = consistency_error {228 failures.push((fixture_name, format!("Consistency error: {err}")));229 continue;230 }231232 passed += 1;233 }234235 println!(236 "\n{passed}/{total} fixtures passed scope info round-trip ({skipped} skipped - no scope.json)"237 );238239 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_count252 ));253 }254 panic!("{msg}");255 }256}257258// ============================================================================259// Typed AST traversal for identifier renaming260// ============================================================================261262/// 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.264fn 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}276277/// Fallback walker for serde_json::Value fields (class bodies, type annotations, decorators, etc.)278fn 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) = map286 .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}313314fn 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}319320fn 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}325326fn 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}331332fn rename_identifiers(file: &mut react_compiler_ast::File, si: &ScopeInfo) {333 visit_program(&mut file.program, si);334}335336fn visit_program(prog: &mut react_compiler_ast::Program, si: &ScopeInfo) {337 for stmt in &mut prog.body {338 visit_stmt(stmt, si);339 }340}341342fn visit_block(block: &mut BlockStatement, si: &ScopeInfo) {343 for stmt in &mut block.body {344 visit_stmt(stmt, si);345 }346}347348fn 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}549550fn 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}746747fn 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}807808fn 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}814815fn 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}823824fn 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}836837fn 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}854855fn 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}867868fn 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}885886fn 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 opaque891 ExportDefaultDecl::Expression(e) => visit_expr(e, si),892 }893}894895fn 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}956957fn 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}963964fn 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}991992fn 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}10051006fn visit_jsx_expr(expr: &mut JSXExpressionContainerExpr, si: &ScopeInfo) {1007 match expr {1008 JSXExpressionContainerExpr::Expression(e) => visit_expr(e, si),1009 JSXExpressionContainerExpr::JSXEmptyExpression(_) => {}1010 }1011}10121013#[test]1014fn 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;10201021 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 ];10281029 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);10431044 if !scope_path.exists() || !renamed_path.exists() {1045 skipped += 1;1046 continue;1047 }10481049 let fixture_name = entry1050 .path()1051 .strip_prefix(&json_dir)1052 .unwrap()1053 .display()1054 .to_string();10551056 if known_failures.iter().any(|kf| fixture_name.contains(kf)) {1057 continue;1058 }10591060 total += 1;10611062 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();10651066 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 };10741075 // Deserialize into typed AST, rename using scope info, re-serialize1076 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();10851086 let babel_renamed_value: serde_json::Value =1087 serde_json::from_str(&babel_renamed_json).unwrap();10881089 let rust_normalized = normalize_json(&rust_renamed);1090 let babel_normalized = normalize_json(&babel_renamed_value);10911092 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 }11011102 println!("\n{passed}/{total} fixtures passed scope resolution rename ({skipped} skipped)");11031104 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_count1117 ));1118 }1119 panic!("{msg}");1120 }1121}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.