From d22bd2082a5ce188338a28e35ddbd8b6fe5bd966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Deng=20=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90?= Date: Sun, 25 Aug 2024 18:22:44 +0800 Subject: [PATCH] feat(module_lexer): distinguish for types-only imports and exports (#5184) Co-authored-by: Boshen --- crates/oxc_module_lexer/src/lib.rs | 45 +++++++++- crates/oxc_module_lexer/tests/esm.rs | 96 +++++++++++---------- crates/oxc_module_lexer/tests/typescript.rs | 69 +++++++++++++++ 3 files changed, 165 insertions(+), 45 deletions(-) create mode 100644 crates/oxc_module_lexer/tests/typescript.rs diff --git a/crates/oxc_module_lexer/src/lib.rs b/crates/oxc_module_lexer/src/lib.rs index 3484fba3c28ca..650929cb5d70b 100644 --- a/crates/oxc_module_lexer/src/lib.rs +++ b/crates/oxc_module_lexer/src/lib.rs @@ -36,6 +36,9 @@ pub struct ImportSpecifier<'a> { /// If this import has an import assertion, this is the start value pub a: Option, + + /// If this import is for types only + pub t: bool, } #[derive(Debug, Clone)] @@ -57,6 +60,9 @@ pub struct ExportSpecifier<'a> { /// End of local name pub le: Option, + + /// If this export is for types only + pub t: bool, } #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)] @@ -142,6 +148,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { se: prop.span.end, d: ImportType::ImportMeta, a: None, + t: false, }); } walk_meta_property(self, prop); @@ -164,10 +171,32 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { se: expr.span.end, d: ImportType::DynamicImport(expr.span.start + 6), a: expr.arguments.first().map(|e| e.span().start), + t: false, }); walk_import_expression(self, expr); } + fn visit_ts_import_type(&mut self, impt: &TSImportType<'a>) { + let (source, source_span) = match &impt.parameter { + TSType::TSLiteralType(literal_type) => match &literal_type.literal { + TSLiteral::StringLiteral(s) => (Some(s.value.clone()), s.span()), + _ => (None, literal_type.span()), + }, + _ => (None, impt.parameter.span()), + }; + + self.imports.push(ImportSpecifier { + n: source, + s: source_span.start, + e: source_span.end, + ss: impt.span.start, + se: impt.span.end, + d: ImportType::DynamicImport(impt.span.start + 6), + a: None, + t: true, + }); + } + fn visit_import_declaration(&mut self, decl: &ImportDeclaration<'a>) { let assertions = decl .with_clause @@ -182,6 +211,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { se: decl.span.end, d: ImportType::StaticImport, a: assertions, + t: decl.import_kind.is_type(), }); walk_import_declaration(self, decl); } @@ -197,6 +227,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { se: decl.span.end, d: ImportType::StaticImport, a: None, + t: decl.export_kind.is_type(), }); } @@ -213,6 +244,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { e: ident.span.end, ls: None, le: None, + t: false, }); }); } @@ -232,6 +264,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { e: exported_end, ls: Some(s.local.span().start), le: Some(s.local.span().end), + t: decl.export_kind.is_type(), } })); walk_export_named_declaration(self, decl); @@ -254,6 +287,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { e: decl.exported.span().end, ls: None, le: None, + t: false, }); } @@ -263,7 +297,15 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { let n = exported.name().clone(); let s = exported.span().start; let e = exported.span().end; - self.exports.push(ExportSpecifier { n: n.clone(), ln: None, s, e, ls: None, le: None }); + self.exports.push(ExportSpecifier { + n: n.clone(), + ln: None, + s, + e, + ls: None, + le: None, + t: decl.export_kind.is_type(), + }); self.imports.push(ImportSpecifier { n: Some(n), s, @@ -272,6 +314,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> { se: decl.span.end, d: ImportType::StaticImport, a: None, + t: decl.export_kind.is_type(), }); } walk_export_all_declaration(self, decl); diff --git a/crates/oxc_module_lexer/tests/esm.rs b/crates/oxc_module_lexer/tests/esm.rs index f84389e122fa3..5870d3113fd67 100644 --- a/crates/oxc_module_lexer/tests/esm.rs +++ b/crates/oxc_module_lexer/tests/esm.rs @@ -6,32 +6,63 @@ use oxc_parser::Parser; use oxc_span::SourceType; #[derive(Debug, Clone)] -struct ImportSpecifier { - n: Option, - s: u32, - e: u32, - ss: u32, - se: u32, - d: ImportType, - a: Option, +pub struct ImportSpecifier { + pub n: Option, + pub s: u32, + pub e: u32, + pub ss: u32, + pub se: u32, + pub d: ImportType, + pub a: Option, + pub t: bool, +} + +impl From> for ImportSpecifier { + fn from(value: oxc_module_lexer::ImportSpecifier) -> Self { + Self { + n: value.n.map(|n| n.to_string()), + s: value.s, + e: value.e, + ss: value.ss, + se: value.se, + d: value.d, + a: value.a, + t: value.t, + } + } } #[derive(Debug, Clone)] pub struct ExportSpecifier { - n: String, - ln: Option, - s: u32, - e: u32, - ls: Option, - le: Option, + pub n: String, + pub ln: Option, + pub s: u32, + pub e: u32, + pub ls: Option, + pub le: Option, + pub t: bool, +} + +impl From> for ExportSpecifier { + fn from(value: oxc_module_lexer::ExportSpecifier) -> Self { + Self { + n: value.n.to_string(), + ln: value.ln.map(|ln| ln.to_string()), + s: value.s, + e: value.e, + ls: value.ls, + le: value.le, + t: value.t, + } + } } #[non_exhaustive] -struct ModuleLexer { - imports: Vec, - exports: Vec, - has_module_syntax: bool, - facade: bool, +pub struct ModuleLexer { + pub imports: Vec, + pub exports: Vec, + pub has_module_syntax: bool, + pub facade: bool, } fn parse(source: &str) -> ModuleLexer { @@ -42,31 +73,8 @@ fn parse(source: &str) -> ModuleLexer { let module_lexer = oxc_module_lexer::ModuleLexer::new().build(&ret.program); // Copy data over because `ModuleLexer<'a>` can't be returned ModuleLexer { - imports: module_lexer - .imports - .into_iter() - .map(|i| ImportSpecifier { - n: i.n.map(|n| n.to_string()), - s: i.s, - e: i.e, - ss: i.ss, - se: i.se, - d: i.d, - a: i.a, - }) - .collect(), - exports: module_lexer - .exports - .into_iter() - .map(|e| ExportSpecifier { - n: e.n.to_string(), - ln: e.ln.map(|ln| ln.to_string()), - s: e.s, - e: e.e, - ls: e.ls, - le: e.le, - }) - .collect(), + imports: module_lexer.imports.into_iter().map(Into::into).collect(), + exports: module_lexer.exports.into_iter().map(Into::into).collect(), has_module_syntax: module_lexer.has_module_syntax, facade: module_lexer.facade, } diff --git a/crates/oxc_module_lexer/tests/typescript.rs b/crates/oxc_module_lexer/tests/typescript.rs new file mode 100644 index 0000000000000..60a6acc552dc4 --- /dev/null +++ b/crates/oxc_module_lexer/tests/typescript.rs @@ -0,0 +1,69 @@ +mod esm; +use esm::ModuleLexer; +use oxc_allocator::Allocator; +use oxc_parser::Parser; +use oxc_span::SourceType; + +fn parse(source: &str) -> ModuleLexer { + let allocator = Allocator::default(); + let source_type = SourceType::default().with_module(true).with_typescript_definition(true); + let ret = Parser::new(&allocator, source, source_type).parse(); + assert!(ret.errors.is_empty(), "{source} should not produce errors.\n{:?}", ret.errors); + let module_lexer = oxc_module_lexer::ModuleLexer::new().build(&ret.program); + ModuleLexer { + imports: module_lexer.imports.into_iter().map(Into::into).collect(), + exports: module_lexer.exports.into_iter().map(Into::into).collect(), + has_module_syntax: module_lexer.has_module_syntax, + facade: module_lexer.facade, + } +} + +#[test] +fn import_type_named() { + let source = "import type { foo } from 'foo'"; + let impt = &parse(source).imports[0]; + assert!(impt.t); +} + +#[test] +fn import_type_namespace() { + let source = "import type * as foo from 'foo'"; + let impt = &parse(source).imports[0]; + assert!(impt.t); +} + +#[test] +fn import_type_default() { + let source = "import type foo from 'foo'"; + let impt = &parse(source).imports[0]; + assert!(impt.t); +} + +#[test] +fn dynamic_import_value() { + let source = "import('foo')"; + let impt = &parse(source).imports[0]; + assert!(!impt.t); +} + +#[test] +fn dynamic_import_type() { + let source = "const foo: import('foo')"; + let impt = &parse(source).imports[0]; + assert!(impt.t); + assert_eq!(impt.n.as_ref().unwrap(), "foo"); +} + +#[test] +fn export_type_named() { + let source = "export type { foo } from 'foo'"; + let expt = &parse(source).exports[0]; + assert!(expt.t); +} + +#[test] +fn export_type_namespace() { + let source = "export type * as foo from 'foo'"; + let expt = &parse(source).exports[0]; + assert!(expt.t); +}