Last active
January 19, 2026 07:18
-
-
Save zhiyuanzmj/2eb38b5e02eaf1ae3c8109c7c3e4af06 to your computer and use it in GitHub Desktop.
fake_js.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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), ¤t_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), ¤t_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), ¤t_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), ¤t_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), ¤t_inferred) { | |
| deps.push(key.clone_in(ast.allocator)); | |
| } | |
| } | |
| } | |
| if is_dependency(Either::B(&node.value), ¤t_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), ¤t_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), ¤t_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), ¤t_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), ¤t_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