mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(napi/parser): return esm info (#7602)
The parser now returns import / export statement information, which can be used for parser plugins.
This commit is contained in:
parent
521df425de
commit
7c62a33a06
9 changed files with 1990 additions and 25 deletions
|
|
@ -10,3 +10,31 @@ rule = "run -p rulegen"
|
|||
|
||||
# Build oxlint in release mode
|
||||
oxlint = "build --release --bin oxlint --features allocator"
|
||||
|
||||
# Fix napi breaking in test environment <https://github.com/napi-rs/napi-rs/issues/1005#issuecomment-1011034770>
|
||||
# To be able to run unit tests on macOS, support compilation to 'x86_64-apple-darwin'.
|
||||
[target.'cfg(target_vendor = "apple")']
|
||||
rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup,-no_fixup_chains"]
|
||||
|
||||
# To be able to run unit tests on Linux, support compilation to 'x86_64-unknown-linux-gnu'.
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-C", "link-args=-Wl,--warn-unresolved-symbols"]
|
||||
|
||||
# To be able to run unit tests on Windows, support compilation to 'x86_64-pc-windows-msvc'.
|
||||
# Use Hybrid CRT to reduce the size of the binary (Coming by default with Windows 10 and later versions).
|
||||
[target.'cfg(target_os = "windows")']
|
||||
rustflags = [
|
||||
"-C",
|
||||
"link-args=/FORCE",
|
||||
"-C",
|
||||
"link-args=/NODEFAULTLIB:libucrt.lib",
|
||||
"-C",
|
||||
"link-args=/DEFAULTLIB:ucrt.lib",
|
||||
]
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
[target.i686-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
[target.aarch64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
use napi_derive::napi;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use oxc_span::Span;
|
||||
use oxc_syntax::module_record::{self, ModuleRecord};
|
||||
|
||||
/// Babel Parser Options
|
||||
///
|
||||
/// <https://github.com/babel/babel/blob/v7.26.2/packages/babel-parser/typings/babel-parser.d.ts>
|
||||
#[napi(object)]
|
||||
#[derive(Default)]
|
||||
pub struct ParserOptions {
|
||||
|
|
@ -23,6 +26,7 @@ pub struct ParserOptions {
|
|||
pub struct ParseResult {
|
||||
#[napi(ts_type = "import(\"@oxc-project/types\").Program")]
|
||||
pub program: String,
|
||||
pub module: EcmaScriptModule,
|
||||
pub comments: Vec<Comment>,
|
||||
pub errors: Vec<String>,
|
||||
}
|
||||
|
|
@ -35,3 +39,324 @@ pub struct Comment {
|
|||
pub start: u32,
|
||||
pub end: u32,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct EcmaScriptModule {
|
||||
/// Import Statements.
|
||||
pub static_imports: Vec<StaticImport>,
|
||||
/// Export Statements.
|
||||
pub static_exports: Vec<StaticExport>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ValueSpan {
|
||||
pub value: String,
|
||||
pub start: u32,
|
||||
pub end: u32,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct StaticImport {
|
||||
/// Start of import statement.
|
||||
pub start: u32,
|
||||
/// End of import statement.
|
||||
pub end: u32,
|
||||
/// Import source.
|
||||
///
|
||||
/// ```js
|
||||
/// import { foo } from "mod";
|
||||
/// // ^^^
|
||||
/// ```
|
||||
pub module_request: ValueSpan,
|
||||
/// Import specifiers.
|
||||
///
|
||||
/// Empty for `import "mod"`.
|
||||
pub entries: Vec<ImportEntry>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ImportEntry {
|
||||
/// The name under which the desired binding is exported by the module.
|
||||
///
|
||||
/// ```js
|
||||
/// import { foo } from "mod";
|
||||
/// // ^^^
|
||||
/// import { foo as bar } from "mod";
|
||||
/// // ^^^
|
||||
/// ```
|
||||
pub import_name: ImportName,
|
||||
/// The name that is used to locally access the imported value from within the importing module.
|
||||
/// ```js
|
||||
/// import { foo } from "mod";
|
||||
/// // ^^^
|
||||
/// import { foo as bar } from "mod";
|
||||
/// // ^^^
|
||||
/// ```
|
||||
pub local_name: ValueSpan,
|
||||
|
||||
/// Whether this binding is for a TypeScript type-only import.
|
||||
///
|
||||
/// `true` for the following imports:
|
||||
/// ```ts
|
||||
/// import type { foo } from "mod";
|
||||
/// import { type foo } from "mod";
|
||||
/// ```
|
||||
pub is_type: bool,
|
||||
}
|
||||
|
||||
#[napi(string_enum)]
|
||||
pub enum ImportNameKind {
|
||||
/// `import { x } from "mod"`
|
||||
Name,
|
||||
/// `import * as ns from "mod"`
|
||||
NamespaceObject,
|
||||
/// `import defaultExport from "mod"`
|
||||
Default,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ImportName {
|
||||
pub kind: ImportNameKind,
|
||||
pub name: Option<String>,
|
||||
pub start: Option<u32>,
|
||||
pub end: Option<u32>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct StaticExport {
|
||||
pub start: u32,
|
||||
pub end: u32,
|
||||
pub entries: Vec<ExportEntry>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ExportEntry {
|
||||
pub start: u32,
|
||||
pub end: u32,
|
||||
pub module_request: Option<ValueSpan>,
|
||||
/// The name under which the desired binding is exported by the module`.
|
||||
pub import_name: ExportImportName,
|
||||
/// The name used to export this binding by this module.
|
||||
pub export_name: ExportExportName,
|
||||
/// The name that is used to locally access the exported value from within the importing module.
|
||||
pub local_name: ExportLocalName,
|
||||
}
|
||||
|
||||
#[napi(string_enum)]
|
||||
pub enum ExportImportNameKind {
|
||||
/// `export { name }
|
||||
Name,
|
||||
/// `export * as ns from "mod"`
|
||||
All,
|
||||
/// `export * from "mod"`
|
||||
AllButDefault,
|
||||
/// Does not have a specifier.
|
||||
None,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ExportImportName {
|
||||
pub kind: ExportImportNameKind,
|
||||
pub name: Option<String>,
|
||||
pub start: Option<u32>,
|
||||
pub end: Option<u32>,
|
||||
}
|
||||
|
||||
#[napi(string_enum)]
|
||||
pub enum ExportExportNameKind {
|
||||
/// `export { name }
|
||||
Name,
|
||||
/// `export default expression`
|
||||
Default,
|
||||
/// `export * from "mod"
|
||||
None,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ExportExportName {
|
||||
pub kind: ExportExportNameKind,
|
||||
pub name: Option<String>,
|
||||
pub start: Option<u32>,
|
||||
pub end: Option<u32>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ExportLocalName {
|
||||
pub kind: ExportLocalNameKind,
|
||||
pub name: Option<String>,
|
||||
pub start: Option<u32>,
|
||||
pub end: Option<u32>,
|
||||
}
|
||||
|
||||
#[napi(string_enum)]
|
||||
pub enum ExportLocalNameKind {
|
||||
/// `export { name }
|
||||
Name,
|
||||
/// `export default expression`
|
||||
Default,
|
||||
/// If the exported value is not locally accessible from within the module.
|
||||
/// `export default function () {}`
|
||||
None,
|
||||
}
|
||||
|
||||
impl From<&ModuleRecord<'_>> for EcmaScriptModule {
|
||||
fn from(record: &ModuleRecord<'_>) -> Self {
|
||||
let mut static_imports = record
|
||||
.requested_modules
|
||||
.iter()
|
||||
.flat_map(|(name, requested_modules)| {
|
||||
requested_modules.iter().filter(|m| m.is_import).map(|m| {
|
||||
let entries = record
|
||||
.import_entries
|
||||
.iter()
|
||||
.filter(|e| e.statement_span == m.statement_span)
|
||||
.map(ImportEntry::from)
|
||||
.collect::<Vec<_>>();
|
||||
{
|
||||
StaticImport {
|
||||
start: m.statement_span.start,
|
||||
end: m.statement_span.end,
|
||||
module_request: ValueSpan {
|
||||
value: name.to_string(),
|
||||
start: m.span.start,
|
||||
end: m.span.end,
|
||||
},
|
||||
entries,
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
static_imports.sort_unstable_by_key(|e| e.start);
|
||||
|
||||
let mut static_exports = record
|
||||
.local_export_entries
|
||||
.iter()
|
||||
.chain(record.indirect_export_entries.iter())
|
||||
.chain(record.star_export_entries.iter())
|
||||
.map(|e| (e.statement_span, ExportEntry::from(e)))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.fold(FxHashMap::<Span, Vec<ExportEntry>>::default(), |mut acc, (span, e)| {
|
||||
acc.entry(span).or_default().push(e);
|
||||
acc
|
||||
})
|
||||
.into_iter()
|
||||
.map(|(span, entries)| StaticExport { start: span.start, end: span.end, entries })
|
||||
.collect::<Vec<_>>();
|
||||
static_exports.sort_unstable_by_key(|e| e.start);
|
||||
|
||||
Self { static_imports, static_exports }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&module_record::ImportEntry<'_>> for ImportEntry {
|
||||
fn from(e: &module_record::ImportEntry<'_>) -> Self {
|
||||
Self {
|
||||
import_name: ImportName::from(&e.import_name),
|
||||
local_name: ValueSpan::from(&e.local_name),
|
||||
is_type: e.is_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&module_record::ImportImportName<'_>> for ImportName {
|
||||
fn from(e: &module_record::ImportImportName<'_>) -> Self {
|
||||
let (kind, name, start, end) = match e {
|
||||
module_record::ImportImportName::Name(name_span) => (
|
||||
ImportNameKind::Name,
|
||||
Some(name_span.name.to_string()),
|
||||
Some(name_span.span.start),
|
||||
Some(name_span.span.end),
|
||||
),
|
||||
module_record::ImportImportName::NamespaceObject => {
|
||||
(ImportNameKind::NamespaceObject, None, None, None)
|
||||
}
|
||||
module_record::ImportImportName::Default(span) => {
|
||||
(ImportNameKind::Default, None, Some(span.start), Some(span.end))
|
||||
}
|
||||
};
|
||||
Self { kind, name, start, end }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&module_record::NameSpan<'_>> for ValueSpan {
|
||||
fn from(name_span: &module_record::NameSpan) -> Self {
|
||||
Self {
|
||||
value: name_span.name.to_string(),
|
||||
start: name_span.span.start,
|
||||
end: name_span.span.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&module_record::ExportEntry<'_>> for ExportEntry {
|
||||
fn from(e: &module_record::ExportEntry) -> Self {
|
||||
Self {
|
||||
start: e.span.start,
|
||||
end: e.span.end,
|
||||
module_request: e.module_request.as_ref().map(ValueSpan::from),
|
||||
import_name: ExportImportName::from(&e.import_name),
|
||||
export_name: ExportExportName::from(&e.export_name),
|
||||
local_name: ExportLocalName::from(&e.local_name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&module_record::ExportImportName<'_>> for ExportImportName {
|
||||
fn from(e: &module_record::ExportImportName<'_>) -> Self {
|
||||
let (kind, name, start, end) = match e {
|
||||
module_record::ExportImportName::Name(name_span) => (
|
||||
ExportImportNameKind::Name,
|
||||
Some(name_span.name.to_string()),
|
||||
Some(name_span.span.start),
|
||||
Some(name_span.span.end),
|
||||
),
|
||||
module_record::ExportImportName::All => (ExportImportNameKind::All, None, None, None),
|
||||
module_record::ExportImportName::AllButDefault => {
|
||||
(ExportImportNameKind::AllButDefault, None, None, None)
|
||||
}
|
||||
module_record::ExportImportName::Null => (ExportImportNameKind::None, None, None, None),
|
||||
};
|
||||
Self { kind, name, start, end }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&module_record::ExportExportName<'_>> for ExportExportName {
|
||||
fn from(e: &module_record::ExportExportName<'_>) -> Self {
|
||||
let (kind, name, start, end) = match e {
|
||||
module_record::ExportExportName::Name(name_span) => (
|
||||
ExportExportNameKind::Name,
|
||||
Some(name_span.name.to_string()),
|
||||
Some(name_span.span.start),
|
||||
Some(name_span.span.end),
|
||||
),
|
||||
module_record::ExportExportName::Default(span) => {
|
||||
(ExportExportNameKind::Default, None, Some(span.start), Some(span.end))
|
||||
}
|
||||
module_record::ExportExportName::Null => (ExportExportNameKind::None, None, None, None),
|
||||
};
|
||||
Self { kind, name, start, end }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&module_record::ExportLocalName<'_>> for ExportLocalName {
|
||||
fn from(e: &module_record::ExportLocalName<'_>) -> Self {
|
||||
let (kind, name, start, end) = match e {
|
||||
module_record::ExportLocalName::Name(name_span) => (
|
||||
ExportLocalNameKind::Name,
|
||||
Some(name_span.name.to_string()),
|
||||
Some(name_span.span.start),
|
||||
Some(name_span.span.end),
|
||||
),
|
||||
module_record::ExportLocalName::Default(name_span) => (
|
||||
ExportLocalNameKind::Default,
|
||||
Some(name_span.name.to_string()),
|
||||
Some(name_span.span.start),
|
||||
Some(name_span.span.end),
|
||||
),
|
||||
module_record::ExportLocalName::Null => (ExportLocalNameKind::None, None, None, None),
|
||||
};
|
||||
Self { kind, name, start, end }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,11 +166,11 @@ pub struct ImportEntry<'a> {
|
|||
/// `ImportName` For `ImportEntry`
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ImportImportName<'a> {
|
||||
/// Name
|
||||
/// `import { x } from "mod"`
|
||||
Name(NameSpan<'a>),
|
||||
/// Namespace Object
|
||||
/// `import * as ns from "mod"`
|
||||
NamespaceObject,
|
||||
/// Default
|
||||
/// `import defaultExport from "mod"`
|
||||
Default(Span),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -361,6 +361,10 @@ if (!nativeBinding) {
|
|||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
module.exports.ExportExportNameKind = nativeBinding.ExportExportNameKind
|
||||
module.exports.ExportImportNameKind = nativeBinding.ExportImportNameKind
|
||||
module.exports.ExportLocalNameKind = nativeBinding.ExportLocalNameKind
|
||||
module.exports.ImportNameKind = nativeBinding.ImportNameKind
|
||||
module.exports.parseAsync = nativeBinding.parseAsync
|
||||
module.exports.parseSync = nativeBinding.parseSync
|
||||
module.exports.parseWithoutReturn = nativeBinding.parseWithoutReturn
|
||||
|
|
|
|||
180
napi/parser/index.d.ts
vendored
180
napi/parser/index.d.ts
vendored
|
|
@ -9,24 +9,143 @@ export interface Comment {
|
|||
end: number
|
||||
}
|
||||
|
||||
export interface EcmaScriptModule {
|
||||
/** Import Statements. */
|
||||
staticImports: Array<StaticImport>
|
||||
/** Export Statements. */
|
||||
staticExports: Array<StaticExport>
|
||||
}
|
||||
|
||||
export interface ExportEntry {
|
||||
start: number
|
||||
end: number
|
||||
moduleRequest?: ValueSpan
|
||||
/** The name under which the desired binding is exported by the module`. */
|
||||
importName: ExportImportName
|
||||
/** The name used to export this binding by this module. */
|
||||
exportName: ExportExportName
|
||||
/** The name that is used to locally access the exported value from within the importing module. */
|
||||
localName: ExportLocalName
|
||||
}
|
||||
|
||||
export interface ExportExportName {
|
||||
kind: ExportExportNameKind
|
||||
name?: string
|
||||
start?: number
|
||||
end?: number
|
||||
}
|
||||
|
||||
export declare const enum ExportExportNameKind {
|
||||
/** `export { name } */
|
||||
Name = 'Name',
|
||||
/** `export default expression` */
|
||||
Default = 'Default',
|
||||
/** `export * from "mod" */
|
||||
None = 'None'
|
||||
}
|
||||
|
||||
export interface ExportImportName {
|
||||
kind: ExportImportNameKind
|
||||
name?: string
|
||||
start?: number
|
||||
end?: number
|
||||
}
|
||||
|
||||
export declare const enum ExportImportNameKind {
|
||||
/** `export { name } */
|
||||
Name = 'Name',
|
||||
/** `export * as ns from "mod"` */
|
||||
All = 'All',
|
||||
/** `export * from "mod"` */
|
||||
AllButDefault = 'AllButDefault',
|
||||
/** Does not have a specifier. */
|
||||
None = 'None'
|
||||
}
|
||||
|
||||
export interface ExportLocalName {
|
||||
kind: ExportLocalNameKind
|
||||
name?: string
|
||||
start?: number
|
||||
end?: number
|
||||
}
|
||||
|
||||
export declare const enum ExportLocalNameKind {
|
||||
/** `export { name } */
|
||||
Name = 'Name',
|
||||
/** `export default expression` */
|
||||
Default = 'Default',
|
||||
/**
|
||||
* If the exported value is not locally accessible from within the module.
|
||||
* `export default function () {}`
|
||||
*/
|
||||
None = 'None'
|
||||
}
|
||||
|
||||
export interface ImportEntry {
|
||||
/**
|
||||
* The name under which the desired binding is exported by the module.
|
||||
*
|
||||
* ```js
|
||||
* import { foo } from "mod";
|
||||
* // ^^^
|
||||
* import { foo as bar } from "mod";
|
||||
* // ^^^
|
||||
* ```
|
||||
*/
|
||||
importName: ImportName
|
||||
/**
|
||||
* The name that is used to locally access the imported value from within the importing module.
|
||||
* ```js
|
||||
* import { foo } from "mod";
|
||||
* // ^^^
|
||||
* import { foo as bar } from "mod";
|
||||
* // ^^^
|
||||
* ```
|
||||
*/
|
||||
localName: ValueSpan
|
||||
/**
|
||||
* Whether this binding is for a TypeScript type-only import.
|
||||
*
|
||||
* `true` for the following imports:
|
||||
* ```ts
|
||||
* import type { foo } from "mod";
|
||||
* import { type foo } from "mod";
|
||||
* ```
|
||||
*/
|
||||
isType: boolean
|
||||
}
|
||||
|
||||
export interface ImportName {
|
||||
kind: ImportNameKind
|
||||
name?: string
|
||||
start?: number
|
||||
end?: number
|
||||
}
|
||||
|
||||
export declare const enum ImportNameKind {
|
||||
/** `import { x } from "mod"` */
|
||||
Name = 'Name',
|
||||
/** `import * as ns from "mod"` */
|
||||
NamespaceObject = 'NamespaceObject',
|
||||
/** `import defaultExport from "mod"` */
|
||||
Default = 'Default'
|
||||
}
|
||||
|
||||
/**
|
||||
* # Panics
|
||||
* Parse asynchronously.
|
||||
*
|
||||
* * Tokio crashes
|
||||
* Note: This function can be slower than `parseSync` due to the overhead of spawning a thread.
|
||||
*/
|
||||
export declare function parseAsync(sourceText: string, options?: ParserOptions | undefined | null): Promise<ParseResult>
|
||||
|
||||
export interface ParseResult {
|
||||
program: import("@oxc-project/types").Program
|
||||
module: EcmaScriptModule
|
||||
comments: Array<Comment>
|
||||
errors: Array<string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Babel Parser Options
|
||||
*
|
||||
* <https://github.com/babel/babel/blob/v7.26.2/packages/babel-parser/typings/babel-parser.d.ts>
|
||||
*/
|
||||
/** Babel Parser Options */
|
||||
export interface ParserOptions {
|
||||
sourceType?: 'script' | 'module' | 'unambiguous' | undefined
|
||||
sourceFilename?: string
|
||||
|
|
@ -42,22 +161,47 @@ export interface ParserOptions {
|
|||
preserveParens?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* # Panics
|
||||
*
|
||||
* * File extension is invalid
|
||||
* * Serde JSON serialization
|
||||
*/
|
||||
/** Parse synchronously. */
|
||||
export declare function parseSync(sourceText: string, options?: ParserOptions | undefined | null): ParseResult
|
||||
|
||||
/**
|
||||
* Parse without returning anything.
|
||||
*
|
||||
* This is for benchmark purposes such as measuring napi communication overhead.
|
||||
*
|
||||
* # Panics
|
||||
*
|
||||
* * File extension is invalid
|
||||
* * Serde JSON serialization
|
||||
*/
|
||||
export declare function parseWithoutReturn(sourceText: string, options?: ParserOptions | undefined | null): void
|
||||
|
||||
export interface StaticExport {
|
||||
start: number
|
||||
end: number
|
||||
entries: Array<ExportEntry>
|
||||
}
|
||||
|
||||
export interface StaticImport {
|
||||
/** Start of import statement. */
|
||||
start: number
|
||||
/** End of import statement. */
|
||||
end: number
|
||||
/**
|
||||
* Import source.
|
||||
*
|
||||
* ```js
|
||||
* import { foo } from "mod";
|
||||
* // ^^^
|
||||
* ```
|
||||
*/
|
||||
moduleRequest: ValueSpan
|
||||
/**
|
||||
* Import specifiers.
|
||||
*
|
||||
* Empty for `import "mod"`.
|
||||
*/
|
||||
entries: Array<ImportEntry>
|
||||
}
|
||||
|
||||
export interface ValueSpan {
|
||||
value: string
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"build": "napi build --platform --release --js bindings.js",
|
||||
"build-dev": "napi build --platform --js bindings.js",
|
||||
"test": "vitest --typecheck run ./test"
|
||||
},
|
||||
"napi": {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use oxc::{
|
|||
allocator::Allocator,
|
||||
ast::CommentKind,
|
||||
diagnostics::{Error, NamedSource},
|
||||
napi::parse::{Comment, ParseResult, ParserOptions},
|
||||
napi::parse::{Comment, EcmaScriptModule, ParseResult, ParserOptions},
|
||||
parser::{ParseOptions, Parser, ParserReturn},
|
||||
span::SourceType,
|
||||
};
|
||||
|
|
@ -81,7 +81,8 @@ fn parse_with_return(source_text: &str, options: &ParserOptions) -> ParseResult
|
|||
})
|
||||
.collect::<Vec<Comment>>();
|
||||
|
||||
ParseResult { program, comments, errors }
|
||||
let module = EcmaScriptModule::from(&ret.module_record);
|
||||
ParseResult { program, module, comments, errors }
|
||||
}
|
||||
|
||||
/// Parse synchronously.
|
||||
|
|
|
|||
1398
napi/parser/test/__snapshots__/esm.test.ts.snap
Normal file
1398
napi/parser/test/__snapshots__/esm.test.ts.snap
Normal file
File diff suppressed because it is too large
Load diff
64
napi/parser/test/esm.test.ts
Normal file
64
napi/parser/test/esm.test.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { describe, expect, it, test } from 'vitest';
|
||||
|
||||
import * as oxc from '../index.js';
|
||||
|
||||
describe('esm', () => {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#syntax
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export#syntax
|
||||
let code = `
|
||||
import defaultExport from "module-name";
|
||||
import * as name from "module-name";
|
||||
import { export1 } from "module-name";
|
||||
import { export1 as alias1 } from "module-name";
|
||||
import { default as alias } from "module-name";
|
||||
import { export1, export2 } from "module-name";
|
||||
import { export1, export2 as alias2, /* … */ } from "module-name";
|
||||
import { "string name" as alias } from "module-name";
|
||||
import defaultExport, { export1, /* … */ } from "module-name";
|
||||
import defaultExport, * as name from "module-name";
|
||||
import "module-name";
|
||||
|
||||
export let name1, name2/*, … */; // also var
|
||||
export const name1 = 1, name2 = 2/*, … */; // also var, let
|
||||
export function functionName() { /* … */ }
|
||||
export class ClassName { /* … */ }
|
||||
export function* generatorFunctionName() { /* … */ }
|
||||
export const { name1, name2: bar } = o;
|
||||
export const [ name1, name2 ] = array;
|
||||
|
||||
export { name1, /* …, */ nameN };
|
||||
export { variable1 as name1, variable2 as name2, /* …, */ nameN };
|
||||
export { variable1 as "string name" };
|
||||
export { name1 as default /*, … */ };
|
||||
|
||||
export default expression;
|
||||
export default function functionName() { /* … */ }
|
||||
export default class ClassName { /* … */ }
|
||||
export default function* generatorFunctionName() { /* … */ }
|
||||
export default function () { /* … */ }
|
||||
export default class { /* … */ }
|
||||
export default function* () { /* … */ }
|
||||
|
||||
export * from "module-name";
|
||||
export * as name1 from "module-name";
|
||||
export { name1, /* …, */ nameN } from "module-name";
|
||||
export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name";
|
||||
export { default, /* …, */ } from "module-name";
|
||||
export { default as name1 } from "module-name";
|
||||
`.split('\n').map((s) => s.trim()).filter(Boolean);
|
||||
|
||||
test.each(code)('%s', (s) => {
|
||||
const ret = oxc.parseSync(s, { sourceFilename: 'test.ts' });
|
||||
expect(ret.program.body.length).toBeGreaterThan(0);
|
||||
expect(ret.errors.length).toBe(0);
|
||||
expect(JSON.stringify(ret.module, null, 2)).toMatchSnapshot();
|
||||
if (s.startsWith('import')) {
|
||||
expect(ret.module.staticImports.length).toBe(1);
|
||||
expect(ret.module.staticExports.length).toBe(0);
|
||||
}
|
||||
if (s.startsWith('export')) {
|
||||
expect(ret.module.staticImports.length).toBe(0);
|
||||
expect(ret.module.staticExports.length).toBe(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue