compiler/crates/react_compiler_ast/tests/scope_resolution.rs RUST 1,122 lines View on github.com → Search inside
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}

Code quality findings 30

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.
warning correctness unwrap-usage
.unwrap()
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.
warning correctness unwrap-usage
let scope_json = std::fs::read_to_string(scope_path).unwrap();
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.
warning correctness unwrap-usage
let round_tripped = serde_json::to_string_pretty(&scope_info).unwrap();
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.
warning correctness unwrap-usage
let original_value: serde_json::Value = serde_json::from_str(&scope_json).unwrap();
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.
warning correctness unwrap-usage
let round_tripped_value: serde_json::Value = serde_json::from_str(&round_tripped).unwrap();
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.
warning correctness unwrap-usage
let orig_str = serde_json::to_string_pretty(&original_normalized).unwrap();
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.
warning correctness unwrap-usage
let rt_str = serde_json::to_string_pretty(&round_tripped_normalized).unwrap();
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let scope = si.bindings[bid.0 as usize].scope.0;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let scope = si.bindings[bid.0 as usize].scope.0;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
fn visit_json_vec(vals: &mut [react_compiler_ast::common::RawNode], si: &ScopeInfo) {
Warning: '.expect()' will panic with a custom message on None/Err. While better than unwrap() for debugging, prefer non-panicking error handling in production code (match, if let, ?).
warning correctness expect-usage
.expect("identifier rename preserves the node `type`");
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.
warning correctness unwrap-usage
.unwrap()
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.
warning correctness unwrap-usage
let ast_json = std::fs::read_to_string(entry.path()).unwrap();
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.
warning correctness unwrap-usage
let scope_json = std::fs::read_to_string(scope_path).unwrap();
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.
warning correctness unwrap-usage
let babel_renamed_json = std::fs::read_to_string(renamed_path).unwrap();
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.
warning correctness unwrap-usage
let rust_renamed = serde_json::to_value(&file).unwrap();
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.
warning correctness unwrap-usage
serde_json::from_str(&babel_renamed_json).unwrap();
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.
warning correctness unwrap-usage
let rust_str = serde_json::to_string_pretty(&rust_normalized).unwrap();
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.
warning correctness unwrap-usage
let babel_str = serde_json::to_string_pretty(&babel_normalized).unwrap();
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use react_compiler_ast::declarations::*;
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use react_compiler_ast::expressions::*;
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use react_compiler_ast::jsx::*;
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use react_compiler_ast::patterns::*;
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use react_compiler_ast::statements::*;
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info performance to-string-in-loop
let ast_path_str = entry.path().to_string_lossy().to_string();
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
failures.push((fixture_name, format!("Round-trip mismatch:\n{diff}")));
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
println!(
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info performance to-string-in-loop
"name".to_string(),
Performance Info: Calling .to_string() (especially on &str) allocates a new String. If done repeatedly in loops, consider alternatives like working with &str or using crates like `itoa`/`ryu` for number-to-string conversion.
info performance to-string-in-loop
let ast_path_str = entry.path().to_string_lossy().to_string();
Info: Direct printing to stdout/stderr. For application logging, prefer using a logging facade like `log` or `tracing` for better control over levels, formatting, and output destinations.
info maintainability println-macro
println!("\n{passed}/{total} fixtures passed scope resolution rename ({skipped} skipped)");

Security findings 1

Security Info: Reading environment variables. Treat them as untrusted input; validate or sanitize values before use, especially if controlling paths, commands, or network addresses.
security env-var-untrusted
if let Ok(dir) = std::env::var("FIXTURE_JSON_DIR") {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.