mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(module_lexer): distinguish for types-only imports and exports (#5184)
Co-authored-by: Boshen <boshenc@gmail.com>
This commit is contained in:
parent
666282a13b
commit
d22bd2082a
3 changed files with 165 additions and 45 deletions
|
|
@ -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)]
|
||||
|
|
@ -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)]
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
69
crates/oxc_module_lexer/tests/typescript.rs
Normal file
69
crates/oxc_module_lexer/tests/typescript.rs
Normal file
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in a new issue