mirror of
https://github.com/danbulant/oxc
synced 2026-05-20 12:48:38 +00:00
# Oxc Module Lexer This is not a lexer. The name "lexer" is used for easier recognition. ## [es-module-lexer](https://github.com/guybedford/es-module-lexer) Outputs the list of exports and locations of import specifiers, including dynamic import and import meta handling. Does not have any [limitations](https://github.com/guybedford/es-module-lexer?tab=readme-ov-file#limitations) mentioned in `es-module-lexer`. I'll also work on the following cases to make this feature complete. - [ ] get imported variables https://github.com/guybedford/es-module-lexer/issues/163 - [ ] track star exports as imports as well https://github.com/guybedford/es-module-lexer/issues/76 - [ ] TypeScript specific syntax - [ ] TypeScript `type` import / export keyword ## [cjs-module-lexer](https://github.com/nodejs/cjs-module-lexer) - [ ] TODO ## Benchmark This is 2 times slower than `es-module-lexer`, but will be significantly faster when TypeScript is processed. The difference is around 10ms vs 20ms on a large file (700k).
146 lines
3.9 KiB
Rust
146 lines
3.9 KiB
Rust
use napi_derive::napi;
|
|
|
|
use oxc_allocator::Allocator;
|
|
use oxc_module_lexer::ImportType;
|
|
|
|
use crate::{parse, ParserOptions};
|
|
|
|
#[napi(object)]
|
|
pub struct ImportSpecifier {
|
|
/// Module name
|
|
///
|
|
/// To handle escape sequences in specifier strings, the .n field of imported specifiers will be provided where possible.
|
|
///
|
|
/// For dynamic import expressions, this field will be empty if not a valid JS string.
|
|
pub n: Option<String>,
|
|
|
|
/// Start of module specifier
|
|
pub s: u32,
|
|
|
|
/// End of module specifier
|
|
pub e: u32,
|
|
|
|
/// Start of import statement
|
|
pub ss: u32,
|
|
|
|
/// End of import statement
|
|
pub se: u32,
|
|
|
|
/// Import Type
|
|
/// * If this import keyword is a dynamic import, this is the start value.
|
|
/// * If this import keyword is a static import, this is -1.
|
|
/// * If this import keyword is an import.meta expression, this is -2.
|
|
pub d: i64,
|
|
|
|
/// If this import has an import assertion, this is the start value
|
|
/// Otherwise this is `-1`.
|
|
pub a: i64,
|
|
}
|
|
|
|
#[napi(object)]
|
|
pub struct ExportSpecifier {
|
|
/// Exported name
|
|
pub n: String,
|
|
|
|
/// Local name, or undefined.
|
|
pub ln: Option<String>,
|
|
|
|
/// Start of exported name
|
|
pub s: u32,
|
|
|
|
/// End of exported name
|
|
pub e: u32,
|
|
|
|
/// Start of local name
|
|
pub ls: Option<u32>,
|
|
|
|
/// End of local name
|
|
pub le: Option<u32>,
|
|
}
|
|
|
|
impl<'a> From<oxc_module_lexer::ImportSpecifier<'a>> for ImportSpecifier {
|
|
#[allow(clippy::cast_lossless)]
|
|
fn from(i: oxc_module_lexer::ImportSpecifier) -> Self {
|
|
Self {
|
|
n: i.n.map(|n| n.to_string()),
|
|
s: i.s,
|
|
e: i.e,
|
|
ss: i.ss,
|
|
se: i.se,
|
|
d: match i.d {
|
|
ImportType::DynamicImport(start) => start as i64,
|
|
ImportType::StaticImport => -1,
|
|
ImportType::ImportMeta => -2,
|
|
},
|
|
a: i.a.map_or(-1, |a| a as i64),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<oxc_module_lexer::ExportSpecifier<'a>> for ExportSpecifier {
|
|
fn from(e: oxc_module_lexer::ExportSpecifier) -> Self {
|
|
Self {
|
|
n: e.n.to_string(),
|
|
ln: e.ln.map(|ln| ln.to_string()),
|
|
s: e.s,
|
|
e: e.e,
|
|
ls: e.ls,
|
|
le: e.le,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[napi(object)]
|
|
pub struct ModuleLexer {
|
|
pub imports: Vec<ImportSpecifier>,
|
|
|
|
pub exports: Vec<ExportSpecifier>,
|
|
|
|
/// ESM syntax detection
|
|
///
|
|
/// The use of ESM syntax: import / export statements and `import.meta`
|
|
pub has_module_syntax: bool,
|
|
|
|
/// Facade modules that only use import / export syntax
|
|
pub facade: bool,
|
|
}
|
|
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
fn module_lexer(source_text: String, options: Option<ParserOptions>) -> ModuleLexer {
|
|
let options = options.unwrap_or_default();
|
|
let allocator = Allocator::default();
|
|
let ret = parse(&allocator, &source_text, &options);
|
|
let module_lexer = oxc_module_lexer::ModuleLexer::new().build(&ret.program);
|
|
let imports = module_lexer.imports.into_iter().map(ImportSpecifier::from).collect();
|
|
let exports = module_lexer.exports.into_iter().map(ExportSpecifier::from).collect();
|
|
ModuleLexer {
|
|
imports,
|
|
exports,
|
|
has_module_syntax: module_lexer.has_module_syntax,
|
|
facade: module_lexer.facade,
|
|
}
|
|
}
|
|
|
|
/// Outputs the list of exports and locations of import specifiers,
|
|
/// including dynamic import and import meta handling.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// * File extension is invalid
|
|
#[napi]
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
pub fn module_lexer_sync(source_text: String, options: Option<ParserOptions>) -> ModuleLexer {
|
|
module_lexer(source_text, options)
|
|
}
|
|
|
|
/// # Panics
|
|
///
|
|
/// * Tokio crashes
|
|
#[napi]
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
pub async fn module_lexer_async(
|
|
source_text: String,
|
|
options: Option<ParserOptions>,
|
|
) -> ModuleLexer {
|
|
tokio::spawn(async move { module_lexer(source_text, options) }).await.unwrap()
|
|
}
|