oxc_parser_napi/
lib.rs

1// Napi value need to be passed as value
2#![expect(clippy::needless_pass_by_value)]
3
4#[cfg(all(feature = "allocator", not(target_arch = "arm"), not(target_family = "wasm")))]
5#[global_allocator]
6static ALLOC: mimalloc_safe::MiMalloc = mimalloc_safe::MiMalloc;
7
8use std::mem;
9
10use napi::{Task, bindgen_prelude::AsyncTask};
11use napi_derive::napi;
12
13use oxc::{
14    allocator::Allocator,
15    parser::{ParseOptions, Parser, ParserReturn},
16    semantic::SemanticBuilder,
17    span::SourceType,
18};
19use oxc_napi::{OxcError, convert_utf8_to_utf16};
20
21mod convert;
22mod raw_transfer;
23mod raw_transfer_types;
24mod types;
25pub use raw_transfer::{get_buffer_offset, parse_sync_raw, raw_transfer_supported};
26pub use types::{EcmaScriptModule, ParseResult, ParserOptions};
27
28mod generated {
29    // Note: We intentionally don't import `generated/derive_estree.rs`. It's not needed.
30    #[cfg(debug_assertions)]
31    pub mod assert_layouts;
32}
33
34#[derive(Clone, Copy, PartialEq, Eq)]
35enum AstType {
36    JavaScript,
37    TypeScript,
38}
39
40fn get_source_and_ast_type(filename: &str, options: &ParserOptions) -> (SourceType, AstType) {
41    let source_type = match options.lang.as_deref() {
42        Some("js") => SourceType::mjs(),
43        Some("jsx") => SourceType::jsx(),
44        Some("ts") => SourceType::ts(),
45        Some("tsx") => SourceType::tsx(),
46        _ => {
47            let mut source_type = SourceType::from_path(filename).unwrap_or_default();
48            // Force `script` or `module`
49            match options.source_type.as_deref() {
50                Some("script") => source_type = source_type.with_script(true),
51                Some("module") => source_type = source_type.with_module(true),
52                _ => {}
53            }
54            source_type
55        }
56    };
57
58    let ast_type = match options.ast_type.as_deref() {
59        Some("js") => AstType::JavaScript,
60        Some("ts") => AstType::TypeScript,
61        _ => {
62            if source_type.is_javascript() {
63                AstType::JavaScript
64            } else {
65                AstType::TypeScript
66            }
67        }
68    };
69
70    (source_type, ast_type)
71}
72
73fn parse<'a>(
74    allocator: &'a Allocator,
75    source_type: SourceType,
76    source_text: &'a str,
77    options: &ParserOptions,
78) -> ParserReturn<'a> {
79    Parser::new(allocator, source_text, source_type)
80        .with_options(ParseOptions {
81            preserve_parens: options.preserve_parens.unwrap_or(true),
82            ..ParseOptions::default()
83        })
84        .parse()
85}
86
87fn parse_with_return(filename: &str, source_text: String, options: &ParserOptions) -> ParseResult {
88    let allocator = Allocator::default();
89    let (source_type, ast_type) = get_source_and_ast_type(filename, options);
90    let ret = parse(&allocator, source_type, &source_text, options);
91
92    let mut program = ret.program;
93    let mut module_record = ret.module_record;
94    let mut diagnostics = ret.errors;
95
96    if options.show_semantic_errors == Some(true) {
97        let semantic_ret = SemanticBuilder::new().with_check_syntax_error(true).build(&program);
98        diagnostics.extend(semantic_ret.errors);
99    }
100
101    let mut errors = OxcError::from_diagnostics(filename, &source_text, diagnostics);
102
103    let comments =
104        convert_utf8_to_utf16(&source_text, &mut program, &mut module_record, &mut errors);
105
106    let program = match ast_type {
107        AstType::JavaScript => program.to_estree_js_json(),
108        AstType::TypeScript => program.to_estree_ts_json(),
109    };
110
111    let module = EcmaScriptModule::from(&module_record);
112
113    ParseResult { program, module, comments, errors }
114}
115
116/// Parse synchronously.
117#[napi]
118pub fn parse_sync(
119    filename: String,
120    source_text: String,
121    options: Option<ParserOptions>,
122) -> ParseResult {
123    let options = options.unwrap_or_default();
124    parse_with_return(&filename, source_text, &options)
125}
126
127pub struct ResolveTask {
128    filename: String,
129    source_text: String,
130    options: ParserOptions,
131}
132
133#[napi]
134impl Task for ResolveTask {
135    type JsValue = ParseResult;
136    type Output = ParseResult;
137
138    fn compute(&mut self) -> napi::Result<Self::Output> {
139        let source_text = mem::take(&mut self.source_text);
140        Ok(parse_with_return(&self.filename, source_text, &self.options))
141    }
142
143    fn resolve(&mut self, _: napi::Env, result: Self::Output) -> napi::Result<Self::JsValue> {
144        Ok(result)
145    }
146}
147
148/// Parse asynchronously.
149///
150/// Note: This function can be slower than `parseSync` due to the overhead of spawning a thread.
151#[napi]
152pub fn parse_async(
153    filename: String,
154    source_text: String,
155    options: Option<ParserOptions>,
156) -> AsyncTask<ResolveTask> {
157    let options = options.unwrap_or_default();
158    AsyncTask::new(ResolveTask { filename, source_text, options })
159}