Skip to content

Commit

Permalink
feat(transformer): add import helpers to manage module imports
Browse files Browse the repository at this point in the history
  • Loading branch information
Boshen committed Apr 16, 2024
1 parent fd5002b commit 681b5e5
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 71 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/oxc_transformer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
9 changes: 8 additions & 1 deletion crates/oxc_transformer/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TransformCtx<'a>>;

pub struct TransformCtx<'a> {
Expand All @@ -16,6 +18,10 @@ pub struct TransformCtx<'a> {
filename: String,

errors: RefCell<Vec<Error>>,

// Helpers
/// Manage import statement globally
pub module_imports: ModuleImports<'a>,
}

impl<'a> TransformCtx<'a> {
Expand All @@ -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<Error> {
Expand Down
136 changes: 136 additions & 0 deletions crates/oxc_transformer/src/helpers/module_imports.rs
Original file line number Diff line number Diff line change
@@ -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))
}
}
4 changes: 4 additions & 0 deletions crates/oxc_transformer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
83 changes: 19 additions & 64 deletions crates/oxc_transformer/src/react/jsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -42,7 +42,6 @@ pub struct ReactJsx<'a> {
jsx_source: ReactJsxSource<'a>,

// States
imports: std::vec::Vec<Statement<'a>>,
require_jsx_runtime: bool,
jsx_runtime_importer: CompactStr,
default_runtime: ReactJsxRuntime,
Expand All @@ -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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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());
}
}

Expand All @@ -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());
}
}

Expand All @@ -186,73 +183,31 @@ 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();
}
}

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);
}
}

Expand Down
6 changes: 2 additions & 4 deletions tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Passed: 93/227
Passed: 95/227

# All Passed:
* babel-plugin-transform-react-jsx-source
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 681b5e5

Please sign in to comment.