mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 20:32: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(_))
|
||||
}
|
||||
|
||||
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 {
|
||||
match self {
|
||||
PropertyKey::Identifier(ident) => ident.name == name,
|
||||
|
|
|
|||
|
|
@ -66,6 +66,15 @@ impl<'a> AstBuilder<'a> {
|
|||
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(
|
||||
&self,
|
||||
span: Span,
|
||||
|
|
@ -715,6 +724,32 @@ impl<'a> AstBuilder<'a> {
|
|||
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(
|
||||
&self,
|
||||
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 es2019;
|
||||
mod es2021;
|
||||
mod es2022;
|
||||
mod options;
|
||||
mod react_jsx;
|
||||
mod typescript;
|
||||
|
|
@ -35,6 +36,8 @@ pub use crate::options::{
|
|||
pub struct Transformer<'a> {
|
||||
typescript: Option<TypeScript<'a>>,
|
||||
react_jsx: Option<ReactJsx<'a>>,
|
||||
// es2022
|
||||
es2022_class_static_block: Option<es2022::ClassStaticBlock<'a>>,
|
||||
// es2021
|
||||
es2021_logical_assignment_operators: Option<LogicalAssignmentOperators<'a>>,
|
||||
// es2019
|
||||
|
|
@ -60,6 +63,9 @@ impl<'a> Transformer<'a> {
|
|||
if let Some(react_options) = options.react {
|
||||
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 {
|
||||
t.es2021_logical_assignment_operators
|
||||
.replace(LogicalAssignmentOperators::new(Rc::clone(&ast)));
|
||||
|
|
@ -109,4 +115,12 @@ impl<'a, 'b> VisitMut<'a, 'b> for Transformer<'a> {
|
|||
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,
|
||||
ES2019,
|
||||
ES2021,
|
||||
ES2022,
|
||||
#[default]
|
||||
ESNext,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Passed: 91/1078
|
||||
Passed: 95/1078
|
||||
|
||||
# babel-plugin-transform-class-properties
|
||||
* Failed: assumption-constantSuper/complex-super-class/input.js
|
||||
|
|
@ -269,10 +269,6 @@ Passed: 91/1078
|
|||
* Passed: public-loose/arrow-this-without-transform/input.js
|
||||
|
||||
# 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/new-target/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/preserve-comments/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
|
||||
|
||||
# babel-plugin-transform-private-methods
|
||||
|
|
|
|||
Loading…
Reference in a new issue