use std::{
alloc::Layout,
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::{ACTIVE_SIZE, BLOCK_ALIGN, BLOCK_SIZE, CURSOR_MIN_ALIGN},
raw_transfer_types::{EcmaScriptModule, Error, RawTransferData, RawTransferMetadata},
};
const BLOCK_LAYOUT: Layout = match Layout::from_size_align(BLOCK_SIZE, BLOCK_ALIGN) {
Ok(layout) => layout,
Err(_) => unreachable!(),
};
#[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 = (BLOCK_ALIGN - (buffer.as_ptr().addr() % BLOCK_ALIGN)) % BLOCK_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_start: u32,
source_len: u32,
options: Option<ParserOptions>,
) {
let buffer = unsafe { buffer.as_mut() };
unsafe { parse_raw_impl(&filename, buffer, source_start, source_len, options) };
}
#[napi(skip_typescript)]
pub fn parse_raw(
filename: String,
buffer: Uint8Array,
source_start: u32,
source_len: u32,
options: Option<ParserOptions>,
) -> AsyncTask<ResolveTask> {
AsyncTask::new(ResolveTask { filename, buffer, source_start, source_len, options })
}
pub struct ResolveTask {
filename: String,
buffer: Uint8Array,
source_start: u32,
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_start,
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_start: u32,
source_len: u32,
options: Option<ParserOptions>,
) {
assert_eq!(buffer.len(), BLOCK_SIZE);
let buffer_ptr = NonNull::from_mut(buffer).cast::<u8>();
assert!(buffer_ptr.addr().get().is_multiple_of(BLOCK_ALIGN));
const _: () = {
assert!(BLOCK_SIZE.is_multiple_of(Allocator::RAW_MIN_ALIGN));
assert!(BLOCK_SIZE >= Allocator::RAW_MIN_SIZE);
assert!(BLOCK_ALIGN.is_multiple_of(Allocator::RAW_MIN_ALIGN));
};
let allocator =
unsafe { Allocator::from_raw_parts(buffer_ptr, BLOCK_SIZE, buffer_ptr, BLOCK_LAYOUT) };
let allocator = ManuallyDrop::new(allocator);
let source_start = source_start as usize;
let source_end = source_start + (source_len as usize);
assert!(source_end <= ACTIVE_SIZE);
unsafe {
let cursor_pos = source_start & !(CURSOR_MIN_ALIGN - 1);
debug_assert!(cursor_pos <= ACTIVE_SIZE);
let cursor_ptr = buffer_ptr.add(cursor_pos);
allocator.set_cursor_ptr(cursor_ptr);
}
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 = if cfg!(debug_assertions) {
let source_bytes = &buffer[source_start..source_end];
str::from_utf8(source_bytes).expect("Source text is not valid UTF-8")
} else {
unsafe {
let source_bytes = buffer.get_unchecked(source_start..source_end);
str::from_utf8_unchecked(source_bytes)
}
};
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 = ACTIVE_SIZE;
unsafe {
let metadata_ptr = buffer_ptr.add(RAW_METADATA_OFFSET).cast::<RawTransferMetadata>();
debug_assert!(metadata_ptr.addr().get().is_multiple_of(align_of::<RawTransferMetadata>()));
metadata_ptr.write(metadata);
}
}