oxc_parser_napi/
lib.rs

1use std::mem;
2
3use napi::{Task, bindgen_prelude::AsyncTask};
4use napi_derive::napi;
5
6use oxc::{
7    allocator::Allocator,
8    parser::{ParseOptions, Parser, ParserReturn},
9    semantic::SemanticBuilder,
10    span::SourceType,
11};
12use oxc_napi::{Comment, OxcError, convert_utf8_to_utf16, get_source_type};
13
14mod convert;
15mod types;
16pub use types::*;
17
18#[cfg(all(
19    feature = "allocator",
20    not(any(target_arch = "arm", target_os = "freebsd", target_family = "wasm"))
21))]
22#[global_allocator]
23static ALLOC: mimalloc_safe::MiMalloc = mimalloc_safe::MiMalloc;
24
25// Raw transfer is only supported on 64-bit little-endian systems.
26// Don't include raw transfer code on other platforms (notably WASM32).
27// `raw_transfer_types` still needs to be compiled, as `assert_layouts` refers to those types,
28// but it's all dead code on unsupported platforms, and will be excluded from binary.
29#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
30mod raw_transfer;
31mod raw_transfer_types;
32#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
33pub use raw_transfer::{get_buffer_offset, parse_raw, parse_raw_sync};
34
35/// Returns `true` if raw transfer is supported on this platform.
36#[napi]
37pub fn raw_transfer_supported() -> bool {
38    cfg!(all(target_pointer_width = "64", target_endian = "little"))
39}
40
41mod generated {
42    // Note: We intentionally don't import `generated/derive_estree.rs`. It's not needed.
43    #[cfg(debug_assertions)]
44    mod assert_layouts;
45    #[cfg(all(target_pointer_width = "64", target_endian = "little"))]
46    pub mod raw_transfer_constants;
47}
48#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
49use generated::raw_transfer_constants;
50
51#[derive(Clone, Copy, PartialEq, Eq)]
52enum AstType {
53    JavaScript,
54    TypeScript,
55}
56
57fn get_ast_type(source_type: SourceType, options: &ParserOptions) -> AstType {
58    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
71fn parse_impl<'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: &str, options: &ParserOptions) -> ParseResult {
86    let allocator = Allocator::default();
87    let source_type =
88        get_source_type(filename, options.lang.as_deref(), options.source_type.as_deref());
89    let ast_type = get_ast_type(source_type, options);
90    let ranges = options.range.unwrap_or(false);
91    let ret = parse_impl(&allocator, source_type, source_text, options);
92
93    let mut program = ret.program;
94    let mut module_record = ret.module_record;
95    let mut diagnostics = ret.errors;
96
97    if options.show_semantic_errors == Some(true) {
98        let semantic_ret = SemanticBuilder::new().with_check_syntax_error(true).build(&program);
99        diagnostics.extend(semantic_ret.errors);
100    }
101
102    let mut errors = OxcError::from_diagnostics(filename, source_text, diagnostics);
103
104    let mut comments =
105        convert_utf8_to_utf16(source_text, &mut program, &mut module_record, &mut errors);
106
107    let program_and_fixes = match ast_type {
108        AstType::JavaScript => {
109            // Add hashbang to start of comments
110            if let Some(hashbang) = &program.hashbang {
111                comments.insert(
112                    0,
113                    Comment {
114                        r#type: "Line".to_string(),
115                        value: hashbang.value.to_string(),
116                        start: hashbang.span.start,
117                        end: hashbang.span.end,
118                    },
119                );
120            }
121
122            program.to_estree_js_json_with_fixes(ranges)
123        }
124        AstType::TypeScript => {
125            // Note: `@typescript-eslint/parser` ignores hashbangs,
126            // despite appearances to the contrary in AST explorers.
127            // So we ignore them too.
128            // See: https://github.com/typescript-eslint/typescript-eslint/issues/6500
129            program.to_estree_ts_json_with_fixes(ranges)
130        }
131    };
132
133    let module = EcmaScriptModule::from(&module_record);
134
135    ParseResult { program_and_fixes, module, comments, errors }
136}
137
138/// Parse synchronously.
139#[napi]
140#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)]
141pub fn parse_sync(
142    filename: String,
143    source_text: String,
144    options: Option<ParserOptions>,
145) -> ParseResult {
146    let options = options.unwrap_or_default();
147    parse_with_return(&filename, &source_text, &options)
148}
149
150pub struct ResolveTask {
151    filename: String,
152    source_text: String,
153    options: ParserOptions,
154}
155
156#[napi]
157impl Task for ResolveTask {
158    type JsValue = ParseResult;
159    type Output = ParseResult;
160
161    fn compute(&mut self) -> napi::Result<Self::Output> {
162        let source_text = mem::take(&mut self.source_text);
163        Ok(parse_with_return(&self.filename, &source_text, &self.options))
164    }
165
166    fn resolve(&mut self, _: napi::Env, result: Self::Output) -> napi::Result<Self::JsValue> {
167        Ok(result)
168    }
169}
170
171/// Parse asynchronously.
172///
173/// Note: This function can be slower than `parseSync` due to the overhead of spawning a thread.
174#[napi]
175pub fn parse(
176    filename: String,
177    source_text: String,
178    options: Option<ParserOptions>,
179) -> AsyncTask<ResolveTask> {
180    let options = options.unwrap_or_default();
181    AsyncTask::new(ResolveTask { filename, source_text, options })
182}