mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
fix(oxc_transformer): correct generate ThisExpr and import.meta in jsx pragma (#7553)
This commit is contained in:
parent
6dd71c6d05
commit
6af8659624
8 changed files with 108 additions and 25 deletions
|
|
@ -314,8 +314,7 @@ fn get_import_source(jsx_runtime_importer: &str, react_importer_len: u32) -> Ato
|
||||||
|
|
||||||
/// Pragma used in classic mode
|
/// Pragma used in classic mode
|
||||||
struct Pragma<'a> {
|
struct Pragma<'a> {
|
||||||
object: Atom<'a>,
|
ident_parts: Vec<Atom<'a>>,
|
||||||
properties: Vec<Atom<'a>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Pragma<'a> {
|
impl<'a> Pragma<'a> {
|
||||||
|
|
@ -329,16 +328,12 @@ impl<'a> Pragma<'a> {
|
||||||
ctx: &TransformCtx<'a>,
|
ctx: &TransformCtx<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if let Some(pragma) = pragma {
|
if let Some(pragma) = pragma {
|
||||||
let mut parts = pragma.split('.');
|
if pragma.is_empty() {
|
||||||
|
|
||||||
let object_name = parts.next().unwrap();
|
|
||||||
if object_name.is_empty() {
|
|
||||||
return Self::invalid(default_property_name, ctx);
|
return Self::invalid(default_property_name, ctx);
|
||||||
}
|
}
|
||||||
let props = parts.map(|item| ast.atom(item)).collect();
|
|
||||||
|
|
||||||
let object = ast.atom(object_name);
|
let ident_parts = pragma.split('.').map(|item| ast.atom(item)).collect::<Vec<_>>();
|
||||||
Self { object, properties: props }
|
Self { ident_parts }
|
||||||
} else {
|
} else {
|
||||||
Self::default(default_property_name)
|
Self::default(default_property_name)
|
||||||
}
|
}
|
||||||
|
|
@ -350,23 +345,28 @@ impl<'a> Pragma<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default(default_property_name: &'static str) -> Self {
|
fn default(default_property_name: &'static str) -> Self {
|
||||||
Self { object: Atom::from("React"), properties: vec![Atom::from(default_property_name)] }
|
Self { ident_parts: vec![Atom::from("React"), Atom::from(default_property_name)] }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_expression(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
|
fn create_expression(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
|
||||||
let object = get_read_identifier_reference(SPAN, self.object.clone(), ctx);
|
let (mut expr, parts) = match &self.ident_parts[..] {
|
||||||
Self::create_arbitrary_length_member_expr_or_ident(object, &self.properties, ctx)
|
[] => unreachable!(),
|
||||||
}
|
[atom, rest @ ..] if atom == "this" => (ctx.ast.expression_this(SPAN), rest),
|
||||||
|
[first, second, rest @ ..] if first == "import" && second == "meta" => (
|
||||||
/// create a static member expression without caring about the referenceId,
|
ctx.ast.expression_meta_property(
|
||||||
/// this function is always used to creat a tail part of a real member expression
|
SPAN,
|
||||||
fn create_arbitrary_length_member_expr_or_ident(
|
ctx.ast.identifier_name(SPAN, first),
|
||||||
object: IdentifierReference<'a>,
|
ctx.ast.identifier_name(SPAN, second),
|
||||||
list: &[Atom<'a>],
|
),
|
||||||
ctx: &mut TraverseCtx<'a>,
|
rest,
|
||||||
) -> Expression<'a> {
|
),
|
||||||
let mut expr = Expression::Identifier(ctx.alloc(object));
|
[first, rest @ ..] => {
|
||||||
for item in list {
|
let object = get_read_identifier_reference(SPAN, first.clone(), ctx);
|
||||||
|
let expr = Expression::Identifier(ctx.alloc(object));
|
||||||
|
(expr, rest)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for item in parts {
|
||||||
let name = ctx.ast.identifier_name(SPAN, item.clone());
|
let name = ctx.ast.identifier_name(SPAN, item.clone());
|
||||||
expr = ctx.ast.member_expression_static(SPAN, expr, name, false).into();
|
expr = ctx.ast.member_expression_static(SPAN, expr, name, false).into();
|
||||||
}
|
}
|
||||||
|
|
@ -1067,3 +1067,60 @@ fn create_static_member_expression<'a>(
|
||||||
fn has_proto(e: &ObjectExpression<'_>) -> bool {
|
fn has_proto(e: &ObjectExpression<'_>) -> bool {
|
||||||
e.properties.iter().any(|p| p.prop_name().is_some_and(|name| name.0 == "__proto__"))
|
e.properties.iter().any(|p| p.prop_name().is_some_and(|name| name.0 == "__proto__"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use oxc_allocator::Allocator;
|
||||||
|
use oxc_ast::{ast::Expression, AstBuilder};
|
||||||
|
use oxc_semantic::{ScopeTree, SymbolTable};
|
||||||
|
use oxc_traverse::ReusableTraverseCtx;
|
||||||
|
|
||||||
|
use crate::{context::TransformCtx, TransformOptions};
|
||||||
|
|
||||||
|
use super::Pragma;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn this_expr_pragma() {
|
||||||
|
let alloc = Allocator::default();
|
||||||
|
let ast = AstBuilder::new(&alloc);
|
||||||
|
let ctx = TransformCtx::new(Path::new("test.jsx"), &TransformOptions::default());
|
||||||
|
// SAFETY: constructed to avoid unsoundness
|
||||||
|
let mut traverse_ctx = unsafe {
|
||||||
|
ReusableTraverseCtx::new(ScopeTree::default(), SymbolTable::default(), &alloc).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let pragma = Pragma::parse(Some(&"this.a.b".to_string()), "createElement", ast, &ctx);
|
||||||
|
let expr = pragma.create_expression(&mut traverse_ctx);
|
||||||
|
|
||||||
|
let Expression::StaticMemberExpression(outer_member) = &expr else { panic!() };
|
||||||
|
let Expression::StaticMemberExpression(inner_member) = &outer_member.object else {
|
||||||
|
panic!()
|
||||||
|
};
|
||||||
|
assert!(matches!(&inner_member.object, Expression::ThisExpression(_)));
|
||||||
|
assert_eq!(inner_member.property.name, "a");
|
||||||
|
assert_eq!(outer_member.property.name, "b");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn import_meta_pragma() {
|
||||||
|
let alloc = Allocator::default();
|
||||||
|
let ast = AstBuilder::new(&alloc);
|
||||||
|
let ctx = TransformCtx::new(Path::new("test.jsx"), &TransformOptions::default());
|
||||||
|
// SAFETY: constructed to avoid unsoundness
|
||||||
|
let mut traverse_ctx = unsafe {
|
||||||
|
ReusableTraverseCtx::new(ScopeTree::default(), SymbolTable::default(), &alloc).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let pragma =
|
||||||
|
Pragma::parse(Some(&"import.meta.prop".to_string()), "createElement", ast, &ctx);
|
||||||
|
let expr = pragma.create_expression(&mut traverse_ctx);
|
||||||
|
|
||||||
|
let Expression::StaticMemberExpression(member) = &expr else { panic!() };
|
||||||
|
let Expression::MetaProperty(meta_prop) = &member.object else { panic!() };
|
||||||
|
assert_eq!(&meta_prop.meta.name, "import");
|
||||||
|
assert_eq!(&meta_prop.property.name, "meta");
|
||||||
|
assert_eq!(member.property.name, "prop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
commit: 54a8389f
|
commit: 54a8389f
|
||||||
|
|
||||||
Passed: 91/102
|
Passed: 93/104
|
||||||
|
|
||||||
# All Passed:
|
# All Passed:
|
||||||
* babel-plugin-transform-class-static-block
|
* babel-plugin-transform-class-static-block
|
||||||
|
|
@ -170,7 +170,7 @@ rebuilt : SymbolId(2): []
|
||||||
x Output mismatch
|
x Output mismatch
|
||||||
|
|
||||||
|
|
||||||
# babel-plugin-transform-react-jsx (32/35)
|
# babel-plugin-transform-react-jsx (34/37)
|
||||||
* refresh/does-not-transform-it-because-it-is-not-used-in-the-AST/input.jsx
|
* refresh/does-not-transform-it-because-it-is-not-used-in-the-AST/input.jsx
|
||||||
x Output mismatch
|
x Output mismatch
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<test></test>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"transform-react-jsx",
|
||||||
|
{
|
||||||
|
"runtime": "classic",
|
||||||
|
"pragma": "import.meta.a"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import.meta.a("test", null);
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<test></test>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"transform-react-jsx",
|
||||||
|
{
|
||||||
|
"runtime": "classic",
|
||||||
|
"pragma": "this.a"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
this.a("test", null);
|
||||||
Loading…
Reference in a new issue