oxc/napi/parser/src/module_lexer.rs
Boshen 32303b20fb
New tool: oxc_module_lexer (#2650)
# 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).
2024-03-09 23:23:55 +08:00

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()
}