From 9ed9501bcdd283582feafb7aef9c9b7e9061ff71 Mon Sep 17 00:00:00 2001 From: Boshen <1430279+Boshen@users.noreply.github.com> Date: Sun, 3 Nov 2024 16:31:19 +0000 Subject: [PATCH] fix(module_lexer): add missing `export * from 'foo';` case (#7103) fixes #7039 --- crates/oxc_module_lexer/src/lib.rs | 41 +++++++++++-------- .../oxc_module_lexer/tests/integration/esm.rs | 17 +++++++- napi/parser/index.d.ts | 1 + napi/parser/src/module_lexer.rs | 2 + napi/parser/test/module_lexer.test.mjs | 14 ++++++- 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/crates/oxc_module_lexer/src/lib.rs b/crates/oxc_module_lexer/src/lib.rs index c10cbbe41a6e6..a72df58ea2f55 100644 --- a/crates/oxc_module_lexer/src/lib.rs +++ b/crates/oxc_module_lexer/src/lib.rs @@ -2,11 +2,7 @@ //! //! * -use oxc_ast::visit::walk::{ - walk_export_all_declaration, walk_export_named_declaration, walk_import_declaration, - walk_import_expression, walk_meta_property, walk_module_declaration, walk_statement, -}; -use oxc_ast::{ast::*, Visit}; +use oxc_ast::{ast::*, visit::walk, Visit}; use oxc_ecmascript::BoundNames; use oxc_span::{Atom, GetSpan}; @@ -67,11 +63,13 @@ pub struct ExportSpecifier<'a> { #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] pub enum ImportType { - /// If this import keyword is a dynamic import, this is the start value. - DynamicImport(u32), /// If this import keyword is a static import #[default] StaticImport, + /// If this import is an `export *` + ExportStar, + /// If this import keyword is a dynamic import, this is the start value. + DynamicImport(u32), /// If this import keyword is an import.meta expression ImportMeta, } @@ -80,7 +78,7 @@ impl ImportType { pub fn as_dynamic_import(&self) -> Option { match self { Self::DynamicImport(start) => Some(*start), - Self::StaticImport | Self::ImportMeta => None, + Self::StaticImport | Self::ExportStar | Self::ImportMeta => None, } } } @@ -123,15 +121,14 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { if self.facade && !stmt.is_module_declaration() && !stmt.is_declaration() { self.facade = false; } - - walk_statement(self, stmt); + walk::walk_statement(self, stmt); } fn visit_module_declaration(&mut self, decl: &ModuleDeclaration<'a>) { if !self.has_module_syntax { self.has_module_syntax = true; } - walk_module_declaration(self, decl); + walk::walk_module_declaration(self, decl); } // import.meta @@ -151,7 +148,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { t: false, }); } - walk_meta_property(self, prop); + walk::walk_meta_property(self, prop); } // import("foo") @@ -173,7 +170,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { a: expr.arguments.first().map(|e| e.span().start), t: false, }); - walk_import_expression(self, expr); + walk::walk_import_expression(self, expr); } fn visit_ts_import_type(&mut self, impt: &TSImportType<'a>) { @@ -213,7 +210,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { a: assertions, t: decl.import_kind.is_type(), }); - walk_import_declaration(self, decl); + walk::walk_import_declaration(self, decl); } fn visit_export_named_declaration(&mut self, decl: &ExportNamedDeclaration<'a>) { @@ -267,7 +264,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { t: decl.export_kind.is_type(), } })); - walk_export_named_declaration(self, decl); + walk::walk_export_named_declaration(self, decl); } // export default foo @@ -316,7 +313,19 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { a: None, t: decl.export_kind.is_type(), }); + } else { + // export * from 'foo' + self.imports.push(ImportSpecifier { + n: Some(decl.source.value.clone()), + s: decl.source.span.start + 1, // +- 1 for removing string quotes + e: decl.source.span.end - 1, + ss: decl.span.start, + se: decl.span.end, + d: ImportType::ExportStar, + a: None, + t: decl.export_kind.is_type(), + }); } - walk_export_all_declaration(self, decl); + walk::walk_export_all_declaration(self, decl); } } diff --git a/crates/oxc_module_lexer/tests/integration/esm.rs b/crates/oxc_module_lexer/tests/integration/esm.rs index 3cd57b604e660..2e84808cedd8b 100644 --- a/crates/oxc_module_lexer/tests/integration/esm.rs +++ b/crates/oxc_module_lexer/tests/integration/esm.rs @@ -64,8 +64,21 @@ fn named_imports() { let source = "import { a, b, c } from 'foo'"; let imports = &parse(source).imports; assert_eq!(imports.len(), 1); - // assert_eq!(source.slice(impt.ss, impt.se), r#"import(("asdf"))"#); - // assert_eq!(source.slice(impt.s, impt.e), r#"("asdf")"#); + let impt = &parse(source).imports[0]; + assert_eq!(source.slice(impt.ss, impt.se), source); + assert_eq!(source.slice(impt.s, impt.e), "foo"); +} + +#[test] +fn export_star_from() { + let source = "export * from 'foo'"; + let imports = &parse(source).imports; + assert_eq!(imports.len(), 1); + let impt = &parse(source).imports[0]; + assert_eq!(source.slice(impt.ss, impt.se), source); + assert_eq!(source.slice(impt.s, impt.e), "foo"); + assert_eq!(impt.n.as_deref(), Some("foo")); + assert_eq!(impt.d, ImportType::ExportStar); } /* Suite Lexer */ diff --git a/napi/parser/index.d.ts b/napi/parser/index.d.ts index 77423644b5bbe..615f6528dce46 100644 --- a/napi/parser/index.d.ts +++ b/napi/parser/index.d.ts @@ -44,6 +44,7 @@ export interface ImportSpecifier { * * If this import keyword is a dynamic import, this is the start value. * * If this import keyword is a static import, this is -1. * * If this import keyword is an import.meta expression, this is -2. + * * If this import is an `export *`, this is -3. */ d: number /** diff --git a/napi/parser/src/module_lexer.rs b/napi/parser/src/module_lexer.rs index ba262ae727ea0..39f53852cf057 100644 --- a/napi/parser/src/module_lexer.rs +++ b/napi/parser/src/module_lexer.rs @@ -31,6 +31,7 @@ pub struct ImportSpecifier { /// * If this import keyword is a dynamic import, this is the start value. /// * If this import keyword is a static import, this is -1. /// * If this import keyword is an import.meta expression, this is -2. + /// * If this import is an `export *`, this is -3. pub d: i64, /// If this import has an import assertion, this is the start value @@ -72,6 +73,7 @@ impl<'a> From> for ImportSpecifier { ImportType::DynamicImport(start) => start as i64, ImportType::StaticImport => -1, ImportType::ImportMeta => -2, + ImportType::ExportStar => -3, }, a: i.a.map_or(-1, |a| a as i64), } diff --git a/napi/parser/test/module_lexer.test.mjs b/napi/parser/test/module_lexer.test.mjs index d5ecea021d3f2..fb266a50cfb98 100644 --- a/napi/parser/test/module_lexer.test.mjs +++ b/napi/parser/test/module_lexer.test.mjs @@ -1,4 +1,4 @@ -import { assert, describe, it } from 'vitest'; +import { assert, describe, expect, it } from 'vitest'; import * as oxc from '../index.js'; @@ -14,4 +14,16 @@ describe('module lexer', () => { const ret = await oxc.moduleLexerAsync(code); assert(ret.exports.length == 1); }); + + it('returns export *', async () => { + const ret = await oxc.moduleLexerAsync("export * from 'foo';"); + expect(ret).toEqual( + { + imports: [{ n: 'foo', s: 15, e: 18, ss: 0, se: 20, d: -3, a: -1 }], + exports: [], + hasModuleSyntax: true, + facade: true, + }, + ); + }); });