oxc_syntax/
module_record.rs

1//! [ECMAScript Module Record](https://tc39.es/ecma262/#sec-abstract-module-records)
2
3use oxc_allocator::{Allocator, HashMap, Vec};
4use oxc_ast_macros::ast;
5use oxc_estree::ESTree;
6use oxc_span::{Atom, Span};
7
8/// ESM Module Record
9///
10/// All data inside this data structure are for ESM, no commonjs data is allowed.
11///
12/// See
13/// * <https://tc39.es/ecma262/#table-additional-fields-of-source-text-module-records>
14/// * <https://tc39.es/ecma262/#cyclic-module-record>
15#[derive(Debug)]
16pub struct ModuleRecord<'a> {
17    /// This module has ESM syntax: `import` and `export`.
18    pub has_module_syntax: bool,
19
20    /// `[[RequestedModules]]`
21    ///
22    /// 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.
23    ///
24    /// Module requests from:
25    ///   import ImportClause FromClause
26    ///   import ModuleSpecifier
27    ///   export ExportFromClause FromClause
28    ///
29    /// Keyed by ModuleSpecifier, valued by all node occurrences
30    pub requested_modules: HashMap<'a, Atom<'a>, Vec<'a, RequestedModule>>,
31
32    /// `[[ImportEntries]]`
33    ///
34    /// A List of ImportEntry records derived from the code of this module
35    pub import_entries: Vec<'a, ImportEntry<'a>>,
36
37    /// `[[LocalExportEntries]]`
38    ///
39    /// A List of [`ExportEntry`] records derived from the code of this module
40    /// that correspond to declarations that occur within the module
41    pub local_export_entries: Vec<'a, ExportEntry<'a>>,
42
43    /// `[[IndirectExportEntries]]`
44    ///
45    /// A List of [`ExportEntry`] records derived from the code of this module
46    /// that correspond to reexported imports that occur within the module
47    /// or exports from `export * as namespace` declarations.
48    pub indirect_export_entries: Vec<'a, ExportEntry<'a>>,
49
50    /// `[[StarExportEntries]]`
51    ///
52    /// A List of [`ExportEntry`] records derived from the code of this module
53    /// that correspond to `export *` declarations that occur within the module,
54    /// not including `export * as namespace` declarations.
55    pub star_export_entries: Vec<'a, ExportEntry<'a>>,
56
57    /// Local exported bindings
58    pub exported_bindings: HashMap<'a, Atom<'a>, Span>,
59
60    /// Dynamic import expressions `import(specifier)`.
61    pub dynamic_imports: Vec<'a, DynamicImport>,
62
63    /// Span position of `import.meta`.
64    pub import_metas: Vec<'a, Span>,
65}
66
67impl<'a> ModuleRecord<'a> {
68    /// Constructor
69    pub fn new(allocator: &'a Allocator) -> Self {
70        Self {
71            has_module_syntax: false,
72            requested_modules: HashMap::new_in(allocator),
73            import_entries: Vec::new_in(allocator),
74            local_export_entries: Vec::new_in(allocator),
75            indirect_export_entries: Vec::new_in(allocator),
76            star_export_entries: Vec::new_in(allocator),
77            exported_bindings: HashMap::new_in(allocator),
78            dynamic_imports: Vec::new_in(allocator),
79            import_metas: Vec::new_in(allocator),
80        }
81    }
82}
83
84/// Name and Span
85#[ast]
86#[derive(Debug, Clone, PartialEq, Eq)]
87#[generate_derive(ESTree)]
88#[estree(no_type, no_ts_def)]
89pub struct NameSpan<'a> {
90    /// Name
91    #[estree(rename = "value")]
92    pub name: Atom<'a>,
93
94    /// Span
95    pub span: Span,
96}
97
98impl<'a> NameSpan<'a> {
99    /// Constructor
100    pub fn new(name: Atom<'a>, span: Span) -> Self {
101        Self { span, name }
102    }
103}
104
105/// [`ImportEntry`](https://tc39.es/ecma262/#importentry-record)
106///
107/// ## Examples
108///
109/// ```ts
110/// //     _ local_name
111/// import v from "mod";
112/// //             ^^^ module_request
113///
114/// //     ____ is_type will be `true`
115/// import type { foo as bar } from "mod";
116/// // import_name^^^    ^^^ local_name
117///
118/// import * as ns from "mod";
119/// ```
120#[ast]
121#[derive(Debug, Clone, PartialEq, Eq)]
122#[generate_derive(ESTree)]
123#[estree(no_type, no_ts_def)]
124pub struct ImportEntry<'a> {
125    /// Span of the import statement.
126    #[estree(skip)]
127    pub statement_span: Span,
128
129    /// String value of the ModuleSpecifier of the ImportDeclaration.
130    ///
131    /// ## Examples
132    ///
133    /// ```ts
134    /// import { foo } from "mod";
135    /// //                   ^^^
136    /// ```
137    #[estree(skip)]
138    pub module_request: NameSpan<'a>,
139
140    /// The name under which the desired binding is exported by the module identified by `[[ModuleRequest]]`.
141    ///
142    /// ## Examples
143    ///
144    /// ```ts
145    /// import { foo } from "mod";
146    /// //       ^^^
147    /// import { foo as bar } from "mod";
148    /// //       ^^^
149    /// ```
150    pub import_name: ImportImportName<'a>,
151
152    /// The name that is used to locally access the imported value from within the importing module.
153    ///
154    /// ## Examples
155    ///
156    /// ```ts
157    /// import { foo } from "mod";
158    /// //       ^^^
159    /// import { foo as bar } from "mod";
160    /// //              ^^^
161    /// ```
162    pub local_name: NameSpan<'a>,
163
164    /// Whether this binding is for a TypeScript type-only import. This is a non-standard field.
165    /// When creating a [`ModuleRecord`] for a JavaScript file, this will always be false.
166    ///
167    /// ## Examples
168    ///
169    /// `is_type` will be `true` for the following imports:
170    /// ```ts
171    /// import type { foo } from "mod";
172    /// import { type foo } from "mod";
173    /// ```
174    ///
175    /// and will be `false` for these imports:
176    /// ```ts
177    /// import { foo } from "mod";
178    /// import { foo as type } from "mod";
179    /// ```
180    pub is_type: bool,
181}
182
183/// `ImportName` For `ImportEntry`
184#[ast]
185#[derive(Debug, Clone, PartialEq, Eq)]
186#[generate_derive(ESTree)]
187#[estree(no_ts_def)]
188pub enum ImportImportName<'a> {
189    /// `import { x } from "mod"`
190    #[estree(via = ImportOrExportNameName)]
191    Name(NameSpan<'a>) = 0,
192    /// `import * as ns from "mod"`
193    #[estree(via = ImportImportNameNamespaceObject)]
194    NamespaceObject = 1,
195    /// `import defaultExport from "mod"`
196    #[estree(via = ImportOrExportNameDefault)]
197    Default(Span) = 2,
198}
199
200impl ImportImportName<'_> {
201    /// Is `default`
202    pub fn is_default(&self) -> bool {
203        matches!(self, Self::Default(_))
204    }
205
206    /// Is namespace
207    pub fn is_namespace_object(&self) -> bool {
208        matches!(self, Self::NamespaceObject)
209    }
210}
211
212/// [`ExportEntry`](https://tc39.es/ecma262/#exportentry-record)
213///
214/// Describes a single exported binding from a module. Named export statements that contain more
215/// than one binding produce multiple ExportEntry records.
216///
217/// ## Examples
218///
219/// ```ts
220/// // foo's ExportEntry nas no `module_request` or `import_name.
221/// //       ___ local_name
222/// export { foo };
223/// //       ^^^ export_name. Since there's no alias, it's the same as local_name.
224///
225/// // re-exports do not produce local bindings, so `local_name` is null.
226/// //       ___ import_name    __ module_request
227/// export { foo as bar } from "mod";
228/// //              ^^^ export_name
229///
230/// ```
231#[ast]
232#[derive(Debug, Default, Clone, PartialEq, Eq)]
233#[generate_derive(ESTree)]
234#[estree(no_type, no_ts_def)]
235pub struct ExportEntry<'a> {
236    /// Span of the import statement.
237    #[estree(skip)]
238    pub statement_span: Span,
239
240    /// Span for the entire export entry
241    pub span: Span,
242
243    /// The String value of the ModuleSpecifier of the ExportDeclaration.
244    /// null if the ExportDeclaration does not have a ModuleSpecifier.
245    pub module_request: Option<NameSpan<'a>>,
246
247    /// The name under which the desired binding is exported by the module identified by `[[ModuleRequest]]`.
248    /// null if the ExportDeclaration does not have a ModuleSpecifier.
249    /// "all" is used for `export * as ns from "mod"`` declarations.
250    /// "all-but-default" is used for `export * from "mod" declarations`.
251    pub import_name: ExportImportName<'a>,
252
253    /// The name used to export this binding by this module.
254    pub export_name: ExportExportName<'a>,
255
256    /// The name that is used to locally access the exported value from within the importing module.
257    /// null if the exported value is not locally accessible from within the module.
258    pub local_name: ExportLocalName<'a>,
259
260    /// Whether the export is a TypeScript `export type`.
261    ///
262    /// Examples:
263    ///
264    /// ```ts
265    /// export type * from 'mod'
266    /// export type * as ns from 'mod'
267    /// export type { foo }
268    /// export { type foo }
269    /// export type { foo } from 'mod'
270    /// ```
271    pub is_type: bool,
272}
273
274/// `ImportName` for `ExportEntry`
275#[ast]
276#[derive(Debug, Default, Clone, PartialEq, Eq)]
277#[generate_derive(ESTree)]
278#[estree(no_ts_def)]
279pub enum ExportImportName<'a> {
280    /// Name
281    #[estree(via = ImportOrExportNameName)]
282    Name(NameSpan<'a>) = 0,
283    /// all is used for export * as ns from "mod" declarations.
284    #[estree(via = ExportImportNameAll)]
285    All = 1,
286    /// all-but-default is used for export * from "mod" declarations.
287    #[estree(via = ExportImportNameAllButDefault)]
288    AllButDefault = 2,
289    /// the ExportDeclaration does not have a ModuleSpecifier
290    #[default]
291    #[estree(via = ExportNameNull)]
292    Null = 3,
293}
294
295/// Export Import Name
296impl ExportImportName<'_> {
297    /// Is all
298    pub fn is_all(&self) -> bool {
299        matches!(self, Self::All)
300    }
301
302    /// Is all but default
303    pub fn is_all_but_default(&self) -> bool {
304        matches!(self, Self::AllButDefault)
305    }
306}
307
308/// `ExportName` for `ExportEntry`
309#[ast]
310#[derive(Debug, Default, Clone, PartialEq, Eq)]
311#[generate_derive(ESTree)]
312#[estree(no_ts_def)]
313pub enum ExportExportName<'a> {
314    /// Name
315    #[estree(via = ImportOrExportNameName)]
316    Name(NameSpan<'a>) = 0,
317    /// Default
318    #[estree(via = ImportOrExportNameDefault)]
319    Default(Span) = 1,
320    /// Null
321    #[estree(via = ExportNameNull)]
322    #[default]
323    Null = 2,
324}
325
326impl ExportExportName<'_> {
327    /// Returns `true` if this is [`ExportExportName::Default`].
328    pub fn is_default(&self) -> bool {
329        matches!(self, Self::Default(_))
330    }
331
332    /// Returns `true` if this is [`ExportExportName::Null`].
333    pub fn is_null(&self) -> bool {
334        matches!(self, Self::Null)
335    }
336
337    /// Attempt to get the [`Span`] of this export name.
338    pub fn span(&self) -> Option<Span> {
339        match self {
340            Self::Name(name) => Some(name.span),
341            Self::Default(span) => Some(*span),
342            Self::Null => None,
343        }
344    }
345
346    /// Get default export span
347    /// `export default foo`
348    /// `export { default }`
349    pub fn default_export_span(&self) -> Option<Span> {
350        match self {
351            Self::Default(span) => Some(*span),
352            Self::Name(name_span) if name_span.name == "default" => Some(name_span.span),
353            _ => None,
354        }
355    }
356}
357
358/// `LocalName` for `ExportEntry`
359#[ast]
360#[derive(Debug, Default, Clone, PartialEq, Eq)]
361#[generate_derive(ESTree)]
362#[estree(no_ts_def)]
363pub enum ExportLocalName<'a> {
364    /// Name
365    #[estree(via = ImportOrExportNameName)]
366    Name(NameSpan<'a>) = 0,
367    /// `export default name_span`
368    #[estree(via = ExportLocalNameDefault)]
369    Default(NameSpan<'a>) = 1,
370    /// Null
371    #[default]
372    #[estree(via = ExportNameNull)]
373    Null = 2,
374}
375
376impl<'a> ExportLocalName<'a> {
377    /// `true` if this is a [`ExportLocalName::Default`].
378    pub fn is_default(&self) -> bool {
379        matches!(self, Self::Default(_))
380    }
381
382    /// `true` if this is a [`ExportLocalName::Null`].
383    pub fn is_null(&self) -> bool {
384        matches!(self, Self::Null)
385    }
386
387    /// Get the bound name of this export. [`None`] for [`ExportLocalName::Null`].
388    pub fn name(&self) -> Option<Atom<'a>> {
389        match self {
390            Self::Name(name) | Self::Default(name) => Some(name.name),
391            Self::Null => None,
392        }
393    }
394}
395
396/// RequestedModule
397#[derive(Debug, Clone, Copy)]
398pub struct RequestedModule {
399    /// Span of the import statement.
400    pub statement_span: Span,
401
402    /// Span
403    pub span: Span,
404
405    /// `true` if a `type` modifier was used in the import statement.
406    ///
407    /// ## Examples
408    /// ```ts
409    /// import type { foo } from "foo"; // true, `type` is on module request
410    /// import { type bar } from "bar"; // false, `type` is on specifier
411    /// import { baz } from "baz";      // false, no `type` modifier
412    /// ```
413    pub is_type: bool,
414
415    /// `true` if the module is requested by an import statement.
416    pub is_import: bool,
417}
418
419/// Dynamic import expression.
420#[ast]
421#[derive(Debug, Clone, Copy)]
422#[generate_derive(ESTree)]
423#[estree(no_type, no_ts_def)]
424pub struct DynamicImport {
425    /// Span of the import expression.
426    pub span: Span,
427    /// Span the ModuleSpecifier, which is an expression.
428    #[estree(no_flatten)]
429    pub module_request: Span,
430}
431
432#[expect(missing_docs)]
433pub trait VisitMutModuleRecord {
434    fn visit_module_record(&mut self, module_record: &mut ModuleRecord) {
435        module_record.requested_modules.values_mut().for_each(|e| {
436            e.iter_mut().for_each(|e| self.visit_requested_module(e));
437        });
438        module_record.import_entries.iter_mut().for_each(|e| self.visit_import_entry(e));
439        module_record.local_export_entries.iter_mut().for_each(|e| self.visit_export_entry(e));
440        module_record.indirect_export_entries.iter_mut().for_each(|e| self.visit_export_entry(e));
441        module_record.star_export_entries.iter_mut().for_each(|e| self.visit_export_entry(e));
442        module_record.dynamic_imports.iter_mut().for_each(|e| self.visit_dynamic_import(e));
443        module_record.import_metas.iter_mut().for_each(|e| self.visit_span(e));
444    }
445
446    fn visit_requested_module(&mut self, requested_module: &mut RequestedModule) {
447        self.visit_span(&mut requested_module.span);
448        self.visit_span(&mut requested_module.statement_span);
449    }
450
451    fn visit_import_entry(&mut self, import_entry: &mut ImportEntry) {
452        self.visit_span(&mut import_entry.statement_span);
453        self.visit_name_span(&mut import_entry.module_request);
454        self.visit_name_span(&mut import_entry.local_name);
455        self.visit_import_import_name(&mut import_entry.import_name);
456    }
457
458    fn visit_import_import_name(&mut self, import_import_name: &mut ImportImportName) {
459        match import_import_name {
460            ImportImportName::Name(name_span) => self.visit_name_span(name_span),
461            ImportImportName::NamespaceObject => {}
462            ImportImportName::Default(span) => self.visit_span(span),
463        }
464    }
465
466    fn visit_export_entry(&mut self, export_entry: &mut ExportEntry) {
467        self.visit_span(&mut export_entry.statement_span);
468        self.visit_span(&mut export_entry.span);
469        if let Some(module_request) = &mut export_entry.module_request {
470            self.visit_name_span(module_request);
471        }
472        self.visit_export_import_name(&mut export_entry.import_name);
473        self.visit_export_export_name(&mut export_entry.export_name);
474        self.visit_export_local_name(&mut export_entry.local_name);
475    }
476
477    fn visit_export_import_name(&mut self, export_import_name: &mut ExportImportName) {
478        match export_import_name {
479            ExportImportName::Name(name_span) => self.visit_name_span(name_span),
480            ExportImportName::All | ExportImportName::AllButDefault | ExportImportName::Null => {}
481        }
482    }
483
484    fn visit_export_export_name(&mut self, export_export_name: &mut ExportExportName) {
485        match export_export_name {
486            ExportExportName::Name(name_span) => self.visit_name_span(name_span),
487            ExportExportName::Default(span) => self.visit_span(span),
488            ExportExportName::Null => {}
489        }
490    }
491
492    fn visit_export_local_name(&mut self, export_local_name: &mut ExportLocalName) {
493        match export_local_name {
494            ExportLocalName::Name(name_span) | ExportLocalName::Default(name_span) => {
495                self.visit_name_span(name_span);
496            }
497            ExportLocalName::Null => {}
498        }
499    }
500
501    fn visit_dynamic_import(&mut self, dynamic_import: &mut DynamicImport) {
502        self.visit_span(&mut dynamic_import.module_request);
503        self.visit_span(&mut dynamic_import.span);
504    }
505
506    fn visit_name_span(&mut self, name_span: &mut NameSpan) {
507        self.visit_span(&mut name_span.span);
508    }
509
510    #[expect(unused_variables)]
511    #[inline(always)]
512    fn visit_span(&mut self, span: &mut Span) {}
513}
514
515#[cfg(test)]
516mod test {
517    use oxc_span::Span;
518
519    use super::{ExportExportName, ExportLocalName, ImportImportName, NameSpan};
520
521    #[test]
522    fn import_import_name() {
523        let name = NameSpan::new("name".into(), Span::new(0, 0));
524        assert!(!ImportImportName::Name(name.clone()).is_default());
525        assert!(!ImportImportName::NamespaceObject.is_default());
526        assert!(ImportImportName::Default(Span::new(0, 0)).is_default());
527
528        assert!(!ImportImportName::Name(name.clone()).is_namespace_object());
529        assert!(ImportImportName::NamespaceObject.is_namespace_object());
530        assert!(!ImportImportName::Default(Span::new(0, 0)).is_namespace_object());
531    }
532
533    #[test]
534    fn export_import_name() {
535        let name = NameSpan::new("name".into(), Span::new(0, 0));
536        assert!(!ExportExportName::Name(name.clone()).is_default());
537        assert!(ExportExportName::Default(Span::new(0, 0)).is_default());
538        assert!(!ExportExportName::Null.is_default());
539
540        assert!(!ExportExportName::Name(name).is_null());
541        assert!(!ExportExportName::Default(Span::new(0, 0)).is_null());
542        assert!(ExportExportName::Null.is_null());
543    }
544
545    #[test]
546    fn export_local_name() {
547        let name = NameSpan::new("name".into(), Span::new(0, 0));
548        assert!(!ExportLocalName::Name(name.clone()).is_default());
549        assert!(ExportLocalName::Default(name.clone()).is_default());
550        assert!(!ExportLocalName::Null.is_default());
551
552        assert!(!ExportLocalName::Name(name.clone()).is_null());
553        assert!(!ExportLocalName::Default(name.clone()).is_null());
554        assert!(ExportLocalName::Null.is_null());
555    }
556}