use std::cmp;
use oxc_ast_macros::ast_meta;
use oxc_estree::{
CompactFixesJSSerializer, CompactFixesTSSerializer, CompactJSSerializer, CompactTSSerializer,
Concat2, ESTree, JsonSafeString, PrettyFixesJSSerializer, PrettyFixesTSSerializer,
PrettyJSSerializer, PrettyTSSerializer, Serializer, StructSerializer,
};
use oxc_span::GetSpan;
use crate::ast::*;
pub mod basic;
pub mod js;
pub mod jsx;
pub mod literal;
pub mod ts;
use basic::{EmptyArray, Null};
const JSON_CAPACITY_RATIO_COMPACT: usize = 16;
const JSON_CAPACITY_RATIO_PRETTY: usize = 80;
impl Program<'_> {
pub fn to_estree_ts_json(&self, ranges: bool) -> String {
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT;
let mut serializer = CompactTSSerializer::with_capacity(capacity, ranges);
self.serialize(&mut serializer);
serializer.into_string()
}
pub fn to_estree_js_json(&self, ranges: bool) -> String {
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT;
let mut serializer = CompactJSSerializer::with_capacity(capacity, ranges);
self.serialize(&mut serializer);
serializer.into_string()
}
pub fn to_pretty_estree_ts_json(&self, ranges: bool) -> String {
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY;
let mut serializer = PrettyTSSerializer::with_capacity(capacity, ranges);
self.serialize(&mut serializer);
serializer.into_string()
}
pub fn to_pretty_estree_js_json(&self, ranges: bool) -> String {
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY;
let mut serializer = PrettyJSSerializer::with_capacity(capacity, ranges);
self.serialize(&mut serializer);
serializer.into_string()
}
pub fn to_estree_ts_json_with_fixes(&self, ranges: bool) -> String {
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT;
let serializer = CompactFixesTSSerializer::with_capacity(capacity, ranges);
serializer.serialize_with_fixes(self)
}
pub fn to_estree_js_json_with_fixes(&self, ranges: bool) -> String {
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT;
let serializer = CompactFixesJSSerializer::with_capacity(capacity, ranges);
serializer.serialize_with_fixes(self)
}
pub fn to_pretty_estree_ts_json_with_fixes(&self, ranges: bool) -> String {
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY;
let serializer = PrettyFixesTSSerializer::with_capacity(capacity, ranges);
serializer.serialize_with_fixes(self)
}
pub fn to_pretty_estree_js_json_with_fixes(&self, ranges: bool) -> String {
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY;
let serializer = PrettyFixesJSSerializer::with_capacity(capacity, ranges);
serializer.serialize_with_fixes(self)
}
}
#[ast_meta]
#[estree(raw_deser = "
const start = IS_TS ? 0 : DESER[i32](POS_OFFSET.span.start),
end = DESER[i32](POS_OFFSET.span.end);
const program = parent = {
type: 'Program',
body: null,
sourceType: DESER[ModuleKind](POS_OFFSET.source_type.module_kind),
/* IF !LINTER */
hashbang: null,
/* END_IF */
/* IF LINTER */
get comments() {
if (comments === null) initComments();
return comments;
},
get tokens() {
if (tokens === null) initTokens();
return tokens;
},
/* END_IF */
start,
end,
...(RANGE && { range: [start, end] }),
...(PARENT && { parent: null }),
};
if (!LINTER) program.hashbang = DESER[Option<Hashbang>](POS_OFFSET.hashbang);
const body = program.body = DESER[Vec<Directive>](POS_OFFSET.directives);
body.push(...DESER[Vec<Statement>](POS_OFFSET.body));
if (IS_TS) {
let start;
if (body.length > 0) {
const first = body[0];
start = first.start;
if (first.type === 'ExportNamedDeclaration' || first.type === 'ExportDefaultDeclaration') {
const { declaration } = first;
if (
declaration !== null && declaration.type === 'ClassDeclaration'
&& declaration.decorators.length > 0
) {
const decoratorStart = declaration.decorators[0].start;
if (decoratorStart < start) start = decoratorStart;
}
}
} else {
start = end;
}
if (RANGE) {
program.start = program.range[0] = start;
} else {
program.start = start;
}
}
if (PARENT) parent = null;
program
")]
pub struct ProgramConverter<'a, 'b>(pub &'b Program<'a>);
impl ESTree for ProgramConverter<'_, '_> {
fn serialize<S: Serializer>(&self, serializer: S) {
let program = self.0;
let mut state = serializer.serialize_struct();
state.serialize_field("type", &JsonSafeString("Program"));
state.serialize_field("body", &Concat2(&program.directives, &program.body));
state.serialize_field("sourceType", &program.source_type.module_kind());
state.serialize_field("hashbang", &program.hashbang);
let span = if S::INCLUDE_TS_FIELDS {
Span::new(get_ts_start_span(program), program.span.end)
} else {
program.span
};
state.serialize_span(span);
state.end();
}
}
fn get_ts_start_span(program: &Program<'_>) -> u32 {
if let Some(first_directive) = program.directives.first() {
return first_directive.span.start;
}
let Some(first_stmt) = program.body.first() else {
return program.span.end;
};
let start = first_stmt.span().start;
match first_stmt {
Statement::ExportNamedDeclaration(decl) => {
if let Some(Declaration::ClassDeclaration(class)) = &decl.declaration
&& let Some(decorator) = class.decorators.first()
{
return cmp::min(start, decorator.span.start);
}
}
Statement::ExportDefaultDeclaration(decl) => {
if let ExportDefaultDeclarationKind::ClassDeclaration(class) = &decl.declaration
&& let Some(decorator) = class.decorators.first()
{
return cmp::min(start, decorator.span.start);
}
}
_ => {}
}
start
}
#[ast_meta]
#[estree(
ts_type = "string",
raw_deser = "SOURCE_TEXT.slice(THIS.start + 2, THIS.end - (THIS.type === 'Line' ? 0 : 2))",
raw_deser_inline
)]
pub struct CommentValue<'b>(#[expect(dead_code)] pub &'b Comment);
impl ESTree for CommentValue<'_> {
#[expect(clippy::unimplemented)]
fn serialize<S: Serializer>(&self, _serializer: S) {
unimplemented!();
}
}