use oxc_allocator::{Allocator, Box as ArenaBox, CloneIn, Vec as ArenaVec};
use oxc_ast::{NONE, ast::*};
use oxc_span::{ContentEq, GetSpan, SPAN};
use crate::{
IsolatedDeclarations,
diagnostics::{
accessor_must_have_explicit_return_type, computed_property_name, extends_clause_expression,
method_must_have_explicit_return_type, property_must_have_explicit_type,
},
};
struct AccessorAnnotation<'a> {
setter: Option<ArenaBox<'a, TSTypeAnnotation<'a>>>,
getter: Option<ArenaBox<'a, TSTypeAnnotation<'a>>>,
}
impl<'a> AccessorAnnotation<'a> {
fn get_setter_annotation(
&self,
allocator: &'a Allocator,
) -> Option<ArenaBox<'a, TSTypeAnnotation<'a>>> {
self.setter.as_ref().or(self.getter.as_ref()).map(|t| t.clone_in(allocator))
}
fn get_getter_annotation(
&self,
allocator: &'a Allocator,
) -> Option<ArenaBox<'a, TSTypeAnnotation<'a>>> {
self.getter.as_ref().or(self.setter.as_ref()).map(|t| t.clone_in(allocator))
}
}
impl<'a> IsolatedDeclarations<'a> {
pub(crate) fn is_literal_key(key: &PropertyKey<'a>) -> bool {
match key {
PropertyKey::StringLiteral(_) | PropertyKey::NumericLiteral(_) => true,
PropertyKey::TemplateLiteral(l) => l.expressions.is_empty(),
PropertyKey::UnaryExpression(expr) => {
expr.operator.is_arithmetic()
&& matches!(
expr.argument,
Expression::NumericLiteral(_) | Expression::BigIntLiteral(_)
)
}
_ => false,
}
}
pub(crate) fn is_global_symbol(key: &PropertyKey<'a>) -> bool {
let PropertyKey::StaticMemberExpression(member) = key else {
return false;
};
match &member.object {
Expression::Identifier(ident) => ident.name == "Symbol",
Expression::StaticMemberExpression(expr) => {
expr.property.name == "Symbol"
&& matches!(
&expr.object, Expression::Identifier(ident)
if matches!(ident.name.as_str(), "window" | "globalThis")
)
}
_ => false,
}
}
pub(crate) fn is_valid_property_key(key: &PropertyKey<'a>) -> bool {
Self::is_literal_key(key) || Self::is_global_symbol(key)
}
pub(crate) fn report_property_key(&self, key: &PropertyKey<'a>) -> bool {
if Self::is_valid_property_key(key) {
false
} else {
self.error(computed_property_name(key.span()));
true
}
}
pub(crate) fn transform_accessibility(
accessibility: Option<TSAccessibility>,
) -> Option<TSAccessibility> {
if accessibility.is_none() || accessibility.is_some_and(|a| a == TSAccessibility::Public) {
None
} else {
accessibility
}
}
fn transform_class_property_definition(
&self,
property: &PropertyDefinition<'a>,
) -> ClassElement<'a> {
let mut type_annotation = None;
let mut value = None;
if property.accessibility.is_none_or(|a| !a.is_private()) {
if property.type_annotation.is_some() {
type_annotation = property.type_annotation.clone_in(self.ast.allocator);
} else if let Some(expr) = property.value.as_ref() {
let ts_type = if property.readonly {
if Self::is_need_to_infer_type_from_expression(expr) {
self.transform_expression_to_ts_type(expr)
} else {
if let Expression::TemplateLiteral(lit) = expr {
value = self
.transform_template_to_string(lit)
.map(Expression::StringLiteral);
} else {
value = Some(expr.clone_in(self.ast.allocator));
}
None
}
} else {
self.infer_type_from_expression(expr)
};
type_annotation = ts_type.map(|t| self.ast.alloc_ts_type_annotation(SPAN, t));
}
if type_annotation.is_none() && value.is_none() {
self.error(property_must_have_explicit_type(property.key.span()));
}
}
self.ast.class_element_property_definition(
property.span,
property.r#type,
self.ast.vec(),
property.key.clone_in(self.ast.allocator),
type_annotation,
value,
property.computed,
property.r#static,
false,
property.r#override,
property.optional,
property.definite,
property.readonly,
Self::transform_accessibility(property.accessibility),
)
}
fn transform_class_method_definition(
&self,
definition: &MethodDefinition<'a>,
params: ArenaBox<'a, FormalParameters<'a>>,
return_type: Option<ArenaBox<'a, TSTypeAnnotation<'a>>>,
) -> ClassElement<'a> {
let function = &definition.value;
let value = self.ast.alloc_function(
function.span,
FunctionType::TSEmptyBodyFunctionExpression,
function.id.clone_in(self.ast.allocator),
false,
false,
false,
function.type_parameters.clone_in(self.ast.allocator),
function.this_param.clone_in(self.ast.allocator),
params,
return_type,
NONE,
);
self.ast.class_element_method_definition(
definition.span,
definition.r#type,
self.ast.vec(),
definition.key.clone_in(self.ast.allocator),
value,
definition.kind,
definition.computed,
definition.r#static,
definition.r#override,
definition.optional,
Self::transform_accessibility(definition.accessibility),
)
}
fn create_class_property(
&self,
r#type: PropertyDefinitionType,
span: Span,
key: PropertyKey<'a>,
r#static: bool,
r#override: bool,
accessibility: Option<TSAccessibility>,
) -> ClassElement<'a> {
self.ast.class_element_property_definition(
span,
r#type,
self.ast.vec(),
key,
NONE,
None,
false,
r#static,
false,
r#override,
false,
false,
false,
accessibility,
)
}
fn transform_formal_parameter_to_class_property(
&self,
param: &FormalParameter<'a>,
type_annotation: Option<ArenaBox<'a, TSTypeAnnotation<'a>>>,
) -> Option<ClassElement<'a>> {
let Some(ident_name) = param.pattern.get_identifier_name() else {
return None;
};
let key = self.ast.property_key_static_identifier(SPAN, ident_name);
Some(self.ast.class_element_property_definition(
param.span,
PropertyDefinitionType::PropertyDefinition,
self.ast.vec(),
key,
type_annotation,
None,
false,
false,
false,
param.r#override,
param.optional,
false,
param.readonly,
Self::transform_accessibility(param.accessibility),
))
}
fn transform_private_modifier_method(&self, method: &MethodDefinition<'a>) -> ClassElement<'a> {
match method.kind {
MethodDefinitionKind::Method => {
let r#type = match method.r#type {
MethodDefinitionType::MethodDefinition => {
PropertyDefinitionType::PropertyDefinition
}
MethodDefinitionType::TSAbstractMethodDefinition => {
PropertyDefinitionType::TSAbstractPropertyDefinition
}
};
self.create_class_property(
r#type,
method.span,
method.key.clone_in(self.ast.allocator),
method.r#static,
method.r#override,
Self::transform_accessibility(method.accessibility),
)
}
MethodDefinitionKind::Get | MethodDefinitionKind::Constructor => {
let params = self.ast.alloc_formal_parameters(
SPAN,
FormalParameterKind::Signature,
self.ast.vec(),
NONE,
);
self.transform_class_method_definition(method, params, None)
}
MethodDefinitionKind::Set => {
let params = self.create_formal_parameters(
self.ast.binding_pattern_binding_identifier(SPAN, "value"),
);
self.transform_class_method_definition(method, params, None)
}
}
}
fn transform_constructor_parameter_properties(
&self,
function: &Function<'a>,
typed_params: &FormalParameters<'a>,
) -> ArenaVec<'a, ClassElement<'a>> {
self.ast.vec_from_iter(
function
.params
.items
.iter()
.filter(|param| {
typed_params.items.len() == function.params.items.len() || param.has_modifier()
})
.enumerate()
.filter_map(|(index, param)| {
if !param.has_modifier() {
return None;
}
let type_annotation =
if param.accessibility.is_some_and(TSAccessibility::is_private) {
None
} else {
typed_params.items[index].type_annotation.clone_in(self.ast.allocator)
};
self.transform_formal_parameter_to_class_property(param, type_annotation)
}),
)
}
fn collect_accessor_annotations(
&self,
decl: &Class<'a>,
) -> Vec<(PropertyKey<'a>, AccessorAnnotation<'a>)> {
let mut method_annotations: Vec<(PropertyKey<'_>, AccessorAnnotation<'_>)> = Vec::new();
for element in &decl.body.body {
if let ClassElement::MethodDefinition(method) = element {
if (method.key.is_private_identifier()
|| method.accessibility.is_some_and(TSAccessibility::is_private))
|| (method.computed && !Self::is_valid_property_key(&method.key))
{
continue;
}
match method.kind {
MethodDefinitionKind::Set => {
let Some(first_param) = method.value.params.items.first() else {
continue;
};
if let Some(annotation) =
first_param.type_annotation.clone_in(self.ast.allocator)
{
if let Some(entry) = method_annotations
.iter_mut()
.find(|(key, _)| method.key.content_eq(key))
{
entry.1.setter = Some(annotation);
} else {
method_annotations.push((
method.key.clone_in(self.ast.allocator),
AccessorAnnotation { setter: Some(annotation), getter: None },
));
}
}
}
MethodDefinitionKind::Get => {
let function = &method.value;
if let Some(annotation) = self.infer_function_return_type(function) {
if let Some(entry) = method_annotations
.iter_mut()
.find(|(key, _)| method.key.content_eq(key))
{
entry.1.getter = Some(annotation);
} else {
method_annotations.push((
method.key.clone_in(self.ast.allocator),
AccessorAnnotation { setter: None, getter: Some(annotation) },
));
}
}
}
_ => {}
}
}
}
method_annotations
}
pub(crate) fn transform_class(
&self,
decl: &Class<'a>,
declare: Option<bool>,
) -> ArenaBox<'a, Class<'a>> {
if let Some(super_class) = &decl.super_class {
let is_not_allowed = match super_class {
Expression::Identifier(_) => false,
Expression::StaticMemberExpression(expr) => {
!expr.get_first_object().is_identifier_reference()
}
_ => true,
};
if is_not_allowed {
self.error(extends_clause_expression(super_class.span()));
}
}
let accessor_annotations = self.collect_accessor_annotations(decl);
let mut has_private_key = false;
let mut elements = self.ast.vec();
let mut is_function_overloads = false;
for element in &decl.body.body {
match element {
ClassElement::StaticBlock(_) => {}
ClassElement::MethodDefinition(method) => {
if self.has_internal_annotation(method.span) {
continue;
}
if !(
method.r#type.is_abstract()
|| method.optional
) && method.value.body.is_none()
{
is_function_overloads = true;
} else if is_function_overloads && !method.kind.is_constructor() {
is_function_overloads = false;
continue;
}
if method.key.is_private_identifier() {
has_private_key = true;
continue;
}
if method.computed && self.report_property_key(&method.key) {
continue;
}
let function = &method.value;
let params = match method.kind {
MethodDefinitionKind::Set => {
if method.accessibility.is_some_and(TSAccessibility::is_private) {
elements.push(self.transform_private_modifier_method(method));
continue;
}
let params = &method.value.params;
if params.items.is_empty() {
self.create_formal_parameters(
self.ast.binding_pattern_binding_identifier(SPAN, "value"),
)
} else {
let mut params = params.clone_in(self.ast.allocator);
if let Some(param) = params.items.first_mut()
&& let Some(annotation) =
accessor_annotations.iter().find_map(|(key, annotation)| {
if method.key.content_eq(key) {
Some(
annotation
.get_setter_annotation(self.ast.allocator),
)
} else {
None
}
})
{
param.type_annotation = annotation;
}
params
}
}
MethodDefinitionKind::Constructor => {
let is_private =
method.accessibility.is_some_and(TSAccessibility::is_private);
let params =
self.transform_formal_parameters(&function.params, is_private);
elements.splice(
0..0,
self.transform_constructor_parameter_properties(function, ¶ms),
);
if is_function_overloads && function.body.is_some() {
is_function_overloads = false;
continue;
}
if is_private {
elements.push(self.transform_private_modifier_method(method));
continue;
}
params
}
_ => {
let is_private =
method.accessibility.is_some_and(TSAccessibility::is_private);
if is_private {
elements.push(self.transform_private_modifier_method(method));
continue;
}
self.transform_formal_parameters(&function.params, is_private)
}
};
let return_type = match method.kind {
MethodDefinitionKind::Method => {
let rt = self.infer_function_return_type(function);
if rt.is_none() {
self.error(method_must_have_explicit_return_type(
method.key.span(),
));
}
rt
}
MethodDefinitionKind::Get => {
let rt = accessor_annotations.iter().find_map(|(key, annotation)| {
if method.key.content_eq(key) {
if method.value.return_type.is_none() {
annotation.get_setter_annotation(self.ast.allocator)
} else {
annotation.get_getter_annotation(self.ast.allocator)
}
} else {
None
}
});
if rt.is_none() {
self.error(accessor_must_have_explicit_return_type(
method.key.span(),
));
}
rt
}
MethodDefinitionKind::Set | MethodDefinitionKind::Constructor => None,
};
let new_element =
self.transform_class_method_definition(method, params, return_type);
elements.push(new_element);
}
ClassElement::PropertyDefinition(property) => {
if self.has_internal_annotation(property.span) {
continue;
}
if property.computed && self.report_property_key(&property.key) {
continue;
}
if property.key.is_private_identifier() {
has_private_key = true;
} else {
elements.push(self.transform_class_property_definition(property));
}
}
ClassElement::AccessorProperty(property) => {
if self.has_internal_annotation(property.span) {
continue;
}
if property.computed && self.report_property_key(&property.key) {
continue;
}
if property.key.is_private_identifier() {
has_private_key = true;
continue;
}
let type_annotation = match property.accessibility {
Some(TSAccessibility::Private) => None,
_ => property.type_annotation.clone_in(self.ast.allocator),
};
let new_element = self.ast.class_element_accessor_property(
property.span,
property.r#type,
self.ast.vec(),
property.key.clone_in(self.ast.allocator),
type_annotation,
None,
property.computed,
property.r#static,
property.r#override,
property.definite,
property.accessibility,
);
elements.push(new_element);
}
ClassElement::TSIndexSignature(signature) => elements.push({
if self.has_internal_annotation(signature.span) {
continue;
}
element.clone_in(self.ast.allocator)
}),
}
}
if has_private_key {
let ident = self.ast.property_key_private_identifier(SPAN, "private");
let r#type = PropertyDefinitionType::PropertyDefinition;
let decorators = self.ast.vec();
let element = self.ast.class_element_property_definition(
SPAN, r#type, decorators, ident, NONE, None, false, false, false, false, false,
false, false, None,
);
elements.insert(0, element);
}
let body = self.ast.class_body(decl.body.span, elements);
self.ast.alloc_class(
decl.span,
decl.r#type,
self.ast.vec(),
decl.id.clone_in(self.ast.allocator),
decl.type_parameters.clone_in(self.ast.allocator),
decl.super_class.clone_in(self.ast.allocator),
decl.super_type_arguments.clone_in(self.ast.allocator),
decl.implements.clone_in(self.ast.allocator),
body,
decl.r#abstract,
declare.unwrap_or_else(|| self.is_declare()),
)
}
pub(crate) fn create_formal_parameters(
&self,
kind: BindingPattern<'a>,
) -> ArenaBox<'a, FormalParameters<'a>> {
let parameter = self.ast.formal_parameter(
SPAN,
self.ast.vec(),
kind,
NONE,
NONE,
false,
None,
false,
false,
);
let items = self.ast.vec1(parameter);
self.ast.alloc_formal_parameters(SPAN, FormalParameterKind::Signature, items, NONE)
}
}