1#![expect(clippy::needless_pass_by_value)]
3
4#[cfg(all(
5 feature = "allocator",
6 not(any(target_arch = "arm", target_os = "freebsd", target_family = "wasm"))
7))]
8#[global_allocator]
9static ALLOC: mimalloc_safe::MiMalloc = mimalloc_safe::MiMalloc;
10
11use std::mem;
12
13use napi::{Task, bindgen_prelude::AsyncTask};
14use napi_derive::napi;
15
16use oxc::{
17 allocator::Allocator,
18 parser::{ParseOptions, Parser, ParserReturn},
19 semantic::SemanticBuilder,
20 span::SourceType,
21};
22use oxc_napi::{Comment, OxcError, convert_utf8_to_utf16, get_source_type};
23
24mod convert;
25mod raw_transfer;
26mod raw_transfer_types;
27mod types;
28pub use raw_transfer::{
29 get_buffer_offset, parse_async_raw, parse_sync_raw, raw_transfer_supported,
30};
31pub use types::{EcmaScriptModule, ParseResult, ParserOptions};
32
33mod generated {
34 #[cfg(debug_assertions)]
36 pub mod assert_layouts;
37}
38
39#[derive(Clone, Copy, PartialEq, Eq)]
40enum AstType {
41 JavaScript,
42 TypeScript,
43}
44
45fn get_ast_type(source_type: SourceType, options: &ParserOptions) -> AstType {
46 match options.ast_type.as_deref() {
47 Some("js") => AstType::JavaScript,
48 Some("ts") => AstType::TypeScript,
49 _ => {
50 if source_type.is_javascript() {
51 AstType::JavaScript
52 } else {
53 AstType::TypeScript
54 }
55 }
56 }
57}
58
59fn parse<'a>(
60 allocator: &'a Allocator,
61 source_type: SourceType,
62 source_text: &'a str,
63 options: &ParserOptions,
64) -> ParserReturn<'a> {
65 Parser::new(allocator, source_text, source_type)
66 .with_options(ParseOptions {
67 preserve_parens: options.preserve_parens.unwrap_or(true),
68 ..ParseOptions::default()
69 })
70 .parse()
71}
72
73fn parse_with_return(filename: &str, source_text: String, options: &ParserOptions) -> ParseResult {
74 let allocator = Allocator::default();
75 let source_type =
76 get_source_type(filename, options.lang.as_deref(), options.source_type.as_deref());
77 let ast_type = get_ast_type(source_type, options);
78 let ret = parse(&allocator, source_type, &source_text, options);
79
80 let mut program = ret.program;
81 let mut module_record = ret.module_record;
82 let mut diagnostics = ret.errors;
83
84 if options.show_semantic_errors == Some(true) {
85 let semantic_ret = SemanticBuilder::new().with_check_syntax_error(true).build(&program);
86 diagnostics.extend(semantic_ret.errors);
87 }
88
89 let mut errors = OxcError::from_diagnostics(filename, &source_text, diagnostics);
90
91 let mut comments =
92 convert_utf8_to_utf16(&source_text, &mut program, &mut module_record, &mut errors);
93
94 let program_and_fixes = match ast_type {
95 AstType::JavaScript => {
96 if let Some(hashbang) = &program.hashbang {
98 comments.insert(
99 0,
100 Comment {
101 r#type: "Line".to_string(),
102 value: hashbang.value.to_string(),
103 start: hashbang.span.start,
104 end: hashbang.span.end,
105 },
106 );
107 }
108
109 program.to_estree_js_json_with_fixes()
110 }
111 AstType::TypeScript => {
112 program.to_estree_ts_json_with_fixes()
117 }
118 };
119
120 let module = EcmaScriptModule::from(&module_record);
121
122 ParseResult { program_and_fixes, module, comments, errors }
123}
124
125#[napi]
127pub fn parse_sync(
128 filename: String,
129 source_text: String,
130 options: Option<ParserOptions>,
131) -> ParseResult {
132 let options = options.unwrap_or_default();
133 parse_with_return(&filename, source_text, &options)
134}
135
136pub struct ResolveTask {
137 filename: String,
138 source_text: String,
139 options: ParserOptions,
140}
141
142#[napi]
143impl Task for ResolveTask {
144 type JsValue = ParseResult;
145 type Output = ParseResult;
146
147 fn compute(&mut self) -> napi::Result<Self::Output> {
148 let source_text = mem::take(&mut self.source_text);
149 Ok(parse_with_return(&self.filename, source_text, &self.options))
150 }
151
152 fn resolve(&mut self, _: napi::Env, result: Self::Output) -> napi::Result<Self::JsValue> {
153 Ok(result)
154 }
155}
156
157#[napi]
161pub fn parse_async(
162 filename: String,
163 source_text: String,
164 options: Option<ParserOptions>,
165) -> AsyncTask<ResolveTask> {
166 let options = options.unwrap_or_default();
167 AsyncTask::new(ResolveTask { filename, source_text, options })
168}