mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(transformer): Class Static Block (#962)
This commit is contained in:
parent
77dd0f1395
commit
9ad2634091
7 changed files with 189 additions and 5 deletions
|
|
@ -414,6 +414,13 @@ impl<'a> PropertyKey<'a> {
|
||||||
matches!(self, Self::PrivateIdentifier(_))
|
matches!(self, Self::PrivateIdentifier(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn private_name(&self) -> Option<Atom> {
|
||||||
|
match self {
|
||||||
|
Self::PrivateIdentifier(ident) => Some(ident.name.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_specific_id(&self, name: &str) -> bool {
|
pub fn is_specific_id(&self, name: &str) -> bool {
|
||||||
match self {
|
match self {
|
||||||
PropertyKey::Identifier(ident) => ident.name == name,
|
PropertyKey::Identifier(ident) => ident.name == name,
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,15 @@ impl<'a> AstBuilder<'a> {
|
||||||
mem::replace(expr, null_expr)
|
mem::replace(expr, null_expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_statement(&self, stmt: &mut Statement<'a>) -> Statement<'a> {
|
||||||
|
let empty_stmt = self.empty_statement(stmt.span());
|
||||||
|
mem::replace(stmt, empty_stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_statement_vec(&self, stmts: &mut Vec<'a, Statement<'a>>) -> Vec<'a, Statement<'a>> {
|
||||||
|
mem::replace(stmts, self.new_vec())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn program(
|
pub fn program(
|
||||||
&self,
|
&self,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
|
@ -715,6 +724,32 @@ impl<'a> AstBuilder<'a> {
|
||||||
ClassElement::StaticBlock(self.alloc(StaticBlock { span, body }))
|
ClassElement::StaticBlock(self.alloc(StaticBlock { span, body }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn class_property(
|
||||||
|
&self,
|
||||||
|
span: Span,
|
||||||
|
key: PropertyKey<'a>,
|
||||||
|
value: Option<Expression<'a>>,
|
||||||
|
computed: bool,
|
||||||
|
r#static: bool,
|
||||||
|
decorators: Vec<'a, Decorator<'a>>,
|
||||||
|
) -> ClassElement<'a> {
|
||||||
|
ClassElement::PropertyDefinition(self.alloc(PropertyDefinition {
|
||||||
|
span,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
computed,
|
||||||
|
r#static,
|
||||||
|
declare: false,
|
||||||
|
r#override: false,
|
||||||
|
optional: false,
|
||||||
|
definite: false,
|
||||||
|
readonly: false,
|
||||||
|
type_annotation: None,
|
||||||
|
accessibility: None,
|
||||||
|
decorators,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn accessor_property(
|
pub fn accessor_property(
|
||||||
&self,
|
&self,
|
||||||
span: Span,
|
span: Span,
|
||||||
|
|
|
||||||
124
crates/oxc_transformer/src/es2022/class_static_block.rs
Normal file
124
crates/oxc_transformer/src/es2022/class_static_block.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
use oxc_ast::{ast::*, AstBuilder};
|
||||||
|
use oxc_span::{Atom, Span};
|
||||||
|
|
||||||
|
use std::{collections::HashSet, rc::Rc};
|
||||||
|
|
||||||
|
/// ES2022: Class Static Block
|
||||||
|
///
|
||||||
|
/// References:
|
||||||
|
/// * <https://babel.dev/docs/babel-plugin-transform-class-static-block>
|
||||||
|
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-class-static-block>
|
||||||
|
pub struct ClassStaticBlock<'a> {
|
||||||
|
ast: Rc<AstBuilder<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ClassStaticBlock<'a> {
|
||||||
|
pub fn new(ast: Rc<AstBuilder<'a>>) -> Self {
|
||||||
|
Self { ast }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_class_body<'b>(&mut self, class_body: &'b mut ClassBody<'a>) {
|
||||||
|
if !class_body.body.iter().any(|e| matches!(e, ClassElement::StaticBlock(..))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let private_names: HashSet<Atom> = class_body
|
||||||
|
.body
|
||||||
|
.iter()
|
||||||
|
.filter_map(ClassElement::property_key)
|
||||||
|
.filter_map(PropertyKey::private_name)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
for element in class_body.body.iter_mut() {
|
||||||
|
let ClassElement::StaticBlock(block) = element else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let span = block.span;
|
||||||
|
|
||||||
|
let static_block_private_id = generate_uid(&private_names, &mut i);
|
||||||
|
let key = PropertyKey::PrivateIdentifier(self.ast.alloc(PrivateIdentifier {
|
||||||
|
span: Span::default(),
|
||||||
|
name: static_block_private_id.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let value = match block.body.len() {
|
||||||
|
0 => None,
|
||||||
|
1 if matches!(block.body[0], Statement::ExpressionStatement(..)) => {
|
||||||
|
// We special-case the single expression case to avoid the iife, since it's common.
|
||||||
|
//
|
||||||
|
// We prefer to emit:
|
||||||
|
// ```JavaScript
|
||||||
|
// class Foo {
|
||||||
|
// static bar = 42;
|
||||||
|
// static #_ = this.foo = Foo.bar;
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// instead of:
|
||||||
|
// ```JavaScript
|
||||||
|
// class Foo {
|
||||||
|
// static bar = 42;
|
||||||
|
// static #_ = (() => this.foo = Foo.bar)();
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
|
||||||
|
let stmt = self.ast.move_statement(&mut (*block.body)[0]);
|
||||||
|
let Statement::ExpressionStatement(mut expr_stmt) = stmt else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let value = self.ast.move_expression(&mut expr_stmt.expression);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let params = self.ast.formal_parameters(
|
||||||
|
Span::default(),
|
||||||
|
FormalParameterKind::ArrowFormalParameters,
|
||||||
|
self.ast.new_vec(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let statements = self.ast.move_statement_vec(&mut block.body);
|
||||||
|
let function_body =
|
||||||
|
self.ast.function_body(Span::default(), self.ast.new_vec(), statements);
|
||||||
|
|
||||||
|
let callee = self.ast.arrow_expression(
|
||||||
|
Span::default(),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
params,
|
||||||
|
function_body,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let callee = self.ast.parenthesized_expression(Span::default(), callee);
|
||||||
|
|
||||||
|
let value = self.ast.call_expression(
|
||||||
|
Span::default(),
|
||||||
|
callee,
|
||||||
|
self.ast.new_vec(),
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
*element = self.ast.class_property(span, key, value, false, true, self.ast.new_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_uid(deny_list: &HashSet<Atom>, i: &mut u32) -> Atom {
|
||||||
|
*i += 1;
|
||||||
|
|
||||||
|
let mut uid: Atom = if *i == 1 { "_".to_string() } else { format!("_{i}") }.into();
|
||||||
|
while deny_list.contains(&uid) {
|
||||||
|
*i += 1;
|
||||||
|
uid = format!("_{i}").into();
|
||||||
|
}
|
||||||
|
|
||||||
|
uid
|
||||||
|
}
|
||||||
3
crates/oxc_transformer/src/es2022/mod.rs
Normal file
3
crates/oxc_transformer/src/es2022/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod class_static_block;
|
||||||
|
|
||||||
|
pub use class_static_block::ClassStaticBlock;
|
||||||
|
|
@ -11,6 +11,7 @@ mod es2015;
|
||||||
mod es2016;
|
mod es2016;
|
||||||
mod es2019;
|
mod es2019;
|
||||||
mod es2021;
|
mod es2021;
|
||||||
|
mod es2022;
|
||||||
mod options;
|
mod options;
|
||||||
mod react_jsx;
|
mod react_jsx;
|
||||||
mod typescript;
|
mod typescript;
|
||||||
|
|
@ -35,6 +36,8 @@ pub use crate::options::{
|
||||||
pub struct Transformer<'a> {
|
pub struct Transformer<'a> {
|
||||||
typescript: Option<TypeScript<'a>>,
|
typescript: Option<TypeScript<'a>>,
|
||||||
react_jsx: Option<ReactJsx<'a>>,
|
react_jsx: Option<ReactJsx<'a>>,
|
||||||
|
// es2022
|
||||||
|
es2022_class_static_block: Option<es2022::ClassStaticBlock<'a>>,
|
||||||
// es2021
|
// es2021
|
||||||
es2021_logical_assignment_operators: Option<LogicalAssignmentOperators<'a>>,
|
es2021_logical_assignment_operators: Option<LogicalAssignmentOperators<'a>>,
|
||||||
// es2019
|
// es2019
|
||||||
|
|
@ -60,6 +63,9 @@ impl<'a> Transformer<'a> {
|
||||||
if let Some(react_options) = options.react {
|
if let Some(react_options) = options.react {
|
||||||
t.react_jsx.replace(ReactJsx::new(Rc::clone(&ast), react_options));
|
t.react_jsx.replace(ReactJsx::new(Rc::clone(&ast), react_options));
|
||||||
}
|
}
|
||||||
|
if options.target < TransformTarget::ES2022 {
|
||||||
|
t.es2022_class_static_block.replace(es2022::ClassStaticBlock::new(Rc::clone(&ast)));
|
||||||
|
}
|
||||||
if options.target < TransformTarget::ES2021 {
|
if options.target < TransformTarget::ES2021 {
|
||||||
t.es2021_logical_assignment_operators
|
t.es2021_logical_assignment_operators
|
||||||
.replace(LogicalAssignmentOperators::new(Rc::clone(&ast)));
|
.replace(LogicalAssignmentOperators::new(Rc::clone(&ast)));
|
||||||
|
|
@ -109,4 +115,12 @@ impl<'a, 'b> VisitMut<'a, 'b> for Transformer<'a> {
|
||||||
self.visit_expression(init);
|
self.visit_expression(init);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_class_body(&mut self, class_body: &'b mut ClassBody<'a>) {
|
||||||
|
self.es2022_class_static_block.as_mut().map(|t| t.transform_class_body(class_body));
|
||||||
|
|
||||||
|
class_body.body.iter_mut().for_each(|class_element| {
|
||||||
|
self.visit_class_element(class_element);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ pub enum TransformTarget {
|
||||||
ES2016,
|
ES2016,
|
||||||
ES2019,
|
ES2019,
|
||||||
ES2021,
|
ES2021,
|
||||||
|
ES2022,
|
||||||
#[default]
|
#[default]
|
||||||
ESNext,
|
ESNext,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Passed: 91/1078
|
Passed: 95/1078
|
||||||
|
|
||||||
# babel-plugin-transform-class-properties
|
# babel-plugin-transform-class-properties
|
||||||
* Failed: assumption-constantSuper/complex-super-class/input.js
|
* Failed: assumption-constantSuper/complex-super-class/input.js
|
||||||
|
|
@ -269,10 +269,6 @@ Passed: 91/1078
|
||||||
* Passed: public-loose/arrow-this-without-transform/input.js
|
* Passed: public-loose/arrow-this-without-transform/input.js
|
||||||
|
|
||||||
# babel-plugin-transform-class-static-block
|
# babel-plugin-transform-class-static-block
|
||||||
* Failed: class-static-block/class-binding/input.js
|
|
||||||
* Failed: class-static-block/class-declaration/input.js
|
|
||||||
* Failed: class-static-block/in-class-heritage/input.js
|
|
||||||
* Failed: class-static-block/multiple-static-initializers/input.js
|
|
||||||
* Failed: class-static-block/name-conflict/input.js
|
* Failed: class-static-block/name-conflict/input.js
|
||||||
* Failed: class-static-block/new-target/input.js
|
* Failed: class-static-block/new-target/input.js
|
||||||
* Failed: class-static-block/preserve-comments/input.js
|
* Failed: class-static-block/preserve-comments/input.js
|
||||||
|
|
@ -290,6 +286,10 @@ Passed: 91/1078
|
||||||
* Failed: integration-loose/name-conflict/input.js
|
* Failed: integration-loose/name-conflict/input.js
|
||||||
* Failed: integration-loose/preserve-comments/input.js
|
* Failed: integration-loose/preserve-comments/input.js
|
||||||
* Failed: integration-loose/super-static-block/input.js
|
* Failed: integration-loose/super-static-block/input.js
|
||||||
|
* Passed: class-static-block/class-binding/input.js
|
||||||
|
* Passed: class-static-block/class-declaration/input.js
|
||||||
|
* Passed: class-static-block/in-class-heritage/input.js
|
||||||
|
* Passed: class-static-block/multiple-static-initializers/input.js
|
||||||
* Passed: integration-loose/.new-target/input.js
|
* Passed: integration-loose/.new-target/input.js
|
||||||
|
|
||||||
# babel-plugin-transform-private-methods
|
# babel-plugin-transform-private-methods
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue