From 681b5e5177ccf909c5501ce3c2f8366ebc9b4079 Mon Sep 17 00:00:00 2001 From: Boshen Date: Mon, 15 Apr 2024 21:24:00 +0800 Subject: [PATCH] feat(transformer): add import helpers to manage module imports --- Cargo.lock | 1 + crates/oxc_transformer/Cargo.toml | 4 +- crates/oxc_transformer/src/context.rs | 9 +- .../src/helpers/module_imports.rs | 136 ++++++++++++++++++ crates/oxc_transformer/src/lib.rs | 4 + crates/oxc_transformer/src/react/jsx/mod.rs | 83 +++-------- tasks/transform_conformance/babel.snap.md | 6 +- 7 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 crates/oxc_transformer/src/helpers/module_imports.rs diff --git a/Cargo.lock b/Cargo.lock index 6f88e7ba507c48..cb9719a118a502 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1678,6 +1678,7 @@ dependencies = [ name = "oxc_transformer" version = "0.12.3" dependencies = [ + "indexmap", "oxc_allocator", "oxc_ast", "oxc_codegen", diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index b9ac5ed71af576..b5c06051b5ea25 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -27,8 +27,8 @@ oxc_diagnostics = { workspace = true } oxc_syntax = { workspace = true } rustc-hash = { workspace = true } - -serde = { workspace = true, features = ["derive"] } +indexmap = { workspace = true } +serde = { workspace = true, features = ["derive"] } [dev-dependencies] oxc_parser = { workspace = true } diff --git a/crates/oxc_transformer/src/context.rs b/crates/oxc_transformer/src/context.rs index 1dce7a44d32fcd..ba6ee84c99209a 100644 --- a/crates/oxc_transformer/src/context.rs +++ b/crates/oxc_transformer/src/context.rs @@ -5,6 +5,8 @@ use oxc_ast::AstBuilder; use oxc_diagnostics::Error; use oxc_semantic::Semantic; +use crate::helpers::module_imports::ModuleImports; + pub type Ctx<'a> = Rc>; pub struct TransformCtx<'a> { @@ -16,6 +18,10 @@ pub struct TransformCtx<'a> { filename: String, errors: RefCell>, + + // Helpers + /// Manage import statement globally + pub module_imports: ModuleImports<'a>, } impl<'a> TransformCtx<'a> { @@ -25,7 +31,8 @@ impl<'a> TransformCtx<'a> { .file_stem() // omit file extension .map_or_else(|| String::from("unknown"), |name| name.to_string_lossy().to_string()); 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 { diff --git a/crates/oxc_transformer/src/helpers/module_imports.rs b/crates/oxc_transformer/src/helpers/module_imports.rs new file mode 100644 index 00000000000000..458e920ee64f8d --- /dev/null +++ b/crates/oxc_transformer/src/helpers/module_imports.rs @@ -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, // Not used in `require` +} + +impl NamedImport { + pub fn new(imported: CompactStr, local: Option) -> 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 +/// +pub struct ModuleImports<'a> { + ast: AstBuilder<'a>, + + imports: RefCell>>, +} + +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, + ) -> 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) -> 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)) + } +} diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 12a7de14a310eb..ce7d2d955b93a9 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -15,6 +15,10 @@ mod options; mod react; mod typescript; +mod helpers { + pub mod module_imports; +} + use std::{path::Path, rc::Rc}; use oxc_allocator::{Allocator, Vec}; diff --git a/crates/oxc_transformer/src/react/jsx/mod.rs b/crates/oxc_transformer/src/react/jsx/mod.rs index ba80ad9bd595e4..6c9b6a98c62ac3 100644 --- a/crates/oxc_transformer/src/react/jsx/mod.rs +++ b/crates/oxc_transformer/src/react/jsx/mod.rs @@ -10,7 +10,7 @@ use oxc_syntax::{ xml_entities::XML_ENTITIES, }; -use crate::context::Ctx; +use crate::{context::Ctx, helpers::module_imports::NamedImport}; pub use super::{ jsx_self::ReactJsxSelf, @@ -42,7 +42,6 @@ pub struct ReactJsx<'a> { jsx_source: ReactJsxSource<'a>, // States - imports: std::vec::Vec>, require_jsx_runtime: bool, jsx_runtime_importer: CompactStr, default_runtime: ReactJsxRuntime, @@ -69,7 +68,6 @@ impl<'a> ReactJsx<'a> { ctx: Rc::clone(ctx), jsx_self: ReactJsxSelf::new(ctx), jsx_source: ReactJsxSource::new(ctx), - imports: vec![], require_jsx_runtime: false, jsx_runtime_importer, import_jsx: false, @@ -118,7 +116,7 @@ impl<'a> ReactJsx<'a> { 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 .body .iter() @@ -154,21 +152,20 @@ impl<'a> ReactJsx<'a> { fn add_require_jsx_runtime(&mut self) { if !self.require_jsx_runtime { self.require_jsx_runtime = true; - let source = self.ast().string_literal(SPAN, self.jsx_runtime_importer.as_str()); - self.add_require_statement("_reactJsxRuntime", source, false); + self.add_require_statement( + "_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) { if self.is_script() { self.add_require_jsx_runtime(); } else if !self.import_jsx { 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(); } else if !self.import_jsxs { 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(); } else if !self.import_fragment { 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(); } } @@ -194,65 +191,23 @@ impl<'a> ReactJsx<'a> { fn add_import_create_element(&mut self) { if !self.import_create_element { 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() { - self.add_require_statement("_react", source, true); + self.add_require_statement("_react", source.into(), true); } 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>) { - let mut specifiers = self.ast().new_vec_with_capacity(1); - let imported = - 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); + fn add_import_statement(&mut self, imported: &str, local: &str, source: CompactStr) { + let import = NamedImport::new(imported.into(), Some(local.into())); + self.ctx.module_imports.add_import(source, import); } - /// `var variable_name = require(source);` - fn add_require_statement( - &mut self, - 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); - } + fn add_require_statement(&mut self, variable_name: &str, source: CompactStr, front: bool) { + let import = NamedImport::new(variable_name.into(), None); + self.ctx.module_imports.add_require(source, import, front); } } diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 282df51c717de6..3a7e018305a767 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 93/227 +Passed: 95/227 # All Passed: * babel-plugin-transform-react-jsx-source @@ -137,10 +137,8 @@ Passed: 93/227 * regression/another-preset-with-custom-jsx-keep-source-self/input.mjs * regression/runtime-classic-allow-multiple-source-self/input.mjs -# babel-plugin-transform-react-jsx (33/36) -* autoImport/auto-import-react-source-type-module/input.js +# babel-plugin-transform-react-jsx (35/36) * autoImport/complicated-scope-module/input.js -* autoImport/react-defined/input.js # babel-plugin-transform-react-display-name (3/4) * display-name/nested/input.js