use std::{
mem::{self, ManuallyDrop},
ptr::{self, NonNull},
str,
};
use napi::{
Task,
bindgen_prelude::{AsyncTask, Uint8Array},
};
use napi_derive::napi;
use oxc::{
allocator::{Allocator, FromIn, Vec as ArenaVec},
ast_visit::utf8_to_utf16::Utf8ToUtf16,
semantic::SemanticBuilder,
};
#[cfg(feature = "tokens")]
use oxc_estree_tokens::{ESTreeTokenOptions, update_tokens};
use oxc_napi::get_source_type;
use crate::{
AstType, ParserOptions, get_ast_type, parse_impl,
raw_transfer_constants::{BLOCK_ALIGN as BUFFER_ALIGN, BUFFER_SIZE},
raw_transfer_types::{EcmaScriptModule, Error, RawTransferData, RawTransferMetadata},
};
const ARENA_ALIGN: usize = Allocator::RAW_MIN_ALIGN;
#[napi(skip_typescript)]
#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)]
pub fn get_buffer_offset(buffer: Uint8Array) -> u32 {
let buffer = &*buffer;
let offset = (BUFFER_ALIGN - (buffer.as_ptr() as usize % BUFFER_ALIGN)) % BUFFER_ALIGN;
#[expect(clippy::cast_possible_truncation)]
return offset as u32;
}
#[napi(skip_typescript)]
#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)]
pub unsafe fn parse_raw_sync(
filename: String,
mut buffer: Uint8Array,
source_len: u32,
options: Option<ParserOptions>,
) {
let buffer = unsafe { buffer.as_mut() };
unsafe { parse_raw_impl(&filename, buffer, source_len, options) };
}
#[napi(skip_typescript)]
pub fn parse_raw(
filename: String,
buffer: Uint8Array,
source_len: u32,
options: Option<ParserOptions>,
) -> AsyncTask<ResolveTask> {
AsyncTask::new(ResolveTask { filename, buffer, source_len, options })
}
pub struct ResolveTask {
filename: String,
buffer: Uint8Array,
source_len: u32,
options: Option<ParserOptions>,
}
#[napi]
impl Task for ResolveTask {
type JsValue = ();
type Output = ();
fn compute(&mut self) -> napi::Result<()> {
let buffer = unsafe { self.buffer.as_mut() };
unsafe { parse_raw_impl(&self.filename, buffer, self.source_len, self.options.take()) };
Ok(())
}
fn resolve(&mut self, _: napi::Env, _result: ()) -> napi::Result<()> {
Ok(())
}
}
#[allow(clippy::items_after_statements, clippy::allow_attributes)]
unsafe fn parse_raw_impl(
filename: &str,
buffer: &mut [u8],
source_len: u32,
options: Option<ParserOptions>,
) {
assert_eq!(buffer.len(), BUFFER_SIZE);
let buffer_ptr = ptr::from_mut(buffer).cast::<u8>();
assert!((buffer_ptr as usize).is_multiple_of(BUFFER_ALIGN));
const RAW_METADATA_SIZE: usize = size_of::<RawTransferMetadata>();
const {
assert!(RAW_METADATA_SIZE >= ARENA_ALIGN);
assert!(RAW_METADATA_SIZE.is_multiple_of(ARENA_ALIGN));
};
let source_len = source_len as usize;
let data_offset = source_len.next_multiple_of(ARENA_ALIGN);
let data_size = (BUFFER_SIZE - RAW_METADATA_SIZE).saturating_sub(data_offset);
assert!(data_size >= Allocator::RAW_MIN_SIZE, "Source text is too long");
let data_ptr = unsafe { buffer_ptr.add(data_offset) };
debug_assert!((data_ptr as usize).is_multiple_of(ARENA_ALIGN));
debug_assert!(data_size.is_multiple_of(ARENA_ALIGN));
let allocator =
unsafe { Allocator::from_raw_parts(NonNull::new_unchecked(data_ptr), data_size) };
let allocator = ManuallyDrop::new(allocator);
let options = options.unwrap_or_default();
let source_type =
get_source_type(filename, options.lang.as_deref(), options.source_type.as_deref());
let is_ts = get_ast_type(source_type, &options) == AstType::TypeScript;
let (data_offset, tokens_offset, tokens_len) = {
let source_text = unsafe { buffer.get_unchecked(..source_len) };
let source_text = unsafe { str::from_utf8_unchecked(source_text) };
let ret = parse_impl(&allocator, source_type, source_text, &options);
let mut program = ret.program;
let mut comments = mem::replace(&mut program.comments, ArenaVec::new_in(&allocator));
let mut module_record = ret.module_record;
let mut errors = if options.show_semantic_errors == Some(true) {
let semantic_ret = SemanticBuilder::new().with_check_syntax_error(true).build(&program);
if !ret.errors.is_empty() || !semantic_ret.errors.is_empty() {
Error::from_diagnostics_in(
ret.errors.into_iter().chain(semantic_ret.errors),
source_text,
filename,
&allocator,
)
} else {
ArenaVec::new_in(&allocator)
}
} else if !ret.errors.is_empty() {
Error::from_diagnostics_in(ret.errors, source_text, filename, &allocator)
} else {
ArenaVec::new_in(&allocator)
};
let span_converter = Utf8ToUtf16::new(source_text);
#[cfg(feature = "tokens")]
let (tokens_offset, tokens_len) = if options.tokens == Some(true) {
let mut tokens = ret.tokens;
update_tokens(&mut tokens, &program, &span_converter, ESTreeTokenOptions::new(is_ts));
let tokens_offset = tokens.as_ptr() as u32;
#[expect(clippy::cast_possible_truncation)]
let tokens_len = tokens.len() as u32;
(tokens_offset, tokens_len)
} else {
(0, 0)
};
#[cfg(not(feature = "tokens"))]
let (tokens_offset, tokens_len) = (0, 0);
span_converter.convert_program(&mut program);
span_converter.convert_comments(&mut comments);
span_converter.convert_module_record(&mut module_record);
if let Some(mut converter) = span_converter.converter() {
for error in &mut errors {
for label in &mut error.labels {
converter.convert_span(&mut label.span);
}
}
}
let module = EcmaScriptModule::from_in(module_record, &allocator);
let data = RawTransferData { program, comments, module, errors };
let data = allocator.alloc(data);
let data_offset = ptr::from_ref(data).cast::<u8>() as u32;
(data_offset, tokens_offset, tokens_len)
};
let metadata = RawTransferMetadata::new(data_offset, is_ts, tokens_offset, tokens_len);
const RAW_METADATA_OFFSET: usize = BUFFER_SIZE - RAW_METADATA_SIZE;
const _: () = assert!(RAW_METADATA_OFFSET.is_multiple_of(ARENA_ALIGN));
#[expect(clippy::cast_ptr_alignment)]
unsafe {
buffer_ptr.add(RAW_METADATA_OFFSET).cast::<RawTransferMetadata>().write(metadata);
}
}