feat(transformer): add import helpers to manage module imports (#2996)

closes #2971
This commit is contained in:
Boshen 2024-04-16 14:33:44 +08:00 committed by GitHub
parent df2036eea0
commit e43c245388
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 172 additions and 71 deletions

1
Cargo.lock generated
View file

@ -1678,6 +1678,7 @@ dependencies = [
name = "oxc_transformer" name = "oxc_transformer"
version = "0.12.3" version = "0.12.3"
dependencies = [ dependencies = [
"indexmap",
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
"oxc_codegen", "oxc_codegen",

View file

@ -27,8 +27,8 @@ oxc_diagnostics = { workspace = true }
oxc_syntax = { workspace = true } oxc_syntax = { workspace = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }
indexmap = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
[dev-dependencies] [dev-dependencies]
oxc_parser = { workspace = true } oxc_parser = { workspace = true }

View file

@ -5,6 +5,8 @@ use oxc_ast::AstBuilder;
use oxc_diagnostics::Error; use oxc_diagnostics::Error;
use oxc_semantic::Semantic; use oxc_semantic::Semantic;
use crate::helpers::module_imports::ModuleImports;
pub type Ctx<'a> = Rc<TransformCtx<'a>>; pub type Ctx<'a> = Rc<TransformCtx<'a>>;
pub struct TransformCtx<'a> { pub struct TransformCtx<'a> {
@ -16,6 +18,10 @@ pub struct TransformCtx<'a> {
filename: String, filename: String,
errors: RefCell<Vec<Error>>, errors: RefCell<Vec<Error>>,
// Helpers
/// Manage import statement globally
pub module_imports: ModuleImports<'a>,
} }
impl<'a> TransformCtx<'a> { impl<'a> TransformCtx<'a> {
@ -25,7 +31,8 @@ impl<'a> TransformCtx<'a> {
.file_stem() // omit file extension .file_stem() // omit file extension
.map_or_else(|| String::from("unknown"), |name| name.to_string_lossy().to_string()); .map_or_else(|| String::from("unknown"), |name| name.to_string_lossy().to_string());
let errors = RefCell::new(vec![]); let errors = RefCell::new(vec![]);
Self { ast, semantic, filename, errors } let module_imports = ModuleImports::new(allocator);
Self { ast, semantic, filename, errors, module_imports }
} }
pub fn take_errors(&self) -> Vec<Error> { pub fn take_errors(&self) -> Vec<Error> {

View file

@ -0,0 +1,136 @@
use indexmap::IndexMap;
use std::cell::RefCell;
use oxc_allocator::{Allocator, Vec};
use oxc_ast::{ast::*, AstBuilder};
use oxc_span::{CompactStr, SPAN};
pub struct NamedImport {
imported: CompactStr,
local: Option<CompactStr>, // Not used in `require`
}
impl NamedImport {
pub fn new(imported: CompactStr, local: Option<CompactStr>) -> NamedImport {
Self { imported, local }
}
}
#[derive(Hash, Eq, PartialEq)]
pub enum ImportKind {
Import,
Require,
}
#[derive(Hash, Eq, PartialEq)]
pub struct ImportType {
kind: ImportKind,
source: CompactStr,
}
impl ImportType {
fn new(kind: ImportKind, source: CompactStr) -> Self {
Self { kind, source }
}
}
/// Manage import statement globally
/// <https://github.com/nicolo-ribaudo/babel/tree/main/packages/babel-helper-module-imports>
pub struct ModuleImports<'a> {
ast: AstBuilder<'a>,
imports: RefCell<IndexMap<ImportType, std::vec::Vec<NamedImport>>>,
}
impl<'a> ModuleImports<'a> {
pub fn new(allocator: &'a Allocator) -> ModuleImports<'a> {
let ast = AstBuilder::new(allocator);
Self { ast, imports: RefCell::new(IndexMap::default()) }
}
/// Add `import { named_import } from 'source'`
pub fn add_import(&self, source: CompactStr, import: NamedImport) {
self.imports
.borrow_mut()
.entry(ImportType::new(ImportKind::Import, source))
.or_default()
.push(import);
}
/// Add `var named_import from 'source'`
pub fn add_require(&self, source: CompactStr, import: NamedImport, front: bool) {
let len = self.imports.borrow().len();
self.imports
.borrow_mut()
.entry(ImportType::new(ImportKind::Require, source))
.or_default()
.push(import);
if front {
self.imports.borrow_mut().move_index(len, 0);
}
}
pub fn get_import_statements(&self) -> Vec<'a, Statement<'a>> {
self.ast.new_vec_from_iter(self.imports.borrow_mut().drain(..).map(
|(import_type, names)| match import_type.kind {
ImportKind::Import => self.get_named_import(&import_type.source, names),
ImportKind::Require => self.get_require(&import_type.source, names),
},
))
}
fn get_named_import(
&self,
source: &CompactStr,
names: std::vec::Vec<NamedImport>,
) -> Statement<'a> {
let specifiers = self.ast.new_vec_from_iter(names.into_iter().map(|name| {
ImportDeclarationSpecifier::ImportSpecifier(ImportSpecifier {
span: SPAN,
imported: ModuleExportName::Identifier(IdentifierName::new(
SPAN,
self.ast.new_atom(name.imported.as_str()),
)),
local: BindingIdentifier::new(
SPAN,
self.ast.new_atom(name.local.unwrap_or(name.imported).as_str()),
),
import_kind: ImportOrExportKind::Value,
})
}));
let import_stmt = self.ast.import_declaration(
SPAN,
Some(specifiers),
self.ast.string_literal(SPAN, source.as_str()),
None,
ImportOrExportKind::Value,
);
self.ast.module_declaration(ModuleDeclaration::ImportDeclaration(import_stmt))
}
fn get_require(&self, source: &CompactStr, names: std::vec::Vec<NamedImport>) -> Statement<'a> {
let var_kind = VariableDeclarationKind::Var;
let callee = {
let ident = IdentifierReference::new(SPAN, "require".into());
self.ast.identifier_reference_expression(ident)
};
let args = {
let string = self.ast.string_literal(SPAN, source.as_str());
let arg = Argument::Expression(self.ast.literal_string_expression(string));
self.ast.new_vec_single(arg)
};
let name = names.into_iter().next().unwrap();
let id = {
let ident = BindingIdentifier::new(SPAN, self.ast.new_atom(&name.imported));
self.ast.binding_pattern(self.ast.binding_pattern_identifier(ident), None, false)
};
let decl = {
let init = self.ast.call_expression(SPAN, callee, args, false, None);
let decl = self.ast.variable_declarator(SPAN, var_kind, id, Some(init), false);
self.ast.new_vec_single(decl)
};
let variable_declaration =
self.ast.variable_declaration(SPAN, var_kind, decl, Modifiers::empty());
Statement::Declaration(Declaration::VariableDeclaration(variable_declaration))
}
}

View file

@ -15,6 +15,10 @@ mod options;
mod react; mod react;
mod typescript; mod typescript;
mod helpers {
pub mod module_imports;
}
use std::{path::Path, rc::Rc}; use std::{path::Path, rc::Rc};
use oxc_allocator::{Allocator, Vec}; use oxc_allocator::{Allocator, Vec};

View file

@ -10,7 +10,7 @@ use oxc_syntax::{
xml_entities::XML_ENTITIES, xml_entities::XML_ENTITIES,
}; };
use crate::context::Ctx; use crate::{context::Ctx, helpers::module_imports::NamedImport};
pub use super::{ pub use super::{
jsx_self::ReactJsxSelf, jsx_self::ReactJsxSelf,
@ -42,7 +42,6 @@ pub struct ReactJsx<'a> {
jsx_source: ReactJsxSource<'a>, jsx_source: ReactJsxSource<'a>,
// States // States
imports: std::vec::Vec<Statement<'a>>,
require_jsx_runtime: bool, require_jsx_runtime: bool,
jsx_runtime_importer: CompactStr, jsx_runtime_importer: CompactStr,
default_runtime: ReactJsxRuntime, default_runtime: ReactJsxRuntime,
@ -69,7 +68,6 @@ impl<'a> ReactJsx<'a> {
ctx: Rc::clone(ctx), ctx: Rc::clone(ctx),
jsx_self: ReactJsxSelf::new(ctx), jsx_self: ReactJsxSelf::new(ctx),
jsx_source: ReactJsxSource::new(ctx), jsx_source: ReactJsxSource::new(ctx),
imports: vec![],
require_jsx_runtime: false, require_jsx_runtime: false,
jsx_runtime_importer, jsx_runtime_importer,
import_jsx: false, import_jsx: false,
@ -118,7 +116,7 @@ impl<'a> ReactJsx<'a> {
return; return;
} }
let imports = self.ctx.ast.new_vec_from_iter(self.imports.drain(..)); let imports = self.ctx.module_imports.get_import_statements();
let index = program let index = program
.body .body
.iter() .iter()
@ -154,21 +152,20 @@ impl<'a> ReactJsx<'a> {
fn add_require_jsx_runtime(&mut self) { fn add_require_jsx_runtime(&mut self) {
if !self.require_jsx_runtime { if !self.require_jsx_runtime {
self.require_jsx_runtime = true; self.require_jsx_runtime = true;
let source = self.ast().string_literal(SPAN, self.jsx_runtime_importer.as_str()); self.add_require_statement(
self.add_require_statement("_reactJsxRuntime", source, false); "_reactJsxRuntime",
self.jsx_runtime_importer.clone(),
false,
);
} }
} }
fn get_jsx_runtime_source(&self) -> StringLiteral<'a> {
self.ast().string_literal(SPAN, self.jsx_runtime_importer.as_str())
}
fn add_import_jsx(&mut self) { fn add_import_jsx(&mut self) {
if self.is_script() { if self.is_script() {
self.add_require_jsx_runtime(); self.add_require_jsx_runtime();
} else if !self.import_jsx { } else if !self.import_jsx {
self.import_jsx = true; self.import_jsx = true;
self.add_import_statement("jsx", "_jsx", self.get_jsx_runtime_source()); self.add_import_statement("jsx", "_jsx", self.jsx_runtime_importer.clone());
} }
} }
@ -177,7 +174,7 @@ impl<'a> ReactJsx<'a> {
self.add_require_jsx_runtime(); self.add_require_jsx_runtime();
} else if !self.import_jsxs { } else if !self.import_jsxs {
self.import_jsxs = true; self.import_jsxs = true;
self.add_import_statement("jsxs", "_jsxs", self.get_jsx_runtime_source()); self.add_import_statement("jsxs", "_jsxs", self.jsx_runtime_importer.clone());
} }
} }
@ -186,7 +183,7 @@ impl<'a> ReactJsx<'a> {
self.add_require_jsx_runtime(); self.add_require_jsx_runtime();
} else if !self.import_fragment { } else if !self.import_fragment {
self.import_fragment = true; self.import_fragment = true;
self.add_import_statement("Fragment", "_Fragment", self.get_jsx_runtime_source()); self.add_import_statement("Fragment", "_Fragment", self.jsx_runtime_importer.clone());
self.add_import_jsx(); self.add_import_jsx();
} }
} }
@ -194,65 +191,23 @@ impl<'a> ReactJsx<'a> {
fn add_import_create_element(&mut self) { fn add_import_create_element(&mut self) {
if !self.import_create_element { if !self.import_create_element {
self.import_create_element = true; self.import_create_element = true;
let source = self.ast().string_literal(SPAN, self.options.import_source.as_ref()); let source = self.options.import_source.as_ref();
if self.is_script() { if self.is_script() {
self.add_require_statement("_react", source, true); self.add_require_statement("_react", source.into(), true);
} else { } else {
self.add_import_statement("createElement", "_createElement", source); self.add_import_statement("createElement", "_createElement", source.into());
} }
} }
} }
fn add_import_statement(&mut self, imported: &str, local: &str, source: StringLiteral<'a>) { fn add_import_statement(&mut self, imported: &str, local: &str, source: CompactStr) {
let mut specifiers = self.ast().new_vec_with_capacity(1); let import = NamedImport::new(imported.into(), Some(local.into()));
let imported = self.ctx.module_imports.add_import(source, import);
ModuleExportName::Identifier(IdentifierName::new(SPAN, self.ast().new_atom(imported)));
specifiers.push(ImportDeclarationSpecifier::ImportSpecifier(ImportSpecifier {
span: SPAN,
imported,
local: BindingIdentifier::new(SPAN, self.ast().new_atom(local)),
import_kind: ImportOrExportKind::Value,
}));
let value = ImportOrExportKind::Value;
let import_stmt =
self.ast().import_declaration(SPAN, Some(specifiers), source, None, value);
let decl = self.ast().module_declaration(ModuleDeclaration::ImportDeclaration(import_stmt));
self.imports.push(decl);
} }
/// `var variable_name = require(source);` fn add_require_statement(&mut self, variable_name: &str, source: CompactStr, front: bool) {
fn add_require_statement( let import = NamedImport::new(variable_name.into(), None);
&mut self, self.ctx.module_imports.add_require(source, import, front);
variable_name: &str,
source: StringLiteral<'a>,
front: bool,
) {
let var_kind = VariableDeclarationKind::Var;
let callee = {
let ident = IdentifierReference::new(SPAN, "require".into());
self.ctx.ast.identifier_reference_expression(ident)
};
let args = {
let arg = Argument::Expression(self.ast().literal_string_expression(source));
self.ctx.ast.new_vec_single(arg)
};
let id = {
let ident = BindingIdentifier::new(SPAN, self.ast().new_atom(variable_name));
self.ast().binding_pattern(self.ast().binding_pattern_identifier(ident), None, false)
};
let decl = {
let init = self.ast().call_expression(SPAN, callee, args, false, None);
let decl = self.ast().variable_declarator(SPAN, var_kind, id, Some(init), false);
self.ast().new_vec_single(decl)
};
let variable_declaration =
self.ast().variable_declaration(SPAN, var_kind, decl, Modifiers::empty());
let stmt = Statement::Declaration(Declaration::VariableDeclaration(variable_declaration));
if front {
self.imports.insert(0, stmt);
} else {
self.imports.push(stmt);
}
} }
} }

View file

@ -1,4 +1,4 @@
Passed: 123/227 Passed: 125/227
# All Passed: # All Passed:
* babel-plugin-transform-react-jsx-source * babel-plugin-transform-react-jsx-source
@ -107,10 +107,8 @@ Passed: 123/227
* regression/another-preset-with-custom-jsx-keep-source-self/input.mjs * regression/another-preset-with-custom-jsx-keep-source-self/input.mjs
* regression/runtime-classic-allow-multiple-source-self/input.mjs * regression/runtime-classic-allow-multiple-source-self/input.mjs
# babel-plugin-transform-react-jsx (33/36) # babel-plugin-transform-react-jsx (35/36)
* autoImport/auto-import-react-source-type-module/input.js
* autoImport/complicated-scope-module/input.js * autoImport/complicated-scope-module/input.js
* autoImport/react-defined/input.js
# babel-plugin-transform-react-display-name (3/4) # babel-plugin-transform-react-display-name (3/4)
* display-name/nested/input.js * display-name/nested/input.js