//! [ECMAScript Module Record](https://tc39.es/ecma262/#sec-abstract-module-records) use std::{fmt, hash::BuildHasherDefault, path::PathBuf, sync::Arc}; use dashmap::DashMap; use rustc_hash::{FxHashMap, FxHasher}; use oxc_span::{CompactStr, Span}; /// ESM Module Record /// /// All data inside this data structure are for ESM, no commonjs data is allowed. /// /// See /// * /// * #[derive(Default)] pub struct ModuleRecord { /// This module has no import / export statements pub not_esm: bool, /// Resolved absolute path to this module record pub resolved_absolute_path: PathBuf, /// `[[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>, /// `[[LoadedModules]]` /// /// A map from the specifier strings used by the module represented by this record to request the importation of a module to the resolved Module Record. /// The list does not contain two different Records with the same `[[Specifier]]`. pub loaded_modules: DashMap, BuildHasherDefault>, /// `[[ImportEntries]]` /// /// A List of ImportEntry records derived from the code of this module pub import_entries: Vec, /// `[[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, /// `[[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, /// `[[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, /// Local exported bindings pub exported_bindings: FxHashMap, /// Local duplicated exported bindings, for diagnostics pub exported_bindings_duplicated: Vec, /// Reexported bindings from `export * from 'specifier'` /// Keyed by resolved path pub exported_bindings_from_star_export: DashMap>, /// `export default name` /// ^^^^^^^ span pub export_default: Option, /// Duplicated span of `export default` for diagnostics pub export_default_duplicated: Vec, } impl ModuleRecord { pub fn new(resolved_absolute_path: PathBuf) -> Self { Self { resolved_absolute_path, ..Self::default() } } } impl fmt::Debug for ModuleRecord { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { // recursively formatting loaded modules can crash when the module graph is cyclic let loaded_modules = self .loaded_modules .iter() .map(|entry| (entry.key().to_string())) .reduce(|acc, key| format!("{acc}, {key}")) .unwrap_or_default(); let loaded_modules = format!("{{ {loaded_modules} }}"); f.debug_struct("ModuleRecord") .field("not_esm", &self.not_esm) .field("resolved_absolute_path", &self.resolved_absolute_path) .field("requested_modules", &self.requested_modules) .field("loaded_modules", &loaded_modules) .field("import_entries", &self.import_entries) .field("local_export_entries", &self.local_export_entries) .field("indirect_export_entries", &self.indirect_export_entries) .field("star_export_entries", &self.star_export_entries) .field("exported_bindings", &self.exported_bindings) .field("exported_bindings_duplicated", &self.exported_bindings_duplicated) .field("exported_bindings_from_star_export", &self.exported_bindings_from_star_export) .field("export_default", &self.export_default) .field("export_default_duplicated", &self.export_default_duplicated) .finish() } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct NameSpan { name: CompactStr, span: Span, } impl NameSpan { pub fn new(name: CompactStr, span: Span) -> Self { Self { name, span } } pub const fn name(&self) -> &CompactStr { &self.name } pub const fn span(&self) -> Span { self.span } } /// [`ImportEntry`](https://tc39.es/ecma262/#importentry-record) #[derive(Debug, Clone, PartialEq, Eq)] pub struct ImportEntry { /// String value of the ModuleSpecifier of the ImportDeclaration. pub module_request: NameSpan, /// The name under which the desired binding is exported by the module identified by `[[ModuleRequest]]`. pub import_name: ImportImportName, /// The name that is used to locally access the imported value from within the importing module. pub local_name: NameSpan, pub is_type: bool, } /// `ImportName` For `ImportEntry` #[derive(Debug, Clone, PartialEq, Eq)] pub enum ImportImportName { Name(NameSpan), NamespaceObject, Default(Span), } impl ImportImportName { pub fn is_default(&self) -> bool { matches!(self, Self::Default(_)) } pub fn is_namespace_object(&self) -> bool { matches!(self, Self::NamespaceObject) } } /// [`ExportEntry`](https://tc39.es/ecma262/#exportentry-record) #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct ExportEntry { /// 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, /// 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, /// 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. /// null if the exported value is not locally accessible from within the module. pub local_name: ExportLocalName, } /// `ImportName` for `ExportEntry` #[derive(Debug, Default, Clone, PartialEq, Eq)] pub enum ExportImportName { Name(NameSpan), /// 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, } impl ExportImportName { pub fn is_all(&self) -> bool { matches!(self, Self::All) } pub fn is_all_but_default(&self) -> bool { matches!(self, Self::AllButDefault) } } /// `ExportName` for `ExportEntry` #[derive(Debug, Default, Clone, PartialEq, Eq)] pub enum ExportExportName { Name(NameSpan), Default(Span), #[default] Null, } impl ExportExportName { pub fn is_default(&self) -> bool { matches!(self, Self::Default(_)) } pub fn is_null(&self) -> bool { matches!(self, Self::Null) } pub fn span(&self) -> Option { match self { Self::Name(name) => Some(name.span()), Self::Default(span) => Some(*span), Self::Null => None, } } } /// `LocalName` for `ExportEntry` #[derive(Debug, Default, Clone, PartialEq, Eq)] pub enum ExportLocalName { Name(NameSpan), Default(Span), #[default] Null, } impl ExportLocalName { pub fn is_default(&self) -> bool { matches!(self, Self::Default(_)) } pub fn is_null(&self) -> bool { matches!(self, Self::Null) } pub const fn name(&self) -> Option<&CompactStr> { match self { Self::Name(name) => Some(name.name()), Self::Default(_) | Self::Null => None, } } } pub struct FunctionMeta { pub deprecated: bool, } #[derive(Debug, Clone)] pub struct RequestedModule { span: Span, is_type: bool, /// is_import is true if the module is requested by an import statement. is_import: bool, } impl RequestedModule { pub fn new(span: Span, is_type: bool, is_import: bool) -> Self { Self { span, is_type, is_import } } pub fn span(&self) -> Span { self.span } pub fn is_type(&self) -> bool { self.is_type } pub fn is_import(&self) -> bool { self.is_import } } #[cfg(test)] mod test { use super::{ExportExportName, ExportLocalName, ImportImportName, NameSpan}; use oxc_span::Span; #[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(Span::new(0, 0)).is_default()); assert!(!ExportLocalName::Null.is_default()); assert!(!ExportLocalName::Name(name).is_null()); assert!(!ExportLocalName::Default(Span::new(0, 0)).is_null()); assert!(ExportLocalName::Null.is_null()); } }