use crate::LoweredTarget;
use crate::TsSynError;
use crate::abi::*;
use ::oxc::ast::ast::*;
use oxc::span::GetSpan;
use std::collections::HashSet;
fn oxc_span_ir(span: oxc::span::Span) -> SpanIR {
SpanIR::new(span.start + 1, span.end + 1)
}
fn collect_leading_decorators_oxc(
source: &str,
target_start_0: usize,
valid_annotations: Option<&HashSet<String>>,
) -> Vec<DecoratorIR> {
use crate::jsdoc::parse_all_macro_directives;
if target_start_0 == 0 || target_start_0 > source.len() {
return Vec::new();
}
let search_area = &source[..target_start_0];
let Some(end_idx) = search_area.rfind("*/") else {
return Vec::new();
};
let Some(start_idx) = search_area[..end_idx].rfind("/**") else {
return Vec::new();
};
let end_of_comment_block = end_idx + 2;
let between = &search_area[end_of_comment_block..];
let between_trimmed = between.trim();
if !between_trimmed.is_empty() {
let allowed_modifiers = ["export", "declare", "abstract", "default", "async"];
let remaining: String = between_trimmed
.split_whitespace()
.filter(|word| !allowed_modifiers.contains(word))
.collect::<Vec<_>>()
.join(" ");
if !remaining.is_empty() {
return Vec::new();
}
}
let mut all_directives = Vec::new();
let mut current_start = start_idx;
let mut current_end = end_idx;
loop {
let comment_body = &search_area[current_start + 3..current_end];
let body_lower = comment_body.to_ascii_lowercase();
let is_macro_import = body_lower.contains("import") && body_lower.contains("macro");
let directives = if is_macro_import {
Vec::new()
} else {
parse_all_macro_directives(comment_body, valid_annotations)
};
for (name, args_src) in directives {
all_directives.push(DecoratorIR {
name,
args_src,
span: SpanIR::new(current_start as u32 + 1, (current_end + 2) as u32 + 1),
#[cfg(feature = "swc")]
node: None,
});
}
let before_comment = &search_area[..current_start];
let before_trimmed = before_comment.trim_end();
if !before_trimmed.ends_with("*/") {
break;
}
let prev_end = before_trimmed.len() - 2;
let Some(prev_start) = before_trimmed[..prev_end].rfind("/**") else {
break;
};
current_start = prev_start;
current_end = prev_end;
}
all_directives
}
pub fn lower_classes_oxc(
program: &Program<'_>,
source: &str,
filter: Option<&HashSet<String>>,
) -> Result<Vec<ClassIR>, TsSynError> {
let mut classes = Vec::new();
for stmt in &program.body {
match stmt {
Statement::ClassDeclaration(decl) => {
if let Some(class_ir) = lower_class(decl, source, filter) {
classes.push(class_ir);
}
}
Statement::ExportNamedDeclaration(decl) => {
if let Some(Declaration::ClassDeclaration(class_decl)) = &decl.declaration
&& let Some(class_ir) = lower_class(class_decl, source, filter)
{
classes.push(class_ir);
}
}
Statement::ExportDefaultDeclaration(decl) => {
if let ExportDefaultDeclarationKind::ClassDeclaration(class_decl) =
&decl.declaration
&& let Some(class_ir) = lower_class(class_decl, source, filter)
{
classes.push(class_ir);
}
}
_ => {}
}
}
Ok(classes)
}
fn lower_class(
decl: &Class<'_>,
source: &str,
filter: Option<&HashSet<String>>,
) -> Option<ClassIR> {
let name = decl.id.as_ref()?.name.to_string();
let span = oxc_span_ir(decl.span);
let body_span = oxc_span_ir(decl.body.span);
let type_params = decl
.type_parameters
.as_ref()
.map(|tp| tp.params.iter().map(|p| p.name.to_string()).collect())
.unwrap_or_default();
let mut heritage = Vec::new();
if let Some(super_class) = &decl.super_class
&& let Expression::Identifier(ident) = super_class
{
heritage.push(ident.name.to_string());
}
for item in &decl.implements {
if let TSTypeName::IdentifierReference(ident) = &item.expression {
heritage.push(ident.name.to_string());
}
}
let mut decorators = Vec::new();
for dec in &decl.decorators {
if let Expression::CallExpression(call) = &dec.expression {
if let Expression::Identifier(ident) = &call.callee {
decorators.push(DecoratorIR {
name: ident.name.to_string(),
args_src: source[call.span.start as usize..call.span.end as usize].to_string(),
span: oxc_span_ir(dec.span),
#[cfg(feature = "swc")]
node: None,
});
}
} else if let Expression::Identifier(ident) = &dec.expression {
decorators.push(DecoratorIR {
name: ident.name.to_string(),
args_src: String::new(),
span: oxc_span_ir(dec.span),
#[cfg(feature = "swc")]
node: None,
});
}
}
decorators.extend(collect_leading_decorators_oxc(
source,
decl.span.start as usize,
filter,
));
let mut fields = Vec::new();
let mut methods = Vec::new();
for element in &decl.body.body {
match element {
ClassElement::PropertyDefinition(prop) => {
if let PropertyKey::StaticIdentifier(ident) = &prop.key {
let field_decorators =
collect_leading_decorators_oxc(source, prop.span.start as usize, filter);
fields.push(FieldIR {
name: ident.name.to_string(),
span: oxc_span_ir(prop.span),
ts_type: prop
.type_annotation
.as_ref()
.map(|ann| {
let sp = ann.type_annotation.span();
source[sp.start as usize..sp.end as usize].to_string()
})
.unwrap_or_else(|| "any".to_string()),
#[cfg(feature = "swc")]
type_ann: None,
optional: prop.optional,
readonly: prop.readonly,
visibility: match prop.accessibility {
Some(TSAccessibility::Private) => Visibility::Private,
Some(TSAccessibility::Protected) => Visibility::Protected,
_ => Visibility::Public,
},
decorators: field_decorators,
#[cfg(feature = "swc")]
prop_ast: None,
});
}
}
ClassElement::MethodDefinition(method) => {
if let PropertyKey::StaticIdentifier(ident) = &method.key {
let func = &method.value;
let type_params_src = func
.type_parameters
.as_ref()
.map(|tp| source[tp.span.start as usize..tp.span.end as usize].to_string())
.unwrap_or_default();
let params_src = {
let sp = func.params.span;
let raw = &source[sp.start as usize..sp.end as usize];
raw.strip_prefix('(')
.and_then(|s| s.strip_suffix(')'))
.unwrap_or(raw)
.trim()
.to_string()
};
let return_type_src = func
.return_type
.as_ref()
.map(|ann| {
let sp = ann.type_annotation.span();
source[sp.start as usize..sp.end as usize].to_string()
})
.unwrap_or_default();
let method_decorators =
collect_leading_decorators_oxc(source, method.span.start as usize, filter);
let (method_body_span, method_body_src) = if let Some(body) = &func.body {
let bspan = oxc_span_ir(body.span);
let inner_start = body.span.start as usize + 1;
let inner_end = body.span.end as usize - 1;
let src = source.get(inner_start..inner_end).unwrap_or("").to_string();
(Some(bspan), Some(src))
} else {
(None, None)
};
methods.push(MethodSigIR {
name: ident.name.to_string(),
span: oxc_span_ir(method.span),
type_params_src,
params_src,
return_type_src,
is_static: method.r#static,
is_async: method.value.r#async,
visibility: match method.accessibility {
Some(TSAccessibility::Private) => Visibility::Private,
Some(TSAccessibility::Protected) => Visibility::Protected,
_ => Visibility::Public,
},
decorators: method_decorators,
body_span: method_body_span,
body_src: method_body_src,
#[cfg(feature = "swc")]
member_ast: None,
});
}
}
_ => {}
}
}
Some(ClassIR {
name,
span,
body_span,
is_abstract: decl.r#abstract,
type_params,
heritage,
decorators,
#[cfg(feature = "swc")]
decorators_ast: Vec::new(),
fields,
methods,
#[cfg(feature = "swc")]
members: Vec::new(),
})
}
pub fn lower_interfaces_oxc(
program: &Program<'_>,
source: &str,
filter: Option<&HashSet<String>>,
) -> Result<Vec<InterfaceIR>, TsSynError> {
let mut interfaces = Vec::new();
for stmt in &program.body {
let decl = match stmt {
Statement::TSInterfaceDeclaration(d) => Some(d.as_ref()),
Statement::ExportNamedDeclaration(d) => match &d.declaration {
Some(Declaration::TSInterfaceDeclaration(d)) => Some(d.as_ref()),
_ => None,
},
_ => None,
};
if let Some(d) = decl {
interfaces.push(lower_interface(d, source, filter));
}
}
Ok(interfaces)
}
fn lower_interface(
decl: &TSInterfaceDeclaration<'_>,
source: &str,
filter: Option<&HashSet<String>>,
) -> InterfaceIR {
let name = decl.id.name.to_string();
let span = oxc_span_ir(decl.span);
let body_span = oxc_span_ir(decl.body.span);
let type_params = decl
.type_parameters
.as_ref()
.map(|tp| tp.params.iter().map(|p| p.name.to_string()).collect())
.unwrap_or_default();
let mut heritage = Vec::new();
for ext in &decl.extends {
if let Expression::Identifier(ident) = &ext.expression {
heritage.push(ident.name.to_string());
}
}
let (fields, methods) = lower_interface_members(&decl.body.body, source, filter);
let decorators = collect_leading_decorators_oxc(source, decl.span.start as usize, filter);
InterfaceIR {
name,
span,
body_span,
type_params,
heritage,
decorators,
fields,
methods,
}
}
fn lower_interface_members(
body: &[TSSignature<'_>],
source: &str,
filter: Option<&HashSet<String>>,
) -> (Vec<InterfaceFieldIR>, Vec<InterfaceMethodIR>) {
let mut fields = Vec::new();
let mut methods = Vec::new();
for elem in body {
match elem {
TSSignature::TSPropertySignature(prop) => {
if let PropertyKey::StaticIdentifier(ident) = &prop.key {
let ts_type = prop
.type_annotation
.as_ref()
.map(|ann| {
let sp = ann.type_annotation.span();
source[sp.start as usize..sp.end as usize].to_string()
})
.unwrap_or_else(|| "any".to_string());
let field_decorators =
collect_leading_decorators_oxc(source, prop.span.start as usize, filter);
fields.push(InterfaceFieldIR {
name: ident.name.to_string(),
span: oxc_span_ir(prop.span),
ts_type,
optional: prop.optional,
readonly: prop.readonly,
decorators: field_decorators,
});
}
}
TSSignature::TSMethodSignature(meth) => {
if let PropertyKey::StaticIdentifier(ident) = &meth.key {
let params_src = {
let sp = meth.params.span;
let raw = &source[sp.start as usize..sp.end as usize];
raw.strip_prefix('(')
.and_then(|s| s.strip_suffix(')'))
.unwrap_or(raw)
.trim()
.to_string()
};
let type_params_src = meth
.type_parameters
.as_ref()
.map(|tp| source[tp.span.start as usize..tp.span.end as usize].to_string())
.unwrap_or_default();
let return_type_src = meth
.return_type
.as_ref()
.map(|ann| {
let sp = ann.type_annotation.span();
source[sp.start as usize..sp.end as usize].to_string()
})
.unwrap_or_else(|| "void".to_string());
let meth_decorators =
collect_leading_decorators_oxc(source, meth.span.start as usize, filter);
methods.push(InterfaceMethodIR {
name: ident.name.to_string(),
span: oxc_span_ir(meth.span),
type_params_src,
params_src,
return_type_src,
optional: meth.optional,
decorators: meth_decorators,
});
}
}
_ => {}
}
}
(fields, methods)
}
pub fn lower_enums_oxc(
program: &Program<'_>,
source: &str,
_filter: Option<&HashSet<String>>,
) -> Result<Vec<EnumIR>, TsSynError> {
let mut enums = Vec::new();
for stmt in &program.body {
let decl = match stmt {
Statement::TSEnumDeclaration(d) => Some(d.as_ref()),
Statement::ExportNamedDeclaration(d) => match &d.declaration {
Some(Declaration::TSEnumDeclaration(d)) => Some(d.as_ref()),
_ => None,
},
_ => None,
};
if let Some(d) = decl {
enums.push(lower_enum(d, source));
}
}
Ok(enums)
}
fn lower_enum(decl: &TSEnumDeclaration<'_>, source: &str) -> EnumIR {
let name = decl.id.name.to_string();
let span = oxc_span_ir(decl.span);
let enum_source = &source[decl.span.start as usize..decl.span.end as usize];
let body_span =
if let (Some(open), Some(close)) = (enum_source.find('{'), enum_source.rfind('}')) {
SpanIR::new(
decl.span.start + open as u32 + 1,
decl.span.start + close as u32 + 2,
)
} else {
span
};
let mut variants = Vec::new();
let mut next_auto_value: f64 = 0.0;
for member in &decl.body.members {
let member_name = match &member.id {
TSEnumMemberName::Identifier(i) => i.name.to_string(),
TSEnumMemberName::String(s) | TSEnumMemberName::ComputedString(s) => {
s.value.to_string()
}
TSEnumMemberName::ComputedTemplateString(_) => continue,
};
let value = if let Some(init) = &member.initializer {
match init {
Expression::StringLiteral(s) => EnumValue::String(s.value.to_string()),
Expression::NumericLiteral(n) => {
next_auto_value = n.value + 1.0;
EnumValue::Number(n.value)
}
Expression::UnaryExpression(unary)
if matches!(
unary.operator,
UnaryOperator::UnaryNegation | UnaryOperator::UnaryPlus
) =>
{
if let Expression::NumericLiteral(n) = &unary.argument {
let val = if matches!(unary.operator, UnaryOperator::UnaryNegation) {
-n.value
} else {
n.value
};
next_auto_value = val + 1.0;
EnumValue::Number(val)
} else {
let sp = init.span();
EnumValue::Expr(source[sp.start as usize..sp.end as usize].to_string())
}
}
_ => {
let sp = init.span();
EnumValue::Expr(source[sp.start as usize..sp.end as usize].to_string())
}
}
} else {
let val = next_auto_value;
next_auto_value += 1.0;
EnumValue::Number(val)
};
let variant_decorators =
collect_leading_decorators_oxc(source, member.span.start as usize, None);
variants.push(EnumVariantIR {
name: member_name,
span: oxc_span_ir(member.span),
value,
decorators: variant_decorators,
});
}
let decorators = collect_leading_decorators_oxc(source, decl.span.start as usize, None);
EnumIR {
name,
span,
body_span,
decorators,
variants,
is_const: decl.r#const,
}
}
pub fn lower_type_aliases_oxc(
program: &Program<'_>,
source: &str,
_filter: Option<&HashSet<String>>,
) -> Result<Vec<TypeAliasIR>, TsSynError> {
let mut aliases = Vec::new();
for stmt in &program.body {
let decl = match stmt {
Statement::TSTypeAliasDeclaration(d) => Some(d.as_ref()),
Statement::ExportNamedDeclaration(d) => match &d.declaration {
Some(Declaration::TSTypeAliasDeclaration(d)) => Some(d.as_ref()),
_ => None,
},
_ => None,
};
if let Some(d) = decl {
aliases.push(lower_type_alias(d, source));
}
}
Ok(aliases)
}
fn lower_type_alias(decl: &TSTypeAliasDeclaration<'_>, source: &str) -> TypeAliasIR {
let name = decl.id.name.to_string();
let span = oxc_span_ir(decl.span);
let type_params = decl
.type_parameters
.as_ref()
.map(|tp| tp.params.iter().map(|p| p.name.to_string()).collect())
.unwrap_or_default();
let body = lower_type_body_oxc(&decl.type_annotation, source);
let decorators = collect_leading_decorators_oxc(source, decl.span.start as usize, None);
TypeAliasIR {
name,
span,
decorators,
type_params,
body,
}
}
fn lower_union_member_oxc(t: &TSType<'_>, source: &str) -> TypeMember {
let sp = t.span();
let text = source[sp.start as usize..sp.end as usize].to_string();
let decorators = collect_leading_decorators_oxc(source, sp.start as usize, None);
let kind = match t {
TSType::TSLiteralType(_) => TypeMemberKind::Literal(text),
TSType::TSTypeLiteral(lit) => {
let (fields, _) = lower_interface_members(&lit.members, source, None);
TypeMemberKind::Object { fields }
}
TSType::TSIntersectionType(inter) => {
let members = inter
.types
.iter()
.map(|t| lower_union_member_oxc(t, source))
.collect();
TypeMemberKind::Intersection(members)
}
TSType::TSParenthesizedType(paren) => {
let mut inner = lower_union_member_oxc(&paren.type_annotation, source);
if inner.decorators.is_empty() && !decorators.is_empty() {
inner.decorators = decorators;
}
return inner;
}
_ => TypeMemberKind::TypeRef(text),
};
TypeMember::with_decorators(kind, decorators)
}
fn lower_type_body_oxc(ts_type: &TSType<'_>, source: &str) -> TypeBody {
match ts_type {
TSType::TSUnionType(union) => {
let members = union
.types
.iter()
.map(|t| lower_union_member_oxc(t, source))
.collect();
TypeBody::Union(members)
}
TSType::TSIntersectionType(inter) => {
let members = inter
.types
.iter()
.map(|t| lower_union_member_oxc(t, source))
.collect();
TypeBody::Intersection(members)
}
TSType::TSTypeLiteral(lit) => {
let (fields, _methods) = lower_interface_members(&lit.members, source, None);
TypeBody::Object { fields }
}
TSType::TSTupleType(tuple) => {
let elements = tuple
.element_types
.iter()
.map(|elem| {
let sp = elem.span();
source[sp.start as usize..sp.end as usize].to_string()
})
.collect();
TypeBody::Tuple(elements)
}
_ => {
let sp = ts_type.span();
TypeBody::Other(source[sp.start as usize..sp.end as usize].to_string())
}
}
}
pub fn lower_functions_oxc(
program: &Program<'_>,
source: &str,
_filter: Option<&HashSet<String>>,
) -> Result<Vec<FunctionIR>, TsSynError> {
let mut functions = Vec::new();
for stmt in &program.body {
match stmt {
Statement::FunctionDeclaration(decl) => {
if let Some(func_ir) = lower_function(decl, source, false, false, None) {
functions.push(func_ir);
}
}
Statement::ExportNamedDeclaration(decl) => {
if let Some(Declaration::FunctionDeclaration(func_decl)) = &decl.declaration
&& let Some(func_ir) =
lower_function(func_decl, source, true, false, Some(decl.span))
{
functions.push(func_ir);
}
}
Statement::ExportDefaultDeclaration(decl) => {
if let ExportDefaultDeclarationKind::FunctionDeclaration(func_decl) =
&decl.declaration
&& let Some(func_ir) =
lower_function(func_decl, source, true, true, Some(decl.span))
{
functions.push(func_ir);
}
}
_ => {}
}
}
Ok(functions)
}
fn lower_function(
decl: &Function<'_>,
source: &str,
is_exported: bool,
is_default_export: bool,
export_span: Option<oxc::span::Span>,
) -> Option<FunctionIR> {
let name = decl.id.as_ref()?.name.to_string();
let body = decl.body.as_ref()?;
let outer_span = export_span.unwrap_or(decl.span);
let span = oxc_span_ir(outer_span);
let body_span = oxc_span_ir(body.span);
let sig_end = body.span.start;
let signature_span = SpanIR::new(outer_span.start + 1, sig_end + 1);
let type_params = decl
.type_parameters
.as_ref()
.map(|tp| tp.params.iter().map(|p| p.name.to_string()).collect())
.unwrap_or_default();
let mut params = Vec::new();
for param in &decl.params.items {
let p_span = param.span;
let p_src = &source[p_span.start as usize..p_span.end as usize];
let is_rest = p_src.starts_with("...");
let (p_name, type_src, default_src, is_optional) = match ¶m.pattern {
BindingPattern::BindingIdentifier(ident) => {
let ts = param
.type_annotation
.as_ref()
.map(|ann| {
let sp = ann.type_annotation.span();
source[sp.start as usize..sp.end as usize].to_string()
})
.unwrap_or_default();
(ident.name.to_string(), ts, None, param.optional)
}
BindingPattern::AssignmentPattern(assign) => {
let id_name = match &assign.left {
BindingPattern::BindingIdentifier(ident) => ident.name.to_string(),
_ => p_src.to_string(),
};
let ts = param
.type_annotation
.as_ref()
.map(|ann| {
let sp = ann.type_annotation.span();
source[sp.start as usize..sp.end as usize].to_string()
})
.unwrap_or_default();
let default = {
let sp = assign.right.span();
source[sp.start as usize..sp.end as usize].to_string()
};
(id_name, ts, Some(default), false)
}
_ => (p_src.to_string(), String::new(), None, false),
};
params.push(FunctionParamIR {
name: p_name,
span: oxc_span_ir(p_span),
type_src,
default_src,
is_optional,
is_rest,
decorators: vec![],
});
}
let return_type_src = decl
.return_type
.as_ref()
.map(|ann| {
let sp = ann.type_annotation.span();
source[sp.start as usize..sp.end as usize].to_string()
})
.unwrap_or_default();
let inner_start = body.span.start as usize + 1;
let inner_end = body.span.end as usize - 1;
let body_src = source.get(inner_start..inner_end).unwrap_or("").to_string();
let decorator_start = export_span
.map(|s| s.start as usize)
.unwrap_or(decl.span.start as usize);
let decorators = collect_leading_decorators_oxc(source, decorator_start, None);
Some(FunctionIR {
name,
span,
body_span,
signature_span,
is_async: decl.r#async,
is_generator: decl.generator,
is_exported,
is_default_export,
type_params,
params,
return_type_src,
body_src,
decorators,
})
}
pub fn lower_targets_oxc(
program: &Program<'_>,
source: &str,
filter: Option<&HashSet<String>>,
) -> Result<Vec<LoweredTarget>, TsSynError> {
let mut targets = Vec::new();
for class in lower_classes_oxc(program, source, filter)? {
targets.push(LoweredTarget::Class(class));
}
for iface in lower_interfaces_oxc(program, source, filter)? {
targets.push(LoweredTarget::Interface(iface));
}
for e in lower_enums_oxc(program, source, filter)? {
targets.push(LoweredTarget::Enum(e));
}
for ta in lower_type_aliases_oxc(program, source, filter)? {
targets.push(LoweredTarget::TypeAlias(ta));
}
for func in lower_functions_oxc(program, source, filter)? {
targets.push(LoweredTarget::Function(func));
}
Ok(targets)
}
pub fn collect_exported_names_oxc(program: &Program<'_>) -> HashSet<String> {
let mut names = HashSet::new();
for stmt in &program.body {
match stmt {
Statement::ExportNamedDeclaration(decl) => {
if let Some(decl) = &decl.declaration {
match decl {
Declaration::ClassDeclaration(c) => {
if let Some(id) = &c.id {
names.insert(id.name.to_string());
}
}
Declaration::TSInterfaceDeclaration(i) => {
names.insert(i.id.name.to_string());
}
Declaration::TSEnumDeclaration(e) => {
names.insert(e.id.name.to_string());
}
Declaration::TSTypeAliasDeclaration(t) => {
names.insert(t.id.name.to_string());
}
_ => {}
}
}
for spec in &decl.specifiers {
names.insert(spec.local.name().to_string());
}
}
Statement::ExportDefaultDeclaration(decl) => {
if let ExportDefaultDeclarationKind::ClassDeclaration(c) = &decl.declaration
&& let Some(id) = &c.id
{
names.insert(id.name.to_string());
}
}
_ => {}
}
}
names
}