Skip to content

Commit

Permalink
feat(module_lexer): distinguish for types-only imports and exports (#…
Browse files Browse the repository at this point in the history
…5184)

Co-authored-by: Boshen <boshenc@gmail.com>
  • Loading branch information
sxzz and Boshen authored Aug 25, 2024
1 parent 666282a commit d22bd20
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 45 deletions.
45 changes: 44 additions & 1 deletion crates/oxc_module_lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub struct ImportSpecifier<'a> {

/// If this import has an import assertion, this is the start value
pub a: Option<u32>,

/// If this import is for types only
pub t: bool,
}

#[derive(Debug, Clone)]
Expand All @@ -57,6 +60,9 @@ pub struct ExportSpecifier<'a> {

/// End of local name
pub le: Option<u32>,

/// If this export is for types only
pub t: bool,
}

#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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);
}
Expand All @@ -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(),
});
}

Expand All @@ -213,6 +244,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> {
e: ident.span.end,
ls: None,
le: None,
t: false,
});
});
}
Expand All @@ -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);
Expand All @@ -254,6 +287,7 @@ impl<'a> Visit<'a> for ModuleLexer<'a> {
e: decl.exported.span().end,
ls: None,
le: None,
t: false,
});
}

Expand All @@ -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,
Expand All @@ -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);
Expand Down
96 changes: 52 additions & 44 deletions crates/oxc_module_lexer/tests/esm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,63 @@ use oxc_parser::Parser;
use oxc_span::SourceType;

#[derive(Debug, Clone)]
struct ImportSpecifier {
n: Option<String>,
s: u32,
e: u32,
ss: u32,
se: u32,
d: ImportType,
a: Option<u32>,
pub struct ImportSpecifier {
pub n: Option<String>,
pub s: u32,
pub e: u32,
pub ss: u32,
pub se: u32,
pub d: ImportType,
pub a: Option<u32>,
pub t: bool,
}

impl From<oxc_module_lexer::ImportSpecifier<'_>> 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<String>,
s: u32,
e: u32,
ls: Option<u32>,
le: Option<u32>,
pub n: String,
pub ln: Option<String>,
pub s: u32,
pub e: u32,
pub ls: Option<u32>,
pub le: Option<u32>,
pub t: bool,
}

impl From<oxc_module_lexer::ExportSpecifier<'_>> 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<ImportSpecifier>,
exports: Vec<ExportSpecifier>,
has_module_syntax: bool,
facade: bool,
pub struct ModuleLexer {
pub imports: Vec<ImportSpecifier>,
pub exports: Vec<ExportSpecifier>,
pub has_module_syntax: bool,
pub facade: bool,
}

fn parse(source: &str) -> ModuleLexer {
Expand All @@ -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,
}
Expand Down
69 changes: 69 additions & 0 deletions crates/oxc_module_lexer/tests/typescript.rs
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit d22bd20

Please sign in to comment.