mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(transformer): class static block transform (#6733)
Add ES2022 class static block transform.
This commit is contained in:
parent
78fee6ee24
commit
10484cdeeb
25 changed files with 700 additions and 5 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2018,6 +2018,7 @@ dependencies = [
|
|||
"cow-utils",
|
||||
"dashmap 6.1.0",
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"oxc-browserslist",
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
|
|
|
|||
|
|
@ -173,6 +173,17 @@ impl ScopeTree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Delete a scope.
|
||||
pub fn delete_scope(&mut self, scope_id: ScopeId) {
|
||||
if self.build_child_ids {
|
||||
self.child_ids[scope_id].clear();
|
||||
let parent_id = self.parent_ids[scope_id];
|
||||
if let Some(parent_id) = parent_id {
|
||||
self.child_ids[parent_id].retain(|&child_id| child_id != scope_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a variable binding by name that was declared in the top-level scope
|
||||
#[inline]
|
||||
pub fn get_root_binding(&self, name: &str) -> Option<SymbolId> {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ base64 = { workspace = true }
|
|||
cow-utils = { workspace = true }
|
||||
dashmap = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
itoa = { workspace = true }
|
||||
ropey = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
|
|
|||
446
crates/oxc_transformer/src/es2022/class_static_block.rs
Normal file
446
crates/oxc_transformer/src/es2022/class_static_block.rs
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
//! ES2022: Class Static Block
|
||||
//!
|
||||
//! This plugin transforms class static blocks (`class C { static { foo } }`) to an equivalent
|
||||
//! using private fields (`class C { static #_ = foo }`).
|
||||
//!
|
||||
//! > This plugin is included in `preset-env`, in ES2022
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! Input:
|
||||
//! ```js
|
||||
//! class C {
|
||||
//! static {
|
||||
//! foo();
|
||||
//! }
|
||||
//! static {
|
||||
//! foo();
|
||||
//! bar();
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Output:
|
||||
//! ```js
|
||||
//! class C {
|
||||
//! static #_ = foo();
|
||||
//! static #_2 = (() => {
|
||||
//! foo();
|
||||
//! bar();
|
||||
//! })();
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Implementation
|
||||
//!
|
||||
//! Implementation based on [@babel/plugin-transform-class-static-block](https://babel.dev/docs/babel-plugin-transform-class-static-block).
|
||||
//!
|
||||
//! ## References:
|
||||
//! * Babel plugin implementation: <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-class-static-block>
|
||||
//! * Class static initialization blocks TC39 proposal: <https://github.com/tc39/proposal-class-static-block>
|
||||
|
||||
use itoa::Buffer as ItoaBuffer;
|
||||
|
||||
use oxc_allocator::String as AString;
|
||||
use oxc_ast::{ast::*, Visit, NONE};
|
||||
use oxc_semantic::SymbolTable;
|
||||
use oxc_span::SPAN;
|
||||
use oxc_syntax::{
|
||||
reference::ReferenceFlags,
|
||||
scope::{ScopeFlags, ScopeId},
|
||||
};
|
||||
use oxc_traverse::{Traverse, TraverseCtx};
|
||||
|
||||
pub struct ClassStaticBlock;
|
||||
|
||||
impl ClassStaticBlock {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Traverse<'a> for ClassStaticBlock {
|
||||
fn enter_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
// Loop through class body elements and:
|
||||
// 1. Find if there are any `StaticBlock`s.
|
||||
// 2. Collate list of private keys matching `#_` or `#_[1-9]...`.
|
||||
//
|
||||
// Don't collate private keys list conditionally only if a static block is found, as usually
|
||||
// there will be no matching private keys, so those checks are cheap and will not allocate.
|
||||
let mut has_static_block = false;
|
||||
let mut keys = Keys::default();
|
||||
for element in &body.body {
|
||||
let key = match element {
|
||||
ClassElement::StaticBlock(_) => {
|
||||
has_static_block = true;
|
||||
continue;
|
||||
}
|
||||
ClassElement::MethodDefinition(def) => &def.key,
|
||||
ClassElement::PropertyDefinition(def) => &def.key,
|
||||
ClassElement::AccessorProperty(def) => &def.key,
|
||||
ClassElement::TSIndexSignature(_) => continue,
|
||||
};
|
||||
|
||||
if let PropertyKey::PrivateIdentifier(id) = key {
|
||||
keys.reserve(id.name.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Transform static blocks
|
||||
if !has_static_block {
|
||||
return;
|
||||
}
|
||||
|
||||
for element in body.body.iter_mut() {
|
||||
if let ClassElement::StaticBlock(block) = element {
|
||||
*element = Self::convert_block_to_private_field(block, &mut keys, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClassStaticBlock {
|
||||
/// Convert static block to private field.
|
||||
/// `static { foo }` -> `static #_ = foo;`
|
||||
/// `static { foo; bar; }` -> `static #_ = (() => { foo; bar; })();`
|
||||
fn convert_block_to_private_field<'a>(
|
||||
block: &mut StaticBlock<'a>,
|
||||
keys: &mut Keys<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> ClassElement<'a> {
|
||||
let expr = Self::convert_block_to_expression(block, ctx);
|
||||
|
||||
let key = keys.get_unique(ctx);
|
||||
let key = ctx.ast.property_key_private_identifier(SPAN, key);
|
||||
|
||||
ctx.ast.class_element_property_definition(
|
||||
PropertyDefinitionType::PropertyDefinition,
|
||||
block.span,
|
||||
ctx.ast.vec(),
|
||||
key,
|
||||
Some(expr),
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
NONE,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert static block to expression which will be value of private field.
|
||||
/// `static { foo }` -> `foo`
|
||||
/// `static { foo; bar; }` -> `(() => { foo; bar; })()`
|
||||
fn convert_block_to_expression<'a>(
|
||||
block: &mut StaticBlock<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let scope_id = block.scope_id.get().unwrap();
|
||||
|
||||
// If block contains only a single `ExpressionStatement`, no need to wrap in an IIFE.
|
||||
// `static { foo }` -> `foo`
|
||||
// TODO(improve-on-babel): If block has no statements, could remove it entirely.
|
||||
let stmts = &mut block.body;
|
||||
if stmts.len() == 1 {
|
||||
if let Statement::ExpressionStatement(stmt) = stmts.first_mut().unwrap() {
|
||||
return Self::convert_block_with_single_expression_to_expression(
|
||||
&mut stmt.expression,
|
||||
scope_id,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert block to arrow function IIFE.
|
||||
// `static { foo; bar; }` -> `(() => { foo; bar; })()`
|
||||
|
||||
// Re-use the static block's scope for the arrow function.
|
||||
// Always strict mode since we're in a class.
|
||||
*ctx.scopes_mut().get_flags_mut(scope_id) =
|
||||
ScopeFlags::Function | ScopeFlags::Arrow | ScopeFlags::StrictMode;
|
||||
|
||||
let stmts = ctx.ast.move_vec(stmts);
|
||||
let params = ctx.ast.alloc_formal_parameters(
|
||||
SPAN,
|
||||
FormalParameterKind::ArrowFormalParameters,
|
||||
ctx.ast.vec(),
|
||||
NONE,
|
||||
);
|
||||
let body = ctx.ast.alloc_function_body(SPAN, ctx.ast.vec(), stmts);
|
||||
let arrow = Expression::ArrowFunctionExpression(
|
||||
ctx.ast.alloc_arrow_function_expression_with_scope_id(
|
||||
SPAN, false, false, NONE, params, NONE, body, scope_id,
|
||||
),
|
||||
);
|
||||
ctx.ast.expression_call(SPAN, arrow, NONE, ctx.ast.vec(), false)
|
||||
}
|
||||
|
||||
/// Convert static block to expression which will be value of private field,
|
||||
/// where the static block contains only a single expression.
|
||||
/// `static { foo }` -> `foo`
|
||||
fn convert_block_with_single_expression_to_expression<'a>(
|
||||
expr: &mut Expression<'a>,
|
||||
scope_id: ScopeId,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let expr = ctx.ast.move_expression(expr);
|
||||
|
||||
// Remove the scope for the static block from the scope chain
|
||||
ctx.remove_scope_for_expression(scope_id, &expr);
|
||||
|
||||
// If expression is an assignment, left side has moved from a write-only position to a read + write one.
|
||||
// `static { x = 1; }` -> `static #_ = x = 1;`
|
||||
// So set `ReferenceFlags::Read` on the left side.
|
||||
if let Expression::AssignmentExpression(assign_expr) = &expr {
|
||||
if assign_expr.operator == AssignmentOperator::Assign {
|
||||
let mut setter = ReferenceFlagsSetter { symbols: ctx.symbols_mut() };
|
||||
setter.visit_assignment_target(&assign_expr.left);
|
||||
}
|
||||
}
|
||||
|
||||
expr
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor which sets `ReferenceFlags::Read` flag on all `IdentifierReference`s.
|
||||
/// It skips `MemberExpression`s, because their flags are not affected by the change in position.
|
||||
struct ReferenceFlagsSetter<'s> {
|
||||
symbols: &'s mut SymbolTable,
|
||||
}
|
||||
|
||||
impl<'a, 's> Visit<'a> for ReferenceFlagsSetter<'s> {
|
||||
fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) {
|
||||
let reference_id = ident.reference_id().unwrap();
|
||||
let reference = self.symbols.get_reference_mut(reference_id);
|
||||
*reference.flags_mut() |= ReferenceFlags::Read;
|
||||
}
|
||||
|
||||
fn visit_member_expression(&mut self, _member_expr: &MemberExpression<'a>) {
|
||||
// Don't traverse further
|
||||
}
|
||||
}
|
||||
|
||||
/// Store of private identifier keys matching `#_` or `#_[1-9]...`.
|
||||
///
|
||||
/// Most commonly there will be no existing keys matching this pattern
|
||||
/// (why would you prefix a private key with `_`?).
|
||||
/// It's also uncommon to have more than 1 static block in a class.
|
||||
///
|
||||
/// Therefore common case is only 1 static block, which will use key `#_`.
|
||||
/// So store whether `#_` is in set as a separate `bool`, to make a fast path this common case,
|
||||
/// which does not involve any allocations (`numbered` will remain empty).
|
||||
///
|
||||
/// Use a `Vec` rather than a `HashMap`, because number of matching private keys is usually small,
|
||||
/// and `Vec` is lower overhead in that case.
|
||||
#[derive(Default)]
|
||||
struct Keys<'a> {
|
||||
/// `true` if keys includes `#_`.
|
||||
underscore: bool,
|
||||
/// Keys matching `#_[1-9]...`. Stored without the `_` prefix.
|
||||
numbered: Vec<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Keys<'a> {
|
||||
/// Add a key to set.
|
||||
///
|
||||
/// Key will only be added to set if it's `_`, or starts with `_[1-9]`.
|
||||
fn reserve(&mut self, key: &'a str) {
|
||||
let mut bytes = key.as_bytes().iter().copied();
|
||||
if bytes.next() != Some(b'_') {
|
||||
return;
|
||||
}
|
||||
|
||||
match bytes.next() {
|
||||
None => {
|
||||
self.underscore = true;
|
||||
}
|
||||
Some(b'1'..=b'9') => {
|
||||
self.numbered.push(&key[1..]);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a key which is not in the set.
|
||||
///
|
||||
/// Returned key will be either `_`, or `_<integer>` starting with `_2`.
|
||||
#[inline]
|
||||
fn get_unique(&mut self, ctx: &mut TraverseCtx<'a>) -> Atom<'a> {
|
||||
#[expect(clippy::if_not_else)]
|
||||
if !self.underscore {
|
||||
self.underscore = true;
|
||||
Atom::from("_")
|
||||
} else {
|
||||
self.get_unique_slow(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// `#[cold]` and `#[inline(never)]` as it should be very rare to need a key other than `#_`.
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn get_unique_slow(&mut self, ctx: &mut TraverseCtx<'a>) -> Atom<'a> {
|
||||
// Source text length is limited to `u32::MAX` so impossible to have more than `u32::MAX`
|
||||
// private keys. So `u32` is sufficient here.
|
||||
let mut i = 2u32;
|
||||
let mut buffer = ItoaBuffer::new();
|
||||
let mut num_str;
|
||||
loop {
|
||||
num_str = buffer.format(i);
|
||||
if !self.numbered.contains(&num_str) {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let mut key = AString::with_capacity_in(num_str.len() + 1, ctx.ast.allocator);
|
||||
key.push('_');
|
||||
key.push_str(num_str);
|
||||
let key = Atom::from(key.into_bump_str());
|
||||
|
||||
self.numbered.push(&key.as_str()[1..]);
|
||||
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_semantic::{ScopeTree, SymbolTable};
|
||||
use oxc_traverse::TraverseCtx;
|
||||
|
||||
use super::Keys;
|
||||
|
||||
macro_rules! setup {
|
||||
($ctx:ident) => {
|
||||
let allocator = Allocator::default();
|
||||
let scopes = ScopeTree::default();
|
||||
let symbols = SymbolTable::default();
|
||||
let mut $ctx = TraverseCtx::new(scopes, symbols, &allocator);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_no_reserved() {
|
||||
setup!(ctx);
|
||||
|
||||
let mut keys = Keys::default();
|
||||
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_2");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_3");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_4");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_5");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_6");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_7");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_8");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_9");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_10");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_11");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_12");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_no_relevant_reserved() {
|
||||
setup!(ctx);
|
||||
|
||||
let mut keys = Keys::default();
|
||||
keys.reserve("a");
|
||||
keys.reserve("foo");
|
||||
keys.reserve("__");
|
||||
keys.reserve("_0");
|
||||
keys.reserve("_1");
|
||||
keys.reserve("_a");
|
||||
keys.reserve("_foo");
|
||||
keys.reserve("_2foo");
|
||||
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_2");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_reserved_underscore() {
|
||||
setup!(ctx);
|
||||
|
||||
let mut keys = Keys::default();
|
||||
keys.reserve("_");
|
||||
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_2");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_3");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_reserved_numbers() {
|
||||
setup!(ctx);
|
||||
|
||||
let mut keys = Keys::default();
|
||||
keys.reserve("_2");
|
||||
keys.reserve("_4");
|
||||
keys.reserve("_11");
|
||||
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_3");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_5");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_6");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_7");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_8");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_9");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_10");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_12");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_reserved_later_numbers() {
|
||||
setup!(ctx);
|
||||
|
||||
let mut keys = Keys::default();
|
||||
keys.reserve("_5");
|
||||
keys.reserve("_4");
|
||||
keys.reserve("_12");
|
||||
keys.reserve("_13");
|
||||
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_2");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_3");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_6");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_7");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_8");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_9");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_10");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_11");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_14");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_reserved_underscore_and_numbers() {
|
||||
setup!(ctx);
|
||||
|
||||
let mut keys = Keys::default();
|
||||
keys.reserve("_2");
|
||||
keys.reserve("_4");
|
||||
keys.reserve("_");
|
||||
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_3");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_5");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_reserved_underscore_and_later_numbers() {
|
||||
setup!(ctx);
|
||||
|
||||
let mut keys = Keys::default();
|
||||
keys.reserve("_5");
|
||||
keys.reserve("_4");
|
||||
keys.reserve("_");
|
||||
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_2");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_3");
|
||||
assert_eq!(keys.get_unique(&mut ctx), "_6");
|
||||
}
|
||||
}
|
||||
29
crates/oxc_transformer/src/es2022/mod.rs
Normal file
29
crates/oxc_transformer/src/es2022/mod.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use oxc_ast::ast::*;
|
||||
use oxc_traverse::{Traverse, TraverseCtx};
|
||||
|
||||
mod class_static_block;
|
||||
mod options;
|
||||
|
||||
use class_static_block::ClassStaticBlock;
|
||||
|
||||
pub use options::ES2022Options;
|
||||
|
||||
pub struct ES2022 {
|
||||
options: ES2022Options,
|
||||
// Plugins
|
||||
class_static_block: ClassStaticBlock,
|
||||
}
|
||||
|
||||
impl ES2022 {
|
||||
pub fn new(options: ES2022Options) -> Self {
|
||||
Self { options, class_static_block: ClassStaticBlock::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Traverse<'a> for ES2022 {
|
||||
fn enter_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if self.options.class_static_block {
|
||||
self.class_static_block.enter_class_body(body, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
crates/oxc_transformer/src/es2022/options.rs
Normal file
28
crates/oxc_transformer/src/es2022/options.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::env::{can_enable_plugin, Versions};
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct ES2022Options {
|
||||
#[serde(skip)]
|
||||
pub class_static_block: bool,
|
||||
}
|
||||
|
||||
impl ES2022Options {
|
||||
pub fn with_class_static_block(&mut self, enable: bool) -> &mut Self {
|
||||
self.class_static_block = enable;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_targets_and_bugfixes(targets: Option<&Versions>, bugfixes: bool) -> Self {
|
||||
Self {
|
||||
class_static_block: can_enable_plugin(
|
||||
"transform-class-static-block",
|
||||
targets,
|
||||
bugfixes,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ mod es2018;
|
|||
mod es2019;
|
||||
mod es2020;
|
||||
mod es2021;
|
||||
mod es2022;
|
||||
mod react;
|
||||
mod regexp;
|
||||
mod typescript;
|
||||
|
|
@ -45,6 +46,7 @@ use es2018::ES2018;
|
|||
use es2019::ES2019;
|
||||
use es2020::ES2020;
|
||||
use es2021::ES2021;
|
||||
use es2022::ES2022;
|
||||
use react::React;
|
||||
use regexp::RegExp;
|
||||
use typescript::TypeScript;
|
||||
|
|
@ -93,6 +95,7 @@ impl<'a> Transformer<'a> {
|
|||
let mut transformer = TransformerImpl {
|
||||
x0_typescript: TypeScript::new(&self.options.typescript, &self.ctx),
|
||||
x1_react: React::new(self.options.react, ast_builder, &self.ctx),
|
||||
x2_es2022: ES2022::new(self.options.es2022),
|
||||
x2_es2021: ES2021::new(self.options.es2021, &self.ctx),
|
||||
x2_es2020: ES2020::new(self.options.es2020, &self.ctx),
|
||||
x2_es2019: ES2019::new(self.options.es2019),
|
||||
|
|
@ -113,6 +116,7 @@ struct TransformerImpl<'a, 'ctx> {
|
|||
// NOTE: all callbacks must run in order.
|
||||
x0_typescript: TypeScript<'a, 'ctx>,
|
||||
x1_react: React<'a, 'ctx>,
|
||||
x2_es2022: ES2022,
|
||||
x2_es2021: ES2021<'a, 'ctx>,
|
||||
x2_es2020: ES2020<'a, 'ctx>,
|
||||
x2_es2019: ES2019,
|
||||
|
|
@ -170,6 +174,7 @@ impl<'a, 'ctx> Traverse<'a> for TransformerImpl<'a, 'ctx> {
|
|||
|
||||
fn enter_class_body(&mut self, body: &mut ClassBody<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x0_typescript.enter_class_body(body, ctx);
|
||||
self.x2_es2022.enter_class_body(body, ctx);
|
||||
}
|
||||
|
||||
fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use crate::{
|
|||
es2019::ES2019Options,
|
||||
es2020::ES2020Options,
|
||||
es2021::ES2021Options,
|
||||
es2022::ES2022Options,
|
||||
options::babel::BabelOptions,
|
||||
react::JsxOptions,
|
||||
regexp::RegExpOptions,
|
||||
|
|
@ -59,6 +60,8 @@ pub struct TransformOptions {
|
|||
|
||||
pub es2021: ES2021Options,
|
||||
|
||||
pub es2022: ES2022Options,
|
||||
|
||||
pub helper_loader: HelperLoaderOptions,
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +100,7 @@ impl TransformOptions {
|
|||
es2019: ES2019Options { optional_catch_binding: true },
|
||||
es2020: ES2020Options { nullish_coalescing_operator: true },
|
||||
es2021: ES2021Options { logical_assignment_operators: true },
|
||||
es2022: ES2022Options { class_static_block: true },
|
||||
helper_loader: HelperLoaderOptions {
|
||||
mode: HelperLoaderMode::Runtime,
|
||||
..Default::default()
|
||||
|
|
@ -113,6 +117,7 @@ impl TransformOptions {
|
|||
es2019: ES2019Options::from_targets_and_bugfixes(targets, bugfixes),
|
||||
es2020: ES2020Options::from_targets_and_bugfixes(targets, bugfixes),
|
||||
es2021: ES2021Options::from_targets_and_bugfixes(targets, bugfixes),
|
||||
es2022: ES2022Options::from_targets_and_bugfixes(targets, bugfixes),
|
||||
regexp: RegExpOptions::from_targets_and_bugfixes(targets, bugfixes),
|
||||
..Default::default()
|
||||
}
|
||||
|
|
@ -254,6 +259,11 @@ impl TransformOptions {
|
|||
get_enabled_plugin_options(plugin_name, options, targets.as_ref(), bugfixes).is_some()
|
||||
});
|
||||
|
||||
transformer_options.es2022.with_class_static_block({
|
||||
let plugin_name = "transform-class-static-block";
|
||||
get_enabled_plugin_options(plugin_name, options, targets.as_ref(), bugfixes).is_some()
|
||||
});
|
||||
|
||||
transformer_options.typescript = {
|
||||
let preset_name = "typescript";
|
||||
if options.has_preset("typescript") {
|
||||
|
|
|
|||
|
|
@ -284,6 +284,23 @@ impl<'a> TraverseCtx<'a> {
|
|||
self.scoping.insert_scope_below_expression(expr, flags)
|
||||
}
|
||||
|
||||
/// Remove scope for an expression from the scope chain.
|
||||
///
|
||||
/// Delete the scope and set parent of its child scopes to its parent scope.
|
||||
/// e.g.:
|
||||
/// * Starting scopes parentage `A -> B`, `B -> C`, `B -> D`.
|
||||
/// * Remove scope `B` from chain.
|
||||
/// * End result: scopes `A -> C`, `A -> D`.
|
||||
///
|
||||
/// Use this when removing an expression which owns a scope, without removing its children.
|
||||
/// For example when unwrapping `(() => foo)()` to just `foo`.
|
||||
/// `foo` here could be an expression which itself contains scopes.
|
||||
///
|
||||
/// This is a shortcut for `ctx.scoping.remove_scope_for_expression`.
|
||||
pub fn remove_scope_for_expression(&mut self, scope_id: ScopeId, expr: &Expression) {
|
||||
self.scoping.remove_scope_for_expression(scope_id, expr);
|
||||
}
|
||||
|
||||
/// Generate UID var name.
|
||||
///
|
||||
/// Finds a unique variable name which does clash with any other variables used in the program.
|
||||
|
|
|
|||
|
|
@ -139,6 +139,32 @@ impl TraverseScoping {
|
|||
new_scope_id
|
||||
}
|
||||
|
||||
/// Remove scope for an expression from the scope chain.
|
||||
///
|
||||
/// Delete the scope and set parent of its child scopes to its parent scope.
|
||||
/// e.g.:
|
||||
/// * Starting scopes parentage `A -> B`, `B -> C`, `B -> D`.
|
||||
/// * Remove scope `B` from chain.
|
||||
/// * End result: scopes `A -> C`, `A -> D`.
|
||||
///
|
||||
/// Use this when removing an expression which owns a scope, without removing its children.
|
||||
/// For example when unwrapping `(() => foo)()` to just `foo`.
|
||||
/// `foo` here could be an expression which itself contains scopes.
|
||||
pub fn remove_scope_for_expression(&mut self, scope_id: ScopeId, expr: &Expression) {
|
||||
let mut collector = ChildScopeCollector::new();
|
||||
collector.visit_expression(expr);
|
||||
|
||||
let child_ids = collector.scope_ids;
|
||||
if !child_ids.is_empty() {
|
||||
let parent_id = self.scopes.get_parent_id(scope_id);
|
||||
for child_id in child_ids {
|
||||
self.scopes.set_parent_id(child_id, parent_id);
|
||||
}
|
||||
}
|
||||
|
||||
self.scopes.delete_scope(scope_id);
|
||||
}
|
||||
|
||||
/// Generate UID var name.
|
||||
///
|
||||
/// Finds a unique variable name which does clash with any other variables used in the program.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
commit: d20b314c
|
||||
|
||||
Passed: 342/1051
|
||||
Passed: 348/1058
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
* babel-plugin-transform-logical-assignment-operators
|
||||
* babel-plugin-transform-optional-catch-binding
|
||||
* babel-preset-react
|
||||
|
|
@ -11,7 +12,7 @@ Passed: 342/1051
|
|||
* babel-plugin-transform-react-jsx-source
|
||||
|
||||
|
||||
# babel-preset-env (109/585)
|
||||
# babel-preset-env (108/585)
|
||||
* .plugins-overlapping/chrome-49/input.js
|
||||
x Output mismatch
|
||||
|
||||
|
|
@ -1437,6 +1438,9 @@ x Output mismatch
|
|||
* shipped-proposals/new-class-features-chrome-90/input.js
|
||||
x Output mismatch
|
||||
|
||||
* shipped-proposals/new-class-features-chrome-94/input.js
|
||||
x Output mismatch
|
||||
|
||||
* shipped-proposals/new-class-features-firefox-70/input.js
|
||||
x Output mismatch
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
commit: d20b314c
|
||||
|
||||
Passed: 34/62
|
||||
Passed: 45/73
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
* babel-plugin-transform-logical-assignment-operators
|
||||
* babel-plugin-transform-nullish-coalescing-operator
|
||||
* babel-plugin-transform-optional-catch-binding
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
commit: d20b314c
|
||||
|
||||
Passed: 61/70
|
||||
Passed: 66/75
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-class-static-block
|
||||
* babel-plugin-transform-nullish-coalescing-operator
|
||||
* babel-plugin-transform-optional-catch-binding
|
||||
* babel-plugin-transform-exponentiation-operator
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ pub(crate) const PLUGINS: &[&str] = &[
|
|||
// "babel-plugin-transform-unicode-sets-regex",
|
||||
// // ES2022
|
||||
// "babel-plugin-transform-class-properties",
|
||||
// "babel-plugin-transform-class-static-block",
|
||||
"babel-plugin-transform-class-static-block",
|
||||
// "babel-plugin-transform-private-methods",
|
||||
// "babel-plugin-transform-private-property-in-object",
|
||||
// // [Syntax] "babel-plugin-transform-syntax-top-level-await",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
let a, b, d, e;
|
||||
|
||||
class C {
|
||||
static {
|
||||
a = this;
|
||||
}
|
||||
static {
|
||||
[b, c] = this;
|
||||
}
|
||||
static {
|
||||
d ??= this;
|
||||
}
|
||||
static {
|
||||
e.f = this;
|
||||
}
|
||||
static {
|
||||
[g.h, i] = this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
let a, b, d, e;
|
||||
|
||||
class C {
|
||||
static #_ = a = this;
|
||||
static #_2 = [b, c] = this;
|
||||
static #_3 = d ??= this;
|
||||
static #_4 = e.f = this;
|
||||
static #_5 = [g.h, i] = this;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
class C {
|
||||
static {
|
||||
C;
|
||||
}
|
||||
static {
|
||||
x;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
class C {
|
||||
static #_ = C;
|
||||
static #_2 = x;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
class C {
|
||||
static {}
|
||||
static {}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
class C {
|
||||
static #_ = (() => {})();
|
||||
static #_2 = (() => {})();
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
let x, y;
|
||||
|
||||
class C {
|
||||
static {
|
||||
x = (() => this)();
|
||||
}
|
||||
|
||||
static {
|
||||
if (true) {
|
||||
y = this;
|
||||
z = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
let x, y;
|
||||
|
||||
class C {
|
||||
static #_ = x = (() => this)();
|
||||
static #_2 = (() => {
|
||||
if (true) {
|
||||
y = this;
|
||||
z = this;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"plugins": [
|
||||
"transform-class-static-block"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
function foo() {}
|
||||
|
||||
export class C {
|
||||
// Private properties and methods use up prop names for static block
|
||||
#_ = 1;
|
||||
static #_2 = 2;
|
||||
#_3() {}
|
||||
static #_4() {}
|
||||
accessor #_5 = 5;
|
||||
static accessor #_6 = 6;
|
||||
|
||||
// Non-private don't use up prop names
|
||||
_7 = 7;
|
||||
static _8 = 8;
|
||||
_9() {}
|
||||
static _10() {}
|
||||
|
||||
static {
|
||||
foo();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
function foo() { }
|
||||
|
||||
export class C {
|
||||
#_ = 1;
|
||||
static #_2 = 2;
|
||||
#_3() {}
|
||||
static #_4() {}
|
||||
accessor #_5 = 5;
|
||||
static accessor #_6 = 6;
|
||||
_7 = 7;
|
||||
static _8 = 8;
|
||||
_9() {}
|
||||
static _10() {}
|
||||
|
||||
static #_7 = foo();
|
||||
}
|
||||
Loading…
Reference in a new issue