mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
fix(transformer): JSX set symbol_id on imports (#3523)
JSX transform set `symbol_id` on imported bindings. e.g. `_jsx` in `import {jsx as _jsx} from "react/jsx-runtime";`.
Setting matching `reference_id` on `IdentifierReference`s referring to the imports is not yet implemented.
This commit is contained in:
parent
91519d9e67
commit
591c54bd31
2 changed files with 60 additions and 47 deletions
|
|
@ -1,18 +1,20 @@
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use std::cell::RefCell;
|
use std::cell::{Cell, RefCell};
|
||||||
|
|
||||||
use oxc_allocator::{Allocator, Vec};
|
use oxc_allocator::{Allocator, Vec};
|
||||||
use oxc_ast::{ast::*, AstBuilder};
|
use oxc_ast::{ast::*, AstBuilder};
|
||||||
use oxc_span::{Atom, SPAN};
|
use oxc_span::{Atom, SPAN};
|
||||||
|
use oxc_syntax::symbol::SymbolId;
|
||||||
|
|
||||||
pub struct NamedImport<'a> {
|
pub struct NamedImport<'a> {
|
||||||
imported: Atom<'a>,
|
imported: Atom<'a>,
|
||||||
local: Option<Atom<'a>>, // Not used in `require`
|
local: Option<Atom<'a>>, // Not used in `require`
|
||||||
|
symbol_id: SymbolId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NamedImport<'a> {
|
impl<'a> NamedImport<'a> {
|
||||||
pub fn new(imported: Atom<'a>, local: Option<Atom<'a>>) -> Self {
|
pub fn new(imported: Atom<'a>, local: Option<Atom<'a>>, symbol_id: SymbolId) -> Self {
|
||||||
Self { imported, local }
|
Self { imported, local, symbol_id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,7 +91,11 @@ impl<'a> ModuleImports<'a> {
|
||||||
ImportDeclarationSpecifier::ImportSpecifier(self.ast.alloc(ImportSpecifier {
|
ImportDeclarationSpecifier::ImportSpecifier(self.ast.alloc(ImportSpecifier {
|
||||||
span: SPAN,
|
span: SPAN,
|
||||||
imported: ModuleExportName::Identifier(IdentifierName::new(SPAN, name.imported)),
|
imported: ModuleExportName::Identifier(IdentifierName::new(SPAN, name.imported)),
|
||||||
local: BindingIdentifier::new(SPAN, local),
|
local: BindingIdentifier {
|
||||||
|
span: SPAN,
|
||||||
|
name: local,
|
||||||
|
symbol_id: Cell::new(Some(name.symbol_id)),
|
||||||
|
},
|
||||||
import_kind: ImportOrExportKind::Value,
|
import_kind: ImportOrExportKind::Value,
|
||||||
}))
|
}))
|
||||||
}));
|
}));
|
||||||
|
|
@ -120,7 +126,11 @@ impl<'a> ModuleImports<'a> {
|
||||||
};
|
};
|
||||||
let name = names.into_iter().next().unwrap();
|
let name = names.into_iter().next().unwrap();
|
||||||
let id = {
|
let id = {
|
||||||
let ident = BindingIdentifier::new(SPAN, name.imported);
|
let ident = BindingIdentifier {
|
||||||
|
span: SPAN,
|
||||||
|
name: name.imported,
|
||||||
|
symbol_id: Cell::new(Some(name.symbol_id)),
|
||||||
|
};
|
||||||
self.ast.binding_pattern(self.ast.binding_pattern_identifier(ident), None, false)
|
self.ast.binding_pattern(self.ast.binding_pattern_identifier(ident), None, false)
|
||||||
};
|
};
|
||||||
let decl = {
|
let decl = {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use oxc_ast::{ast::*, AstBuilder};
|
||||||
use oxc_span::{Atom, GetSpan, Span, SPAN};
|
use oxc_span::{Atom, GetSpan, Span, SPAN};
|
||||||
use oxc_syntax::{
|
use oxc_syntax::{
|
||||||
identifier::{is_irregular_whitespace, is_line_terminator},
|
identifier::{is_irregular_whitespace, is_line_terminator},
|
||||||
symbol::SymbolFlags,
|
symbol::{SymbolFlags, SymbolId},
|
||||||
xml_entities::XML_ENTITIES,
|
xml_entities::XML_ENTITIES,
|
||||||
};
|
};
|
||||||
use oxc_traverse::TraverseCtx;
|
use oxc_traverse::TraverseCtx;
|
||||||
|
|
@ -43,15 +43,20 @@ pub struct ReactJsx<'a> {
|
||||||
jsx_runtime_importer: Atom<'a>,
|
jsx_runtime_importer: Atom<'a>,
|
||||||
|
|
||||||
// Doubles as var name for require react
|
// Doubles as var name for require react
|
||||||
import_create_element: Option<Atom<'a>>,
|
import_create_element: Option<BoundIdentifier<'a>>,
|
||||||
// Doubles as var name for require JSX
|
// Doubles as var name for require JSX
|
||||||
import_jsx: Option<Atom<'a>>,
|
import_jsx: Option<BoundIdentifier<'a>>,
|
||||||
import_jsxs: Option<Atom<'a>>,
|
import_jsxs: Option<BoundIdentifier<'a>>,
|
||||||
import_fragment: Option<Atom<'a>>,
|
import_fragment: Option<BoundIdentifier<'a>>,
|
||||||
|
|
||||||
can_add_filename_statement: bool,
|
can_add_filename_statement: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BoundIdentifier<'a> {
|
||||||
|
pub name: Atom<'a>,
|
||||||
|
pub symbol_id: SymbolId,
|
||||||
|
}
|
||||||
|
|
||||||
// Transforms
|
// Transforms
|
||||||
impl<'a> ReactJsx<'a> {
|
impl<'a> ReactJsx<'a> {
|
||||||
pub fn new(options: &Rc<ReactOptions>, ctx: &Ctx<'a>) -> Self {
|
pub fn new(options: &Rc<ReactOptions>, ctx: &Ctx<'a>) -> Self {
|
||||||
|
|
@ -182,9 +187,9 @@ impl<'a> ReactJsx<'a> {
|
||||||
if self.import_jsx.is_none() {
|
if self.import_jsx.is_none() {
|
||||||
let var_name =
|
let var_name =
|
||||||
if self.options.development { "reactJsxDevRuntime" } else { "reactJsxRuntime" };
|
if self.options.development { "reactJsxDevRuntime" } else { "reactJsxRuntime" };
|
||||||
let var_name =
|
let id =
|
||||||
self.add_require_statement(var_name, self.jsx_runtime_importer.clone(), false, ctx);
|
self.add_require_statement(var_name, self.jsx_runtime_importer.clone(), false, ctx);
|
||||||
self.import_jsx = Some(var_name);
|
self.import_jsx = Some(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,8 +199,8 @@ impl<'a> ReactJsx<'a> {
|
||||||
} else if self.options.development {
|
} else if self.options.development {
|
||||||
self.add_import_jsx_dev(ctx);
|
self.add_import_jsx_dev(ctx);
|
||||||
} else if self.import_jsx.is_none() {
|
} else if self.import_jsx.is_none() {
|
||||||
let var_name = self.add_import_statement("jsx", self.jsx_runtime_importer.clone(), ctx);
|
let id = self.add_import_statement("jsx", self.jsx_runtime_importer.clone(), ctx);
|
||||||
self.import_jsx = Some(var_name);
|
self.import_jsx = Some(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,9 +210,8 @@ impl<'a> ReactJsx<'a> {
|
||||||
} else if self.options.development {
|
} else if self.options.development {
|
||||||
self.add_import_jsx_dev(ctx);
|
self.add_import_jsx_dev(ctx);
|
||||||
} else if self.import_jsxs.is_none() {
|
} else if self.import_jsxs.is_none() {
|
||||||
let var_name =
|
let id = self.add_import_statement("jsxs", self.jsx_runtime_importer.clone(), ctx);
|
||||||
self.add_import_statement("jsxs", self.jsx_runtime_importer.clone(), ctx);
|
self.import_jsxs = Some(id);
|
||||||
self.import_jsxs = Some(var_name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,9 +219,8 @@ impl<'a> ReactJsx<'a> {
|
||||||
if self.is_script() {
|
if self.is_script() {
|
||||||
self.add_require_jsx_runtime(ctx);
|
self.add_require_jsx_runtime(ctx);
|
||||||
} else if self.import_jsx.is_none() {
|
} else if self.import_jsx.is_none() {
|
||||||
let var_name =
|
let id = self.add_import_statement("jsxDEV", self.jsx_runtime_importer.clone(), ctx);
|
||||||
self.add_import_statement("jsxDEV", self.jsx_runtime_importer.clone(), ctx);
|
self.import_jsx = Some(id);
|
||||||
self.import_jsx = Some(var_name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,9 +228,8 @@ impl<'a> ReactJsx<'a> {
|
||||||
if self.is_script() {
|
if self.is_script() {
|
||||||
self.add_require_jsx_runtime(ctx);
|
self.add_require_jsx_runtime(ctx);
|
||||||
} else if self.import_fragment.is_none() {
|
} else if self.import_fragment.is_none() {
|
||||||
let var_name =
|
let id = self.add_import_statement("Fragment", self.jsx_runtime_importer.clone(), ctx);
|
||||||
self.add_import_statement("Fragment", self.jsx_runtime_importer.clone(), ctx);
|
self.import_fragment = Some(id);
|
||||||
self.import_fragment = Some(var_name);
|
|
||||||
self.add_import_jsx(ctx);
|
self.add_import_jsx(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -235,12 +237,12 @@ impl<'a> ReactJsx<'a> {
|
||||||
fn add_import_create_element(&mut self, ctx: &mut TraverseCtx<'a>) {
|
fn add_import_create_element(&mut self, ctx: &mut TraverseCtx<'a>) {
|
||||||
if self.import_create_element.is_none() {
|
if self.import_create_element.is_none() {
|
||||||
let source = ctx.ast.new_atom(&self.options.import_source);
|
let source = ctx.ast.new_atom(&self.options.import_source);
|
||||||
let var_name = if self.is_script() {
|
let id = if self.is_script() {
|
||||||
self.add_require_statement("react", source, true, ctx)
|
self.add_require_statement("react", source, true, ctx)
|
||||||
} else {
|
} else {
|
||||||
self.add_import_statement("createElement", source, ctx)
|
self.add_import_statement("createElement", source, ctx)
|
||||||
};
|
};
|
||||||
self.import_create_element = Some(var_name);
|
self.import_create_element = Some(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,14 +251,14 @@ impl<'a> ReactJsx<'a> {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
source: Atom<'a>,
|
source: Atom<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Atom<'a> {
|
) -> BoundIdentifier<'a> {
|
||||||
let root_scope_id = ctx.scopes().root_scope_id();
|
let root_scope_id = ctx.scopes().root_scope_id();
|
||||||
let symbol_id = ctx.generate_uid(name, root_scope_id, SymbolFlags::FunctionScopedVariable);
|
let symbol_id = ctx.generate_uid(name, root_scope_id, SymbolFlags::FunctionScopedVariable);
|
||||||
let local = ctx.ast.new_atom(&ctx.symbols().names[symbol_id]);
|
let local = ctx.ast.new_atom(&ctx.symbols().names[symbol_id]);
|
||||||
|
|
||||||
let import = NamedImport::new(Atom::from(name), Some(local.clone()));
|
let import = NamedImport::new(Atom::from(name), Some(local.clone()), symbol_id);
|
||||||
self.ctx.module_imports.add_import(source, import);
|
self.ctx.module_imports.add_import(source, import);
|
||||||
local
|
BoundIdentifier { name: local, symbol_id }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_require_statement(
|
fn add_require_statement(
|
||||||
|
|
@ -265,15 +267,15 @@ impl<'a> ReactJsx<'a> {
|
||||||
source: Atom<'a>,
|
source: Atom<'a>,
|
||||||
front: bool,
|
front: bool,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) -> Atom<'a> {
|
) -> BoundIdentifier<'a> {
|
||||||
let root_scope_id = ctx.scopes().root_scope_id();
|
let root_scope_id = ctx.scopes().root_scope_id();
|
||||||
let symbol_id =
|
let symbol_id =
|
||||||
ctx.generate_uid(variable_name, root_scope_id, SymbolFlags::FunctionScopedVariable);
|
ctx.generate_uid(variable_name, root_scope_id, SymbolFlags::FunctionScopedVariable);
|
||||||
let variable_name = ctx.ast.new_atom(&ctx.symbols().names[symbol_id]);
|
let variable_name = ctx.ast.new_atom(&ctx.symbols().names[symbol_id]);
|
||||||
|
|
||||||
let import = NamedImport::new(variable_name.clone(), None);
|
let import = NamedImport::new(variable_name.clone(), None, symbol_id);
|
||||||
self.ctx.module_imports.add_require(source, import, front);
|
self.ctx.module_imports.add_require(source, import, front);
|
||||||
variable_name
|
BoundIdentifier { name: variable_name, symbol_id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -615,16 +617,18 @@ impl<'a> ReactJsx<'a> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_script() {
|
let (id, local_id) = if self.is_script() {
|
||||||
let Argument::StaticMemberExpression(member_expr) = arg else { unreachable!() };
|
let Argument::StaticMemberExpression(member_expr) = arg else { unreachable!() };
|
||||||
let Expression::Identifier(id) = &mut member_expr.object else {
|
let Expression::Identifier(id) = &mut member_expr.object else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
id.name = self.import_jsx.as_ref().unwrap().clone();
|
(id, self.import_jsx.as_ref().unwrap())
|
||||||
} else {
|
} else {
|
||||||
let Argument::Identifier(id) = arg else { unreachable!() };
|
let Argument::Identifier(id) = arg else { unreachable!() };
|
||||||
id.name = self.import_fragment.as_ref().unwrap().clone();
|
(id, self.import_fragment.as_ref().unwrap())
|
||||||
}
|
};
|
||||||
|
id.name = local_id.name.clone();
|
||||||
|
// TODO: Set `reference_id`
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_create_element(&self, has_key_after_props_spread: bool, jsxs: bool) -> Expression<'a> {
|
fn get_create_element(&self, has_key_after_props_spread: bool, jsxs: bool) -> Expression<'a> {
|
||||||
|
|
@ -640,11 +644,8 @@ impl<'a> ReactJsx<'a> {
|
||||||
}
|
}
|
||||||
ReactJsxRuntime::Automatic => {
|
ReactJsxRuntime::Automatic => {
|
||||||
if self.is_script() {
|
if self.is_script() {
|
||||||
let (object_ident_name, property_name) = if has_key_after_props_spread {
|
let (object_id, property_name) = if has_key_after_props_spread {
|
||||||
(
|
(self.import_create_element.as_ref().unwrap(), Atom::from("createElement"))
|
||||||
self.import_create_element.as_ref().unwrap().clone(),
|
|
||||||
Atom::from("createElement"),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
let property_name = if self.options.development {
|
let property_name = if self.options.development {
|
||||||
Atom::from("jsxDEV")
|
Atom::from("jsxDEV")
|
||||||
|
|
@ -653,18 +654,20 @@ impl<'a> ReactJsx<'a> {
|
||||||
} else {
|
} else {
|
||||||
Atom::from("jsx")
|
Atom::from("jsx")
|
||||||
};
|
};
|
||||||
(self.import_jsx.as_ref().unwrap().clone(), property_name)
|
(self.import_jsx.as_ref().unwrap(), property_name)
|
||||||
};
|
};
|
||||||
self.get_static_member_expression(object_ident_name, property_name)
|
// TODO: Set `reference_id`
|
||||||
|
self.get_static_member_expression(object_id.name.clone(), property_name)
|
||||||
} else {
|
} else {
|
||||||
let name = if has_key_after_props_spread {
|
let id = if has_key_after_props_spread {
|
||||||
self.import_create_element.as_ref().unwrap().clone()
|
self.import_create_element.as_ref().unwrap()
|
||||||
} else if jsxs && !self.options.development {
|
} else if jsxs && !self.options.development {
|
||||||
self.import_jsxs.as_ref().unwrap().clone()
|
self.import_jsxs.as_ref().unwrap()
|
||||||
} else {
|
} else {
|
||||||
self.import_jsx.as_ref().unwrap().clone()
|
self.import_jsx.as_ref().unwrap()
|
||||||
};
|
};
|
||||||
let ident = IdentifierReference::new(SPAN, name);
|
// TODO: Set `reference_id`
|
||||||
|
let ident = IdentifierReference::new(SPAN, id.name.clone());
|
||||||
self.ast().identifier_reference_expression(ident)
|
self.ast().identifier_reference_expression(ident)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue