mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32:10 +00:00
Pure refactor. Re-order imports for clarity: 1. `std` 2. External crates 3. `oxc_*` crates 4. Current crate `use crate::...` 5. Super `use super::...` 6. Local modules This order is from "furthest away" to "closest". This makes it clearer to see what is coming from where. `cargo +nightly fmt` (#7877) did a lot of the work, but unfortunately `rustfmt` does not have an option to (a) put workspace crates in a separate block from external crates and (b) move `mod` statements to after `use` statements.
418 lines
12 KiB
Rust
418 lines
12 KiB
Rust
//! [ECMAScript Module Record](https://tc39.es/ecma262/#sec-abstract-module-records)
|
|
|
|
use rustc_hash::FxHashMap;
|
|
|
|
use oxc_allocator::{Allocator, Vec};
|
|
use oxc_span::{Atom, Span};
|
|
|
|
/// ESM Module Record
|
|
///
|
|
/// All data inside this data structure are for ESM, no commonjs data is allowed.
|
|
///
|
|
/// See
|
|
/// * <https://tc39.es/ecma262/#table-additional-fields-of-source-text-module-records>
|
|
/// * <https://tc39.es/ecma262/#cyclic-module-record>
|
|
#[derive(Debug)]
|
|
pub struct ModuleRecord<'a> {
|
|
/// This module has ESM syntax: `import` and `export`.
|
|
pub has_module_syntax: bool,
|
|
|
|
/// `[[RequestedModules]]`
|
|
///
|
|
/// A List of all the ModuleSpecifier strings used by the module represented by this record to request the importation of a module. The List is in source text occurrence order.
|
|
///
|
|
/// Module requests from:
|
|
/// import ImportClause FromClause
|
|
/// import ModuleSpecifier
|
|
/// export ExportFromClause FromClause
|
|
///
|
|
/// Keyed by ModuleSpecifier, valued by all node occurrences
|
|
pub requested_modules: FxHashMap<Atom<'a>, Vec<'a, RequestedModule>>,
|
|
|
|
/// `[[ImportEntries]]`
|
|
///
|
|
/// A List of ImportEntry records derived from the code of this module
|
|
pub import_entries: Vec<'a, ImportEntry<'a>>,
|
|
|
|
/// `[[LocalExportEntries]]`
|
|
///
|
|
/// A List of [`ExportEntry`] records derived from the code of this module
|
|
/// that correspond to declarations that occur within the module
|
|
pub local_export_entries: Vec<'a, ExportEntry<'a>>,
|
|
|
|
/// `[[IndirectExportEntries]]`
|
|
///
|
|
/// A List of [`ExportEntry`] records derived from the code of this module
|
|
/// that correspond to reexported imports that occur within the module
|
|
/// or exports from `export * as namespace` declarations.
|
|
pub indirect_export_entries: Vec<'a, ExportEntry<'a>>,
|
|
|
|
/// `[[StarExportEntries]]`
|
|
///
|
|
/// A List of [`ExportEntry`] records derived from the code of this module
|
|
/// that correspond to `export *` declarations that occur within the module,
|
|
/// not including `export * as namespace` declarations.
|
|
pub star_export_entries: Vec<'a, ExportEntry<'a>>,
|
|
|
|
/// Local exported bindings
|
|
pub exported_bindings: FxHashMap<Atom<'a>, Span>,
|
|
|
|
/// Span position of `import.meta`.
|
|
pub import_metas: Vec<'a, Span>,
|
|
}
|
|
|
|
impl<'a> ModuleRecord<'a> {
|
|
/// Constructor
|
|
pub fn new(allocator: &'a Allocator) -> Self {
|
|
Self {
|
|
has_module_syntax: false,
|
|
requested_modules: FxHashMap::default(),
|
|
import_entries: Vec::new_in(allocator),
|
|
local_export_entries: Vec::new_in(allocator),
|
|
indirect_export_entries: Vec::new_in(allocator),
|
|
star_export_entries: Vec::new_in(allocator),
|
|
exported_bindings: FxHashMap::default(),
|
|
import_metas: Vec::new_in(allocator),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Name and Span
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct NameSpan<'a> {
|
|
/// Name
|
|
pub name: Atom<'a>,
|
|
|
|
/// Span
|
|
pub span: Span,
|
|
}
|
|
|
|
impl<'a> NameSpan<'a> {
|
|
/// Constructor
|
|
pub fn new(name: Atom<'a>, span: Span) -> Self {
|
|
Self { name, span }
|
|
}
|
|
}
|
|
|
|
/// [`ImportEntry`](https://tc39.es/ecma262/#importentry-record)
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```ts
|
|
/// // _ local_name
|
|
/// import v from "mod";
|
|
/// // ^^^ module_request
|
|
///
|
|
/// // ____ is_type will be `true`
|
|
/// import type { foo as bar } from "mod";
|
|
/// // import_name^^^ ^^^ local_name
|
|
///
|
|
/// import * as ns from "mod";
|
|
/// ```
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ImportEntry<'a> {
|
|
/// Span of the import statement.
|
|
pub statement_span: Span,
|
|
|
|
/// String value of the ModuleSpecifier of the ImportDeclaration.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```ts
|
|
/// import { foo } from "mod";
|
|
/// // ^^^
|
|
/// ```
|
|
pub module_request: NameSpan<'a>,
|
|
|
|
/// The name under which the desired binding is exported by the module identified by `[[ModuleRequest]]`.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```ts
|
|
/// import { foo } from "mod";
|
|
/// // ^^^
|
|
/// import { foo as bar } from "mod";
|
|
/// // ^^^
|
|
/// ```
|
|
pub import_name: ImportImportName<'a>,
|
|
|
|
/// The name that is used to locally access the imported value from within the importing module.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```ts
|
|
/// import { foo } from "mod";
|
|
/// // ^^^
|
|
/// import { foo as bar } from "mod";
|
|
/// // ^^^
|
|
/// ```
|
|
pub local_name: NameSpan<'a>,
|
|
|
|
/// Whether this binding is for a TypeScript type-only import. This is a non-standard field.
|
|
/// When creating a [`ModuleRecord`] for a JavaScript file, this will always be false.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// `is_type` will be `true` for the following imports:
|
|
/// ```ts
|
|
/// import type { foo } from "mod";
|
|
/// import { type foo } from "mod";
|
|
/// ```
|
|
///
|
|
/// and will be `false` for these imports:
|
|
/// ```ts
|
|
/// import { foo } from "mod";
|
|
/// import { foo as type } from "mod";
|
|
/// ```
|
|
pub is_type: bool,
|
|
}
|
|
|
|
/// `ImportName` For `ImportEntry`
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum ImportImportName<'a> {
|
|
/// `import { x } from "mod"`
|
|
Name(NameSpan<'a>),
|
|
/// `import * as ns from "mod"`
|
|
NamespaceObject,
|
|
/// `import defaultExport from "mod"`
|
|
Default(Span),
|
|
}
|
|
|
|
impl ImportImportName<'_> {
|
|
/// Is `default`
|
|
pub fn is_default(&self) -> bool {
|
|
matches!(self, Self::Default(_))
|
|
}
|
|
|
|
/// Is namespace
|
|
pub fn is_namespace_object(&self) -> bool {
|
|
matches!(self, Self::NamespaceObject)
|
|
}
|
|
}
|
|
|
|
/// [`ExportEntry`](https://tc39.es/ecma262/#exportentry-record)
|
|
///
|
|
/// Describes a single exported binding from a module. Named export statements that contain more
|
|
/// than one binding produce multiple ExportEntry records.
|
|
///
|
|
/// ## Examples
|
|
///
|
|
/// ```ts
|
|
/// // foo's ExportEntry nas no `module_request` or `import_name.
|
|
/// // ___ local_name
|
|
/// export { foo };
|
|
/// // ^^^ export_name. Since there's no alias, it's the same as local_name.
|
|
///
|
|
/// // re-exports do not produce local bindings, so `local_name` is null.
|
|
/// // ___ import_name __ module_request
|
|
/// export { foo as bar } from "mod";
|
|
/// // ^^^ export_name
|
|
///
|
|
/// ```
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub struct ExportEntry<'a> {
|
|
/// Span of the import statement.
|
|
pub statement_span: Span,
|
|
|
|
/// Span for the entire export entry
|
|
pub span: Span,
|
|
|
|
/// The String value of the ModuleSpecifier of the ExportDeclaration.
|
|
/// null if the ExportDeclaration does not have a ModuleSpecifier.
|
|
pub module_request: Option<NameSpan<'a>>,
|
|
|
|
/// The name under which the desired binding is exported by the module identified by `[[ModuleRequest]]`.
|
|
/// null if the ExportDeclaration does not have a ModuleSpecifier.
|
|
/// "all" is used for `export * as ns from "mod"`` declarations.
|
|
/// "all-but-default" is used for `export * from "mod" declarations`.
|
|
pub import_name: ExportImportName<'a>,
|
|
|
|
/// The name used to export this binding by this module.
|
|
pub export_name: ExportExportName<'a>,
|
|
|
|
/// The name that is used to locally access the exported value from within the importing module.
|
|
/// null if the exported value is not locally accessible from within the module.
|
|
pub local_name: ExportLocalName<'a>,
|
|
|
|
/// Whether the export is a TypeScript `export type`.
|
|
///
|
|
/// Examples:
|
|
///
|
|
/// ```ts
|
|
/// export type * from 'mod'
|
|
/// export type * as ns from 'mod'
|
|
/// export type { foo }
|
|
/// export { type foo }
|
|
/// export type { foo } from 'mod'
|
|
/// ```
|
|
pub is_type: bool,
|
|
}
|
|
|
|
/// `ImportName` for `ExportEntry`
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub enum ExportImportName<'a> {
|
|
/// Name
|
|
Name(NameSpan<'a>),
|
|
/// all is used for export * as ns from "mod" declarations.
|
|
All,
|
|
/// all-but-default is used for export * from "mod" declarations.
|
|
AllButDefault,
|
|
/// the ExportDeclaration does not have a ModuleSpecifier
|
|
#[default]
|
|
Null,
|
|
}
|
|
|
|
/// Export Import Name
|
|
impl ExportImportName<'_> {
|
|
/// Is all
|
|
pub fn is_all(&self) -> bool {
|
|
matches!(self, Self::All)
|
|
}
|
|
|
|
/// Is all but default
|
|
pub fn is_all_but_default(&self) -> bool {
|
|
matches!(self, Self::AllButDefault)
|
|
}
|
|
}
|
|
|
|
/// `ExportName` for `ExportEntry`
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub enum ExportExportName<'a> {
|
|
/// Name
|
|
Name(NameSpan<'a>),
|
|
/// Default
|
|
Default(Span),
|
|
/// Null
|
|
#[default]
|
|
Null,
|
|
}
|
|
|
|
impl ExportExportName<'_> {
|
|
/// Returns `true` if this is [`ExportExportName::Default`].
|
|
pub fn is_default(&self) -> bool {
|
|
matches!(self, Self::Default(_))
|
|
}
|
|
|
|
/// Returns `true` if this is [`ExportExportName::Null`].
|
|
pub fn is_null(&self) -> bool {
|
|
matches!(self, Self::Null)
|
|
}
|
|
|
|
/// Attempt to get the [`Span`] of this export name.
|
|
pub fn span(&self) -> Option<Span> {
|
|
match self {
|
|
Self::Name(name) => Some(name.span),
|
|
Self::Default(span) => Some(*span),
|
|
Self::Null => None,
|
|
}
|
|
}
|
|
|
|
/// Get default export span
|
|
/// `export default foo`
|
|
/// `export { default }`
|
|
pub fn default_export_span(&self) -> Option<Span> {
|
|
match self {
|
|
Self::Default(span) => Some(*span),
|
|
Self::Name(name_span) if name_span.name == "default" => Some(name_span.span),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// `LocalName` for `ExportEntry`
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub enum ExportLocalName<'a> {
|
|
/// Name
|
|
Name(NameSpan<'a>),
|
|
/// `export default name_span`
|
|
Default(NameSpan<'a>),
|
|
/// Null
|
|
#[default]
|
|
Null,
|
|
}
|
|
|
|
impl<'a> ExportLocalName<'a> {
|
|
/// `true` if this is a [`ExportLocalName::Default`].
|
|
pub fn is_default(&self) -> bool {
|
|
matches!(self, Self::Default(_))
|
|
}
|
|
|
|
/// `true` if this is a [`ExportLocalName::Null`].
|
|
pub fn is_null(&self) -> bool {
|
|
matches!(self, Self::Null)
|
|
}
|
|
|
|
/// Get the bound name of this export. [`None`] for [`ExportLocalName::Null`].
|
|
pub fn name(&self) -> Option<&Atom<'a>> {
|
|
match self {
|
|
Self::Name(name) | Self::Default(name) => Some(&name.name),
|
|
Self::Null => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// RequestedModule
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct RequestedModule {
|
|
/// Span of the import statement.
|
|
pub statement_span: Span,
|
|
|
|
/// Span
|
|
pub span: Span,
|
|
|
|
/// `true` if a `type` modifier was used in the import statement.
|
|
///
|
|
/// ## Examples
|
|
/// ```ts
|
|
/// import type { foo } from "foo"; // true, `type` is on module request
|
|
/// import { type bar } from "bar"; // false, `type` is on specifier
|
|
/// import { baz } from "baz"; // false, no `type` modifier
|
|
/// ```
|
|
pub is_type: bool,
|
|
|
|
/// `true` if the module is requested by an import statement.
|
|
pub is_import: bool,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use oxc_span::Span;
|
|
|
|
use super::{ExportExportName, ExportLocalName, ImportImportName, NameSpan};
|
|
|
|
#[test]
|
|
fn import_import_name() {
|
|
let name = NameSpan::new("name".into(), Span::new(0, 0));
|
|
assert!(!ImportImportName::Name(name.clone()).is_default());
|
|
assert!(!ImportImportName::NamespaceObject.is_default());
|
|
assert!(ImportImportName::Default(Span::new(0, 0)).is_default());
|
|
|
|
assert!(!ImportImportName::Name(name.clone()).is_namespace_object());
|
|
assert!(ImportImportName::NamespaceObject.is_namespace_object());
|
|
assert!(!ImportImportName::Default(Span::new(0, 0)).is_namespace_object());
|
|
}
|
|
|
|
#[test]
|
|
fn export_import_name() {
|
|
let name = NameSpan::new("name".into(), Span::new(0, 0));
|
|
assert!(!ExportExportName::Name(name.clone()).is_default());
|
|
assert!(ExportExportName::Default(Span::new(0, 0)).is_default());
|
|
assert!(!ExportExportName::Null.is_default());
|
|
|
|
assert!(!ExportExportName::Name(name).is_null());
|
|
assert!(!ExportExportName::Default(Span::new(0, 0)).is_null());
|
|
assert!(ExportExportName::Null.is_null());
|
|
}
|
|
|
|
#[test]
|
|
fn export_local_name() {
|
|
let name = NameSpan::new("name".into(), Span::new(0, 0));
|
|
assert!(!ExportLocalName::Name(name.clone()).is_default());
|
|
assert!(ExportLocalName::Default(name.clone()).is_default());
|
|
assert!(!ExportLocalName::Null.is_default());
|
|
|
|
assert!(!ExportLocalName::Name(name.clone()).is_null());
|
|
assert!(!ExportLocalName::Default(name.clone()).is_null());
|
|
assert!(ExportLocalName::Null.is_null());
|
|
}
|
|
}
|