Skip to content

Commit

Permalink
feat(napi/parser): return esm info (#7602)
Browse files Browse the repository at this point in the history
The parser now returns import / export statement information, which can be used for parser plugins.
  • Loading branch information
Boshen committed Dec 3, 2024
1 parent 521df42 commit 7c62a33
Show file tree
Hide file tree
Showing 9 changed files with 1,990 additions and 25 deletions.
28 changes: 28 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
329 changes: 327 additions & 2 deletions crates/oxc/src/napi/parse.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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>,
}
Expand All @@ -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 }
}
}
6 changes: 3 additions & 3 deletions crates/oxc_syntax/src/module_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down
Loading

0 comments on commit 7c62a33

Please sign in to comment.