mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 20:28:58 +00:00
refactor(transformer/class-properties): split up code into multiple files (#7912)
Split out methods from `class.rs` into separate files. Note: `static_block.rs` is currently very small, but it will get bigger - a new visitor is required here to rename references to class name and `this`.
This commit is contained in:
parent
9989b58b1c
commit
544ffbf6c7
5 changed files with 512 additions and 455 deletions
|
|
@ -15,13 +15,9 @@ use oxc_traverse::{BoundIdentifier, TraverseCtx};
|
|||
use crate::common::helper_loader::Helper;
|
||||
|
||||
use super::{
|
||||
super::ClassStaticBlock,
|
||||
constructor::InstanceInitsInsertLocation,
|
||||
private_props::{PrivateProp, PrivateProps},
|
||||
utils::{
|
||||
create_assignment, create_underscore_ident_name, create_variable_declaration,
|
||||
exprs_into_stmts,
|
||||
},
|
||||
utils::{create_assignment, create_variable_declaration, exprs_into_stmts},
|
||||
ClassBindings, ClassProperties, FxIndexMap,
|
||||
};
|
||||
|
||||
|
|
@ -530,463 +526,18 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
|
|||
self.private_props_stack.pop();
|
||||
}
|
||||
|
||||
/// Convert instance property to initialization expression.
|
||||
/// Property `foo = 123;` -> Expression `this.foo = 123` or `_defineProperty(this, "foo", 123)`.
|
||||
fn convert_instance_property(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
instance_inits: &mut Vec<Expression<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// Get value
|
||||
let value = match &mut prop.value {
|
||||
Some(value) => {
|
||||
self.transform_instance_initializer(value, ctx);
|
||||
ctx.ast.move_expression(value)
|
||||
}
|
||||
None => ctx.ast.void_0(SPAN),
|
||||
};
|
||||
|
||||
let init_expr = if let PropertyKey::PrivateIdentifier(ident) = &mut prop.key {
|
||||
self.create_private_instance_init_assignment(ident, value, ctx)
|
||||
} else {
|
||||
// Convert to assignment or `_defineProperty` call, depending on `loose` option
|
||||
let this = ctx.ast.expression_this(SPAN);
|
||||
self.create_init_assignment(prop, value, this, false, ctx)
|
||||
};
|
||||
instance_inits.push(init_expr);
|
||||
}
|
||||
|
||||
/// Convert static property to initialization expression.
|
||||
/// Property `static foo = 123;` -> Expression `C.foo = 123` or `_defineProperty(C, "foo", 123)`.
|
||||
fn convert_static_property(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// Get value, and transform it to replace `this` with reference to class name,
|
||||
// and transform class property accesses (`object.#prop`)
|
||||
let value = match &mut prop.value {
|
||||
Some(value) => {
|
||||
self.transform_static_initializer(value, ctx);
|
||||
ctx.ast.move_expression(value)
|
||||
}
|
||||
None => ctx.ast.void_0(SPAN),
|
||||
};
|
||||
|
||||
if let PropertyKey::PrivateIdentifier(ident) = &mut prop.key {
|
||||
self.insert_private_static_init_assignment(ident, value, ctx);
|
||||
} else {
|
||||
// Convert to assignment or `_defineProperty` call, depending on `loose` option
|
||||
let class_binding = if self.is_declaration {
|
||||
// Class declarations always have a name except `export default class {}`.
|
||||
// For default export, binding is created when static prop found in 1st pass.
|
||||
self.class_bindings.name.as_ref().unwrap()
|
||||
} else {
|
||||
// Binding is created when static prop found in 1st pass.
|
||||
self.class_bindings.temp.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let assignee = class_binding.create_read_expression(ctx);
|
||||
let init_expr = self.create_init_assignment(prop, value, assignee, true, ctx);
|
||||
self.insert_expr_after_class(init_expr, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// `assignee.foo = value` or `_defineProperty(assignee, "foo", value)`
|
||||
fn create_init_assignment(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
value: Expression<'a>,
|
||||
assignee: Expression<'a>,
|
||||
is_static: bool,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
if self.set_public_class_fields {
|
||||
// `assignee.foo = value`
|
||||
self.create_init_assignment_loose(prop, value, assignee, is_static, ctx)
|
||||
} else {
|
||||
// `_defineProperty(assignee, "foo", value)`
|
||||
self.create_init_assignment_not_loose(prop, value, assignee, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// `this.foo = value` or `_Class.foo = value`
|
||||
fn create_init_assignment_loose(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
value: Expression<'a>,
|
||||
assignee: Expression<'a>,
|
||||
is_static: bool,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
// In-built static props `name` and `length` need to be set with `_defineProperty`
|
||||
let needs_define = |name| is_static && (name == "name" || name == "length");
|
||||
|
||||
let left = match &mut prop.key {
|
||||
PropertyKey::StaticIdentifier(ident) => {
|
||||
if needs_define(&ident.name) {
|
||||
return self.create_init_assignment_not_loose(prop, value, assignee, ctx);
|
||||
}
|
||||
ctx.ast.member_expression_static(SPAN, assignee, ident.as_ref().clone(), false)
|
||||
}
|
||||
PropertyKey::StringLiteral(str_lit) if needs_define(&str_lit.value) => {
|
||||
return self.create_init_assignment_not_loose(prop, value, assignee, ctx);
|
||||
}
|
||||
key @ match_expression!(PropertyKey) => {
|
||||
// TODO: This can also be a numeric key (non-computed). Maybe other key types?
|
||||
let key = self.create_computed_key_temp_var(key.to_expression_mut(), ctx);
|
||||
ctx.ast.member_expression_computed(SPAN, assignee, key, false)
|
||||
}
|
||||
PropertyKey::PrivateIdentifier(_) => {
|
||||
// Handled in `convert_instance_property` and `convert_static_property`
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Should this have span of the original `PropertyDefinition`?
|
||||
ctx.ast.expression_assignment(
|
||||
SPAN,
|
||||
AssignmentOperator::Assign,
|
||||
AssignmentTarget::from(left),
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
/// `_defineProperty(this, "foo", value)` or `_defineProperty(_Class, "foo", value)`
|
||||
fn create_init_assignment_not_loose(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
value: Expression<'a>,
|
||||
assignee: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let key = match &mut prop.key {
|
||||
PropertyKey::StaticIdentifier(ident) => {
|
||||
ctx.ast.expression_string_literal(ident.span, ident.name.clone(), None)
|
||||
}
|
||||
key @ match_expression!(PropertyKey) => {
|
||||
// TODO: This can also be a numeric key (non-computed). Maybe other key types?
|
||||
self.create_computed_key_temp_var(key.to_expression_mut(), ctx)
|
||||
}
|
||||
PropertyKey::PrivateIdentifier(_) => {
|
||||
// Handled in `convert_instance_property` and `convert_static_property`
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
let arguments = ctx.ast.vec_from_array([
|
||||
Argument::from(assignee),
|
||||
Argument::from(key),
|
||||
Argument::from(value),
|
||||
]);
|
||||
// TODO: Should this have span of the original `PropertyDefinition`?
|
||||
self.ctx.helper_call_expr(Helper::DefineProperty, SPAN, arguments, ctx)
|
||||
}
|
||||
|
||||
/// Create init assignment for private instance prop, to be inserted into class constructor.
|
||||
///
|
||||
/// Loose: `Object.defineProperty(this, _prop, {writable: true, value: value})`
|
||||
/// Not loose: `_classPrivateFieldInitSpec(this, _prop, value)`
|
||||
fn create_private_instance_init_assignment(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
if self.private_fields_as_properties {
|
||||
let this = ctx.ast.expression_this(SPAN);
|
||||
self.create_private_init_assignment_loose(ident, value, this, ctx)
|
||||
} else {
|
||||
self.create_private_instance_init_assignment_not_loose(ident, value, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// `Object.defineProperty(<assignee>, _prop, {writable: true, value: value})`
|
||||
fn create_private_init_assignment_loose(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
assignee: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
// `Object.defineProperty`
|
||||
let object_symbol_id = ctx.scopes().find_binding(ctx.current_scope_id(), "Object");
|
||||
let object = ctx.create_ident_expr(
|
||||
SPAN,
|
||||
Atom::from("Object"),
|
||||
object_symbol_id,
|
||||
ReferenceFlags::Read,
|
||||
);
|
||||
let property = ctx.ast.identifier_name(SPAN, "defineProperty");
|
||||
let callee =
|
||||
Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false));
|
||||
|
||||
// `{writable: true, value: <value>}`
|
||||
let prop_def = ctx.ast.expression_object(
|
||||
SPAN,
|
||||
ctx.ast.vec_from_array([
|
||||
ctx.ast.object_property_kind_object_property(
|
||||
SPAN,
|
||||
PropertyKind::Init,
|
||||
ctx.ast.property_key_identifier_name(SPAN, Atom::from("writable")),
|
||||
ctx.ast.expression_boolean_literal(SPAN, true),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
ctx.ast.object_property_kind_object_property(
|
||||
SPAN,
|
||||
PropertyKind::Init,
|
||||
ctx.ast.property_key_identifier_name(SPAN, Atom::from("value")),
|
||||
value,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
]),
|
||||
None,
|
||||
);
|
||||
|
||||
let private_props = self.private_props_stack.last().unwrap();
|
||||
let prop = &private_props.props[&ident.name];
|
||||
let arguments = ctx.ast.vec_from_array([
|
||||
Argument::from(assignee),
|
||||
Argument::from(prop.binding.create_read_expression(ctx)),
|
||||
Argument::from(prop_def),
|
||||
]);
|
||||
// TODO: Should this have span of original `PropertyDefinition`?
|
||||
ctx.ast.expression_call(SPAN, callee, NONE, arguments, false)
|
||||
}
|
||||
|
||||
/// `_classPrivateFieldInitSpec(this, _prop, value)`
|
||||
fn create_private_instance_init_assignment_not_loose(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let private_props = self.private_props_stack.last().unwrap();
|
||||
let prop = &private_props.props[&ident.name];
|
||||
let arguments = ctx.ast.vec_from_array([
|
||||
Argument::from(ctx.ast.expression_this(SPAN)),
|
||||
Argument::from(prop.binding.create_read_expression(ctx)),
|
||||
Argument::from(value),
|
||||
]);
|
||||
// TODO: Should this have span of original `PropertyDefinition`?
|
||||
self.ctx.helper_call_expr(Helper::ClassPrivateFieldInitSpec, SPAN, arguments, ctx)
|
||||
}
|
||||
|
||||
/// Insert after class:
|
||||
///
|
||||
/// Not loose:
|
||||
/// * Class declaration: `var _prop = {_: value};`
|
||||
/// * Class expression: `_prop = {_: value}`
|
||||
///
|
||||
/// Loose:
|
||||
/// `Object.defineProperty(Class, _prop, {writable: true, value: value});`
|
||||
fn insert_private_static_init_assignment(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if self.private_fields_as_properties {
|
||||
self.insert_private_static_init_assignment_loose(ident, value, ctx);
|
||||
} else {
|
||||
self.insert_private_static_init_assignment_not_loose(ident, value, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert after class:
|
||||
/// `Object.defineProperty(Class, _prop, {writable: true, value: value});`
|
||||
fn insert_private_static_init_assignment_loose(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// TODO: This logic appears elsewhere. De-duplicate it.
|
||||
let class_binding = if self.is_declaration {
|
||||
// Class declarations always have a name except `export default class {}`.
|
||||
// For default export, binding is created when static prop found in 1st pass.
|
||||
self.class_bindings.name.as_ref().unwrap()
|
||||
} else {
|
||||
// Binding is created when static prop found in 1st pass.
|
||||
self.class_bindings.temp.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let assignee = class_binding.create_read_expression(ctx);
|
||||
let assignment = self.create_private_init_assignment_loose(ident, value, assignee, ctx);
|
||||
self.insert_expr_after_class(assignment, ctx);
|
||||
}
|
||||
|
||||
/// Insert after class:
|
||||
///
|
||||
/// * Class declaration: `var _prop = {_: value};`
|
||||
/// * Class expression: `_prop = {_: value}`
|
||||
fn insert_private_static_init_assignment_not_loose(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// `_prop = {_: value}`
|
||||
let property = ctx.ast.object_property_kind_object_property(
|
||||
SPAN,
|
||||
PropertyKind::Init,
|
||||
PropertyKey::StaticIdentifier(ctx.ast.alloc(create_underscore_ident_name(ctx))),
|
||||
value,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
let obj = ctx.ast.expression_object(SPAN, ctx.ast.vec1(property), None);
|
||||
|
||||
// Insert after class
|
||||
let private_props = self.private_props_stack.last().unwrap();
|
||||
let prop = &private_props.props[&ident.name];
|
||||
|
||||
if self.is_declaration {
|
||||
// `var _prop = {_: value};`
|
||||
let var_decl = create_variable_declaration(&prop.binding, obj, ctx);
|
||||
self.insert_after_stmts.push(var_decl);
|
||||
} else {
|
||||
// `_prop = {_: value}`
|
||||
let assignment = create_assignment(&prop.binding, obj, ctx);
|
||||
self.insert_after_exprs.push(assignment);
|
||||
}
|
||||
}
|
||||
|
||||
/// Substitute temp var for method computed key.
|
||||
/// `class C { [x()]() {} }` -> `let _x; _x = x(); class C { [_x]() {} }`
|
||||
/// This transform is only required if class has properties or a static block.
|
||||
fn substitute_temp_var_for_method_computed_key(
|
||||
&mut self,
|
||||
method: &mut MethodDefinition<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
let Some(key) = method.key.as_expression_mut() else { return };
|
||||
|
||||
// TODO: Don't alter key if it's provable evaluating it has no side effects.
|
||||
// TODO(improve-on-babel): It's unnecessary to create temp vars for method keys unless:
|
||||
// 1. Properties also have computed keys.
|
||||
// 2. Some of those properties' computed keys have side effects and require temp vars.
|
||||
// 3. At least one property satisfying the above is after this method,
|
||||
// or class contains a static block which is being transformed
|
||||
// (static blocks are always evaluated after computed keys, regardless of order)
|
||||
method.key = PropertyKey::from(self.create_computed_key_temp_var(key, ctx));
|
||||
}
|
||||
|
||||
/// Convert static block to `Expression`.
|
||||
///
|
||||
/// `static { x = 1; }` -> `x = 1`
|
||||
/// `static { x = 1; y = 2; } -> `(() => { x = 1; y = 2; })()`
|
||||
///
|
||||
/// TODO: Add tests for this if there aren't any already.
|
||||
/// Include tests for evaluation order inc that static block goes before class expression
|
||||
/// unless also static properties, or static block uses class name.
|
||||
fn convert_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
// TODO: Convert `this` and references to class name.
|
||||
// `x = class C { static { this.C = C; } }` -> `x = (_C = class C {}, _C.C = _C, _C)`
|
||||
// TODO: Scope of static block contents becomes outer scope, not scope of class.
|
||||
|
||||
// If class expression, assignment in static block moves to a position where it's read from.
|
||||
// e.g.: `x` here now has read+write `ReferenceFlags`:
|
||||
// `C = class C { static { x = 1; } }` -> `C = (_C = class C {}, x = 1, _C)`
|
||||
let expr = ClassStaticBlock::convert_block_to_expression(block, ctx);
|
||||
self.insert_expr_after_class(expr, ctx);
|
||||
}
|
||||
|
||||
/// Insert an expression after the class.
|
||||
fn insert_expr_after_class(&mut self, expr: Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
pub(super) fn insert_expr_after_class(
|
||||
&mut self,
|
||||
expr: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if self.is_declaration {
|
||||
self.insert_after_stmts.push(ctx.ast.statement_expression(SPAN, expr));
|
||||
} else {
|
||||
self.insert_after_exprs.push(expr);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert computed property/method key to a temp var.
|
||||
///
|
||||
/// Transformation is:
|
||||
/// * Class declaration:
|
||||
/// `class C { [x()] = 1; }` -> `let _x; _x = x(); class C { constructor() { this[_x] = 1; } }`
|
||||
/// * Class expression:
|
||||
/// `C = class { [x()] = 1; }` -> `let _x; C = (_x = x(), class C { constructor() { this[_x] = 1; } })`
|
||||
///
|
||||
/// This function:
|
||||
/// * Creates the `let _x;` statement and inserts it.
|
||||
/// * Creates the `_x = x()` assignments.
|
||||
/// * Inserts assignments before class declaration, or adds to `state` if class expression.
|
||||
/// * Returns `_x`.
|
||||
fn create_computed_key_temp_var(
|
||||
&mut self,
|
||||
key: &mut Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let key = ctx.ast.move_expression(key);
|
||||
|
||||
// Bound vars and literals do not need temp var - return unchanged.
|
||||
// e.g. `let x = 'x'; class C { [x] = 1; }` or `class C { ['x'] = 1; }`
|
||||
//
|
||||
// `this` does not have side effects, but it needs a temp var anyway, because `this` in computed
|
||||
// key and `this` within class constructor resolve to different `this` bindings.
|
||||
// So we need to create a temp var outside of the class to get the correct `this`.
|
||||
// `class C { [this] = 1; }`
|
||||
// -> `let _this; _this = this; class C { constructor() { this[_this] = 1; } }`
|
||||
//
|
||||
// TODO(improve-on-babel): Can avoid the temp var if key is for a static prop/method,
|
||||
// as in that case the usage of `this` stays outside the class.
|
||||
//
|
||||
// TODO: Do fuller analysis to detect expressions which cannot have side effects e.g. `'x' + 'y'`.
|
||||
let cannot_have_side_effects = match &key {
|
||||
Expression::BooleanLiteral(_)
|
||||
| Expression::NullLiteral(_)
|
||||
| Expression::NumericLiteral(_)
|
||||
| Expression::BigIntLiteral(_)
|
||||
| Expression::RegExpLiteral(_)
|
||||
| Expression::StringLiteral(_) => true,
|
||||
Expression::Identifier(ident) => {
|
||||
// Cannot have side effects if is bound.
|
||||
// Additional check that the var is not mutated is required for cases like
|
||||
// `let x = 1; class { [x] = 1; [++x] = 2; }`
|
||||
// `++x` is hoisted to before class in output, so `x` in 1st key would get the wrong
|
||||
// value unless it's hoisted out too.
|
||||
// TODO: Add an exec test for this odd case.
|
||||
// TODO(improve-on-babel): That case is rare.
|
||||
// Test for it in first pass over class elements, and avoid temp vars where possible.
|
||||
match ctx.symbols().get_reference(ident.reference_id()).symbol_id() {
|
||||
Some(symbol_id) => {
|
||||
ctx.symbols().get_flags(symbol_id).contains(SymbolFlags::ConstVariable)
|
||||
|| ctx
|
||||
.symbols()
|
||||
.get_resolved_references(symbol_id)
|
||||
.all(|reference| !reference.is_write())
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if cannot_have_side_effects {
|
||||
return key;
|
||||
}
|
||||
|
||||
// We entered transform via `enter_expression` or `enter_statement`,
|
||||
// so `ctx.current_scope_id()` is the scope outside the class
|
||||
let parent_scope_id = ctx.current_scope_id();
|
||||
// TODO: Handle if is a class expression defined in a function's params.
|
||||
let binding =
|
||||
ctx.generate_uid_based_on_node(&key, parent_scope_id, SymbolFlags::BlockScopedVariable);
|
||||
|
||||
self.ctx.var_declarations.insert_let(&binding, None, ctx);
|
||||
|
||||
let assignment = create_assignment(&binding, key, ctx);
|
||||
self.insert_before.push(assignment);
|
||||
|
||||
binding.create_read_expression(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create `new WeakMap()` expression.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
//! ES2022: Class Properties
|
||||
//! Transform of class property/method computed keys.
|
||||
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_syntax::symbol::SymbolFlags;
|
||||
use oxc_traverse::TraverseCtx;
|
||||
|
||||
use super::{utils::create_assignment, ClassProperties};
|
||||
|
||||
impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
|
||||
/// Substitute temp var for method computed key.
|
||||
/// `class C { [x()]() {} }` -> `let _x; _x = x(); class C { [_x]() {} }`
|
||||
/// This transform is only required if class has properties or a static block.
|
||||
pub(super) fn substitute_temp_var_for_method_computed_key(
|
||||
&mut self,
|
||||
method: &mut MethodDefinition<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// TODO: Don't alter numeric literal key e.g. `class C { 123() {} }`
|
||||
// TODO: Don't re-create key if it doesn't need to be altered
|
||||
let Some(key) = method.key.as_expression_mut() else { return };
|
||||
|
||||
// TODO: Don't alter key if it's provable evaluating it has no side effects.
|
||||
// TODO(improve-on-babel): It's unnecessary to create temp vars for method keys unless:
|
||||
// 1. Properties also have computed keys.
|
||||
// 2. Some of those properties' computed keys have side effects and require temp vars.
|
||||
// 3. At least one property satisfying the above is after this method,
|
||||
// or class contains a static block which is being transformed
|
||||
// (static blocks are always evaluated after computed keys, regardless of order)
|
||||
method.key = PropertyKey::from(self.create_computed_key_temp_var(key, ctx));
|
||||
}
|
||||
|
||||
/// Convert computed property/method key to a temp var.
|
||||
///
|
||||
/// Transformation is:
|
||||
/// * Class declaration:
|
||||
/// `class C { [x()] = 1; }` -> `let _x; _x = x(); class C { constructor() { this[_x] = 1; } }`
|
||||
/// * Class expression:
|
||||
/// `C = class { [x()] = 1; }` -> `let _x; C = (_x = x(), class C { constructor() { this[_x] = 1; } })`
|
||||
///
|
||||
/// This function:
|
||||
/// * Creates the `let _x;` statement and inserts it.
|
||||
/// * Creates the `_x = x()` assignments.
|
||||
/// * Inserts assignments before class declaration, or adds to `state` if class expression.
|
||||
/// * Returns `_x`.
|
||||
pub(super) fn create_computed_key_temp_var(
|
||||
&mut self,
|
||||
key: &mut Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let key = ctx.ast.move_expression(key);
|
||||
|
||||
// Bound vars and literals do not need temp var - return unchanged.
|
||||
// e.g. `let x = 'x'; class C { [x] = 1; }` or `class C { ['x'] = 1; }`
|
||||
//
|
||||
// `this` does not have side effects, but it needs a temp var anyway, because `this` in computed
|
||||
// key and `this` within class constructor resolve to different `this` bindings.
|
||||
// So we need to create a temp var outside of the class to get the correct `this`.
|
||||
// `class C { [this] = 1; }`
|
||||
// -> `let _this; _this = this; class C { constructor() { this[_this] = 1; } }`
|
||||
//
|
||||
// TODO(improve-on-babel): Can avoid the temp var if key is for a static prop/method,
|
||||
// as in that case the usage of `this` stays outside the class.
|
||||
//
|
||||
// TODO: Do fuller analysis to detect expressions which cannot have side effects e.g. `'x' + 'y'`.
|
||||
let cannot_have_side_effects = match &key {
|
||||
Expression::BooleanLiteral(_)
|
||||
| Expression::NullLiteral(_)
|
||||
| Expression::NumericLiteral(_)
|
||||
| Expression::BigIntLiteral(_)
|
||||
| Expression::RegExpLiteral(_)
|
||||
| Expression::StringLiteral(_) => true,
|
||||
Expression::Identifier(ident) => {
|
||||
// Cannot have side effects if is bound.
|
||||
// Additional check that the var is not mutated is required for cases like
|
||||
// `let x = 1; class { [x] = 1; [++x] = 2; }`
|
||||
// `++x` is hoisted to before class in output, so `x` in 1st key would get the wrong
|
||||
// value unless it's hoisted out too.
|
||||
// TODO: Add an exec test for this odd case.
|
||||
// TODO(improve-on-babel): That case is rare.
|
||||
// Test for it in first pass over class elements, and avoid temp vars where possible.
|
||||
match ctx.symbols().get_reference(ident.reference_id()).symbol_id() {
|
||||
Some(symbol_id) => {
|
||||
// TODO: Use `SymbolTable::symbol_is_mutated`
|
||||
ctx.symbols().get_flags(symbol_id).contains(SymbolFlags::ConstVariable)
|
||||
|| ctx
|
||||
.symbols()
|
||||
.get_resolved_references(symbol_id)
|
||||
.all(|reference| !reference.is_write())
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if cannot_have_side_effects {
|
||||
return key;
|
||||
}
|
||||
|
||||
// We entered transform via `enter_expression` or `enter_statement`,
|
||||
// so `ctx.current_scope_id()` is the scope outside the class
|
||||
let parent_scope_id = ctx.current_scope_id();
|
||||
// TODO: Handle if is a class expression defined in a function's params.
|
||||
let binding =
|
||||
ctx.generate_uid_based_on_node(&key, parent_scope_id, SymbolFlags::BlockScopedVariable);
|
||||
|
||||
self.ctx.var_declarations.insert_let(&binding, None, ctx);
|
||||
|
||||
let assignment = create_assignment(&binding, key, ctx);
|
||||
self.insert_before.push(assignment);
|
||||
|
||||
binding.create_read_expression(ctx)
|
||||
}
|
||||
}
|
||||
|
|
@ -127,9 +127,12 @@
|
|||
//!
|
||||
//! * `mod.rs`: Setup and visitor.
|
||||
//! * `class.rs`: Transform of class body.
|
||||
//! * `prop_decl.rs`: Transform of property declarations (instance and static).
|
||||
//! * `constructor.rs`: Insertion of property initializers into class constructor.
|
||||
//! * `instance_prop_init.rs`: Transform of instance property initializers.
|
||||
//! * `static_prop_init.rs`: Transform of static property initializers.
|
||||
//! * `static_block.rs`: Transform of static blocks.
|
||||
//! * `computed_key.rs`: Transform of property/method computed keys.
|
||||
//! * `private_field.rs`: Transform of private fields (`this.#prop`).
|
||||
//! * `super.rs`: Transform `super` expressions.
|
||||
//! * `class_bindings.rs`: Structure containing bindings for class name and temp var.
|
||||
|
|
@ -159,10 +162,13 @@ use crate::TransformCtx;
|
|||
|
||||
mod class;
|
||||
mod class_bindings;
|
||||
mod computed_key;
|
||||
mod constructor;
|
||||
mod instance_prop_init;
|
||||
mod private_field;
|
||||
mod private_props;
|
||||
mod prop_decl;
|
||||
mod static_block;
|
||||
mod static_prop_init;
|
||||
mod supers;
|
||||
mod utils;
|
||||
|
|
|
|||
351
crates/oxc_transformer/src/es2022/class_properties/prop_decl.rs
Normal file
351
crates/oxc_transformer/src/es2022/class_properties/prop_decl.rs
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
//! ES2022: Class Properties
|
||||
//! Transform of class property declarations (instance or static properties).
|
||||
|
||||
use oxc_ast::{ast::*, NONE};
|
||||
use oxc_span::SPAN;
|
||||
use oxc_syntax::reference::ReferenceFlags;
|
||||
use oxc_traverse::TraverseCtx;
|
||||
|
||||
use crate::common::helper_loader::Helper;
|
||||
|
||||
use super::{
|
||||
utils::{create_assignment, create_underscore_ident_name, create_variable_declaration},
|
||||
ClassProperties,
|
||||
};
|
||||
|
||||
// Instance properties
|
||||
impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
|
||||
/// Convert instance property to initialization expression.
|
||||
/// Property `foo = 123;` -> Expression `this.foo = 123` or `_defineProperty(this, "foo", 123)`.
|
||||
pub(super) fn convert_instance_property(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
instance_inits: &mut Vec<Expression<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// Get value
|
||||
let value = match &mut prop.value {
|
||||
Some(value) => {
|
||||
self.transform_instance_initializer(value, ctx);
|
||||
ctx.ast.move_expression(value)
|
||||
}
|
||||
None => ctx.ast.void_0(SPAN),
|
||||
};
|
||||
|
||||
let init_expr = if let PropertyKey::PrivateIdentifier(ident) = &mut prop.key {
|
||||
self.create_private_instance_init_assignment(ident, value, ctx)
|
||||
} else {
|
||||
// Convert to assignment or `_defineProperty` call, depending on `loose` option
|
||||
let this = ctx.ast.expression_this(SPAN);
|
||||
self.create_init_assignment(prop, value, this, false, ctx)
|
||||
};
|
||||
instance_inits.push(init_expr);
|
||||
}
|
||||
|
||||
/// Create init assignment for private instance prop, to be inserted into class constructor.
|
||||
///
|
||||
/// Loose: `Object.defineProperty(this, _prop, {writable: true, value: value})`
|
||||
/// Not loose: `_classPrivateFieldInitSpec(this, _prop, value)`
|
||||
fn create_private_instance_init_assignment(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
if self.private_fields_as_properties {
|
||||
let this = ctx.ast.expression_this(SPAN);
|
||||
self.create_private_init_assignment_loose(ident, value, this, ctx)
|
||||
} else {
|
||||
self.create_private_instance_init_assignment_not_loose(ident, value, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// `_classPrivateFieldInitSpec(this, _prop, value)`
|
||||
fn create_private_instance_init_assignment_not_loose(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let private_props = self.private_props_stack.last().unwrap();
|
||||
let prop = &private_props.props[&ident.name];
|
||||
let arguments = ctx.ast.vec_from_array([
|
||||
Argument::from(ctx.ast.expression_this(SPAN)),
|
||||
Argument::from(prop.binding.create_read_expression(ctx)),
|
||||
Argument::from(value),
|
||||
]);
|
||||
// TODO: Should this have span of original `PropertyDefinition`?
|
||||
self.ctx.helper_call_expr(Helper::ClassPrivateFieldInitSpec, SPAN, arguments, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Static properties
|
||||
impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
|
||||
/// Convert static property to initialization expression.
|
||||
/// Property `static foo = 123;` -> Expression `C.foo = 123` or `_defineProperty(C, "foo", 123)`.
|
||||
pub(super) fn convert_static_property(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// Get value, and transform it to replace `this` with reference to class name,
|
||||
// and transform class property accesses (`object.#prop`)
|
||||
let value = match &mut prop.value {
|
||||
Some(value) => {
|
||||
self.transform_static_initializer(value, ctx);
|
||||
ctx.ast.move_expression(value)
|
||||
}
|
||||
None => ctx.ast.void_0(SPAN),
|
||||
};
|
||||
|
||||
if let PropertyKey::PrivateIdentifier(ident) = &mut prop.key {
|
||||
self.insert_private_static_init_assignment(ident, value, ctx);
|
||||
} else {
|
||||
// Convert to assignment or `_defineProperty` call, depending on `loose` option
|
||||
let class_binding = if self.is_declaration {
|
||||
// Class declarations always have a name except `export default class {}`.
|
||||
// For default export, binding is created when static prop found in 1st pass.
|
||||
self.class_bindings.name.as_ref().unwrap()
|
||||
} else {
|
||||
// Binding is created when static prop found in 1st pass.
|
||||
self.class_bindings.temp.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let assignee = class_binding.create_read_expression(ctx);
|
||||
let init_expr = self.create_init_assignment(prop, value, assignee, true, ctx);
|
||||
self.insert_expr_after_class(init_expr, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert after class:
|
||||
///
|
||||
/// Not loose:
|
||||
/// * Class declaration: `var _prop = {_: value};`
|
||||
/// * Class expression: `_prop = {_: value}`
|
||||
///
|
||||
/// Loose:
|
||||
/// `Object.defineProperty(Class, _prop, {writable: true, value: value});`
|
||||
fn insert_private_static_init_assignment(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if self.private_fields_as_properties {
|
||||
self.insert_private_static_init_assignment_loose(ident, value, ctx);
|
||||
} else {
|
||||
self.insert_private_static_init_assignment_not_loose(ident, value, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert after class:
|
||||
/// `Object.defineProperty(Class, _prop, {writable: true, value: value});`
|
||||
fn insert_private_static_init_assignment_loose(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// TODO: This logic appears elsewhere. De-duplicate it.
|
||||
let class_binding = if self.is_declaration {
|
||||
// Class declarations always have a name except `export default class {}`.
|
||||
// For default export, binding is created when static prop found in 1st pass.
|
||||
self.class_bindings.name.as_ref().unwrap()
|
||||
} else {
|
||||
// Binding is created when static prop found in 1st pass.
|
||||
self.class_bindings.temp.as_ref().unwrap()
|
||||
};
|
||||
|
||||
let assignee = class_binding.create_read_expression(ctx);
|
||||
let assignment = self.create_private_init_assignment_loose(ident, value, assignee, ctx);
|
||||
self.insert_expr_after_class(assignment, ctx);
|
||||
}
|
||||
|
||||
/// Insert after class:
|
||||
///
|
||||
/// * Class declaration: `var _prop = {_: value};`
|
||||
/// * Class expression: `_prop = {_: value}`
|
||||
fn insert_private_static_init_assignment_not_loose(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// `_prop = {_: value}`
|
||||
let property = ctx.ast.object_property_kind_object_property(
|
||||
SPAN,
|
||||
PropertyKind::Init,
|
||||
PropertyKey::StaticIdentifier(ctx.ast.alloc(create_underscore_ident_name(ctx))),
|
||||
value,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
let obj = ctx.ast.expression_object(SPAN, ctx.ast.vec1(property), None);
|
||||
|
||||
// Insert after class
|
||||
let private_props = self.private_props_stack.last().unwrap();
|
||||
let prop = &private_props.props[&ident.name];
|
||||
|
||||
if self.is_declaration {
|
||||
// `var _prop = {_: value};`
|
||||
let var_decl = create_variable_declaration(&prop.binding, obj, ctx);
|
||||
self.insert_after_stmts.push(var_decl);
|
||||
} else {
|
||||
// `_prop = {_: value}`
|
||||
let assignment = create_assignment(&prop.binding, obj, ctx);
|
||||
self.insert_after_exprs.push(assignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used for both instance and static properties
|
||||
impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
|
||||
/// `assignee.foo = value` or `_defineProperty(assignee, "foo", value)`
|
||||
fn create_init_assignment(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
value: Expression<'a>,
|
||||
assignee: Expression<'a>,
|
||||
is_static: bool,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
if self.set_public_class_fields {
|
||||
// `assignee.foo = value`
|
||||
self.create_init_assignment_loose(prop, value, assignee, is_static, ctx)
|
||||
} else {
|
||||
// `_defineProperty(assignee, "foo", value)`
|
||||
self.create_init_assignment_not_loose(prop, value, assignee, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// `this.foo = value` or `_Class.foo = value`
|
||||
fn create_init_assignment_loose(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
value: Expression<'a>,
|
||||
assignee: Expression<'a>,
|
||||
is_static: bool,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
// In-built static props `name` and `length` need to be set with `_defineProperty`
|
||||
let needs_define = |name| is_static && (name == "name" || name == "length");
|
||||
|
||||
let left = match &mut prop.key {
|
||||
PropertyKey::StaticIdentifier(ident) => {
|
||||
if needs_define(&ident.name) {
|
||||
return self.create_init_assignment_not_loose(prop, value, assignee, ctx);
|
||||
}
|
||||
ctx.ast.member_expression_static(SPAN, assignee, ident.as_ref().clone(), false)
|
||||
}
|
||||
PropertyKey::StringLiteral(str_lit) if needs_define(&str_lit.value) => {
|
||||
return self.create_init_assignment_not_loose(prop, value, assignee, ctx);
|
||||
}
|
||||
key @ match_expression!(PropertyKey) => {
|
||||
// TODO: This can also be a numeric key (non-computed). Maybe other key types?
|
||||
let key = self.create_computed_key_temp_var(key.to_expression_mut(), ctx);
|
||||
ctx.ast.member_expression_computed(SPAN, assignee, key, false)
|
||||
}
|
||||
PropertyKey::PrivateIdentifier(_) => {
|
||||
// Handled in `convert_instance_property` and `convert_static_property`
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Should this have span of the original `PropertyDefinition`?
|
||||
ctx.ast.expression_assignment(
|
||||
SPAN,
|
||||
AssignmentOperator::Assign,
|
||||
AssignmentTarget::from(left),
|
||||
value,
|
||||
)
|
||||
}
|
||||
|
||||
/// `_defineProperty(this, "foo", value)` or `_defineProperty(_Class, "foo", value)`
|
||||
fn create_init_assignment_not_loose(
|
||||
&mut self,
|
||||
prop: &mut PropertyDefinition<'a>,
|
||||
value: Expression<'a>,
|
||||
assignee: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let key = match &mut prop.key {
|
||||
PropertyKey::StaticIdentifier(ident) => {
|
||||
ctx.ast.expression_string_literal(ident.span, ident.name.clone(), None)
|
||||
}
|
||||
key @ match_expression!(PropertyKey) => {
|
||||
// TODO: This can also be a numeric key (non-computed). Maybe other key types?
|
||||
self.create_computed_key_temp_var(key.to_expression_mut(), ctx)
|
||||
}
|
||||
PropertyKey::PrivateIdentifier(_) => {
|
||||
// Handled in `convert_instance_property` and `convert_static_property`
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
let arguments = ctx.ast.vec_from_array([
|
||||
Argument::from(assignee),
|
||||
Argument::from(key),
|
||||
Argument::from(value),
|
||||
]);
|
||||
// TODO: Should this have span of the original `PropertyDefinition`?
|
||||
self.ctx.helper_call_expr(Helper::DefineProperty, SPAN, arguments, ctx)
|
||||
}
|
||||
|
||||
/// `Object.defineProperty(<assignee>, _prop, {writable: true, value: value})`
|
||||
fn create_private_init_assignment_loose(
|
||||
&mut self,
|
||||
ident: &PrivateIdentifier<'a>,
|
||||
value: Expression<'a>,
|
||||
assignee: Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
// `Object.defineProperty`
|
||||
let object_symbol_id = ctx.scopes().find_binding(ctx.current_scope_id(), "Object");
|
||||
let object = ctx.create_ident_expr(
|
||||
SPAN,
|
||||
Atom::from("Object"),
|
||||
object_symbol_id,
|
||||
ReferenceFlags::Read,
|
||||
);
|
||||
let property = ctx.ast.identifier_name(SPAN, "defineProperty");
|
||||
let callee =
|
||||
Expression::from(ctx.ast.member_expression_static(SPAN, object, property, false));
|
||||
|
||||
// `{writable: true, value: <value>}`
|
||||
let prop_def = ctx.ast.expression_object(
|
||||
SPAN,
|
||||
ctx.ast.vec_from_array([
|
||||
ctx.ast.object_property_kind_object_property(
|
||||
SPAN,
|
||||
PropertyKind::Init,
|
||||
ctx.ast.property_key_identifier_name(SPAN, Atom::from("writable")),
|
||||
ctx.ast.expression_boolean_literal(SPAN, true),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
ctx.ast.object_property_kind_object_property(
|
||||
SPAN,
|
||||
PropertyKind::Init,
|
||||
ctx.ast.property_key_identifier_name(SPAN, Atom::from("value")),
|
||||
value,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
]),
|
||||
None,
|
||||
);
|
||||
|
||||
let private_props = self.private_props_stack.last().unwrap();
|
||||
let prop = &private_props.props[&ident.name];
|
||||
let arguments = ctx.ast.vec_from_array([
|
||||
Argument::from(assignee),
|
||||
Argument::from(prop.binding.create_read_expression(ctx)),
|
||||
Argument::from(prop_def),
|
||||
]);
|
||||
// TODO: Should this have span of original `PropertyDefinition`?
|
||||
ctx.ast.expression_call(SPAN, callee, NONE, arguments, false)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
//! ES2022: Class Properties
|
||||
//! Transform of class static blocks.
|
||||
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_traverse::TraverseCtx;
|
||||
|
||||
use super::super::ClassStaticBlock;
|
||||
|
||||
use super::ClassProperties;
|
||||
|
||||
impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
|
||||
/// Convert static block to `Expression`.
|
||||
///
|
||||
/// `static { x = 1; }` -> `x = 1`
|
||||
/// `static { x = 1; y = 2; } -> `(() => { x = 1; y = 2; })()`
|
||||
///
|
||||
/// TODO: Add tests for this if there aren't any already.
|
||||
/// Include tests for evaluation order inc that static block goes before class expression
|
||||
/// unless also static properties, or static block uses class name.
|
||||
pub(super) fn convert_static_block(
|
||||
&mut self,
|
||||
block: &mut StaticBlock<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
// TODO: Convert `this` and references to class name.
|
||||
// `x = class C { static { this.C = C; } }` -> `x = (_C = class C {}, _C.C = _C, _C)`
|
||||
// TODO: Scope of static block contents becomes outer scope, not scope of class.
|
||||
|
||||
// If class expression, assignment in static block moves to a position where it's read from.
|
||||
// e.g.: `x` here now has read+write `ReferenceFlags`:
|
||||
// `C = class C { static { x = 1; } }` -> `C = (_C = class C {}, x = 1, _C)`
|
||||
let expr = ClassStaticBlock::convert_block_to_expression(block, ctx);
|
||||
self.insert_expr_after_class(expr, ctx);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue