fix(module_lexer): add missing export * from 'foo'; case (#7103)

fixes #7039
This commit is contained in:
Boshen 2024-11-03 16:31:19 +00:00
parent 9f611a18cf
commit 9ed9501bcd
5 changed files with 56 additions and 19 deletions

View file

@ -2,11 +2,7 @@
//!
//! * <https://github.com/guybedford/es-module-lexer>
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<u32> {
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);
}
}

View file

@ -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 */

View file

@ -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
/**

View file

@ -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<oxc_module_lexer::ImportSpecifier<'a>> 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),
}

View file

@ -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,
},
);
});
});