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(
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    // Note: We intentionally don't import `generated/derive_estree.rs`. It's not needed.
35    #[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            // Add hashbang to start of comments
97            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            // Note: `@typescript-eslint/parser` ignores hashbangs,
113            // despite appearances to the contrary in AST explorers.
114            // So we ignore them too.
115            // See: https://github.com/typescript-eslint/typescript-eslint/issues/6500
116            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/// Parse synchronously.
126#[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/// Parse asynchronously.
158///
159/// Note: This function can be slower than `parseSync` due to the overhead of spawning a thread.
160#[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}