Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>,
Boshen marked this conversation as resolved.
Show resolved Hide resolved
}

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: 123/227
Passed: 125/227

# All Passed:
* babel-plugin-transform-react-jsx-source
Expand Down Expand Up @@ -107,10 +107,8 @@ Passed: 123/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
Loading