swc-project/swc · error

illegal conversion: Cannot convert {:?} to ModuleDeclaration

Error message

illegal conversion: Cannot convert {:?} to ModuleDeclaration

What it means

After babelifying a ModuleDecl, the result is a ModuleDeclOutput enum with seven variants, but From<ModuleDeclOutput> for ModuleDeclaration only converts the four plain-ESM ones (Import, ExportDefault, ExportNamed, ExportAll). The three TypeScript-specific variants — TsImportEquals (`import x = require(...)`), TsExportAssignment (`export = ...`), and TsNamespaceExport (`export namespace ...`) — have no plain Babel ModuleDeclaration form, so converting them panics with 'illegal conversion'. This fires in Module::babelify where each module-level item is turned into a Babel node via `.into()`.

Source

Thrown at crates/swc_estree_compat/src/babelify/module_decl.rs:65

                ModuleDeclOutput::TsExportAssignment(a.babelify(ctx))
            }
            ModuleDecl::TsNamespaceExport(e) => {
                ModuleDeclOutput::TsNamespaceExport(e.babelify(ctx))
            }
            #[cfg(swc_ast_unknown)]
            _ => panic!("unable to access unknown nodes"),
        }
    }
}

impl From<ModuleDeclOutput> for ModuleDeclaration {
    fn from(module: ModuleDeclOutput) -> Self {
        match module {
            ModuleDeclOutput::Import(i) => ModuleDeclaration::Import(i),
            ModuleDeclOutput::ExportDefault(e) => ModuleDeclaration::ExportDefault(e),
            ModuleDeclOutput::ExportNamed(n) => ModuleDeclaration::ExportNamed(n),
            ModuleDeclOutput::ExportAll(a) => ModuleDeclaration::ExportAll(a),
            _ => panic!(
                "illegal conversion: Cannot convert {:?} to ModuleDeclaration",
                &module
            ),
        }
    }
}

impl Babelify for ExportDefaultExpr {
    type Output = ExportDefaultDeclaration;

    fn babelify(self, ctx: &Context) -> Self::Output {
        ExportDefaultDeclaration {
            base: ctx.base(self.span),
            declaration: ExportDefaultDeclType::Expr(
                Box::alloc().init(self.expr.babelify(ctx).into()),
            ),
        }
    }

View on GitHub (pinned to 5176682b65)

Solutions

  1. Run swc's TypeScript strip pass (swc_ecma_transforms_typescript strip / @swc/core with typescript syntax stripping) before babelify so TS module decls become plain ESM/CJS
  2. Reject or skip TS inputs that use import-equals/export-assignment before conversion
  3. If you need TS output, babelify per-item and handle ModuleDeclOutput::Ts* variants yourself instead of using the ModuleDeclaration From impl
  4. As a stopgap, wrap the conversion in catch_unwind and report the offending file

Example fix

// before: TS module babelified directly
let program = parse_ts_as_module(fm)?;
let babel_ast = program.babelify(&ctx); // panics on `import x = require(...)`

// after: strip TypeScript first
let program = strip_types(parse_ts_as_module(fm)?)?;
let babel_ast = program.babelify(&ctx);
Defensive patterns

Strategy: validation

Validate before calling

fn module_babelifiable(module: &Module) -> Result<(), String> {
    for item in &module.body {
        if let ModuleItem::ModuleDecl(d) = item {
            let ts = matches!(
                d,
                ModuleDecl::TsImportEquals(_) | ModuleDecl::TsExportAssignment(_) | ModuleDecl::TsNamespaceExport(_)
            );
            if ts {
                return Err("module uses TS import-equals/export-assignment; strip types first".into());
            }
        }
    }
    Ok(())
}

Type guard

fn module_decl_is_plain_esm(d: &ModuleDecl) -> bool {
    !matches!(d, ModuleDecl::TsImportEquals(_) | ModuleDecl::TsExportAssignment(_) | ModuleDecl::TsNamespaceExport(_))
}

Try / catch

let out = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| program.babelify(&ctx)))
    .map_err(|_| anyhow::anyhow!("module contains TypeScript module declarations (import = / export = / export namespace)"))?;

Prevention

When it happens

Trigger: Babelifying a Program::Module parsed from TypeScript that still contains `import lib = require('lib')`, `export = value`, or `export namespace NS { ... }` at module top level — e.g. parsing a .ts file as a module and calling babelify without stripping types first.

Common situations: Tooling that parses TS with swc and requests the Babel AST flavor (acorn/babel output, AST viewers, codemod pipelines) without running swc's TypeScript stripper; CommonJS-style TS (export =) files fed straight into the estree conversion.

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/3294938ce26ef51c. Report an issue: GitHub.