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

perf(transformer/jsx): no string comparisons generating pragma expression #7620

Merged
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
188 changes: 154 additions & 34 deletions crates/oxc_transformer/src/jsx/jsx_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,20 @@ fn get_import_source(jsx_runtime_importer: &str, react_importer_len: u32) -> Ato
Atom::from(&jsx_runtime_importer[..react_importer_len as usize])
}

/// Pragma used in classic mode
struct Pragma<'a> {
ident_parts: Vec<Atom<'a>>,
/// Pragma used in classic mode.
///
/// `Double` is first as it's most common.
enum Pragma<'a> {
/// `React.createElement`
Double(Atom<'a>, Atom<'a>),
/// `createElement`
Single(Atom<'a>),
/// `foo.bar.qux`
Multiple(Vec<Atom<'a>>),
/// `this`, `this.foo`, `this.foo.bar.qux`
This(Vec<Atom<'a>>),
/// `import.meta`, `import.meta.foo`, `import.meta.foo.bar.qux`
ImportMeta(Vec<Atom<'a>>),
}

impl<'a> Pragma<'a> {
Expand All @@ -329,43 +340,64 @@ impl<'a> Pragma<'a> {
) -> Self {
if let Some(pragma) = pragma {
if pragma.is_empty() {
return Self::invalid(default_property_name, ctx);
ctx.error(diagnostics::invalid_pragma());
} else {
return Self::parse_impl(pragma, ast);
}

let ident_parts = pragma.split('.').map(|item| ast.atom(item)).collect::<Vec<_>>();
Self { ident_parts }
} else {
Self::default(default_property_name)
}
}

fn invalid(default_property_name: &'static str, ctx: &TransformCtx<'a>) -> Self {
ctx.error(diagnostics::invalid_pragma());
Self::default(default_property_name)
Self::Double(Atom::from("React"), Atom::from(default_property_name))
}

fn default(default_property_name: &'static str) -> Self {
Self { ident_parts: vec![Atom::from("React"), Atom::from(default_property_name)] }
fn parse_impl(pragma: &str, ast: AstBuilder<'a>) -> Self {
let strs_to_atoms = |parts: &[&str]| parts.iter().map(|part| ast.atom(part)).collect();

let parts = pragma.split('.').collect::<Vec<_>>();
match &parts[..] {
[] => unreachable!(),
["this", rest @ ..] => Self::This(strs_to_atoms(rest)),
["import", "meta", rest @ ..] => Self::ImportMeta(strs_to_atoms(rest)),
[first, second] => Self::Double(ast.atom(first), ast.atom(second)),
[only] => Self::Single(ast.atom(only)),
parts => Self::Multiple(strs_to_atoms(parts)),
}
}

fn create_expression(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
let (mut expr, parts) = match &self.ident_parts[..] {
[] => unreachable!(),
[atom, rest @ ..] if atom == "this" => (ctx.ast.expression_this(SPAN), rest),
[first, second, rest @ ..] if first == "import" && second == "meta" => (
ctx.ast.expression_meta_property(
SPAN,
ctx.ast.identifier_name(SPAN, first),
ctx.ast.identifier_name(SPAN, second),
),
rest,
),
[first, rest @ ..] => {
let (object, parts) = match self {
Self::Double(first, second) => {
let object = get_read_identifier_reference(SPAN, first.clone(), ctx);
let expr = Expression::Identifier(ctx.alloc(object));
(expr, rest)
return Expression::from(ctx.ast.member_expression_static(
SPAN,
object,
ctx.ast.identifier_name(SPAN, second.clone()),
false,
));
}
Self::Single(single) => {
return get_read_identifier_reference(SPAN, single.clone(), ctx);
}
Self::Multiple(parts) => {
let mut parts = parts.iter();
let first = parts.next().unwrap().clone();
let object = get_read_identifier_reference(SPAN, first, ctx);
(object, parts)
}
Self::This(parts) => {
let object = ctx.ast.expression_this(SPAN);
(object, parts.iter())
}
Self::ImportMeta(parts) => {
let object = ctx.ast.expression_meta_property(
SPAN,
ctx.ast.identifier_name(SPAN, Atom::from("import")),
ctx.ast.identifier_name(SPAN, Atom::from("meta")),
);
(object, parts.iter())
}
};

let mut expr = object;
for item in parts {
let name = ctx.ast.identifier_name(SPAN, item.clone());
expr = ctx.ast.member_expression_static(SPAN, expr, name, false).into();
Expand Down Expand Up @@ -1049,10 +1081,11 @@ fn get_read_identifier_reference<'a>(
span: Span,
name: Atom<'a>,
ctx: &mut TraverseCtx<'a>,
) -> IdentifierReference<'a> {
) -> Expression<'a> {
let reference_id =
ctx.create_reference_in_current_scope(name.to_compact_str(), ReferenceFlags::Read);
ctx.ast.identifier_reference_with_reference_id(span, name, reference_id)
let ident = ctx.ast.alloc_identifier_reference_with_reference_id(span, name, reference_id);
Expression::Identifier(ident)
}

fn create_static_member_expression<'a>(
Expand All @@ -1076,6 +1109,7 @@ mod test {
use oxc_allocator::Allocator;
use oxc_ast::ast::Expression;
use oxc_semantic::{ScopeTree, SymbolTable};
use oxc_syntax::{node::NodeId, scope::ScopeFlags};
use oxc_traverse::ReusableTraverseCtx;

use crate::{TransformCtx, TransformOptions};
Expand All @@ -1085,18 +1119,91 @@ mod test {
macro_rules! setup {
($traverse_ctx:ident, $transform_ctx:ident) => {
let allocator = Allocator::default();
let traverse_ctx =
ReusableTraverseCtx::new(ScopeTree::default(), SymbolTable::default(), &allocator);

let mut scopes = ScopeTree::default();
scopes.add_scope(None, NodeId::DUMMY, ScopeFlags::Top);

let traverse_ctx = ReusableTraverseCtx::new(scopes, SymbolTable::default(), &allocator);
// SAFETY: Macro user only gets a `&mut TraverseCtx`, which cannot be abused
let mut traverse_ctx = unsafe { traverse_ctx.unwrap() };
let $traverse_ctx = &mut traverse_ctx;

let $transform_ctx =
TransformCtx::new(Path::new("test.jsx"), &TransformOptions::default());
};
}

#[test]
fn this_expr_pragma() {
fn default_pragma() {
setup!(traverse_ctx, transform_ctx);

let pragma = None;
let pragma = Pragma::parse(pragma, "createElement", traverse_ctx.ast, &transform_ctx);
let expr = pragma.create_expression(traverse_ctx);

let Expression::StaticMemberExpression(member) = &expr else { panic!() };
let Expression::Identifier(object) = &member.object else { panic!() };
assert_eq!(object.name, "React");
assert_eq!(member.property.name, "createElement");
}

#[test]
fn single_part_pragma() {
setup!(traverse_ctx, transform_ctx);

let pragma = Some("single");
let pragma = Pragma::parse(pragma, "createElement", traverse_ctx.ast, &transform_ctx);
let expr = pragma.create_expression(traverse_ctx);

let Expression::Identifier(ident) = &expr else { panic!() };
assert_eq!(ident.name, "single");
}

#[test]
fn two_part_pragma() {
setup!(traverse_ctx, transform_ctx);

let pragma = Some("first.second");
let pragma = Pragma::parse(pragma, "createElement", traverse_ctx.ast, &transform_ctx);
let expr = pragma.create_expression(traverse_ctx);

let Expression::StaticMemberExpression(member) = &expr else { panic!() };
let Expression::Identifier(object) = &member.object else { panic!() };
assert_eq!(object.name, "first");
assert_eq!(member.property.name, "second");
}

#[test]
fn multi_part_pragma() {
setup!(traverse_ctx, transform_ctx);

let pragma = Some("first.second.third");
let pragma = Pragma::parse(pragma, "createElement", traverse_ctx.ast, &transform_ctx);
let expr = pragma.create_expression(traverse_ctx);

let Expression::StaticMemberExpression(outer_member) = &expr else { panic!() };
let Expression::StaticMemberExpression(inner_member) = &outer_member.object else {
panic!()
};
let Expression::Identifier(object) = &inner_member.object else { panic!() };
assert_eq!(object.name, "first");
assert_eq!(inner_member.property.name, "second");
assert_eq!(outer_member.property.name, "third");
}

#[test]
fn this_pragma() {
setup!(traverse_ctx, transform_ctx);

let pragma = Some("this");
let pragma = Pragma::parse(pragma, "createElement", traverse_ctx.ast, &transform_ctx);
let expr = pragma.create_expression(traverse_ctx);

assert!(matches!(&expr, Expression::ThisExpression(_)));
}

#[test]
fn this_prop_pragma() {
setup!(traverse_ctx, transform_ctx);

let pragma = Some("this.a.b");
Expand All @@ -1116,6 +1223,19 @@ mod test {
fn import_meta_pragma() {
setup!(traverse_ctx, transform_ctx);

let pragma = Some("import.meta");
let pragma = Pragma::parse(pragma, "createElement", traverse_ctx.ast, &transform_ctx);
let expr = pragma.create_expression(traverse_ctx);

let Expression::MetaProperty(meta_prop) = &expr else { panic!() };
assert_eq!(&meta_prop.meta.name, "import");
assert_eq!(&meta_prop.property.name, "meta");
}

#[test]
fn import_meta_prop_pragma() {
setup!(traverse_ctx, transform_ctx);

let pragma = Some("import.meta.prop");
let pragma = Pragma::parse(pragma, "createElement", traverse_ctx.ast, &transform_ctx);
let expr = pragma.create_expression(traverse_ctx);
Expand Down
Loading