Skip to content

Instantly share code, notes, and snippets.

@zhiyuanzmj
Last active January 19, 2026 07:18
Show Gist options
  • Select an option

  • Save zhiyuanzmj/2eb38b5e02eaf1ae3c8109c7c3e4af06 to your computer and use it in GitHub Desktop.

Select an option

Save zhiyuanzmj/2eb38b5e02eaf1ae3c8109c7c3e4af06 to your computer and use it in GitHub Desktop.
fake_js.rs
[package]
name = "fake_js"
version = "0.1.0"
edition = "2024"
[dependencies]
oxc_span = "0.108.0"
oxc_ast = "0.108.0"
oxc_allocator = "0.108.0"
oxc_parser = "0.108.0"
oxc_ast_visit = "0.108.0"
napi-derive = "3.3.0"
napi = "3.3.0"
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
};
use napi::Either;
use napi_derive::napi;
use oxc_allocator::{Allocator, CloneIn, TakeIn};
use oxc_ast::{
AstBuilder, AstKind, Comment, NONE,
ast::{
BindingPattern, Declaration, ExportDefaultDeclarationKind, Expression, IdentifierName,
IdentifierReference, ImportDeclarationSpecifier, ImportOrExportKind, ModuleExportName,
Statement, TSImportEqualsDeclaration, TSImportType, TSImportTypeQualifier,
TSModuleDeclarationKind, TSModuleDeclarationName, TSModuleReference, TSType, TSTypeName,
TSTypeParameter, TSTypeParameterDeclaration, TSTypeQueryExprName,
},
};
use oxc_ast_visit::Visit;
use oxc_parser::Parser;
use oxc_span::{GetSpan, SPAN, SourceType, Span};
// A collection of type parameters grouped by parameter name
struct TypeParam<'a> {
pub name: String,
pub type_params: Vec<TSTypeParameter<'a>>,
}
type TypeParams<'a> = Vec<TypeParam<'a>>;
#[napi(object)]
pub struct TransformReturn {
pub code: String,
pub map: Option<String>,
}
#[napi]
pub struct FakeJsOptions {
pub sourcemap: bool,
pub cjs_default: bool,
pub side_effects: bool,
}
#[napi]
pub struct FakeJsPlugin<'a> {
code: String,
allocator: Allocator,
ast: AstBuilder<'a>,
comments_map: HashMap<String, Vec<Comment>>,
identifier_map: RefCell<HashMap<String, i32>>,
}
type Decl<'a> = Either<Option<Declaration<'a>>, ExportDefaultDeclarationKind<'a>>;
type NamespaceMap<'a> = HashMap<String, (Statement<'a>, Expression<'a>)>;
#[napi]
impl<'a> FakeJsPlugin<'a> {
#[napi(constructor)]
pub fn new() -> Self {
let allocator = Allocator::default();
let ast = AstBuilder::new(unsafe { &*(&allocator as *const Allocator) });
let comments_map = HashMap::new();
let identifier_map = RefCell::new(HashMap::new());
Self {
code: String::new(),
comments_map,
identifier_map,
allocator,
ast,
}
}
#[napi]
pub fn transform(&mut self, code: String, id: String) -> TransformReturn {
let ast = &self.ast;
let source_type = SourceType::from_path(&id).unwrap();
let mut program = Parser::new(&self.allocator, &code, source_type)
.parse()
.program;
let comments = program.comments;
let mut type_only_ids: Vec<&str> = vec![];
if !comments.is_empty() {
let directives = collect_reference_directives(&code, comments, false);
self.comments_map.insert(id.clone(), directives);
}
// let append_stmts = vec![];
let mut namespace_stmts: NamespaceMap = HashMap::new();
for (i, stmt) in program.body.iter_mut().enumerate() {
let stmt_ptr = stmt as *mut _;
if self.rewrite_import_export(unsafe { &mut *stmt_ptr }, &mut type_only_ids) {
continue;
}
let side_effect = if let Statement::TSModuleDeclaration(stmt) = stmt
&& let TSModuleDeclarationKind::Module = stmt.kind
{
true
} else {
false
};
if side_effect
&& id.ends_with(".vue.d.ts")
&& stmt.span().source_text(&code).contains("__VLS_")
{
continue;
}
let is_default_export = matches!(stmt, Statement::ExportDefaultDeclaration(_));
let (is_export_decl, mut decl): (
bool,
Decl,
// Box<dyn FnMut(Either<Declaration, ExportDefaultDeclarationKind>)>,
) = match stmt {
Statement::ExportNamedDeclaration(stmt) => {
// export let x
(
stmt.declaration.is_some(),
Either::A(stmt.declaration.clone_in(&self.allocator)),
// Box::new(|decl| {
// if let Either::A(decl) = decl {
// stmt.declaration = Some(decl);
// }
// }),
)
}
Statement::ExportDefaultDeclaration(stmt) => {
// export default function x() {}
(
true,
Either::B(stmt.declaration.clone_in(&self.allocator)),
// Box::new(|decl| {
// if let Either::B(decl) = decl {
// stmt.declaration = decl;
// }
// }),
)
}
_ => (
false,
Either::A(
stmt
.as_declaration_mut()
.map(|stmt| stmt.clone_in(&self.allocator)),
),
// Box::new(|decl| {
// if let Either::A(decl) = decl {
// *stmt = Statement::from(decl);
// }
// }),
),
};
if !match &decl {
Either::A(decl) => decl.is_some(),
Either::B(decl) => matches!(
decl,
ExportDefaultDeclarationKind::FunctionDeclaration(_)
| ExportDefaultDeclarationKind::ClassDeclaration(_)
| ExportDefaultDeclarationKind::TSInterfaceDeclaration(_)
),
} {
continue;
}
match &mut decl {
Either::A(Some(decl)) => match decl {
Declaration::TSEnumDeclaration(decl) => decl.declare = true,
Declaration::ClassDeclaration(decl) => decl.declare = true,
Declaration::FunctionDeclaration(decl) => decl.declare = true,
Declaration::TSModuleDeclaration(decl) => decl.declare = true,
Declaration::VariableDeclaration(decl) => decl.declare = true,
_ => (),
},
Either::B(decl) => match decl {
ExportDefaultDeclarationKind::FunctionDeclaration(decl) => decl.declare = true,
ExportDefaultDeclarationKind::ClassDeclaration(decl) => decl.declare = true,
_ => (),
},
_ => (),
}
let mut bindings = vec![];
let decl_ptr = &mut decl as *mut Decl;
if let Some(decl) = match unsafe { &mut *decl_ptr } {
Either::A(Some(decl)) => match decl {
Declaration::VariableDeclaration(decl) => Some(decl),
_ => None,
},
_ => None,
} {
bindings.extend(
decl
.declarations
.iter()
.filter_map(|decl| {
if let BindingPattern::BindingIdentifier(id) = &decl.id {
Some(id.as_ref().clone_in(&self.allocator))
} else {
None
}
})
.collect::<Vec<_>>(),
)
} else if let Some(binding) = match &mut decl {
Either::A(Some(decl)) => match decl {
Declaration::FunctionDeclaration(decl) => decl.id.as_mut(),
Declaration::ClassDeclaration(decl) => decl.id.as_mut(),
Declaration::TSTypeAliasDeclaration(decl) => Some(&mut decl.id),
Declaration::TSInterfaceDeclaration(decl) => Some(&mut decl.id),
Declaration::TSEnumDeclaration(decl) => Some(&mut decl.id),
Declaration::TSImportEqualsDeclaration(decl) => Some(&mut decl.id),
Declaration::TSModuleDeclaration(decl) => {
if let TSModuleDeclarationName::Identifier(ident) = &mut decl.id {
Some(ident)
} else {
None
}
}
Declaration::TSGlobalDeclaration(_) | Declaration::VariableDeclaration(_) => None,
},
Either::B(decl) => match decl {
ExportDefaultDeclarationKind::FunctionDeclaration(decl) => decl.id.as_mut(),
ExportDefaultDeclarationKind::ClassDeclaration(decl) => decl.id.as_mut(),
ExportDefaultDeclarationKind::TSInterfaceDeclaration(decl) => Some(&mut decl.id),
_ => None,
},
_ => None,
} {
if side_effect {
*binding = ast.binding_identifier(
SPAN,
ast.atom(&format!("_{}", self.get_identifier_index(""))),
);
}
bindings.push(binding.clone_in(&self.allocator));
} else {
// decl.id = ast.binding_identifier(SPAN, "export_default");
// bindings.push(decl.id);
}
let params = self.collect_params(&mut decl);
let mut children = vec![];
let mut deps = vec![];
self.collect_dependencies(
&mut decl,
unsafe { &mut *(&mut namespace_stmts as *mut _) },
unsafe { &mut *(&mut children as *mut _) },
unsafe { &mut *(&mut deps as *mut _) },
);
let children = children.into_iter().filter(|child| {
let child_span: Span = SPAN;
bindings.iter().all(|b| child_span != b.span)
});
// TODO
// if (decl !== stmt) {
// decl.leadingComments = stmt.leadingComments
// }
// let declaration_id = self.register_declaration();
}
TransformReturn {
code: String::new(),
map: None,
}
}
// fix:
// - import type { ... } from '...'
// - import { type ... } from '...'
// - export type { ... }
// - export { type ... }
// - export type * as x '...'
// - import Foo = require("./bar")
// - export = Foo
// - export default x
fn rewrite_import_export(
&'a self,
stmt: &'a mut Statement<'a>,
type_only_ids: &mut Vec<&'a str>,
) -> bool {
let ast = &self.ast;
if let Statement::ImportDeclaration(node) = stmt
&& let Some(specifiers) = node.specifiers.as_mut()
{
for specifier in specifiers {
if let ImportDeclarationSpecifier::ImportSpecifier(specifier) = specifier {
specifier.import_kind = ImportOrExportKind::Value;
}
}
node.import_kind = ImportOrExportKind::Value;
return true;
} else if let Statement::ExportNamedDeclaration(node) = stmt
&& node.declaration.is_none()
{
let node_export_kind_is_type = matches!(node.export_kind, ImportOrExportKind::Type);
for specifier in &mut node.specifiers {
if matches!(specifier.export_kind, ImportOrExportKind::Type) || node_export_kind_is_type {
type_only_ids.push(specifier.exported.span().source_text(&self.code))
}
specifier.export_kind = ImportOrExportKind::Value;
}
node.export_kind = ImportOrExportKind::Value;
return true;
} else if let Statement::ExportAllDeclaration(node) = stmt {
node.export_kind = ImportOrExportKind::Value;
return true;
} else if let Statement::TSImportEqualsDeclaration(node) = stmt {
let node_ptr = node.as_mut() as *mut TSImportEqualsDeclaration;
if let TSModuleReference::ExternalModuleReference(module_reference) =
&mut node.module_reference
{
*stmt = Statement::ImportDeclaration(ast.alloc_import_declaration(
SPAN,
Some(
ast.vec1(ast.import_declaration_specifier_import_default_specifier(
SPAN,
unsafe { &mut *node_ptr }.id.take_in(&self.allocator),
)),
),
module_reference.expression.take_in(&self.allocator),
None,
NONE,
ImportOrExportKind::Value,
));
}
return true;
} else if let Statement::TSExportAssignment(node) = stmt
&& let Expression::Identifier(expression) = &mut node.expression
{
*stmt = Statement::ExportNamedDeclaration(ast.alloc_export_named_declaration(
SPAN,
None,
ast.vec1(ast.export_specifier(
SPAN,
ast.module_export_name_identifier_reference(expression.span, expression.name),
ast.module_export_name_identifier_reference(SPAN, "default"),
ImportOrExportKind::Value,
)),
None,
ImportOrExportKind::Value,
NONE,
));
return true;
} else if let Statement::ExportDefaultDeclaration(node) = stmt
&& let ExportDefaultDeclarationKind::Identifier(declaration) = &node.declaration
{
*stmt = Statement::ExportNamedDeclaration(ast.alloc_export_named_declaration(
SPAN,
None,
ast.vec1(ast.export_specifier(
SPAN,
ast.module_export_name_identifier_reference(declaration.span, declaration.name),
ast.module_export_name_identifier_reference(SPAN, "default"),
ImportOrExportKind::Value,
)),
None,
ImportOrExportKind::Value,
NONE,
));
return true;
}
false
}
fn get_identifier_index(&self, name: &str) -> i32 {
let identifier_map = &mut self.identifier_map.borrow_mut();
if let Some(identifier) = identifier_map.get_mut(name) {
*identifier += 1;
*identifier
} else {
identifier_map.insert(name.to_owned(), 0);
0
}
}
fn register_declaration() {}
// Collects all TSTypeParameter nodes from the given node and groups them by
// their name. One name can associate with one or more type parameters. These
// names will be used as the parameter name in the generated JavaScript
// dependency function.
fn collect_params(&'a self, node: &mut Decl<'a>) -> TypeParams<'a> {
let mut type_params_visiter = TypeParamsVisitor {
allocator: &self.allocator,
type_params: vec![],
};
match node {
Either::A(Some(node)) => {
type_params_visiter.visit_declaration(node);
}
Either::B(node) => type_params_visiter.visit_export_default_declaration_kind(node),
_ => (),
}
let mut param_map: HashMap<String, Vec<TSTypeParameter>> = HashMap::new();
for type_param in type_params_visiter.type_params.drain(..) {
let name = type_param.name.name.to_string();
if let Some(group) = param_map.get_mut(&name) {
group.push(type_param);
} else {
param_map.insert(name, vec![type_param]);
}
}
param_map
.into_iter()
.map(|(name, type_params)| TypeParam { name, type_params })
.collect::<Vec<_>>()
}
fn collect_dependencies(
&'a self,
node: &mut Decl<'a>,
namespace_stmts: &'a mut NamespaceMap<'a>,
children: &'a mut Vec<AstKind<'a>>,
deps: &'a mut Vec<Expression<'a>>,
) {
let ast = &self.ast;
let mut seen = HashSet::new();
let mut inferred_stack: Vec<Vec<String>> = vec![];
let inferred_stack_ptr = &mut inferred_stack as *mut Vec<Vec<String>>;
let mut current_inferred: HashSet<String> = HashSet::new();
fn is_dependency<'a>(
node: Either<&IdentifierReference<'a>, &Expression<'a>>,
current_inferred: &HashSet<String>,
) -> bool {
!match node {
Either::A(node) => node.name.eq("this") || current_inferred.contains(node.name.as_str()),
Either::B(node) => match &node {
Expression::Identifier(node) => node.name.eq("this"),
Expression::StaticMemberExpression(node) => {
is_dependency(Either::B(&node.object), current_inferred)
}
_ => false,
},
}
}
let mut walker = WalkAST::new(
Some(Box::new(move |node, _| {
if let AstKind::TSConditionalType(node) = node {
let inferred = collect_inferred_names(&node.extends_type);
unsafe { &mut *inferred_stack_ptr }.push(inferred);
}
})),
Some(Box::new(move |node, parent| {
// hanlde infer scope
if let AstKind::TSConditionalType(node) = node {
inferred_stack.pop();
} else if let Some(parent) = parent
&& let AstKind::TSConditionalType(parent) = parent
{
let true_branch = parent.true_type.span() == node.span();
current_inferred = if true_branch {
inferred_stack
.iter()
.flatten()
.cloned()
.collect::<HashSet<_>>()
} else {
let inferred_stack_len = inferred_stack.len() - 1;
inferred_stack[..inferred_stack_len]
.into_iter()
.flatten()
.cloned()
.collect::<HashSet<_>>()
};
} else {
current_inferred = HashSet::new();
}
if let AstKind::ExportNamedDeclaration(node) = node {
for specifier in node.specifiers.iter() {
if let ModuleExportName::IdentifierReference(local) = &specifier.local {
if is_dependency(Either::A(local), &current_inferred) {
deps.push(ast.expression_identifier(local.span, local.name));
}
}
}
} else if let AstKind::TSInterfaceDeclaration(node) = node {
for heritage in node.extends.iter() {
if is_dependency(Either::B(&heritage.expression), &current_inferred) {
deps.push(heritage.expression.clone_in(&self.allocator));
}
}
} else if let AstKind::Class(node) = node {
if let Some(super_class) = &node.super_class {
if is_dependency(Either::B(&super_class), &current_inferred) {
deps.push(super_class.clone_in(&self.allocator));
}
}
for implement in node.implements.iter() {
let expression = ts_type_name_to_expression(&implement.expression, ast);
if is_dependency(Either::B(&expression), &current_inferred) {
deps.push(expression)
}
}
} else if let AstKind::ObjectProperty(node) = node {
if node.computed {
if let Some(key) = node.key.as_expression() {
if is_dependency(Either::B(key), &current_inferred) {
deps.push(key.clone_in(ast.allocator));
}
}
}
if is_dependency(Either::B(&node.value), &current_inferred) {
deps.push(node.value.clone_in(ast.allocator));
}
} else if let AstKind::TSPropertySignature(node) = node {
if node.computed {
if let Some(key) = node.key.as_expression() {
if is_dependency(Either::B(key), &current_inferred) {
deps.push(key.clone_in(ast.allocator));
}
}
}
} else if let AstKind::MethodDefinition(node) = node {
if node.computed {
if let Some(key) = node.key.as_expression() {
if is_dependency(Either::B(key), &current_inferred) {
deps.push(key.clone_in(ast.allocator));
}
}
}
} else if let AstKind::TSTypeReference(node) = node {
let expression = ts_type_name_to_expression(&node.type_name, ast);
if is_dependency(Either::B(&expression), &current_inferred) {
deps.push(expression)
}
} else if let AstKind::TSTypeQuery(node) = node
&& seen.contains(&node.expr_name.span())
&& !matches!(node.expr_name, TSTypeQueryExprName::TSImportType(_))
{
let expression = ts_type_name_to_expression(&node.expr_name.to_ts_type_name(), ast);
if is_dependency(Either::B(&expression), &current_inferred) {
deps.push(expression)
}
} else if let AstKind::TSImportType(node) = node {
seen.insert(node.span);
let dep = self.import_namespace(node, namespace_stmts);
}
if let Some(parent) = parent
&& !deps.iter().any(|dep| dep.span().eq(&node.span()))
&& is_child_symbol(&node, &parent)
{
children.push(node);
}
})),
);
match node {
Either::A(Some(node)) => walker.visit_declaration(node),
Either::B(node) => walker.visit_export_default_declaration_kind(node),
_ => (),
}
}
fn import_namespace(&'a self, node: &TSImportType, namespace_stmts: &mut NamespaceMap<'a>) {
let ast = &self.ast;
let imported = node.qualifier.as_ref();
let source = &node.source;
let source_text = source
.value
.chars()
.map(|c| {
if c.is_alphanumeric() || c == '_' || c == '$' {
c
} else {
'_'
}
})
.collect::<String>();
let local_span = source.span;
let local_name = ast.atom(&format!(
"{}{}",
source_text,
self.get_identifier_index(&source_text)
));
let mut local = ast.expression_identifier(local_span, local_name);
if namespace_stmts.contains_key(source.value.as_str()) {
local = namespace_stmts
.get(source.value.as_str())
.unwrap()
.1
.clone_in(&self.allocator);
} else {
// prepend: import * as ${local} from ${source}
namespace_stmts.insert(
source.value.to_string(),
(
Statement::ImportDeclaration(ast.alloc_import_declaration(
SPAN,
Some(
ast.vec1(ast.import_declaration_specifier_import_namespace_specifier(
local_span,
ast.binding_identifier(local_span, local_name),
)),
),
node.source.clone_in(ast.allocator),
None,
NONE,
ImportOrExportKind::Type,
)),
local,
),
);
}
if let Some(imported) = imported {
let imported_left = get_id_from_ts_type_name(imported);
// TODO
// *imported_left = ast.ts_qualified_name(SPAN, local, imported_left);
}
}
}
fn is_child_symbol(node: &AstKind, parent: &AstKind) -> bool {
if matches!(node, AstKind::IdentifierReference(_)) {
return true;
}
if match parent {
AstKind::TSPropertySignature(parent) => parent.key.span() == node.span(),
AstKind::TSMethodSignature(parent) => parent.key.span() == node.span(),
_ => false,
} {
return true;
}
false
}
fn ts_type_name_to_expression<'a>(node: &TSTypeName<'a>, ast: &AstBuilder<'a>) -> Expression<'a> {
match node {
TSTypeName::IdentifierReference(node) => ast.expression_identifier(node.span, node.name),
TSTypeName::QualifiedName(node) => {
let left = ts_type_name_to_expression(&node.left, ast);
Expression::StaticMemberExpression(ast.alloc_static_member_expression(
node.span,
left,
node.right.clone_in(ast.allocator),
false,
))
}
TSTypeName::ThisExpression(node) => ast.expression_this(node.span),
}
}
fn get_id_from_ts_type_name<'a>(node: &'a TSImportTypeQualifier) -> &'a IdentifierName<'a> {
match node {
TSImportTypeQualifier::Identifier(node) => node.as_ref(),
TSImportTypeQualifier::QualifiedName(node) => get_id_from_ts_type_name(&node.left),
}
}
type OnNode<'a> = Option<Box<dyn FnMut(AstKind<'a>, Option<AstKind<'a>>) + 'a>>;
struct WalkAST<'a> {
parent_stack: Vec<AstKind<'a>>,
pub on_enter_node: OnNode<'a>,
pub on_leave_node: OnNode<'a>,
}
impl<'a> WalkAST<'a> {
pub fn new(on_enter_node: OnNode<'a>, on_leave_node: OnNode<'a>) -> Self {
Self {
parent_stack: vec![],
on_enter_node,
on_leave_node,
}
}
}
impl<'a> Visit<'a> for WalkAST<'a> {
fn enter_node(&mut self, node: AstKind<'a>) {
let parent = self.parent_stack.last().copied();
if let Some(on_enter_node) = self.on_enter_node.as_mut() {
on_enter_node(node, parent);
}
self.parent_stack.push(node);
}
fn leave_node(&mut self, node: AstKind<'a>) {
self.parent_stack.pop();
let parent = self.parent_stack.last().copied();
if let Some(on_leave_node) = self.on_leave_node.as_mut() {
on_leave_node(node, parent);
}
}
}
struct TypeParamsVisitor<'a> {
pub allocator: &'a Allocator,
pub type_params: Vec<TSTypeParameter<'a>>,
}
impl<'a> Visit<'a> for TypeParamsVisitor<'a> {
fn visit_ts_type_parameter_declaration(&mut self, node: &TSTypeParameterDeclaration<'a>) {
let p = node
.params
.iter()
.map(|p| p.clone_in(self.allocator))
.collect::<Vec<_>>();
self.type_params.extend(p);
}
}
fn collect_inferred_names<'a>(node: &'a TSType) -> Vec<String> {
let mut inferred = vec![];
let inferred_mut = &mut inferred;
WalkAST::new(
Some(Box::new(|node, _| {
if let AstKind::TSInferType(node) = node {
inferred_mut.push(node.type_parameter.name.name.to_string());
}
})),
None,
)
.visit_ts_type(node);
inferred
}
fn collect_reference_directives(
source_text: &str,
comments: oxc_allocator::Vec<Comment>,
negative: bool,
) -> Vec<Comment> {
comments
.into_iter()
.filter(|c| c.span.source_text(source_text).contains("<reference ") != negative)
.collect()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment