1#![expect(clippy::needless_pass_by_value)]
3
4use std::mem;
5
6use napi::{Task, bindgen_prelude::AsyncTask};
7use napi_derive::napi;
8
9use oxc::{
10 allocator::Allocator,
11 ast::CommentKind,
12 ast_visit::utf8_to_utf16::Utf8ToUtf16,
13 parser::{ParseOptions, Parser, ParserReturn},
14 semantic::SemanticBuilder,
15 span::SourceType,
16};
17use oxc_napi::OxcError;
18
19mod convert;
20mod raw_transfer;
21mod raw_transfer_types;
22mod types;
23pub use raw_transfer::{get_buffer_offset, parse_sync_raw, raw_transfer_supported};
24pub use types::{Comment, EcmaScriptModule, ParseResult, ParserOptions};
25
26mod generated {
27 #[cfg(debug_assertions)]
29 pub mod assert_layouts;
30}
31
32#[derive(Clone, Copy, PartialEq, Eq)]
33enum AstType {
34 JavaScript,
35 TypeScript,
36}
37
38fn get_source_and_ast_type(filename: &str, options: &ParserOptions) -> (SourceType, AstType) {
39 let source_type = match options.lang.as_deref() {
40 Some("js") => SourceType::mjs(),
41 Some("jsx") => SourceType::jsx(),
42 Some("ts") => SourceType::ts(),
43 Some("tsx") => SourceType::tsx(),
44 _ => {
45 let mut source_type = SourceType::from_path(filename).unwrap_or_default();
46 match options.source_type.as_deref() {
48 Some("script") => source_type = source_type.with_script(true),
49 Some("module") => source_type = source_type.with_module(true),
50 _ => {}
51 }
52 source_type
53 }
54 };
55
56 let ast_type = match options.ast_type.as_deref() {
57 Some("js") => AstType::JavaScript,
58 Some("ts") => AstType::TypeScript,
59 _ => {
60 if source_type.is_javascript() {
61 AstType::JavaScript
62 } else {
63 AstType::TypeScript
64 }
65 }
66 };
67
68 (source_type, ast_type)
69}
70
71fn parse<'a>(
72 allocator: &'a Allocator,
73 source_type: SourceType,
74 source_text: &'a str,
75 options: &ParserOptions,
76) -> ParserReturn<'a> {
77 Parser::new(allocator, source_text, source_type)
78 .with_options(ParseOptions {
79 preserve_parens: options.preserve_parens.unwrap_or(true),
80 ..ParseOptions::default()
81 })
82 .parse()
83}
84
85fn parse_with_return(filename: &str, source_text: String, options: &ParserOptions) -> ParseResult {
86 let allocator = Allocator::default();
87 let (source_type, ast_type) = get_source_and_ast_type(filename, options);
88 let ret = parse(&allocator, source_type, &source_text, options);
89
90 let mut program = ret.program;
91 let mut module_record = ret.module_record;
92 let mut errors = ret.errors.into_iter().map(OxcError::from).collect::<Vec<_>>();
93
94 if options.show_semantic_errors == Some(true) {
95 let semantic_ret = SemanticBuilder::new().with_check_syntax_error(true).build(&program);
96 errors.extend(semantic_ret.errors.into_iter().map(OxcError::from));
97 }
98
99 let span_converter = Utf8ToUtf16::new(&source_text);
101 span_converter.convert_program(&mut program);
102
103 let mut offset_converter = span_converter.converter();
105 let comments = program
106 .comments
107 .iter()
108 .map(|comment| {
109 let value = comment.content_span().source_text(&source_text).to_string();
110 let mut span = comment.span;
111 if let Some(converter) = offset_converter.as_mut() {
112 converter.convert_span(&mut span);
113 }
114
115 Comment {
116 r#type: match comment.kind {
117 CommentKind::Line => String::from("Line"),
118 CommentKind::Block => String::from("Block"),
119 },
120 value,
121 start: span.start,
122 end: span.end,
123 }
124 })
125 .collect::<Vec<_>>();
126
127 span_converter.convert_module_record(&mut module_record);
129
130 if let Some(mut converter) = span_converter.converter() {
132 for error in &mut errors {
133 for label in &mut error.labels {
134 converter.convert_offset(&mut label.start);
135 converter.convert_offset(&mut label.end);
136 }
137 }
138 }
139
140 let program = match ast_type {
141 AstType::JavaScript => program.to_estree_js_json(),
142 AstType::TypeScript => program.to_estree_ts_json(),
143 };
144
145 let module = EcmaScriptModule::from(&module_record);
146
147 ParseResult { program, module, comments, errors }
148}
149
150#[napi]
152pub fn parse_sync(
153 filename: String,
154 source_text: String,
155 options: Option<ParserOptions>,
156) -> ParseResult {
157 let options = options.unwrap_or_default();
158 parse_with_return(&filename, source_text, &options)
159}
160
161pub struct ResolveTask {
162 filename: String,
163 source_text: String,
164 options: ParserOptions,
165}
166
167#[napi]
168impl Task for ResolveTask {
169 type JsValue = ParseResult;
170 type Output = ParseResult;
171
172 fn compute(&mut self) -> napi::Result<Self::Output> {
173 let source_text = mem::take(&mut self.source_text);
174 Ok(parse_with_return(&self.filename, source_text, &self.options))
175 }
176
177 fn resolve(&mut self, _: napi::Env, result: Self::Output) -> napi::Result<Self::JsValue> {
178 Ok(result)
179 }
180}
181
182#[napi]
186pub fn parse_async(
187 filename: String,
188 source_text: String,
189 options: Option<ParserOptions>,
190) -> AsyncTask<ResolveTask> {
191 let options = options.unwrap_or_default();
192 AsyncTask::new(ResolveTask { filename, source_text, options })
193}