mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(react_compiler): preserve hook spans for React Refresh (#25393)
Fix React Refresh when used with the React Compiler N-API transform by preserving original hook argument and binding-pattern spans through compiler codegen. This prevents refresh signature generation from panicking and keeps signatures based on the original source. Validated with `just ready` and the React transform N-API suite. Generated with AI assistance; reviewed and validated by the contributor.
This commit is contained in:
1 parent
4a6542bfda
commit
7d8a9b5f2f
5 files changed
+56
-24
No files matched your search
@@ -1010,6 +1010,7 @@ pub struct SpreadPattern {
|
||||
#[derive(Debug)]
|
||||
pub struct ArrayPattern<'a> {
|
||||
pub items: ArenaVec<'a, ArrayPatternElement>,
|
||||
pub span: Option<Span>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -1022,6 +1023,7 @@ pub enum ArrayPatternElement {
|
||||
#[derive(Debug)]
|
||||
pub struct ObjectPattern<'a> {
|
||||
pub properties: ArenaVec<'a, ObjectPropertyOrSpread<'a>>,
|
||||
pub span: Option<Span>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -1884,14 +1886,14 @@ impl<'a> CloneIn<'a> for Pattern<'a> {
|
||||
impl<'a> CloneIn<'a> for ArrayPattern<'a> {
|
||||
type Cloned = ArrayPattern<'a>;
|
||||
fn clone_in_impl(&self, sem: CloneInSemanticIds, alloc: &'a Allocator) -> Self {
|
||||
ArrayPattern { items: self.items.clone_in_impl(sem, alloc) }
|
||||
ArrayPattern { items: self.items.clone_in_impl(sem, alloc), span: self.span }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CloneIn<'a> for ObjectPattern<'a> {
|
||||
type Cloned = ObjectPattern<'a>;
|
||||
fn clone_in_impl(&self, sem: CloneInSemanticIds, alloc: &'a Allocator) -> Self {
|
||||
ObjectPattern { properties: self.properties.clone_in_impl(sem, alloc) }
|
||||
ObjectPattern { properties: self.properties.clone_in_impl(sem, alloc), span: self.span }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1422,7 +1422,10 @@ fn lower_binding_assignment<'a>(
|
||||
let temporary = lower_value_to_temporary(
|
||||
builder,
|
||||
InstructionValue::Destructure {
|
||||
lvalue: LValuePattern { pattern: Pattern::Array(ArrayPattern { items }), kind },
|
||||
lvalue: LValuePattern {
|
||||
pattern: Pattern::Array(ArrayPattern { items, span: Some(pattern.span) }),
|
||||
kind,
|
||||
},
|
||||
value,
|
||||
span: Some(span),
|
||||
},
|
||||
@@ -1576,7 +1579,10 @@ fn lower_binding_assignment<'a>(
|
||||
builder,
|
||||
InstructionValue::Destructure {
|
||||
lvalue: LValuePattern {
|
||||
pattern: Pattern::Object(ObjectPattern { properties }),
|
||||
pattern: Pattern::Object(ObjectPattern {
|
||||
properties,
|
||||
span: Some(pattern.span),
|
||||
}),
|
||||
kind,
|
||||
},
|
||||
value,
|
||||
@@ -2018,7 +2024,10 @@ fn lower_assignment_target<'a>(
|
||||
let temporary = lower_value_to_temporary(
|
||||
builder,
|
||||
InstructionValue::Destructure {
|
||||
lvalue: LValuePattern { pattern: Pattern::Array(ArrayPattern { items }), kind },
|
||||
lvalue: LValuePattern {
|
||||
pattern: Pattern::Array(ArrayPattern { items, span: Some(pattern.span) }),
|
||||
kind,
|
||||
},
|
||||
value,
|
||||
span: Some(span),
|
||||
},
|
||||
@@ -2289,7 +2298,10 @@ fn lower_assignment_target<'a>(
|
||||
builder,
|
||||
InstructionValue::Destructure {
|
||||
lvalue: LValuePattern {
|
||||
pattern: Pattern::Object(ObjectPattern { properties }),
|
||||
pattern: Pattern::Object(ObjectPattern {
|
||||
properties,
|
||||
span: Some(pattern.span),
|
||||
}),
|
||||
kind,
|
||||
},
|
||||
value,
|
||||
|
||||
@@ -609,7 +609,7 @@ fn emit_destructure_props<'a>(
|
||||
lvalue,
|
||||
value: InstructionValue::Destructure {
|
||||
lvalue: LValuePattern {
|
||||
pattern: Pattern::Object(ObjectPattern { properties }),
|
||||
pattern: Pattern::Object(ObjectPattern { properties, span: None }),
|
||||
kind: InstructionKind::Let,
|
||||
},
|
||||
value: *props_obj,
|
||||
|
||||
+32
-15
@@ -48,7 +48,7 @@ use crate::react_compiler_hir::reactive::ReactiveTerminal;
|
||||
use crate::react_compiler_hir::reactive::ReactiveTerminalTargetKind;
|
||||
use crate::react_compiler_hir::reactive::ReactiveValue;
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
use oxc_span::Span;
|
||||
use oxc_span::{GetSpanMut, Span};
|
||||
|
||||
use crate::react_compiler_reactive_scopes::build_reactive_function::build_reactive_function;
|
||||
use crate::react_compiler_reactive_scopes::prune_hoisted_contexts::prune_hoisted_contexts;
|
||||
@@ -1130,10 +1130,10 @@ fn ox_extract_for_in_of_lval<'a>(
|
||||
) -> Result<(oxc::BindingPattern<'a>, oxc::VariableDeclarationKind), OxcDiagnostic> {
|
||||
let (lval, kind) = match instr_value {
|
||||
InstructionValue::StoreLocal { lvalue, .. } => {
|
||||
(ox_codegen_lvalue(cx, &LvalueRef::Place(&lvalue.place))?, lvalue.kind)
|
||||
(ox_codegen_lvalue(cx, &LvalueRef::Place(&lvalue.place), span)?, lvalue.kind)
|
||||
}
|
||||
InstructionValue::Destructure { lvalue, .. } => {
|
||||
(ox_codegen_lvalue(cx, &LvalueRef::Pattern(&lvalue.pattern))?, lvalue.kind)
|
||||
(ox_codegen_lvalue(cx, &LvalueRef::Pattern(&lvalue.pattern), span)?, lvalue.kind)
|
||||
}
|
||||
InstructionValue::StoreContext { .. } => {
|
||||
cx.record_error(
|
||||
@@ -1363,11 +1363,11 @@ fn ox_emit_store<'a>(
|
||||
instr.span,
|
||||
));
|
||||
}
|
||||
let lval = ox_codegen_lvalue(cx, lvalue)?;
|
||||
let lval = ox_codegen_lvalue(cx, lvalue, instr.span)?;
|
||||
Ok(Some(ox_make_var_decl(cx, oxc::VariableDeclarationKind::Const, lval, value)))
|
||||
}
|
||||
InstructionKind::Function => {
|
||||
let lval = ox_codegen_lvalue(cx, lvalue)?;
|
||||
let lval = ox_codegen_lvalue(cx, lvalue, instr.span)?;
|
||||
let oxc::BindingPattern::BindingIdentifier(fn_id) = lval else {
|
||||
return Err(invariant_err(
|
||||
"Expected an identifier as function declaration lvalue",
|
||||
@@ -1413,14 +1413,14 @@ fn ox_emit_store<'a>(
|
||||
instr.span,
|
||||
));
|
||||
}
|
||||
let lval = ox_codegen_lvalue(cx, lvalue)?;
|
||||
let lval = ox_codegen_lvalue(cx, lvalue, instr.span)?;
|
||||
Ok(Some(ox_make_var_decl(cx, oxc::VariableDeclarationKind::Let, lval, value)))
|
||||
}
|
||||
InstructionKind::Reassign => {
|
||||
let Some(rhs) = value else {
|
||||
return Err(invariant_err("Expected a value for reassignment", None));
|
||||
};
|
||||
let lval = ox_codegen_lvalue(cx, lvalue)?;
|
||||
let lval = ox_codegen_lvalue(cx, lvalue, instr.span)?;
|
||||
let target = ox_binding_pattern_to_assignment_target(cx, lval)?;
|
||||
let expr = oxc_ast::ast::Expression::new_assignment_expression(
|
||||
SPAN,
|
||||
@@ -1924,13 +1924,13 @@ fn ox_codegen_base_instruction_value<'a>(
|
||||
SPAN, *operation, true, target, &cx.ast,
|
||||
)))
|
||||
}
|
||||
InstructionValue::StoreLocal { lvalue, value, .. } => {
|
||||
InstructionValue::StoreLocal { lvalue, value, span } => {
|
||||
invariant(
|
||||
lvalue.kind == InstructionKind::Reassign,
|
||||
"Unexpected StoreLocal in codegenInstructionValue",
|
||||
None,
|
||||
)?;
|
||||
let lval = ox_codegen_lvalue(cx, &LvalueRef::Place(&lvalue.place))?;
|
||||
let lval = ox_codegen_lvalue(cx, &LvalueRef::Place(&lvalue.place), *span)?;
|
||||
let target = ox_binding_pattern_to_assignment_target(cx, lval)?;
|
||||
let rhs = ox_codegen_place_to_expression(cx, value)?;
|
||||
Ok(OxValue::Expression(oxc_ast::ast::Expression::new_assignment_expression(
|
||||
@@ -2145,7 +2145,14 @@ fn ox_codegen_place<'a>(
|
||||
let declaration_id = ident.declaration_id;
|
||||
if let Some(tmp) = cx.temp.get(&declaration_id) {
|
||||
if let Some(val) = tmp {
|
||||
return Ok(val.clone_in_with_semantic_ids(cx.ast.allocator()));
|
||||
let mut value = val.clone_in_with_semantic_ids(cx.ast.allocator());
|
||||
if let Some(span) = place.span {
|
||||
match &mut value {
|
||||
OxValue::Expression(expr) => *expr.span_mut() = span,
|
||||
OxValue::JsxText(text) => text.span = span,
|
||||
}
|
||||
}
|
||||
return Ok(value);
|
||||
}
|
||||
} else if ident.name.is_none() {
|
||||
return Err(invariant_err(
|
||||
@@ -2158,7 +2165,7 @@ fn ox_codegen_place<'a>(
|
||||
}
|
||||
let name = ox_identifier_name(cx.env, place.identifier)?;
|
||||
Ok(OxValue::Expression(oxc_ast::ast::Expression::new_identifier(
|
||||
SPAN,
|
||||
place.span.unwrap_or(SPAN),
|
||||
ox_str(&cx.ast, &name),
|
||||
&cx.ast,
|
||||
)))
|
||||
@@ -2167,14 +2174,24 @@ fn ox_codegen_place<'a>(
|
||||
fn ox_codegen_lvalue<'a>(
|
||||
cx: &mut OxcContext<'a, '_>,
|
||||
pattern: &LvalueRef,
|
||||
fallback_span: Option<Span>,
|
||||
) -> Result<oxc::BindingPattern<'a>, OxcDiagnostic> {
|
||||
match pattern {
|
||||
LvalueRef::Place(place) => ox_binding_for_identifier(cx, place.identifier),
|
||||
let (pattern, span) = match pattern {
|
||||
LvalueRef::Place(place) => {
|
||||
(ox_binding_for_identifier(cx, place.identifier), place.span.or(fallback_span))
|
||||
}
|
||||
LvalueRef::Pattern(pat) => match pat {
|
||||
Pattern::Array(arr) => ox_codegen_array_pattern(cx, arr),
|
||||
Pattern::Object(obj) => ox_codegen_object_pattern(cx, obj),
|
||||
Pattern::Array(arr) => (ox_codegen_array_pattern(cx, arr), arr.span.or(fallback_span)),
|
||||
Pattern::Object(obj) => {
|
||||
(ox_codegen_object_pattern(cx, obj), obj.span.or(fallback_span))
|
||||
}
|
||||
},
|
||||
};
|
||||
let mut pattern = pattern?;
|
||||
if let Some(span) = span {
|
||||
*pattern.span_mut() = span;
|
||||
}
|
||||
Ok(pattern)
|
||||
}
|
||||
|
||||
fn ox_codegen_array_pattern<'a>(
|
||||
|
||||
@@ -357,12 +357,13 @@ describe("transformSync", () => {
|
||||
|
||||
it("supports React Fast Refresh through JSX options", () => {
|
||||
const result = transformSync("Component.tsx", fixture, {
|
||||
reactCompiler: false,
|
||||
jsx: { refresh: true },
|
||||
jsx: { refresh: { emitFullSignatures: true } },
|
||||
});
|
||||
expect(result.errors).toEqual([]);
|
||||
expect(result.code).toContain("react/compiler-runtime");
|
||||
expect(result.code).toContain("$RefreshSig$");
|
||||
expect(result.code).toContain("$RefreshReg$");
|
||||
expect(result.code).toContain("useState{[count, setCount](0)}");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user