From c479a587183cc1b9ac5ad35e9223dea3315ad841 Mon Sep 17 00:00:00 2001 From: Boshen Date: Thu, 16 Jan 2025 22:25:22 +0800 Subject: [PATCH] feat(napi/parser): expose dynamic import expressions (#8540) closes #8369 --- crates/oxc_parser/src/js/module.rs | 5 ++- crates/oxc_parser/src/module_record.rs | 15 ++++++++ crates/oxc_syntax/src/module_record.rs | 13 +++++++ napi/parser/index.d.ts | 12 +++++-- napi/parser/src/convert.rs | 13 ++++++- napi/parser/src/types.rs | 13 +++++-- .../test/__snapshots__/esm.test.ts.snap | 35 +++++++++++++++++++ napi/parser/test/esm.test.ts | 1 + 8 files changed, 101 insertions(+), 6 deletions(-) diff --git a/crates/oxc_parser/src/js/module.rs b/crates/oxc_parser/src/js/module.rs index 01255f9e398f7..f22852803824f 100644 --- a/crates/oxc_parser/src/js/module.rs +++ b/crates/oxc_parser/src/js/module.rs @@ -34,7 +34,10 @@ impl<'a> ParserImpl<'a> { self.ctx = self.ctx.and_in(has_in); self.bump(Kind::Comma); self.expect(Kind::RParen)?; - Ok(self.ast.expression_import(self.end_span(span), expression, arguments, phase)) + let expr = + self.ast.alloc_import_expression(self.end_span(span), expression, arguments, phase); + self.module_record_builder.visit_import_expression(&expr); + Ok(Expression::ImportExpression(expr)) } /// Section 16.2.2 Import Declaration diff --git a/crates/oxc_parser/src/module_record.rs b/crates/oxc_parser/src/module_record.rs index 86c59934be73e..4b32af6d446e6 100644 --- a/crates/oxc_parser/src/module_record.rs +++ b/crates/oxc_parser/src/module_record.rs @@ -174,6 +174,12 @@ impl<'a> ModuleRecordBuilder<'a> { } } + pub fn visit_import_expression(&mut self, e: &ImportExpression<'a>) { + self.module_record + .dynamic_imports + .push(DynamicImport { span: e.span, module_request: e.source.span() }); + } + pub fn visit_import_meta(&mut self, span: Span) { self.module_record.has_module_syntax = true; self.module_record.import_metas.push(span); @@ -701,4 +707,13 @@ mod module_record_tests { assert_eq!(module_record.import_metas[0], Span::new(0, 11)); assert_eq!(module_record.import_metas[1], Span::new(17, 28)); } + + #[test] + fn dynamic_imports() { + let allocator = Allocator::default(); + let module_record = build(&allocator, "import('foo')"); + assert_eq!(module_record.dynamic_imports.len(), 1); + assert_eq!(module_record.dynamic_imports[0].span, Span::new(0, 13)); + assert_eq!(module_record.dynamic_imports[0].module_request, Span::new(7, 12)); + } } diff --git a/crates/oxc_syntax/src/module_record.rs b/crates/oxc_syntax/src/module_record.rs index ad72ce41a82f5..87c79efafe4f9 100644 --- a/crates/oxc_syntax/src/module_record.rs +++ b/crates/oxc_syntax/src/module_record.rs @@ -57,6 +57,9 @@ pub struct ModuleRecord<'a> { /// Local exported bindings pub exported_bindings: FxHashMap, Span>, + /// Dynamic import expressions `import(specifier)`. + pub dynamic_imports: Vec<'a, DynamicImport>, + /// Span position of `import.meta`. pub import_metas: Vec<'a, Span>, } @@ -72,6 +75,7 @@ impl<'a> ModuleRecord<'a> { indirect_export_entries: Vec::new_in(allocator), star_export_entries: Vec::new_in(allocator), exported_bindings: FxHashMap::default(), + dynamic_imports: Vec::new_in(allocator), import_metas: Vec::new_in(allocator), } } @@ -374,6 +378,15 @@ pub struct RequestedModule { pub is_import: bool, } +/// Dynamic import expression. +#[derive(Debug, Clone, Copy)] +pub struct DynamicImport { + /// Span of the import expression. + pub span: Span, + /// Span the ModuleSpecifier, which is an expression. + pub module_request: Span, +} + #[cfg(test)] mod test { use oxc_span::Span; diff --git a/napi/parser/index.d.ts b/napi/parser/index.d.ts index 555b92bf9468f..a15b100cb10df 100644 --- a/napi/parser/index.d.ts +++ b/napi/parser/index.d.ts @@ -37,6 +37,12 @@ export interface Comment { end: number } +export interface DynamicImport { + start: number + end: number + moduleRequest: Span +} + export interface EcmaScriptModule { /** * Has ESM syntax. @@ -46,10 +52,12 @@ export interface EcmaScriptModule { * Dynamic imports `import('foo')` are ignored since they can be used in non-ESM files. */ hasModuleSyntax: boolean - /** Import Statements. */ + /** Import statements. */ staticImports: Array - /** Export Statements. */ + /** Export statements. */ staticExports: Array + /** Dynamic import expressions. */ + dynamicImports: Array /** Span positions` of `import.meta` */ importMetas: Array } diff --git a/napi/parser/src/convert.rs b/napi/parser/src/convert.rs index 4bfb1f5a6a032..6f37e85623e64 100644 --- a/napi/parser/src/convert.rs +++ b/napi/parser/src/convert.rs @@ -3,7 +3,7 @@ use rustc_hash::FxHashMap; use oxc::syntax::module_record::{self, ModuleRecord}; use crate::types::{ - EcmaScriptModule, ExportExportName, ExportExportNameKind, ExportImportName, + DynamicImport, EcmaScriptModule, ExportExportName, ExportExportNameKind, ExportImportName, ExportImportNameKind, ExportLocalName, ExportLocalNameKind, ImportName, ImportNameKind, Span, StaticExport, StaticExportEntry, StaticImport, StaticImportEntry, ValueSpan, }; @@ -55,12 +55,23 @@ impl From<&ModuleRecord<'_>> for EcmaScriptModule { .collect::>(); static_exports.sort_unstable_by_key(|e| e.start); + let dynamic_imports = record + .dynamic_imports + .iter() + .map(|i| DynamicImport { + start: i.span.start, + end: i.span.end, + module_request: Span::from(&i.module_request), + }) + .collect::>(); + let import_metas = record.import_metas.iter().map(Span::from).collect(); Self { has_module_syntax: record.has_module_syntax, static_imports, static_exports, + dynamic_imports, import_metas, } } diff --git a/napi/parser/src/types.rs b/napi/parser/src/types.rs index e4f69de5efbb6..66947c4be8bdf 100644 --- a/napi/parser/src/types.rs +++ b/napi/parser/src/types.rs @@ -81,10 +81,12 @@ pub struct EcmaScriptModule { /// /// Dynamic imports `import('foo')` are ignored since they can be used in non-ESM files. pub has_module_syntax: bool, - /// Import Statements. + /// Import statements. pub static_imports: Vec, - /// Export Statements. + /// Export statements. pub static_exports: Vec, + /// Dynamic import expressions. + pub dynamic_imports: Vec, /// Span positions` of `import.meta` pub import_metas: Vec, } @@ -245,3 +247,10 @@ pub enum ExportLocalNameKind { /// `export default function () {}` None, } + +#[napi(object)] +pub struct DynamicImport { + pub start: u32, + pub end: u32, + pub module_request: Span, +} diff --git a/napi/parser/test/__snapshots__/esm.test.ts.snap b/napi/parser/test/__snapshots__/esm.test.ts.snap index 073428784f267..2089dfb570e22 100644 --- a/napi/parser/test/__snapshots__/esm.test.ts.snap +++ b/napi/parser/test/__snapshots__/esm.test.ts.snap @@ -33,6 +33,7 @@ exports[`esm > export * as name1 from "module-name"; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -67,6 +68,7 @@ exports[`esm > export * from "module-name"; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -107,6 +109,7 @@ exports[`esm > export { default as name1 } from "module-name"; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -147,6 +150,7 @@ exports[`esm > export { default, /* …, */ } from "module-name"; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -235,6 +239,7 @@ exports[`esm > export { import1 as name1, import2 as name2, /* …, */ nameN } f ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -270,6 +275,7 @@ exports[`esm > export { name1 as default /*, … */ }; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -334,6 +340,7 @@ exports[`esm > export { name1, /* …, */ nameN } from "module-name"; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -388,6 +395,7 @@ exports[`esm > export { name1, /* …, */ nameN }; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -423,6 +431,7 @@ exports[`esm > export { variable1 as "string name" }; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -496,6 +505,7 @@ exports[`esm > export { variable1 as name1, variable2 as name2, /* …, */ nameN ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -531,6 +541,7 @@ exports[`esm > export class ClassName { /* … */ } 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -585,6 +596,7 @@ exports[`esm > export const [ name1, name2 ] = array; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -639,6 +651,7 @@ exports[`esm > export const { name1, name2: bar } = o; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -693,6 +706,7 @@ exports[`esm > export const name1 = 1, name2 = 2/*, … */; // also var, let 1`] ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -724,6 +738,7 @@ exports[`esm > export default class { /* … */ } 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -758,6 +773,7 @@ exports[`esm > export default class ClassName { /* … */ } 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -792,6 +808,7 @@ exports[`esm > export default expression; 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -823,6 +840,7 @@ exports[`esm > export default function () { /* … */ } 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -857,6 +875,7 @@ exports[`esm > export default function functionName() { /* … */ } 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -888,6 +907,7 @@ exports[`esm > export default function* () { /* … */ } 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -922,6 +942,7 @@ exports[`esm > export default function* generatorFunctionName() { /* … */ } 1` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -957,6 +978,7 @@ exports[`esm > export function functionName() { /* … */ } 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -992,6 +1014,7 @@ exports[`esm > export function* generatorFunctionName() { /* … */ } 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -1046,6 +1069,7 @@ exports[`esm > export let name1, name2/*, … */; // also var 1`] = ` ] } ], + "dynamicImports": [], "importMetas": [] }" `; @@ -1066,6 +1090,7 @@ exports[`esm > import "module-name"; 1`] = ` } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1098,6 +1123,7 @@ exports[`esm > import * as name from "module-name"; 1`] = ` } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1133,6 +1159,7 @@ exports[`esm > import { "string name" as alias } from "module-name"; 1`] = ` } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1168,6 +1195,7 @@ exports[`esm > import { default as alias } from "module-name"; 1`] = ` } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1203,6 +1231,7 @@ exports[`esm > import { export1 } from "module-name"; 1`] = ` } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1238,6 +1267,7 @@ exports[`esm > import { export1 as alias1 } from "module-name"; 1`] = ` } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1287,6 +1317,7 @@ exports[`esm > import { export1, export2 } from "module-name"; 1`] = ` } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1336,6 +1367,7 @@ exports[`esm > import { export1, export2 as alias2, /* … */ } from "module-nam } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1370,6 +1402,7 @@ exports[`esm > import defaultExport from "module-name"; 1`] = ` } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1415,6 +1448,7 @@ exports[`esm > import defaultExport, * as name from "module-name"; 1`] = ` } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; @@ -1463,6 +1497,7 @@ exports[`esm > import defaultExport, { export1, /* … */ } from "module-name"; } ], "staticExports": [], + "dynamicImports": [], "importMetas": [] }" `; diff --git a/napi/parser/test/esm.test.ts b/napi/parser/test/esm.test.ts index e08f44c2b301e..c1a5794ab9a2b 100644 --- a/napi/parser/test/esm.test.ts +++ b/napi/parser/test/esm.test.ts @@ -74,6 +74,7 @@ describe('hasModuleSyntax', () => { test('import expression', () => { const ret = parseSync('test.js', "import('foo')"); expect(ret.module.hasModuleSyntax).toBe(false); + expect(ret.module.dynamicImports).toStrictEqual([{ start: 0, end: 13, moduleRequest: { start: 7, end: 12 } }]); }); test('script', () => {